Compare commits
5 Commits
dd2ffebca2
...
752cf011c3
Author | SHA1 | Date | |
---|---|---|---|
752cf011c3 | |||
7647f870a6 | |||
75d145096d | |||
d61a707591 | |||
5a4d30e162 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
*.jpg
|
||||
*.png
|
@ -17,5 +17,8 @@
|
||||
"margins": [20, 20, 20, 20],
|
||||
"arrowMargin": 4,
|
||||
"valuesGap": 5,
|
||||
"arrowLabelDistance": 5
|
||||
"arrowLabelDistance": 5,
|
||||
"forceDescsOnSide": false,
|
||||
"width": 1200,
|
||||
"height": 800
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
class Config:
|
||||
DEFAULT_FONT_FAMILY = "Ubuntu Mono"
|
||||
DEFAULT_FONT_SIZE = 16
|
||||
@ -21,6 +22,10 @@ class Config:
|
||||
ARROW_MARGIN = 4
|
||||
VALUES_GAP = 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:
|
||||
self.load(path)
|
||||
|
42
main.py
42
main.py
@ -3,6 +3,7 @@ import os
|
||||
|
||||
from schema import InstructionSetSchema
|
||||
|
||||
|
||||
description = """Examples:
|
||||
- Default theme (black on white):
|
||||
python main.py schema.xml -o out.jpg
|
||||
@ -15,20 +16,51 @@ description = """Examples:
|
||||
|
||||
- Transparent background:
|
||||
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__":
|
||||
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("-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("-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()
|
||||
|
||||
output = args.output
|
||||
if output is None:
|
||||
output = os.path.splitext(args.schema)[0] + ".png"
|
||||
if args.directory:
|
||||
if not os.path.isdir(args.schema):
|
||||
print(f"{args.schema} is not a directory")
|
||||
exit(-1)
|
||||
|
||||
schema = InstructionSetSchema(args.schema, args.config, args.display)
|
||||
schema.save(output)
|
||||
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)
|
||||
|
||||
else:
|
||||
output = args.output
|
||||
if output is None:
|
||||
output = os.path.splitext(args.schema)[0] + ".png"
|
||||
|
||||
processFile(args.schema, output, args.config, args.display)
|
3
range.py
3
range.py
@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Range:
|
||||
def __init__(self,
|
||||
start: int,
|
||||
@ -16,6 +18,7 @@ class Range:
|
||||
self.description = description
|
||||
self.values = values
|
||||
self.dependsOn = dependsOn
|
||||
self.lastValueY = -1
|
||||
|
||||
@property
|
||||
def bits(self) -> int:
|
||||
|
81
renderer.py
81
renderer.py
@ -1,28 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from config import Config
|
||||
from range import Range
|
||||
from schema import InstructionSetSchema
|
||||
|
||||
import pygame
|
||||
|
||||
from structure import Structure
|
||||
from vec import Vec
|
||||
|
||||
class Renderer:
|
||||
WIDTH = 1200
|
||||
HEIGHT = 800
|
||||
if TYPE_CHECKING:
|
||||
from config import Config
|
||||
from range import Range
|
||||
from schema import InstructionSetSchema
|
||||
|
||||
|
||||
class Renderer:
|
||||
def __init__(self, config: Config, display: bool = False) -> None:
|
||||
self.config = config
|
||||
self.display = display
|
||||
pygame.init()
|
||||
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.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:
|
||||
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:
|
||||
name = os.path.basename(schema.path)
|
||||
@ -45,8 +50,7 @@ class Renderer:
|
||||
|
||||
def drawStructure(self,
|
||||
struct: Structure,
|
||||
structures: dict[str,
|
||||
Structure],
|
||||
structures: dict[str, Structure],
|
||||
ox: float = 0,
|
||||
oy: float = 0) -> float:
|
||||
|
||||
@ -77,7 +81,21 @@ class Renderer:
|
||||
pygame.draw.line(self.surf, borderCol, [bitX, bitsY], [bitX, bitsY + bitH])
|
||||
|
||||
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
|
||||
|
||||
# Names + simple descriptions
|
||||
@ -123,10 +141,16 @@ class Renderer:
|
||||
bitH = self.config.BIT_HEIGHT
|
||||
arrowMargin = self.config.ARROW_MARGIN
|
||||
|
||||
if endX > startX:
|
||||
endX -= arrowMargin
|
||||
|
||||
else:
|
||||
endX += arrowMargin
|
||||
|
||||
pygame.draw.lines(self.surf, self.config.LINK_COLOR, False, [
|
||||
[startX, startY + bitH*1.5],
|
||||
[startX, endY + bitH/2],
|
||||
[endX - arrowMargin, endY + bitH/2]
|
||||
[endX, endY + bitH/2]
|
||||
])
|
||||
|
||||
def drawDescription(self,
|
||||
@ -140,7 +164,10 @@ class Renderer:
|
||||
bitW = self.config.BIT_WIDTH
|
||||
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)
|
||||
|
||||
@ -148,12 +175,17 @@ class Renderer:
|
||||
self.drawLink(midX, rStartY, descX, descY)
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
@ -191,8 +223,13 @@ class Renderer:
|
||||
rWidth = range_.bits * bitW
|
||||
|
||||
self.drawUnderbracket(rStartX, rStartX + rWidth, bitsY)
|
||||
prevY = bitsY + bitH * 1.5
|
||||
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
|
||||
dependStartX = bitsX + dependStartI * bitW
|
||||
dependWidth = dependRange.bits * bitW
|
||||
@ -200,7 +237,7 @@ class Renderer:
|
||||
self.drawUnderbracket(dependStartX, dependStartX + dependWidth, bitsY)
|
||||
|
||||
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 = {}
|
||||
for i in range(dependRange.bits):
|
||||
@ -232,11 +269,13 @@ class Renderer:
|
||||
self.drawArrow(x1, y, x2, y, data["description"])
|
||||
|
||||
self.drawArrow(rStartX + rWidth - bitW,
|
||||
prevY,
|
||||
prevRangeY,
|
||||
rStartX + rWidth - bitW,
|
||||
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)
|
||||
|
||||
return (descX, descY)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
from config import Config
|
||||
@ -7,9 +8,11 @@ from renderer import Renderer
|
||||
from structure import Structure
|
||||
from xml_loader import XMLLoader
|
||||
|
||||
|
||||
class UnsupportedFormatException(Exception):
|
||||
...
|
||||
|
||||
|
||||
class InstructionSetSchema:
|
||||
VALID_EXTENSIONS = ("yaml", "json", "xml")
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from range import Range
|
||||
|
||||
|
||||
class Structure:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
|
2
vec.py
2
vec.py
@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from math import sqrt
|
||||
|
||||
|
||||
class Vec:
|
||||
def __init__(self, x: float = 0, y: float = 0) -> None:
|
||||
self.x = x
|
||||
|
@ -1,7 +1,9 @@
|
||||
from __future__ import annotations
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from io import TextIOWrapper
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user