Compare commits

...

5 Commits

10 changed files with 121 additions and 29 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
__pycache__ __pycache__
*.jpg *.jpg
*.png

View File

@ -17,5 +17,8 @@
"margins": [20, 20, 20, 20], "margins": [20, 20, 20, 20],
"arrowMargin": 4, "arrowMargin": 4,
"valuesGap": 5, "valuesGap": 5,
"arrowLabelDistance": 5 "arrowLabelDistance": 5,
"forceDescsOnSide": false,
"width": 1200,
"height": 800
} }

View File

@ -1,6 +1,7 @@
import json import json
import re import re
class Config: class Config:
DEFAULT_FONT_FAMILY = "Ubuntu Mono" DEFAULT_FONT_FAMILY = "Ubuntu Mono"
DEFAULT_FONT_SIZE = 16 DEFAULT_FONT_SIZE = 16
@ -21,6 +22,10 @@ class Config:
ARROW_MARGIN = 4 ARROW_MARGIN = 4
VALUES_GAP = 5 VALUES_GAP = 5
ARROW_LABEL_DISTANCE = 5 ARROW_LABEL_DISTANCE = 5
FORCE_DESCS_ON_SIDE = False
LEFT_LABELS = False
WIDTH = 1200
HEIGHT = 800
def __init__(self, path: str = "config.json") -> None: def __init__(self, path: str = "config.json") -> None:
self.load(path) self.load(path)

42
main.py
View File

@ -3,6 +3,7 @@ import os
from schema import InstructionSetSchema from schema import InstructionSetSchema
description = """Examples: description = """Examples:
- Default theme (black on white): - Default theme (black on white):
python main.py schema.xml -o out.jpg python main.py schema.xml -o out.jpg
@ -15,20 +16,51 @@ description = """Examples:
- Transparent background: - Transparent background:
python main.py schema.xml -o out.png -c transparent.json python main.py schema.xml -o out.png -c transparent.json
- Directory mode:
python main.py -d -o images/ schemas/
""" """
def processFile(inPath, outPath, confPath, display):
schema = InstructionSetSchema(inPath, confPath, display)
schema.save(outPath)
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter) parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("schema", help="Path to the schema description. Accepted formats are: YAML, JSON and XML") parser.add_argument("schema", help="Path to the schema description. Accepted formats are: YAML, JSON and XML")
parser.add_argument("-o", "--output", help="Output path. By default, the output file will have the same name as the schema description with the extension .png") parser.add_argument("-o", "--output", help="Output path. By default, the output file will have the same name as the schema description with the extension .png")
parser.add_argument("-c", "--config", help="Path to the config file", default="config.json") parser.add_argument("-c", "--config", help="Path to the config file", default="config.json")
parser.add_argument("-D", "--display", help="Enable pygame display of the result", action="store_true") parser.add_argument("-D", "--display", help="Enable pygame display of the result", action="store_true")
parser.add_argument("-d", "--directory", help="Enable directory mode. If set, the input and output paths are directories and all files inside the input directory are processed", action="store_true")
args = parser.parse_args() args = parser.parse_args()
output = args.output if args.directory:
if output is None: if not os.path.isdir(args.schema):
output = os.path.splitext(args.schema)[0] + ".png" print(f"{args.schema} is not a directory")
exit(-1)
output = args.output
if output is None:
output = args.schema
if not os.path.isdir(output):
print(f"{output} is not a directory")
exit(-1)
paths = os.listdir(args.schema)
for path in paths:
inPath = os.path.join(args.schema, path)
outPath = os.path.join(output, path)
outPath = os.path.splitext(outPath)[0] + ".png"
processFile(inPath, outPath, args.config, args.display)
schema = InstructionSetSchema(args.schema, args.config, args.display) else:
schema.save(output) output = args.output
if output is None:
output = os.path.splitext(args.schema)[0] + ".png"
processFile(args.schema, output, args.config, args.display)

View File

@ -1,6 +1,8 @@
from __future__ import annotations from __future__ import annotations
from typing import Union from typing import Union
class Range: class Range:
def __init__(self, def __init__(self,
start: int, start: int,
@ -16,6 +18,7 @@ class Range:
self.description = description self.description = description
self.values = values self.values = values
self.dependsOn = dependsOn self.dependsOn = dependsOn
self.lastValueY = -1
@property @property
def bits(self) -> int: def bits(self) -> int:

View File

@ -1,28 +1,28 @@
from __future__ import annotations from __future__ import annotations
import os import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING:
from config import Config
from range import Range
from schema import InstructionSetSchema
import pygame import pygame
from structure import Structure from structure import Structure
from vec import Vec from vec import Vec
if TYPE_CHECKING:
from config import Config
from range import Range
from schema import InstructionSetSchema
class Renderer: class Renderer:
WIDTH = 1200
HEIGHT = 800
def __init__(self, config: Config, display: bool = False) -> None: def __init__(self, config: Config, display: bool = False) -> None:
self.config = config self.config = config
self.display = display self.display = display
pygame.init() pygame.init()
if self.display: if self.display:
self.win = pygame.display.set_mode([Renderer.WIDTH, Renderer.HEIGHT]) self.win = pygame.display.set_mode([self.config.WIDTH, self.config.HEIGHT])
self.surf = pygame.Surface([Renderer.WIDTH, Renderer.HEIGHT], pygame.SRCALPHA) self.surf = pygame.Surface([self.config.WIDTH, self.config.HEIGHT], pygame.SRCALPHA)
self.font = pygame.font.SysFont(self.config.DEFAULT_FONT_FAMILY, self.config.DEFAULT_FONT_SIZE) self.font = pygame.font.SysFont(self.config.DEFAULT_FONT_FAMILY, self.config.DEFAULT_FONT_SIZE)
self.italicFont = pygame.font.SysFont(self.config.ITALIC_FONT_FAMILY, self.config.ITALIC_FONT_SIZE, italic=True) self.italicFont = pygame.font.SysFont(self.config.ITALIC_FONT_FAMILY, self.config.ITALIC_FONT_SIZE, italic=True)
@ -31,7 +31,12 @@ class Renderer:
def render(self, schema: InstructionSetSchema) -> None: def render(self, schema: InstructionSetSchema) -> None:
self.surf.fill(self.config.BACKGROUND_COLOR) self.surf.fill(self.config.BACKGROUND_COLOR)
self.drawStructure(schema.structures["main"], schema.structures, self.margins[3], self.margins[0]) mainStruct: Structure = schema.structures["main"]
ox = self.margins[3]
if self.config.LEFT_LABELS:
ox = self.config.WIDTH - self.margins[3] - mainStruct.bits * self.config.BIT_WIDTH
self.drawStructure(schema.structures["main"], schema.structures, ox, self.margins[0])
if self.display: if self.display:
name = os.path.basename(schema.path) name = os.path.basename(schema.path)
@ -45,8 +50,7 @@ class Renderer:
def drawStructure(self, def drawStructure(self,
struct: Structure, struct: Structure,
structures: dict[str, structures: dict[str, Structure],
Structure],
ox: float = 0, ox: float = 0,
oy: float = 0) -> float: oy: float = 0) -> float:
@ -77,7 +81,21 @@ class Renderer:
pygame.draw.line(self.surf, borderCol, [bitX, bitsY], [bitX, bitsY + bitH]) pygame.draw.line(self.surf, borderCol, [bitX, bitsY], [bitX, bitsY + bitH])
ranges = struct.getSortedRanges() ranges = struct.getSortedRanges()
descX = ox + max(0, (struct.bits-12) * bitW) if self.config.LEFT_LABELS:
ranges.reverse()
if self.config.FORCE_DESCS_ON_SIDE:
if self.config.LEFT_LABELS:
descX = self.config.WIDTH - self.margins[3] - structures["main"].bits * bitW
else:
descX = self.margins[3] + structures["main"].bits * bitW
else:
if self.config.LEFT_LABELS:
descX = ox + struct.bits * bitW
else:
descX = ox
descY = bitsY + bitH * 2 descY = bitsY + bitH * 2
# Names + simple descriptions # Names + simple descriptions
@ -123,10 +141,16 @@ class Renderer:
bitH = self.config.BIT_HEIGHT bitH = self.config.BIT_HEIGHT
arrowMargin = self.config.ARROW_MARGIN arrowMargin = self.config.ARROW_MARGIN
if endX > startX:
endX -= arrowMargin
else:
endX += arrowMargin
pygame.draw.lines(self.surf, self.config.LINK_COLOR, False, [ pygame.draw.lines(self.surf, self.config.LINK_COLOR, False, [
[startX, startY + bitH*1.5], [startX, startY + bitH*1.5],
[startX, endY + bitH/2], [startX, endY + bitH/2],
[endX - arrowMargin, endY + bitH/2] [endX, endY + bitH/2]
]) ])
def drawDescription(self, def drawDescription(self,
@ -140,7 +164,10 @@ class Renderer:
bitW = self.config.BIT_WIDTH bitW = self.config.BIT_WIDTH
bitH = self.config.BIT_HEIGHT bitH = self.config.BIT_HEIGHT
descX = max(descX, rStartX + rWidth/2 + bitW) if self.config.LEFT_LABELS:
descX = min(descX, rStartX + rWidth/2 - bitW)
else:
descX = max(descX, rStartX + rWidth/2 + bitW)
self.drawUnderbracket(rStartX, rStartX + rWidth, rStartY) self.drawUnderbracket(rStartX, rStartX + rWidth, rStartY)
@ -148,12 +175,17 @@ class Renderer:
self.drawLink(midX, rStartY, descX, descY) self.drawLink(midX, rStartY, descX, descY)
descTxt = self.font.render(range_.description, True, self.config.TEXT_COLOR) descTxt = self.font.render(range_.description, True, self.config.TEXT_COLOR)
self.surf.blit(descTxt, [descX, descY + (bitH - descTxt.get_height())/2]) txtX = descX
if self.config.LEFT_LABELS:
txtX -= descTxt.get_width()
self.surf.blit(descTxt, [txtX, descY + (bitH - descTxt.get_height())/2])
descY += descTxt.get_height() descY += descTxt.get_height()
if range_.values is not None and range_.dependsOn is None: if range_.values is not None and range_.dependsOn is None:
descX, descY = self.drawValues(range_.values, descX, descY) txtX, descY = self.drawValues(range_.values, txtX, descY)
descY += self.config.DESCRIPTION_MARGIN descY += self.config.DESCRIPTION_MARGIN
@ -191,8 +223,13 @@ class Renderer:
rWidth = range_.bits * bitW rWidth = range_.bits * bitW
self.drawUnderbracket(rStartX, rStartX + rWidth, bitsY) self.drawUnderbracket(rStartX, rStartX + rWidth, bitsY)
prevY = bitsY + bitH * 1.5
dependRange = struct.ranges[range_.dependsOn] dependRange = struct.ranges[range_.dependsOn]
prevRangeY = bitsY + bitH * 1.5
if dependRange.lastValueY == -1:
prevDependY = bitsY + bitH * 1.5
else:
prevDependY = dependRange.lastValueY
dependStartI = struct.bits - dependRange.end - 1 dependStartI = struct.bits - dependRange.end - 1
dependStartX = bitsX + dependStartI * bitW dependStartX = bitsX + dependStartI * bitW
dependWidth = dependRange.bits * bitW dependWidth = dependRange.bits * bitW
@ -200,7 +237,7 @@ class Renderer:
self.drawUnderbracket(dependStartX, dependStartX + dependWidth, bitsY) self.drawUnderbracket(dependStartX, dependStartX + dependWidth, bitsY)
for val, data in sorted(range_.values.items(), key=lambda vd: vd[0]): for val, data in sorted(range_.values.items(), key=lambda vd: vd[0]):
self.drawArrow(dependMid, prevY, dependMid, descY - arrowMargin) self.drawArrow(dependMid, prevDependY, dependMid, descY - arrowMargin)
valRanges = {} valRanges = {}
for i in range(dependRange.bits): for i in range(dependRange.bits):
@ -232,11 +269,13 @@ class Renderer:
self.drawArrow(x1, y, x2, y, data["description"]) self.drawArrow(x1, y, x2, y, data["description"])
self.drawArrow(rStartX + rWidth - bitW, self.drawArrow(rStartX + rWidth - bitW,
prevY, prevRangeY,
rStartX + rWidth - bitW, rStartX + rWidth - bitW,
descY + bitH - arrowMargin) descY + bitH - arrowMargin)
prevY = descY + bitH*2 + arrowMargin prevDependY = descY + bitH*2 + arrowMargin
prevRangeY =prevDependY
dependRange.lastValueY = prevDependY
descY = self.drawStructure(structures[data["structure"]], structures, rStartX, descY) descY = self.drawStructure(structures[data["structure"]], structures, rStartX, descY)
return (descX, descY) return (descX, descY)

View File

@ -1,5 +1,6 @@
import json import json
import os import os
import yaml import yaml
from config import Config from config import Config
@ -7,9 +8,11 @@ from renderer import Renderer
from structure import Structure from structure import Structure
from xml_loader import XMLLoader from xml_loader import XMLLoader
class UnsupportedFormatException(Exception): class UnsupportedFormatException(Exception):
... ...
class InstructionSetSchema: class InstructionSetSchema:
VALID_EXTENSIONS = ("yaml", "json", "xml") VALID_EXTENSIONS = ("yaml", "json", "xml")

View File

@ -1,6 +1,8 @@
from __future__ import annotations from __future__ import annotations
from range import Range from range import Range
class Structure: class Structure:
def __init__(self, def __init__(self,
name: str, name: str,

2
vec.py
View File

@ -1,6 +1,8 @@
from __future__ import annotations from __future__ import annotations
from math import sqrt from math import sqrt
class Vec: class Vec:
def __init__(self, x: float = 0, y: float = 0) -> None: def __init__(self, x: float = 0, y: float = 0) -> None:
self.x = x self.x = x

View File

@ -1,7 +1,9 @@
from __future__ import annotations from __future__ import annotations
from bs4 import BeautifulSoup
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from bs4 import BeautifulSoup
if TYPE_CHECKING: if TYPE_CHECKING:
from io import TextIOWrapper from io import TextIOWrapper