feat: referentiel projets dynamique + validation nommage by design
Durcit la convention de nommage des projets (dérive constatée : 'Sliding Automation', 'code_versioning'... au lieu des formes canoniques). - trilium_api.py : projets_canoniques() lit le référentiel = valeurs du label projet sur les notes de type=projet (source unique, pas de constante en dur). Note-projet CodeVersioning créée (manquait). - mcp_server.py : _valider_projet() branché dans les 6 tools de création (add_decision/history/backlog, new_conversation, create_entite, add_skill). Refuse un projet non canonique (suggestion si faute) ou inconnu (renvoi au processus de création de projet). Ne verrouille pas si référentiel illisible. - lint_audit.py : VAL-nommage aligné sur le référentiel (attrape casse, espace ET snake_case ; l'ancien 'contient un espace' ratait code_versioning). - Données : 79 notes ré-étiquetées vers les 3 formes canoniques. Quality by design : l'erreur de nommage devient impossible à l'écriture, le Lint n'est plus que le filet de sécurité.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import yaml
|
||||
import sys
|
||||
|
||||
from .waveform import WaveDrom
|
||||
from .assign import Assign
|
||||
from .version import version
|
||||
from .bitfield import BitField
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def fixQuotes(inputString):
|
||||
# fix double quotes in the input file. opening with yaml and dumping with json fix the issues.
|
||||
yamlCode = yaml.load(inputString, Loader=yaml.FullLoader)
|
||||
fixedString = json.dumps(yamlCode, indent=4)
|
||||
return fixedString
|
||||
|
||||
|
||||
def render(source="", output=[], strict_js_features = False):
|
||||
source = json.loads(fixQuotes(source))
|
||||
if source.get("signal"):
|
||||
return WaveDrom().render_waveform(0, source, output, strict_js_features)
|
||||
elif source.get("assign"):
|
||||
return Assign().render(0, source, output)
|
||||
elif source.get("reg"):
|
||||
return BitField().renderJson(source)
|
||||
|
||||
|
||||
def render_write(source, output, strict_js_features = False):
|
||||
jinput = source.read()
|
||||
out = render(jinput, strict_js_features=strict_js_features)
|
||||
out.write(output)
|
||||
|
||||
|
||||
def render_file(source, output, strict_js_features = False):
|
||||
out = open(output, "w")
|
||||
render_write(open(source, "r"), out, strict_js_features=strict_js_features)
|
||||
out.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="")
|
||||
parser.add_argument("--input", "-i", help="<input wavedrom source filename>",
|
||||
required=True, type=argparse.FileType('r'))
|
||||
parser.add_argument("--svg", "-s", help="<output SVG image file name>",
|
||||
nargs='?', type=argparse.FileType('w'), default=sys.stdout)
|
||||
args = parser.parse_args()
|
||||
|
||||
render_write(args.input, args.svg, False)
|
||||
@@ -0,0 +1,174 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Translated to Python from original file:
|
||||
# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
|
||||
|
||||
from collections import namedtuple
|
||||
import svgwrite
|
||||
|
||||
from .base import SVGBase
|
||||
|
||||
|
||||
class RenderState:
|
||||
def __init__(self, x=0, y=0, xmax=0):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.xmax = xmax
|
||||
|
||||
def __str__(self):
|
||||
return "x={} y={}, xmax={}".format(self.x, self.y, self.xmax)
|
||||
|
||||
|
||||
RenderObject = namedtuple("RenderObject", "name x y")
|
||||
|
||||
|
||||
class Assign(SVGBase):
|
||||
def render_tree(self, tree, state):
|
||||
state.xmax = max(state.xmax, state.x)
|
||||
y = state.y
|
||||
for i in range(1, len(tree)):
|
||||
if isinstance(tree[i], list):
|
||||
state = self.render_tree(tree[i], RenderState(x=state.x+1, y=state.y, xmax=state.xmax))
|
||||
else:
|
||||
tree[i] = RenderObject(name=tree[i], x=state.x+1, y=state.y)
|
||||
state.y += 2
|
||||
tree[0] = RenderObject(name=tree[0], x=state.x, y=round((y+state.y-2)/2))
|
||||
state.x -= 1
|
||||
|
||||
return state
|
||||
|
||||
def draw_body(self, type, ymin, ymax):
|
||||
circle = ' M 4,0 C 4,1.1 3.1,2 2,2 0.9,2 0,1.1 0,0 c 0,-1.1 0.9,-2 2,-2 1.1,0 2,0.9 2,2 z'
|
||||
gates = {
|
||||
'~': 'M -11,-6 -11,6 0,0 z m -5,6 5,0' + circle,
|
||||
'=': 'M -11,-6 -11,6 0,0 z m -5,6 5,0',
|
||||
'&': 'm -16,-10 5,0 c 6,0 11,4 11,10 0,6 -5,10 -11,10 l -5,0 z',
|
||||
'~&': 'm -16,-10 5,0 c 6,0 11,4 11,10 0,6 -5,10 -11,10 l -5,0 z' + circle,
|
||||
'|': 'm -18,-10 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 2.5,-5 2.5,-15 0,-20 z',
|
||||
'~|': 'm -18,-10 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 2.5,-5 2.5,-15 0,-20 z' + circle,
|
||||
'^': 'm -21,-10 c 1,3 2,6 2,10 m 0,0 c 0,4 -1,7 -2,10 m 3,-20 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 1,-3 2,-6 2,-10 0,-4 -1,-7 -2,-10 z',
|
||||
'~^': 'm -21,-10 c 1,3 2,6 2,10 m 0,0 c 0,4 -1,7 -2,10 m 3,-20 4,0 c 6,0 12,5 14,10 -2,5 -8,10 -14,10 l -4,0 c 1,-3 2,-6 2,-10 0,-4 -1,-7 -2,-10 z' + circle,
|
||||
'+': 'm -8,5 0,-10 m -5,5 10,0 m 3,0 c 0,4.418278 -3.581722,8 -8,8 -4.418278,0 -8,-3.581722 -8,-8 0,-4.418278 3.581722,-8 8,-8 4.418278,0 8,3.581722 8,8 z',
|
||||
'*': 'm -4,4 -8,-8 m 0,8 8,-8 m 4,4 c 0,4.418278 -3.581722,8 -8,8 -4.418278,0 -8,-3.581722 -8,-8 0,-4.418278 3.581722,-8 8,-8 4.418278,0 8,3.581722 8,8 z'
|
||||
}
|
||||
iec = {
|
||||
"BUF": 1, "INV": 1, "AND": '&', "NAND": '&',
|
||||
"OR": '\u22651', "NOR": '\u22651', "XOR": '=1', "XNOR": '=1', "box": ''
|
||||
}
|
||||
circled = { "INV", "NAND", "NOR", "XNOR" }
|
||||
|
||||
if ymax == ymin:
|
||||
ymax = 4
|
||||
ymin = -4
|
||||
|
||||
if type in gates:
|
||||
return self.element.path(class_='gate', d=gates[type])
|
||||
elif type in iec:
|
||||
g = self.container.g()
|
||||
if type in circled:
|
||||
path = self.element.path(class_="gate", d="m -16,{} 16,0 0,{} -16,0 z {}".format(ymin-3, ymax-ymin+6, circle))
|
||||
else:
|
||||
path = self.element.path(class_="gate", d="m -16,{} 16,0 0,{} -16,0 z".format(ymin-3, ymax-ymin+6))
|
||||
g.add(path)
|
||||
tspan = self.element.tspan(iec[type], x=[-14], y=[4], class_='wirename')
|
||||
text = self.element.text('')
|
||||
text.add(tspan)
|
||||
g.add(text)
|
||||
return g
|
||||
else:
|
||||
tspan = self.element.tspan(type, x=[-14], y=[4], class_='wirename')
|
||||
text = self.element.text('')
|
||||
text.add(tspan)
|
||||
return text
|
||||
|
||||
def draw_gate(self, spec):
|
||||
ret = self.container.g()
|
||||
|
||||
ys = [s[1] for s in spec[2:]]
|
||||
|
||||
ymin = min(ys)
|
||||
ymax = max(ys)
|
||||
|
||||
g = self.container.g(transform="translate(16,0)")
|
||||
g.add(self.element.path(d="M {},{} {},{}".format(spec[2][0], ymin, spec[2][0], ymax), class_='wire'))
|
||||
ret.add(g)
|
||||
|
||||
for s in spec[2:]:
|
||||
path = self.element.path(d="m {},{} 16,0".format(s[0], s[1]), class_='wire')
|
||||
ret.add(self.container.g().add(path))
|
||||
|
||||
g = self.container.g(transform="translate({},{})".format(spec[1][0], spec[1][1]))
|
||||
g.add(self.element.title(spec[0]))
|
||||
g.add(self.draw_body(spec[0], ymin - spec[1][1], ymax - spec[1][1]))
|
||||
ret.add(g)
|
||||
|
||||
return ret
|
||||
|
||||
def draw_boxes(self, tree, xmax):
|
||||
ret = self.container.g()
|
||||
spec = []
|
||||
|
||||
if isinstance(tree, list):
|
||||
spec.append(tree[0].name);
|
||||
spec.append([32 * (xmax - tree[0].x), 8 * tree[0].y]);
|
||||
for t in tree[1:]:
|
||||
if isinstance(t, list):
|
||||
spec.append([32 * (xmax - t[0].x), 8 * t[0].y])
|
||||
else:
|
||||
spec.append([32 * (xmax - t.x), 8 * t.y])
|
||||
|
||||
ret.add(self.draw_gate(spec))
|
||||
|
||||
for t in tree[1:]:
|
||||
ret.add(self.draw_boxes(t, xmax))
|
||||
else:
|
||||
fname = tree.name
|
||||
fx = 32 * (xmax - tree.x)
|
||||
fy = 8 * tree.y
|
||||
g = self.container.g(transform="translate({},{})".format(fx, fy))
|
||||
g.add(self.element.title(fname))
|
||||
g.add(self.element.path(d='M 2,0 a 2,2 0 1 1 -4,0 2,2 0 1 1 4,0 z'))
|
||||
tspan = self.element.tspan(fname, x=[-4], y=[4], class_='pinname')
|
||||
text = self.element.text('')
|
||||
text.add(tspan)
|
||||
g.add(text)
|
||||
ret.add(g)
|
||||
|
||||
return ret
|
||||
|
||||
def render(self, index = 0, source = {}, output = []):
|
||||
STYLE = ".pinname {font-size:12px; font-style:normal; font-variant:normal; font-weight:500; font-stretch:normal; text-align:center; text-anchor:end; font-family:Helvetica} .wirename {font-size:12px; font-style:normal; font-variant:normal; font-weight:500; font-stretch:normal; text-align:center; text-anchor:start; font-family:Helvetica} .wirename:hover {fill:blue} .gate {color:#000; fill:#ffc; fill-opacity: 1;stroke:#000; stroke-width:1; stroke-opacity:1} .gate:hover {fill:red !important; } .wire {fill:none; stroke:#000; stroke-width:1; stroke-opacity:1} .grid {fill:#fff; fill-opacity:1; stroke:none}"
|
||||
|
||||
tree = source.get("assign")
|
||||
state = RenderState(x=0, y=2, xmax=0)
|
||||
|
||||
for t in tree:
|
||||
state = self.render_tree(t, state)
|
||||
state.x += 1
|
||||
|
||||
xmax = state.xmax + 3
|
||||
|
||||
width = 32 * (xmax + 1) + 1
|
||||
height = 8 * (state.y + 1) - 7
|
||||
ilen = 4 * (xmax + 1)
|
||||
jlen = state.y + 1
|
||||
|
||||
grid = self.container.g()
|
||||
|
||||
for i in range(ilen+1):
|
||||
for j in range(jlen+1):
|
||||
grid.add(self.element.rect(height=1, width=1, x=(i * 8 - 0.5), y=(j * 8 - 0.5), class_='grid'))
|
||||
|
||||
for t in tree:
|
||||
content = self.draw_boxes(t, xmax)
|
||||
|
||||
|
||||
attr = { 'viewBox': "0 0 {} {}".format(width, height)}
|
||||
template = svgwrite.Drawing(id="svgcontent_{index}".format(index=index), size=[width, height], **attr)
|
||||
template.defs.add(svgwrite.container.Style(content=STYLE))
|
||||
g = self.container.g(transform="translate(0.5,0.5)")
|
||||
g.add(grid)
|
||||
g.add(content)
|
||||
template.add(g)
|
||||
return template
|
||||
@@ -0,0 +1,4 @@
|
||||
class AttrDict(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AttrDict, self).__init__(*args, **kwargs)
|
||||
self.__dict__ = self
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import svgwrite
|
||||
from .attrdict import AttrDict
|
||||
|
||||
class SVGBase(object):
|
||||
container = AttrDict({
|
||||
"defs": svgwrite.container.Defs,
|
||||
"g": svgwrite.container.Group,
|
||||
"marker": svgwrite.container.Marker,
|
||||
"use": svgwrite.container.Use,
|
||||
})
|
||||
element = AttrDict({
|
||||
"rect": svgwrite.shapes.Rect,
|
||||
"path": svgwrite.path.Path,
|
||||
"text": svgwrite.text.Text,
|
||||
"tspan": svgwrite.text.TSpan,
|
||||
"title": svgwrite.base.Title,
|
||||
"line": svgwrite.shapes.Line,
|
||||
})
|
||||
@@ -0,0 +1,259 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Translated to Python from original file:
|
||||
# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
|
||||
|
||||
from math import floor
|
||||
|
||||
import svgwrite
|
||||
|
||||
from .base import SVGBase
|
||||
from .tspan import TspanParser
|
||||
|
||||
class Options:
|
||||
def __init__(self, vspace=80, hspace=800, lanes=1, bits=32, hflip=False, vflip=False, fontsize=14, fontfamily='sans-serif', fontweight='normal'):
|
||||
self.vspace = vspace if vspace > 19 else 80
|
||||
self.hspace = hspace if hspace > 39 else 800
|
||||
self.lanes = lanes if lanes > 0 else 1
|
||||
self.bits = bits if bits > 4 else 32
|
||||
self.hflip = hflip
|
||||
self.vflip = vflip
|
||||
self.fontsize = fontsize if fontsize > 5 else 14
|
||||
self.fontfamily = fontfamily
|
||||
self.fontweight = fontweight
|
||||
|
||||
colors = {2: 0, 3: 80, 4: 170, 5: 45, 6: 126, 7: 215}
|
||||
|
||||
def type_style(t):
|
||||
if t in colors.keys():
|
||||
return ";fill:hsl({},100%,50%)".format(colors[t])
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class BitField(SVGBase):
|
||||
def tspan_parse(self, text):
|
||||
parser = TspanParser()
|
||||
parser.feed(text)
|
||||
return parser.get_text()
|
||||
|
||||
def hline(self, len, x=0, y=0):
|
||||
return self.element.line(start=(x,y), end=(x+len,y))
|
||||
|
||||
def vline(self, len, x=0, y=0):
|
||||
return self.element.line(start=(x,y), end=(x,y+len))
|
||||
|
||||
def get_text(self, body, x, y=None):
|
||||
x_list = None
|
||||
if x:
|
||||
x_list = [x]
|
||||
y_list = None
|
||||
if y:
|
||||
y_list = [y]
|
||||
text = self.element.text('', x=x_list, y=y_list)
|
||||
for t in self.tspan_parse(str(body)):
|
||||
text.add(t)
|
||||
return text
|
||||
|
||||
def get_label(self, attr, x, y, step=0, length=0):
|
||||
if isinstance(attr, int):
|
||||
attr = int(attr)
|
||||
res = []
|
||||
for i in range(length):
|
||||
val = (attr >> i) & 1
|
||||
xi = x + step * (length / 2 - i - 0.5)
|
||||
res.append(self.get_text(val, xi, y))
|
||||
return res
|
||||
else:
|
||||
if '\n' in attr:
|
||||
names = attr.split('\n')
|
||||
count = len(names)
|
||||
return [
|
||||
self.get_text(name, x, y + (-(count - 1) / 2 + i) * self.opt.fontsize)
|
||||
for (i, name) in enumerate(names)
|
||||
]
|
||||
return [self.get_text(attr, x, y)]
|
||||
|
||||
def get_attrs(self, e, step, lsbm, msbm):
|
||||
if self.opt.vflip:
|
||||
x = step * (msbm + lsbm) / 2
|
||||
else:
|
||||
x = step * (self.mod - ((msbm + lsbm) / 2) - 1)
|
||||
attr = e['attr']
|
||||
bits = e['bits']
|
||||
attrs = [attr]
|
||||
# 'attr' supports both a scalar and a list.
|
||||
if isinstance(attr, list):
|
||||
attrs = attr
|
||||
return [self.get_label(a, x, 16 * i, step, bits)
|
||||
for (i, a) in enumerate(attrs)]
|
||||
|
||||
def labelArr(self, desc):
|
||||
step = self.opt.hspace / self.mod
|
||||
bits = self.container.g(transform="translate({},{})".format(step/2, self.opt.vspace/5))
|
||||
names = self.container.g(transform="translate({},{})".format(step/2, self.opt.vspace/2+4))
|
||||
attrs = self.container.g(transform="translate({},{})".format(step/2, self.opt.vspace))
|
||||
blanks = self.container.g(transform="translate(0,{})".format(self.opt.vspace/4))
|
||||
|
||||
for e in desc:
|
||||
lsbm = 0
|
||||
msbm = self.mod - 1
|
||||
lsb = self.index * self.mod
|
||||
msb = (self.index + 1) * self.mod - 1
|
||||
|
||||
if floor(e["lsb"] / self.mod) == self.index:
|
||||
lsbm = e["lsbm"]
|
||||
lsb = e["lsb"]
|
||||
if floor(e["msb"] / self.mod) == self.index:
|
||||
msb = e["msb"]
|
||||
msbm = e["msbm"]
|
||||
else:
|
||||
if floor(e["msb"] / self.mod) == self.index:
|
||||
msb = e["msb"]
|
||||
msbm = e["msbm"]
|
||||
else:
|
||||
continue
|
||||
|
||||
if self.opt.vflip:
|
||||
bits.add(self.get_text(lsb, x=[step*lsbm]))
|
||||
else:
|
||||
bits.add(self.get_text(lsb, x=[step*(self.mod-lsbm - 1)]))
|
||||
if lsbm != msbm:
|
||||
if self.opt.vflip:
|
||||
bits.add(self.get_text(msb, x=[step * msbm]))
|
||||
else:
|
||||
bits.add(self.get_text(msb, x=[step * (self.mod - msbm - 1)]))
|
||||
if e.get('name'):
|
||||
if self.opt.vflip:
|
||||
x = step*(msbm+lsbm)/2
|
||||
else:
|
||||
x = step*(self.mod-((msbm+lsbm)/2)-1)
|
||||
for n in self.get_label(e['name'], x, 0):
|
||||
names.add(n)
|
||||
|
||||
if not e.get('name') or e.get('type'):
|
||||
style = 'fill-opacity:0.1' + type_style(e.get('type', 0))
|
||||
if self.opt.vflip:
|
||||
insert_x = lsbm
|
||||
else:
|
||||
insert_x = self.mod - msbm - 1
|
||||
insert = [step * insert_x, 0]
|
||||
size = [step * (msbm - lsbm + 1), self.opt.vspace/2]
|
||||
blanks.add(self.element.rect(insert=insert, size=size, style=style))
|
||||
if e.get('attr') is not None:
|
||||
for attr in self.get_attrs(e, step, lsbm, msbm):
|
||||
for a in attr:
|
||||
attrs.add(a)
|
||||
|
||||
g = self.container.g()
|
||||
g.add(blanks)
|
||||
g.add(bits)
|
||||
g.add(names)
|
||||
g.add(attrs)
|
||||
return g
|
||||
|
||||
def labels(self, desc):
|
||||
g = self.container.g(text_anchor='middle')
|
||||
g.add(self.labelArr(desc))
|
||||
return g
|
||||
|
||||
def cage(self, desc):
|
||||
hspace = self.opt.hspace
|
||||
vspace = self.opt.vspace
|
||||
mod = self.mod
|
||||
|
||||
g = self.container.g(stroke='black', stroke_width=1, stroke_linecap='round', transform="translate(0,{})".format(vspace/4))
|
||||
|
||||
g.add(self.hline(hspace));
|
||||
if self.opt.vflip:
|
||||
g.add(self.vline(0));
|
||||
else:
|
||||
g.add(self.vline(vspace / 2));
|
||||
g.add(self.hline(hspace, 0, vspace / 2));
|
||||
|
||||
i = self.index * mod
|
||||
if self.opt.vflip:
|
||||
r = range(0, mod + 1)
|
||||
else:
|
||||
r = range(mod, 0, -1)
|
||||
for j in r:
|
||||
if j == mod or any([(e["lsb"] == i) for e in desc]):
|
||||
g.add(self.vline((vspace / 2), j * (hspace / mod)));
|
||||
else:
|
||||
g.add(self.vline((vspace / 16), j * (hspace / mod)));
|
||||
g.add(self.vline((vspace / 16), j * (hspace / mod), vspace * 7 / 16));
|
||||
i += 1
|
||||
|
||||
return g
|
||||
|
||||
def lane(self, desc):
|
||||
x = 4.5
|
||||
if self.opt.hflip:
|
||||
i = self.index
|
||||
else:
|
||||
i = self.opt.lanes-self.index-1
|
||||
y = i * self.opt.vspace + 0.5
|
||||
g = self.container.g(transform = "translate({},{})".format(x, y),
|
||||
text_anchor = "middle",
|
||||
font_size = self.opt.fontsize,
|
||||
font_family = self.opt.fontfamily,
|
||||
font_weight = self.opt.fontweight)
|
||||
|
||||
g.add(self.cage(desc))
|
||||
g.add(self.labels(desc))
|
||||
return g
|
||||
|
||||
def get_max_attrs(self, desc):
|
||||
max_count = 0
|
||||
for e in desc:
|
||||
if 'attr' in e:
|
||||
if isinstance(e['attr'], list):
|
||||
max_count = max(max_count, len(e['attr']))
|
||||
else:
|
||||
max_count = max(max_count, 1)
|
||||
return max_count
|
||||
|
||||
def render(self, desc, opt = Options()):
|
||||
self.opt = opt
|
||||
|
||||
# Compute extra per-lane space needed if there are more than one attr
|
||||
# for any field. This spaces all lanes uniformly, matching the lane
|
||||
# with the most attr's.
|
||||
extra_attrs = 0
|
||||
max_attrs = self.get_max_attrs(desc)
|
||||
if max_attrs > 1:
|
||||
extra_attrs = max_attrs - 1
|
||||
self.extra_attr_space = extra_attrs * 16
|
||||
|
||||
width = opt.hspace + 9
|
||||
height = (opt.vspace + self.extra_attr_space) * opt.lanes + 5
|
||||
|
||||
template = svgwrite.Drawing()
|
||||
template["width"] = width
|
||||
template["height"] = height
|
||||
template["class"] = "WaveDrom"
|
||||
template.viewbox(0, 0, width, height)
|
||||
|
||||
lsb = 0
|
||||
self.mod = int(opt.bits / opt.lanes)
|
||||
|
||||
for e in desc:
|
||||
e["lsb"] = lsb
|
||||
e["lsbm"] = lsb % self.mod
|
||||
lsb += e['bits']
|
||||
e['msb'] = lsb - 1
|
||||
e['msbm'] = e['msb'] % self.mod
|
||||
|
||||
for i in range(opt.lanes):
|
||||
self.index = i
|
||||
template.add(self.lane(desc))
|
||||
|
||||
return template
|
||||
|
||||
def renderJson(self, source):
|
||||
opt = Options()
|
||||
if source.get("config"):
|
||||
opt = Options(**source['config'])
|
||||
if source.get("reg"):
|
||||
return self.render(source['reg'], opt)
|
||||
@@ -0,0 +1,421 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Translated to Python from original file:
|
||||
# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
|
||||
|
||||
from .attrdict import AttrDict
|
||||
css = AttrDict({})
|
||||
css.default = """
|
||||
text{font-size:11pt;
|
||||
font-style:normal;
|
||||
font-variant:normal;
|
||||
font-weight:normal;
|
||||
font-stretch:normal;
|
||||
text-align:center;
|
||||
fill-opacity:1;
|
||||
font-family:Helvetica}
|
||||
.h1{font-size:33pt;
|
||||
font-weight:bold}
|
||||
.h2{font-size:27pt;
|
||||
font-weight:bold}
|
||||
.h3{font-size:20pt;
|
||||
font-weight:bold}
|
||||
.h4{font-size:14pt;
|
||||
font-weight:bold}
|
||||
.h5{font-size:11pt;
|
||||
font-weight:bold}
|
||||
.h6{font-size:8pt;
|
||||
font-weight:bold}
|
||||
.muted{fill:#aaa}
|
||||
.warning{fill:#f6b900}
|
||||
.error{fill:#f60000}
|
||||
.info{fill:#0041c4}
|
||||
.success{fill:#00ab00}
|
||||
.s1{fill:none;
|
||||
stroke:#000;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
.s2{fill:none;
|
||||
stroke:#000;
|
||||
stroke-width:0.5;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
.s3{color:#000;
|
||||
fill:none;
|
||||
stroke:#000;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:1, 3;
|
||||
stroke-dashoffset:0;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s4{color:#000;
|
||||
fill:none;
|
||||
stroke:#000;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none;
|
||||
stroke-dashoffset:0;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s5{fill:#fff;
|
||||
stroke:none}
|
||||
.s6{fill:#000;
|
||||
fill-opacity:1;
|
||||
stroke:none}
|
||||
.s7{color:#000;
|
||||
fill:#fff;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s8{color:#000;
|
||||
fill:#ffffb4;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s9{color:#000;
|
||||
fill:#ffe0b9;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s10{color:#000;
|
||||
fill:#b9e0ff;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s11{color:#000;
|
||||
fill:#ccfdfe;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s12{color:#000;
|
||||
fill:#cdfdc5;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s13{color:#000;
|
||||
fill:#f0c1fb;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s14{color:#000;
|
||||
fill:#f5c2c0;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s15{fill:#0041c4;
|
||||
fill-opacity:1;
|
||||
stroke:none}
|
||||
.s16{fill:none;
|
||||
stroke:#0041c4;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
"""
|
||||
|
||||
css.narrow = """
|
||||
text{font-size:11pt;
|
||||
font-style:normal;
|
||||
font-variant:normal;
|
||||
font-weight:normal;
|
||||
font-stretch:normal;
|
||||
text-align:center;
|
||||
fill-opacity:1;
|
||||
font-family:Helvetica}
|
||||
.muted{fill:#aaa}
|
||||
.warning{fill:#f6b900}
|
||||
.error{fill:#f60000}
|
||||
.info{fill:#0041c4}
|
||||
.success{fill:#00ab00}
|
||||
.h1{font-size:33pt;font-weight:bold}
|
||||
.h2{font-size:27pt;font-weight:bold}
|
||||
.h3{font-size:20pt;font-weight:bold}
|
||||
.h4{font-size:14pt;font-weight:bold}
|
||||
.h5{font-size:11pt;font-weight:bold}
|
||||
.h6{font-size:8pt;font-weight:bold}
|
||||
.s1{fill:none;
|
||||
stroke:#000000;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
.s2{fill:none;
|
||||
stroke:#000000;
|
||||
stroke-width:0.5;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
.s3{color:#000000;
|
||||
fill:none;
|
||||
stroke:#000000;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:1, 3;
|
||||
stroke-dashoffset:0;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s4{color:#000000;
|
||||
fill:none;
|
||||
stroke:#000000;
|
||||
stroke-width:1;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none;
|
||||
stroke-dashoffset:0;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s5{fill:#ffffff;stroke:none}
|
||||
.s6{color:#000000;
|
||||
fill:#ffffb4;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s7{color:#000000;
|
||||
fill:#ffe0b9;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s8{color:#000000;
|
||||
fill:#b9e0ff;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s9{fill:#000000;fill-opacity:1;stroke:none}
|
||||
.s10{color:#000000;
|
||||
fill:#ffffff;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:1px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
"""
|
||||
|
||||
css.lowkey="""
|
||||
text{font-size:11pt;
|
||||
font-style:normal;
|
||||
font-variant:normal;
|
||||
font-weight:normal;
|
||||
font-stretch:normal;
|
||||
text-align:center;
|
||||
fill-opacity:1;
|
||||
font-family:Helvetica}
|
||||
.muted{fill:#aaa}
|
||||
.warning{fill:#f6b900}
|
||||
.error{fill:#f60000}
|
||||
.info{fill:#0041c4}
|
||||
.success{fill:#00ab00}
|
||||
.h1{font-size:33pt;
|
||||
font-weight:bold}
|
||||
.h2{font-size:27pt;
|
||||
font-weight:bold}
|
||||
.h3{font-size:20pt;
|
||||
font-weight:bold}
|
||||
.h4{font-size:14pt;
|
||||
font-weight:bold}
|
||||
.h5{font-size:11pt;
|
||||
font-weight:bold}
|
||||
.h6{font-size:8pt;
|
||||
font-weight:bold}
|
||||
.s1{fill:none;
|
||||
stroke:#606060;
|
||||
stroke-width:0.75px;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
.s2{fill:none;
|
||||
stroke:#606060;
|
||||
stroke-width:0.5;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
.s3{color:#000;
|
||||
fill:none;
|
||||
stroke:#606060;
|
||||
stroke-width:0.75px;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:1, 3;
|
||||
stroke-dashoffset:0;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s4{color:#000;
|
||||
fill:none;
|
||||
stroke:#606060;
|
||||
stroke-width:0.75px;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none;
|
||||
stroke-dashoffset:0;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible}
|
||||
.s5{fill:#ffffff;
|
||||
stroke:none}
|
||||
.s6{color:#000;
|
||||
fill:#eaeaea;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:0.25px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s7{color:#000;
|
||||
fill:#d7d7d7;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:0.25px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s8{color:#000;
|
||||
fill:#c0c0c0;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:0.25px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s9{fill:#606060;
|
||||
fill-opacity:1;
|
||||
stroke:none}
|
||||
.s10{color:#000;
|
||||
fill:#fff;
|
||||
fill-opacity:1;
|
||||
fill-rule:nonzero;
|
||||
stroke:none;
|
||||
stroke-width:0.25px;
|
||||
marker:none;
|
||||
visibility:visible;
|
||||
display:inline;
|
||||
overflow:visible;
|
||||
enable-background:accumulate}
|
||||
.s11{fill:#0041c4;
|
||||
fill-opacity:1;
|
||||
stroke:none}
|
||||
.s12{fill:none;
|
||||
stroke:#0041c4;
|
||||
stroke-width:0.75px;
|
||||
stroke-linecap:round;
|
||||
stroke-linejoin:miter;
|
||||
stroke-miterlimit:4;
|
||||
stroke-opacity:1;
|
||||
stroke-dasharray:none}
|
||||
"""
|
||||
@@ -0,0 +1,140 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import sys
|
||||
|
||||
import svgwrite
|
||||
from six import string_types
|
||||
from svgwrite.base import BaseElement
|
||||
from svgwrite.etree import etree
|
||||
from .attrdict import AttrDict
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
from HTMLParser import HTMLParser
|
||||
else:
|
||||
from html.parser import HTMLParser
|
||||
|
||||
|
||||
class TspanParser(HTMLParser, object):
|
||||
tags = {
|
||||
'o': {'text_decoration': 'overline'},
|
||||
'ins': {'text_decoration': 'underline'},
|
||||
'sub': {'baseline_shift': 'sub'},
|
||||
'sup': {'baseline_shift': 'super'},
|
||||
'b': {'font_weight': 'bold'},
|
||||
'i': {'font_style': 'italic'},
|
||||
's': {'text_decoration': 'line-through'},
|
||||
'tt': {'font_family': 'monospace'},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super(TspanParser, self).__init__()
|
||||
self.text = []
|
||||
self.state = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
self.state.append(tag)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if self.state.pop() != tag:
|
||||
raise RuntimeError("Unexpected closing tag: {}".format(tag))
|
||||
|
||||
def get_style(self):
|
||||
return {k: v for d in [self.tags[t] for t in self.state] for k, v in d.items()}
|
||||
|
||||
def handle_data(self, data):
|
||||
if len(self.state) == 0:
|
||||
self.text.append(svgwrite.text.TSpan(data))
|
||||
else:
|
||||
self.text.append(svgwrite.text.TSpan(data, **self.get_style()))
|
||||
|
||||
def get_text(self):
|
||||
return self.text
|
||||
|
||||
|
||||
class JsonMLElement(BaseElement):
|
||||
"""Class that generates xml elements from jsonml."""
|
||||
def __init__(self, source, **extra):
|
||||
"""Constructs from jsonml source."""
|
||||
self._jsonml = self.extract_element(source)
|
||||
self.elementname = self._jsonml.tagname
|
||||
self._jsonml.attributes.update(extra)
|
||||
super(JsonMLElement, self).__init__(**extra)
|
||||
|
||||
def extract_element(self, e):
|
||||
"""Extract AttrDict from jsonml
|
||||
|
||||
This function non-recursively extracts an AttrDict from jsonml.
|
||||
This AttrDict has the three elements tagname, attributes and
|
||||
element_list according to the jsonml specification.
|
||||
|
||||
:param e: element as jsonml list/tuple
|
||||
:return: AttrDict
|
||||
"""
|
||||
if not isinstance(e, (list, tuple)):
|
||||
raise ValueError("JsonML must be a list")
|
||||
if len(e) == 0:
|
||||
raise ValueError("JsonML cannot be an empty list")
|
||||
if not isinstance(e[0], string_types):
|
||||
raise ValueError("JsonML tagname must be string")
|
||||
ret = AttrDict({"tagname": e[0], "attributes": {}, "element-list": []})
|
||||
if len(e) > 1:
|
||||
if isinstance(e[1], dict):
|
||||
ret.attributes = e[1]
|
||||
if len(e) > 2:
|
||||
ret.element_list = e[2:]
|
||||
else:
|
||||
ret.element_list = e[1:]
|
||||
return ret
|
||||
|
||||
def get_xml_element(self, e):
|
||||
"""Generate xml element from jsonml AttrDict
|
||||
|
||||
Recursively generates xml element from jsonml AttrDict.
|
||||
|
||||
:param e: jsonml AttrDict
|
||||
:return: xml element
|
||||
"""
|
||||
|
||||
# create element
|
||||
element = etree.Element(e.tagname)
|
||||
# set element attributes
|
||||
for attribute, value in sorted(e.attributes.items()):
|
||||
# filter 'None' values
|
||||
if value is not None:
|
||||
value = self.value_to_string(value)
|
||||
if value: # just add not empty attributes
|
||||
element.set(attribute, value)
|
||||
# store the last xml sibling, because we may need to add
|
||||
# text to it's tail. This is to support the tagged text
|
||||
# style ("<tspan>a<tspan>b</tspan>c</tspan>")
|
||||
last = None
|
||||
for c in e.element_list:
|
||||
if isinstance(c, string_types):
|
||||
# Strings need special treatment for insertion
|
||||
# as those are not elements
|
||||
if last is None:
|
||||
# No non-text element seen so far
|
||||
if element.text is None:
|
||||
# No other element seen so far
|
||||
element.text = c
|
||||
else:
|
||||
# Append to other texts
|
||||
element.text += c
|
||||
else:
|
||||
# There was an element already
|
||||
if last.tail is None:
|
||||
# No text after that so far
|
||||
last.tail = c
|
||||
else:
|
||||
# Append to other text
|
||||
last.tail += c
|
||||
else:
|
||||
# Recurse
|
||||
last = self.get_xml_element(self.extract_element(c))
|
||||
element.append(last)
|
||||
|
||||
return element
|
||||
|
||||
def get_xml(self):
|
||||
return self.get_xml_element(self._jsonml)
|
||||
@@ -0,0 +1,34 @@
|
||||
# file generated by setuptools-scm
|
||||
# don't change, don't track in version control
|
||||
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"__version_tuple__",
|
||||
"version",
|
||||
"version_tuple",
|
||||
"__commit_id__",
|
||||
"commit_id",
|
||||
]
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
||||
COMMIT_ID = Union[str, None]
|
||||
else:
|
||||
VERSION_TUPLE = object
|
||||
COMMIT_ID = object
|
||||
|
||||
version: str
|
||||
__version__: str
|
||||
__version_tuple__: VERSION_TUPLE
|
||||
version_tuple: VERSION_TUPLE
|
||||
commit_id: COMMIT_ID
|
||||
__commit_id__: COMMIT_ID
|
||||
|
||||
__version__ = version = '2.0.3.post3'
|
||||
__version_tuple__ = version_tuple = (2, 0, 3, 'post3')
|
||||
|
||||
__commit_id__ = commit_id = None
|
||||
@@ -0,0 +1,925 @@
|
||||
# Copyright wavedrompy contributors.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Originally translated to Python from original file:
|
||||
# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
|
||||
# Now many parts have been rewritten and diverged
|
||||
|
||||
import sys
|
||||
import math
|
||||
import re
|
||||
from itertools import chain
|
||||
import svgwrite
|
||||
from .attrdict import AttrDict
|
||||
from collections import deque
|
||||
|
||||
from six import string_types
|
||||
|
||||
from wavedrom.tspan import JsonMLElement
|
||||
from . import waveskin, css
|
||||
from .base import SVGBase
|
||||
|
||||
|
||||
class WaveDrom(SVGBase):
|
||||
def __init__(self):
|
||||
self.font_width = 7
|
||||
self.lane = AttrDict({
|
||||
"xs": 20, # tmpgraphlane0.width
|
||||
"ys": 20, # tmpgraphlane0.height
|
||||
"xg": 120, # tmpgraphlane0.x
|
||||
"yg": 0, # head gap
|
||||
"yh0": 0, # head gap title
|
||||
"yh1": 0, # head gap
|
||||
"yf0": 0, # foot gap
|
||||
"yf1": 0, # foot gap
|
||||
"y0": 5, # tmpgraphlane0.y
|
||||
"yo": 30, # tmpgraphlane1.y - y0
|
||||
"tgo": -10, # tmptextlane0.x - xg
|
||||
"ym": 15, # tmptextlane0.y - y0
|
||||
"xlabel": 6, # tmptextlabel.x - xg
|
||||
"xmax": 1,
|
||||
"scale": 1,
|
||||
"head": {},
|
||||
"foot": {}
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def stretch_bricks(wave, stretch):
|
||||
stretcher = {
|
||||
"Pclk": "111", "Nclk": "000",
|
||||
"pclk": "111", "nclk": "000",
|
||||
"0": "000", "1": "111", "x": "xxx", "d": "ddd", "u": "uuu", "z": "zzz",
|
||||
"2": "vvv-2", "3": "vvv-3", "4": "vvv-4", "5": "vvv-5", "6": "vvv-6",
|
||||
"7": "vvv-7", "8": "vvv-8", "9": "vvv-9"}
|
||||
|
||||
if stretch == -0.5:
|
||||
# This is the only valid non-integer value, it essentially means halfing down. Further subsampling
|
||||
# does not work I think..
|
||||
return wave[0::2]
|
||||
else:
|
||||
stretch = int(stretch)
|
||||
|
||||
def getBrick(w):
|
||||
if w in stretcher:
|
||||
return stretcher[w]
|
||||
elif w[2] in stretcher:
|
||||
return stretcher[w[2]]
|
||||
else:
|
||||
return stretcher[w[-1]]
|
||||
|
||||
if stretch > 0:
|
||||
return list(chain.from_iterable(([w] + [getBrick(w)]*stretch for w in wave)))
|
||||
else:
|
||||
return wave
|
||||
|
||||
def gen_wave_brick(self, prev=None, this=None, stretch=0, repeat=0, subcycle=False):
|
||||
sharpedge_clk = { "p": "pclk", "n": "nclk", "P": "Pclk", "N": "Nclk" }
|
||||
sharpedge_sig = { "h": "pclk", "l": "nclk", "H": "Pclk", "L": "Nclk" }
|
||||
sharpedge = sharpedge_clk.copy()
|
||||
sharpedge.update(sharpedge_sig)
|
||||
|
||||
# level: logical levels of symbols at wave
|
||||
level = {"=": "v", "2": "v", "3": "v", "4": "v", "5": "v", "6": "v",
|
||||
"7": "v", "8": "v", "9": "v", "h": "1", "H": "1", "l": "0", "L": "0"}
|
||||
# translevel: Those are the levels at the end of a cycle (special for clocks)
|
||||
translevel = level.copy()
|
||||
translevel.update({"p": "0", "P": "0", "n": "1", "N": "1"})
|
||||
# data: Modifiers of wavebricks that add data
|
||||
data = {"=": "-2", "2": "-2", "3": "-3", "4": "-4", "5": "-5", "6":
|
||||
"-6", "7": "-7", "8": "-8", "9": "-9"}
|
||||
# clkinvert: The inverse brick to clock symbols
|
||||
clkinvert = {"p": "nclk", "n": "pclk", "P": "nclk", "N": "pclk"}
|
||||
# xclude: Those are actually identical levels, no transition
|
||||
xclude = {"hp": "111", "Hp": "111", "ln": "000", "Ln": "000",
|
||||
"nh": "111", "Nh": "111", "pl": "000", "Pl": "000"}
|
||||
|
||||
if this in sharpedge.keys():
|
||||
if prev is None:
|
||||
if this in sharpedge_clk.keys():
|
||||
first = sharpedge[this]
|
||||
else:
|
||||
first = level.get(this, this)*3
|
||||
else:
|
||||
first = xclude.get(prev+this, sharpedge[this])
|
||||
|
||||
if this in sharpedge_clk.keys():
|
||||
wave = [first, clkinvert[this]] * (1 + repeat)
|
||||
else:
|
||||
wave = [first] + [level.get(this, this)*3]*(2 * repeat + 1)
|
||||
else:
|
||||
if prev is None:
|
||||
transition = level.get(this, this)*3 + data.get(this, "")
|
||||
else:
|
||||
transition = translevel.get(prev, prev) + 'm' + level.get(this, this) + data.get(prev, "") + data.get(this, "")
|
||||
value = level.get(this, this)*3 + data.get(this, "")
|
||||
wave = [transition, value] + [value, value] * repeat
|
||||
|
||||
if subcycle:
|
||||
wave = wave[0:repeat+1]
|
||||
|
||||
if not (stretch == -0.5 and this in sharpedge_clk.keys()):
|
||||
wave = self.stretch_bricks(wave, stretch)
|
||||
|
||||
return wave
|
||||
|
||||
def parse_wave_lane(self, text, stretch=0):
|
||||
R = []
|
||||
|
||||
Stack = deque(text)
|
||||
|
||||
This = None
|
||||
subCycle = False
|
||||
|
||||
while len(Stack) > 0:
|
||||
Top = This
|
||||
This = Stack.popleft()
|
||||
repeat = 0
|
||||
if This == '|':
|
||||
This = 'x'
|
||||
if This == '<':
|
||||
subCycle = True
|
||||
This = Top
|
||||
Top = None
|
||||
if Stack[0] in ['.', '|']:
|
||||
Stack.popleft()
|
||||
else:
|
||||
continue
|
||||
if This == '>':
|
||||
subCycle = False
|
||||
This = Top
|
||||
Top = None
|
||||
if Stack and Stack[0] in ['.', '|']:
|
||||
Stack.popleft()
|
||||
else:
|
||||
continue
|
||||
while Stack and Stack[0] in ['.', '|']:
|
||||
Stack.popleft()
|
||||
repeat += 1
|
||||
R.extend(self.gen_wave_brick(Top, This, stretch, repeat, subCycle))
|
||||
|
||||
for i in range(int(math.ceil(self.lane.phase))):
|
||||
R = R[1:]
|
||||
|
||||
return R
|
||||
|
||||
def parse_wave_lanes(self, sig=""):
|
||||
def data_extract(e):
|
||||
tmp = e.get("data")
|
||||
if tmp is not None:
|
||||
tmp = tmp.split() if isinstance(tmp, string_types) else tmp
|
||||
return tmp
|
||||
|
||||
content = []
|
||||
for sigx in sig:
|
||||
self.lane.period = sigx.get("period", 1)
|
||||
self.lane.phase = sigx.get("phase", 0) * 2
|
||||
sub_content = []
|
||||
sub_content.append([sigx.get("name", " "), sigx.get("phase", 0)])
|
||||
if sigx.get("wave"):
|
||||
sub_content.append(self.parse_wave_lane(sigx["wave"], self.lane.period * self.lane.hscale - 1))
|
||||
else:
|
||||
sub_content.append(None)
|
||||
sub_content.append(data_extract(sigx))
|
||||
content.append(sub_content)
|
||||
|
||||
return content
|
||||
|
||||
def find_lane_markers(self, lanetext=""):
|
||||
|
||||
lcount = 0
|
||||
gcount = 0
|
||||
ret = []
|
||||
for idx, val in enumerate(lanetext):
|
||||
if val in ["vvv-2", "vvv-3", "vvv-4", "vvv-5", "vvv-6", "vvv-7", "vvv-8", "vvv-9"]:
|
||||
lcount += 1
|
||||
else:
|
||||
if lcount != 0:
|
||||
ret.append(gcount - ((lcount + 1) / 2))
|
||||
lcount = 0
|
||||
|
||||
gcount += 1
|
||||
|
||||
if lcount != 0:
|
||||
ret.append(gcount - ((lcount + 1) / 2))
|
||||
|
||||
return ret
|
||||
|
||||
def render_lane_uses(self, val, g):
|
||||
if val[1]:
|
||||
for i in range(len(val[1])):
|
||||
b = self.container.use(href="#{}".format(val[1][i]))
|
||||
b.translate(i * self.lane.xs)
|
||||
g.add(b)
|
||||
|
||||
if val[2] and len(val[2]):
|
||||
labels = self.find_lane_markers(val[1])
|
||||
if len(labels) != 0:
|
||||
for k in range(len(labels)):
|
||||
if val[2] and k < len(val[2]):
|
||||
tx = int(labels[k]) * self.lane.xs + self.lane.xlabel
|
||||
title = self.element.text("", x=[tx], y=[self.lane.ym], text_anchor="middle")
|
||||
title.add(self.element.tspan(val[2][k]))
|
||||
title["xml:space"] = "preserve"
|
||||
g.add(title)
|
||||
|
||||
def text_width(self, string, size=11):
|
||||
chars = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,47,74,74,118,89,25,44,44,52,78,37,
|
||||
44,37,37,74,74,74,74,74,74,74,74,74,74,37,37,78,78,78,74,135,89,89,96,96,89,81,103,96,37,67,89,74,109,
|
||||
96,103,89,103,96,89,81,96,89,127,89,87,81,37,37,37,61,74,44,74,74,67,74,74,37,74,74,30,30,67,30,112,74,
|
||||
74,74,74,44,67,37,74,67,95,66,65,67,44,34,44,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,37,43,74,74,74,74,34,74,44,98,49,74,78,0,98,73,53,73,44,44,44,77,71,37,44,44,49,74,111,111,
|
||||
111,81,89,89,89,89,89,89,133,96,89,89,89,89,37,37,37,37,96,96,103,103,103,103,103,78,103,96,96,96,96,
|
||||
87,89,81,74,74,74,74,74,74,118,67,74,74,74,74,36,36,36,36,74,74,74,74,74,74,74,73,81,74,74,74,74,65,74,
|
||||
65,89,74,89,74,89,74,96,67,96,67,96,67,96,67,96,82,96,74,89,74,89,74,89,74,89,74,89,74,103,74,103,74,
|
||||
103,74,103,74,96,74,96,74,37,36,37,36,37,36,37,30,37,36,98,59,67,30,89,67,67,74,30,74,30,74,39,74,44,
|
||||
74,30,96,74,96,74,96,74,80,96,74,103,74,103,74,103,74,133,126,96,44,96,44,96,44,89,67,89,67,89,67,89,
|
||||
67,81,38,81,50,81,37,96,74,96,74,96,74,96,74,96,74,96,74,127,95,87,65,87,81,67,81,67,81,67,30,84,97,91,
|
||||
84,91,84,94,92,73,104,109,91,84,81,84,100,82,76,74,103,91,131,47,40,99,77,37,79,130,100,84,104,114,87,
|
||||
126,101,87,84,93,84,69,84,46,52,82,52,82,114,89,102,96,100,98,91,70,88,88,77,70,85,89,77,67,84,39,65,
|
||||
61,39,189,173,153,111,105,61,123,123,106,89,74,37,30,103,74,96,74,96,74,96,74,96,74,96,74,81,91,81,91,
|
||||
81,130,131,102,84,103,84,87,78,104,81,104,81,88,76,37,189,173,153,103,84,148,90,100,84,89,74,133,118,
|
||||
103,81]
|
||||
|
||||
return sum([(chars[ord(c)] if ord(c) <= len(chars) else 114) for c in string])*size/100
|
||||
|
||||
def render_wave_lane(self, content="", index=0):
|
||||
xmax = 0
|
||||
xgmax = 0
|
||||
glengths = []
|
||||
groups = []
|
||||
|
||||
for j, val in enumerate(content):
|
||||
name = val[0][0].strip()
|
||||
if name is not None:
|
||||
dy = self.lane.y0 + j * self.lane.yo
|
||||
g = self.container.g(id="wavelane_{j}_{index}".format(j=j, index=index))
|
||||
g.translate(0, dy)
|
||||
title = self.element.text("", x=[self.lane.tgo], y=[self.lane.ym], text_anchor="end")
|
||||
title.add(self.element.tspan(name))
|
||||
title["xml:space"] = "preserve"
|
||||
title["class"] = "info"
|
||||
g.add(title)
|
||||
|
||||
glengths.append(self.text_width(name))
|
||||
|
||||
xoffset = val[0][1]
|
||||
xoffset = math.ceil(2 * xoffset) - 2 * xoffset if xoffset > 0 else -2 * xoffset
|
||||
gg = self.container.g(id="wavelane_draw_{j}_{index}".format(j=j, index=index))
|
||||
gg.translate(xoffset * self.lane.xs, 0)
|
||||
|
||||
self.render_lane_uses(val, gg)
|
||||
|
||||
if val[1] and len(val[1]) > xmax:
|
||||
xmax = len(val[1])
|
||||
|
||||
g.add(gg)
|
||||
groups.append(g)
|
||||
self.lane.xmax = xmax
|
||||
self.lane.xg = xgmax + 20
|
||||
return (glengths, groups)
|
||||
|
||||
def captext(self, g, cxt, anchor, y):
|
||||
if cxt.get(anchor) and cxt[anchor].get("text"):
|
||||
tmark = self.element.text("", x=[float(cxt.xmax)*float(cxt.xs)/2], y=[y], text_anchor="middle", fill="#000")
|
||||
tmark["xml:space"] = "preserve"
|
||||
if isinstance(cxt[anchor]["text"], string_types):
|
||||
tmark.add(self.element.tspan(cxt[anchor]["text"]))
|
||||
else:
|
||||
tmark.add(JsonMLElement(cxt[anchor]["text"]))
|
||||
g.add(tmark)
|
||||
|
||||
def ticktock(self, g, cxt, ref1, ref2, x, dx, y, length):
|
||||
L = []
|
||||
|
||||
if cxt.get(ref1) is None or cxt[ref1].get(ref2) is None:
|
||||
return
|
||||
|
||||
val = cxt[ref1][ref2]
|
||||
|
||||
if isinstance(val, string_types):
|
||||
val = val.split()
|
||||
elif isinstance(val, (int, float, bool)):
|
||||
offset = int(val)
|
||||
val = []
|
||||
for i in range(length):
|
||||
val.append(i + offset)
|
||||
|
||||
if type(val) is list:
|
||||
if len(val) == 0:
|
||||
return
|
||||
elif len(val) == 1:
|
||||
offset = val[0]
|
||||
if isinstance(offset, string_types):
|
||||
L = val
|
||||
else:
|
||||
for i in range(length):
|
||||
L[i] = i + offset
|
||||
elif len(val) == 2:
|
||||
offset = int(val[0])
|
||||
step = int(val[1])
|
||||
tmp = val[1].split(".")
|
||||
if len(tmp) == 2:
|
||||
dp = len(tmp[1])
|
||||
|
||||
if isinstance(offset, string_types) or isinstance(step, string_types):
|
||||
L = val
|
||||
else:
|
||||
offset = step * offset
|
||||
for i in range(length):
|
||||
L[i] = "{0:.", dp, "f}".format(step * i + offset)
|
||||
else:
|
||||
L = val
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
for i in range(length):
|
||||
tmp = L[i]
|
||||
tmark = self.element.text(tmp, x=[i * dx + x], y=[y], text_anchor="middle")
|
||||
tmark["class"] = "muted"
|
||||
tmark["xml:space"] = "preserve"
|
||||
g.add(tmark)
|
||||
|
||||
def render_marks(self, content="", index=0):
|
||||
def get_elem(e):
|
||||
if len(e) == 3:
|
||||
ret = self.element[e[0]](e[2])
|
||||
ret.attribs = e[1]
|
||||
elif len(e) == 2:
|
||||
ret = self.element[e[0]](e[1])
|
||||
else:
|
||||
ret = self.element.tspan(e)
|
||||
return ret
|
||||
|
||||
mstep = 2 * int(self.lane.hscale)
|
||||
mmstep = mstep * self.lane.xs
|
||||
marks = int(self.lane.xmax / mstep)
|
||||
gy = len(content) * int(self.lane.yo)
|
||||
|
||||
g = self.container.g(id="gmarks_{}".format(index))
|
||||
|
||||
for i in range(marks + 1):
|
||||
gg = self.element.path(id="gmark_{i}_{index}".format(i=i, index=index),
|
||||
d="m {dx},0 0,{gy}".format(dx=i * mmstep, gy=gy),
|
||||
style="stroke:#888;stroke-width:0.5;stroke-dasharray:1,3")
|
||||
g.add(gg)
|
||||
|
||||
self.captext(g, self.lane, "head", -33 if (self.lane.yh0 > 0) else -13)
|
||||
self.captext(g, self.lane, "foot", gy + (45 if (self.lane.yf0 > 0) else 25))
|
||||
self.ticktock(g, self.lane, "head", "tick", 0, mmstep, -5, marks + 1)
|
||||
self.ticktock(g, self.lane, "head", "tock", mmstep / 2, mmstep, -5, marks)
|
||||
self.ticktock(g, self.lane, "foot", "tick", 0, mmstep, gy + 15, marks + 1)
|
||||
self.ticktock(g, self.lane, "foot", "tock", mmstep / 2, mmstep, gy + 15, marks)
|
||||
|
||||
return g
|
||||
|
||||
def render_labels(self, root, source, index):
|
||||
if source:
|
||||
gg = self.container.g(id="labels_{index}".format(index=index))
|
||||
|
||||
for idx, val in enumerate(source):
|
||||
self.lane.period = val.get("period", 1)
|
||||
self.lane.phase = val.get("phase", 0) * 2
|
||||
|
||||
dy = self.lane.y0 + idx * self.lane.yo
|
||||
g = self.container.g(id="labels_{i}_{index}".format(i=idx, index=index))
|
||||
g.translate(0, dy)
|
||||
|
||||
label = val.get("label")
|
||||
if label:
|
||||
pos = 0
|
||||
for l in re.findall(r"([\.\w]|(?:\{\w+\}))(?:\((\d*\.?\d+)\))?", label):
|
||||
if l[0] == ".":
|
||||
pos += 1
|
||||
continue
|
||||
|
||||
text = l[0]
|
||||
try:
|
||||
offset = float(l[1])
|
||||
except ValueError:
|
||||
offset = 0
|
||||
|
||||
m = re.match(r"\{(\w+)\}", l[0])
|
||||
if m:
|
||||
text = m.group(1)
|
||||
x = int(float(self.lane.xs) * (2 * (pos + offset) * self.lane.period *
|
||||
self.lane.hscale - self.lane.phase) + float(self.lane.xlabel))
|
||||
y = int(idx * self.lane.yo + self.lane.y0 + float(self.lane.ys) * 0.5) - dy
|
||||
|
||||
lwidth = len(text) * self.font_width
|
||||
lx = float(x) - float(lwidth) / 2
|
||||
ly = int(y) - 5
|
||||
underlabel = self.element.rect(insert=(lx, ly),
|
||||
size=(lwidth, 8), style="fill:#FFF;")
|
||||
g.add(underlabel)
|
||||
lx = float(x)
|
||||
ly = int(y) + 2
|
||||
label = self.element.text(text, style="font-size:8px;", text_anchor="middle",
|
||||
x=[lx], y=[ly])
|
||||
g.add(label)
|
||||
pos += 1
|
||||
gg.add(g)
|
||||
root.add(gg)
|
||||
|
||||
def arc_shape(self, Edge, frm, to):
|
||||
dx = float(to.x) - float(frm.x)
|
||||
dy = float(to.y) - float(frm.y)
|
||||
lx = (float(frm.x) + float(to.x)) / 2
|
||||
ly = (float(frm.y) + float(to.y)) / 2
|
||||
|
||||
const_style = AttrDict({
|
||||
"a": "marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none",
|
||||
"b": "marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none"
|
||||
})
|
||||
|
||||
pattern = {
|
||||
"-": { },
|
||||
"~": {"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(0.7 * dx), dy=0,
|
||||
dxx=(0.3 * dx), dyy=dy,
|
||||
dxxx=dx, dyyy=dy)},
|
||||
"-~": {"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(0.7 * dx), dy=0,
|
||||
dxx=dx, dyy=dy,
|
||||
dxxx=dx, dyyy=dy)},
|
||||
"~-": {"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=0, dy=0,
|
||||
dxx=(0.3 * dx), dyy=dy,
|
||||
dxxx=dx, dyyy=dy)},
|
||||
"-|": {"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=dx, dy=0,
|
||||
dxx=0, dyy=dy)},
|
||||
"|-": {"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=0, dy=dy,
|
||||
dxx=dx, dyy=0)},
|
||||
"-|-": {"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(dx / 2), dy=0,
|
||||
dxx=0, dyy=dy,
|
||||
dxxx=(dx / 2), dyyy=0)},
|
||||
"->": {"style": const_style.a},
|
||||
"~>": {"style": const_style.a,
|
||||
"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(0.7 * dx), dy=0,
|
||||
dxx=(0.3 * dx), dyy=dy,
|
||||
dxxx=dx, dyyy=dy)},
|
||||
"-~>": {"style": const_style.a,
|
||||
"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(0.7 * dx), dy=0,
|
||||
dxx=dx, dyy=dy,
|
||||
dxxx=dx, dyyy=dy)},
|
||||
"~->": {"style": const_style.a,
|
||||
"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=0, dy=0,
|
||||
dxx=(0.3 * dx), dyy=dy,
|
||||
dxxx=dx, dyyy=dy)},
|
||||
"-|>": {"style": const_style.a,
|
||||
"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=dx, dy=0,
|
||||
dxx=0, dyy=dy)},
|
||||
"|->": {"style": const_style.a,
|
||||
"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=0, dy=dy,
|
||||
dxx=dx, dyy=0
|
||||
)},
|
||||
"-|->": {"style": const_style.a,
|
||||
"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(dx / 2), dy=0,
|
||||
dxx=0, dyy=dy,
|
||||
dxxx=(dx / 2), dyyy=0
|
||||
)},
|
||||
"<->": {"style": const_style.b},
|
||||
"<~>": {"style": const_style.b,
|
||||
"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(0.7 * dx), dy=0,
|
||||
dxx=(0.3 * dx), dyy=dy,
|
||||
dxxx=dx, dyyy=dy
|
||||
)},
|
||||
"<-~>": {"style": const_style.b,
|
||||
"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(0.7 * dx), dy=0,
|
||||
dxx=dx, dyy=dy,
|
||||
dxxx=dx, dyyy=dy
|
||||
)},
|
||||
"<-|>": {"style": const_style.b,
|
||||
"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=dx, dy=0,
|
||||
dxx=0, dyy=dy
|
||||
)},
|
||||
"<-|->": {"style": const_style.b,
|
||||
"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
|
||||
dx=(dx / 2), dy=0,
|
||||
dxx=0, dyy=dy,
|
||||
dxxx=(dx / 2), dyyy=0,
|
||||
)}
|
||||
}
|
||||
|
||||
props = AttrDict({"lx": lx, "ly": ly, "style": "fill:none;stroke:#00F;stroke-width:1",
|
||||
"d": "M {fx},{fy} {tx},{ty}".format(fx=frm.x, fy=frm.y, tx=to.x, ty=to.y)})
|
||||
|
||||
if Edge.shape in pattern:
|
||||
props.d = pattern[Edge.shape].get("d", props.d)
|
||||
props.style = pattern[Edge.shape].get("style", props.style)
|
||||
|
||||
if Edge.label:
|
||||
if Edge.shape in ["-~", "-~>", "<-~>"]:
|
||||
props.lx = float(frm.x) + (float(to.x) - float(frm.x)) * 0.75
|
||||
elif Edge.shape in ["~-", "~->"]:
|
||||
props.lx = float(frm.x) + (float(to.x) - float(frm.x)) * 0.25
|
||||
elif Edge.shape in ["-|", "-|>", "<-|>"]:
|
||||
props.lx = float(to.x)
|
||||
elif Edge.shape in ["|-", "|->"]:
|
||||
props.lx = float(frm.x)
|
||||
|
||||
return props
|
||||
|
||||
def render_arc(self, Edge, frm, to, shapeProps):
|
||||
return self.element.path(id="gmark_{frm}_{to}".format(frm=Edge.frm, to=Edge.to),
|
||||
d=shapeProps.d, style=shapeProps.style)
|
||||
|
||||
def render_label(self, p, text):
|
||||
w = self.text_width(text,8) + 2
|
||||
g = self.container.g(transform = "translate({},{})".format(p.x, p.y))
|
||||
# todo: I don't think this is correct. reported:
|
||||
# https://github.com/wavedrom/wavedrom/issues/252
|
||||
rect = self.element.rect(insert=(int(0-w/2), -5), size=(w, 10), style="fill:#FFF;")
|
||||
label = self.element.text("", style="font-size:8px;", text_anchor="middle", y=[3])
|
||||
label.add(self.element.tspan(text))
|
||||
g.add(rect)
|
||||
g.add(label)
|
||||
return g
|
||||
|
||||
def render_arcs(self, source, index, top):
|
||||
Edge = AttrDict({"words": [], "frm": 0, "shape": "", "to": 0, "label": ""})
|
||||
Events = AttrDict({})
|
||||
|
||||
if source:
|
||||
for idx, val in enumerate(source):
|
||||
self.lane.period = val.get("period", 1)
|
||||
self.lane.phase = val.get("phase", 0) * 2
|
||||
text = val.get("node")
|
||||
if text:
|
||||
Stack = list(text)
|
||||
Stack.reverse()
|
||||
pos = 0
|
||||
step = 1
|
||||
while len(Stack) > 0:
|
||||
eventname = Stack.pop()
|
||||
if eventname == "<":
|
||||
step = 0.25
|
||||
continue
|
||||
elif eventname == ">":
|
||||
step = 1
|
||||
continue
|
||||
x = int(float(self.lane.xs) * (2 * pos * self.lane.period *
|
||||
self.lane.hscale - self.lane.phase) + float(self.lane.xlabel))
|
||||
y = int(idx * self.lane.yo + self.lane.y0 + float(self.lane.ys) * 0.5)
|
||||
if eventname != ".":
|
||||
Events[eventname] = AttrDict({"x": str(x), "y": str(y)})
|
||||
pos += step
|
||||
|
||||
gg = self.container.g(id="wavearcs_{index}".format(index=index))
|
||||
|
||||
if top.get("edge"):
|
||||
for i, val in enumerate(top["edge"]):
|
||||
Edge.words = val.split()
|
||||
Edge.label = val[len(Edge.words[0]):]
|
||||
Edge.label = Edge.label[1:]
|
||||
Edge.frm = Edge.words[0][0]
|
||||
Edge.to = Edge.words[0][-1]
|
||||
Edge.shape = Edge.words[0][1:-1]
|
||||
frm = AttrDict(Events[Edge.frm])
|
||||
to = AttrDict(Events[Edge.to])
|
||||
|
||||
shapeProps = self.arc_shape(Edge, frm, to)
|
||||
gg.add(self.render_arc(Edge, frm, to, shapeProps))
|
||||
|
||||
if Edge.label:
|
||||
gg.add(self.render_label(AttrDict({"x": shapeProps.lx, "y": shapeProps.ly}), Edge.label))
|
||||
|
||||
|
||||
for k in Events:
|
||||
if k.islower() or k.isdigit():
|
||||
if int(Events[k].x) > 0:
|
||||
gg.add(self.render_label(AttrDict({"x": Events[k].x, "y": Events[k].y}), k))
|
||||
|
||||
return gg
|
||||
|
||||
def parse_config(self, source={}):
|
||||
self.lane.hscale = 1
|
||||
if self.lane.get("hscale0"):
|
||||
self.lane.hscale = self.lane.hscale0
|
||||
|
||||
if source and source.get("config") and source.get("config").get("hscale"):
|
||||
hscale = round(source.get("config").get("hscale"))
|
||||
if hscale > 0:
|
||||
if hscale > 100:
|
||||
hscale = 100
|
||||
self.lane.hscale = hscale
|
||||
|
||||
self.lane.xmin_cfg = 0
|
||||
self.lane.xmax_cfg = sys.maxsize
|
||||
if source and "config" in source and "hbounds" in source["config"]:
|
||||
if len(source["config"]["hbounds"]) == 2:
|
||||
source["config"]["hbounds"][0] = math.floor(source["config"]["hbounds"][0])
|
||||
source["config"]["hbounds"][1] = math.ceil(source["config"]["hbounds"][0])
|
||||
if source["config"]["hbounds"][0] < source["config"]["hbounds"][1]:
|
||||
self.lane.xmin_cfg = 2 * source["config"]["hbounds"][0]
|
||||
self.lane.xmax_cfg = 2 * source["config"]["hbounds"][1]
|
||||
|
||||
self.lane.yh0 = 0
|
||||
self.lane.yh1 = 0
|
||||
if source and source.get("head"):
|
||||
self.lane.head = source["head"]
|
||||
if "tick" in source["head"] or "tock" in source["head"]:
|
||||
self.lane.yh0 = 20
|
||||
if "tick" in source["head"]:
|
||||
source["head"]["tick"] += self.lane.xmin_cfg/2
|
||||
if "tock" in source["head"]:
|
||||
source["head"]["tock"] += self.lane.xmin_cfg/2
|
||||
if source.get("head").get("text"):
|
||||
self.lane.yh1 = 46
|
||||
self.lane.head["text"] = source["head"]["text"]
|
||||
|
||||
self.lane.yf0 = 0
|
||||
self.lane.yf1 = 0
|
||||
if source and source.get("foot"):
|
||||
self.lane.foot = source["foot"]
|
||||
if "tick" in source["foot"] or "tock" in source["foot"]:
|
||||
self.lane.yf0 = 20
|
||||
if "tick" in source["foot"]:
|
||||
source["foot"]["tick"] += self.lane.xmin_cfg/2
|
||||
if "tock" in source["foot"]:
|
||||
source["foot"]["tock"] += self.lane.xmin_cfg/2
|
||||
if source.get("foot").get("text"):
|
||||
self.lane.yf1 = 46
|
||||
self.lane.foot["text"] = source["foot"]["text"]
|
||||
|
||||
def rec(self, tmp=[], state={}):
|
||||
name = None
|
||||
delta = AttrDict({"x": 10})
|
||||
if isinstance(tmp[0], str) or isinstance(tmp[0], int):
|
||||
name = str(tmp[0])
|
||||
delta.x = 25
|
||||
|
||||
state.x += delta.x
|
||||
for idx, val in enumerate(tmp):
|
||||
if isinstance(val, list):
|
||||
old_y = state.y
|
||||
self.rec(val, state)
|
||||
state["groups"].append({"x": state.xx,
|
||||
"y": old_y,
|
||||
"height": state.y - old_y,
|
||||
"name": state.name})
|
||||
elif isinstance(val, dict):
|
||||
state["lanes"].append(val)
|
||||
state["width"].append(state.x)
|
||||
state.y += 1
|
||||
|
||||
state.xx = state.x
|
||||
state.x -= delta.x
|
||||
state.name = name
|
||||
|
||||
def another_template(self, index, source):
|
||||
def get_container(elem):
|
||||
ctype = elem[0]
|
||||
ret = self.container[ctype]()
|
||||
ret.attribs = elem[1]
|
||||
|
||||
def gen_elem(e):
|
||||
if e[0] == "path":
|
||||
attr = e[1]
|
||||
elem = self.element.path(d=attr["d"])
|
||||
elem.attribs = attr
|
||||
elif e[0] == "rect":
|
||||
attr = e[1]
|
||||
x = attr["x"]
|
||||
y = attr["y"]
|
||||
w = attr["width"]
|
||||
h = attr["height"]
|
||||
elem = self.element.rect(insert=(x, y), size=(w, h))
|
||||
elem.attribs = attr
|
||||
|
||||
return elem
|
||||
[ret.add(gen_elem(e)) for e in elem[2:]]
|
||||
|
||||
return ret
|
||||
|
||||
skinname = source.get("config", {"skin" : "default"}).get("skin", "default")
|
||||
skin = waveskin.WaveSkin.get(skinname, waveskin.WaveSkin["default"])
|
||||
|
||||
template = svgwrite.Drawing(id="svgcontent_{index}".format(index=index))
|
||||
if index == 0:
|
||||
template.add(template.style(skin[2][2]))
|
||||
[template.defs.add(get_container(e)) for e in skin[3][1:]]
|
||||
self.lane.xs = int(skin[3][1][2][1]["width"])
|
||||
self.lane.ys = int(skin[3][1][2][1]["height"])
|
||||
self.lane.xlabel = int(skin[3][1][2][1]["x"])
|
||||
self.lane.ym = int(skin[3][1][2][1]["y"])
|
||||
|
||||
template["class"] = "WaveDrom"
|
||||
template["overflow"] = "hidden"
|
||||
|
||||
return template
|
||||
|
||||
def insert_svg_template(self, index=0, parent=[], source={}):
|
||||
e = waveskin.WaveSkin["default"]
|
||||
|
||||
if source.get("config") and source.get("config").get("skin"):
|
||||
if waveskin.WaveSkin.get(source.get("config").get("skin")):
|
||||
e = waveskin.WaveSkin[source.get("config").get("skin")]
|
||||
|
||||
if index == 0:
|
||||
self.lane.xs = int(e[3][1][2][1]["width"])
|
||||
self.lane.ys = int(e[3][1][2][1]["height"])
|
||||
self.lane.xlabel = int(e[3][1][2][1]["x"])
|
||||
self.lane.ym = int(e[3][1][2][1]["y"])
|
||||
|
||||
else:
|
||||
e = ["svg",
|
||||
{"id": "svg",
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
||||
"height": "0"},
|
||||
[ # e[-1]
|
||||
"g", # e[-1][0]
|
||||
{"id": "waves"}, # e[-1][1]
|
||||
[ # e[-1][2]
|
||||
"g", # e[-1][2][0]
|
||||
{"id": "lanes"} # e[-1][2][1]
|
||||
],
|
||||
[ # e[-1][3]
|
||||
"g", # e[-1][3][0]
|
||||
{"id": "groups"} # e[-1][3][1]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
e[-1][1]["id"] = "waves_{index}".format(index=index)
|
||||
e[-1][2][1]["id"] = "lanes_{index}".format(index=index)
|
||||
e[-1][3][1]["id"] = "groups_{index}".format(index=index)
|
||||
e[1]["id"] = "svgcontent_{index}".format(index=index)
|
||||
e[1]["height"] = 0
|
||||
|
||||
parent.extend(e)
|
||||
|
||||
def render_waveform(self, index=0, source={}, output=[], strict_js_features=False):
|
||||
xmax = 0
|
||||
|
||||
if source.get("signal"):
|
||||
template = self.another_template(index, source)
|
||||
waves = template.g(id="waves_{index}".format(index=index))
|
||||
lanes = template.g(id="lanes_{index}".format(index=index))
|
||||
groups = template.g(id="groups_{index}".format(index=index))
|
||||
self.parse_config(source)
|
||||
ret = AttrDict({"x": 0, "y": 0, "xmax": 0, "width": [], "lanes": [], "groups": []})
|
||||
self.rec(source["signal"], ret) # parse lanes
|
||||
content = self.parse_wave_lanes(ret.lanes)
|
||||
(glengths, lanegroups) = self.render_wave_lane(content, index)
|
||||
for i, val in enumerate(glengths):
|
||||
xmax = max(xmax, (val + ret.width[i]))
|
||||
marks = self.render_marks(content, index)
|
||||
gaps = self.render_gaps(ret.lanes, index)
|
||||
if not strict_js_features:
|
||||
self.render_labels(lanes, ret.lanes, index)
|
||||
arcs = self.render_arcs(ret.lanes, index, source)
|
||||
|
||||
# Render
|
||||
lanes.add(marks)
|
||||
[lanes.add(l) for l in lanegroups]
|
||||
lanes.add(arcs)
|
||||
lanes.add(gaps)
|
||||
|
||||
self.render_groups(groups, ret.groups, index)
|
||||
self.lane.xg = int(math.ceil(float(xmax - self.lane.tgo) / float(self.lane.xs))) * self.lane.xs
|
||||
width = self.lane.xg + self.lane.xs * (self.lane.xmax + 1)
|
||||
height = len(content) * self.lane.yo + self.lane.yh0 + self.lane.yh1 + self.lane.yf0 + self.lane.yf1
|
||||
template["width"] = width
|
||||
template["height"] = height
|
||||
template.viewbox(0, 0, width, height)
|
||||
dx = self.lane.xg + 0.5
|
||||
dy = float(self.lane.yh0) + float(self.lane.yh1) + 0.5
|
||||
lanes.translate(dx, dy)
|
||||
|
||||
waves.add(lanes)
|
||||
waves.add(groups)
|
||||
template.add(waves)
|
||||
return template
|
||||
|
||||
def render_groups(self, root=[], groups=[], index=0):
|
||||
for i, val in enumerate(groups):
|
||||
dx = groups[i]["x"] + 0.5
|
||||
dy = groups[i]["y"] * self.lane.yo + 3.5 + self.lane.yh0 + self.lane.yh1
|
||||
h = int(groups[i]["height"] * self.lane.yo - 16)
|
||||
group = self.element.path(id="group_{i}_{index}".format(i=i, index=index),
|
||||
d="m {dx},{dy} c -3,0 -5,2 -5,5 l 0,{h} c 0,3 2,5 5,5".format(dx=dx, dy=dy, h=h),
|
||||
style="stroke:#0041c4;stroke-width:1;fill:none")
|
||||
|
||||
root.add(group)
|
||||
|
||||
name = groups[i]["name"]
|
||||
x = int(groups[i]["x"] - 10)
|
||||
y = int(self.lane.yo * (groups[i]["y"] + (float(groups[i]["height"]) / 2)) +
|
||||
self.lane.yh0 + self.lane.yh1)
|
||||
label = self.container.g()
|
||||
label.translate(x, y)
|
||||
gg = self.container.g()
|
||||
gg.rotate(270)
|
||||
t = self.element.text("", text_anchor="middle")
|
||||
t["class"] = "info"
|
||||
t["xml:space"] = "preserve"
|
||||
t.add(self.element.tspan(name))
|
||||
gg.add(t)
|
||||
label.add(gg)
|
||||
root.add(label)
|
||||
|
||||
def render_gap_uses(self, wave, g):
|
||||
subCycle = False
|
||||
|
||||
if wave:
|
||||
Stack = deque(wave)
|
||||
pos = 0
|
||||
while len(Stack):
|
||||
next = Stack.popleft()
|
||||
if next == '<':
|
||||
subCycle = True
|
||||
continue
|
||||
if next == '>':
|
||||
subCycle = False
|
||||
continue
|
||||
if subCycle:
|
||||
pos += self.lane.period
|
||||
else:
|
||||
pos += 2 * self.lane.period
|
||||
if next == "|":
|
||||
if subCycle:
|
||||
dx = float(self.lane.xs) * (pos * float(self.lane.hscale) - float(self.lane.phase))
|
||||
else:
|
||||
dx = float(self.lane.xs) * ((pos - self.lane.period) * float(self.lane.hscale) - float(self.lane.phase))
|
||||
b = self.container.use(href="#gap")
|
||||
b.translate(dx)
|
||||
g.add(b)
|
||||
|
||||
def render_gaps(self, source, index):
|
||||
if source:
|
||||
gg = self.container.g(id="wavegaps_{index}".format(index=index))
|
||||
|
||||
for idx, val in enumerate(source):
|
||||
self.lane.period = val.get("period", 1)
|
||||
self.lane.phase = int(val.get("phase", 0) * 2) + self.lane.xmin_cfg
|
||||
|
||||
dy = self.lane.y0 + idx * self.lane.yo
|
||||
g = self.container.g(id="wavegap_{i}_{index}".format(i=idx, index=index))
|
||||
g.translate(0, dy)
|
||||
|
||||
if "wave" in val:
|
||||
self.render_gap_uses(val["wave"], g)
|
||||
|
||||
gg.add(g)
|
||||
|
||||
return gg
|
||||
|
||||
def convert_to_svg(self, root):
|
||||
svg_output = ""
|
||||
|
||||
if type(root) is list:
|
||||
if len(root) >= 2 and type(root[1]) is dict:
|
||||
if len(root) == 2:
|
||||
svg_output += "<{}{}/>\n".format(root[0], self.convert_to_svg(root[1]))
|
||||
elif len(root) >= 3:
|
||||
svg_output += "<{}{}/>\n".format(root[0], self.convert_to_svg(root[1]))
|
||||
if len(root) == 3:
|
||||
svg_output += self.convert_to_svg(root[2])
|
||||
else:
|
||||
svg_output += self.convert_to_svg(root[2:])
|
||||
svg_output += "</{}>\n".format(root[0])
|
||||
elif type(root[0]) is list:
|
||||
for eleml in root:
|
||||
svg_output += self.convert_to_svg(eleml)
|
||||
else:
|
||||
svg_output += "<{}>\n".format(root[0])
|
||||
for eleml in root[1:]:
|
||||
svg_output += self.convert_to_svg(eleml)
|
||||
svg_output += "</{}>\n".format(root[0])
|
||||
elif type(root) is dict:
|
||||
for elemd in root:
|
||||
svg_output += " {}=\"{}\"".format(elemd, root[elemd])
|
||||
else:
|
||||
svg_output += root
|
||||
|
||||
return svg_output
|
||||
|
||||
# Backward compatibility
|
||||
genWaveBrick = gen_wave_brick
|
||||
parseWaveLane = parse_wave_lane
|
||||
parseWaveLanes = parse_wave_lanes
|
||||
findLaneMarkers = find_lane_markers
|
||||
renderWaveLane = render_wave_lane
|
||||
renderMarks = render_marks
|
||||
renderLabels = render_labels
|
||||
renderArcs = render_arcs
|
||||
parseConfig = parse_config
|
||||
anotherTemplate = another_template
|
||||
insertSVGTemplate = insert_svg_template
|
||||
renderWaveForm = render_waveform
|
||||
renderGroups = render_groups
|
||||
renderGaps = render_gaps
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user