Skip to content

Commit

Permalink
merge: Don't use a stylesheet in SVGs
Browse files Browse the repository at this point in the history
  • Loading branch information
Wuestengecko committed Oct 5, 2023
2 parents 3a234ef + 2a7db43 commit ce0c7cc
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 922 deletions.
29 changes: 13 additions & 16 deletions capellambse/diagram/capstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,25 +141,20 @@ class in the form::
The type can be: ``Box``, ``Edge``. The style class can be any
known style class.
"""
if "." not in objectclass:
raise ValueError(f"Malformed objectclass: {objectclass}")

if "symbol" in objectclass.lower():
return {}

if (
objectclass not in STYLES["__GLOBAL__"]
and diagramclass
and objectclass not in STYLES.get(diagramclass, {})
):
LOGGER.debug(
"No default style for %r in %r", objectclass, diagramclass
)
try:
retval = STYLES["__GLOBAL__"][objectclass].copy()
except KeyError:
retval = {}

if diagramclass:
retval.update(STYLES.get(diagramclass, {}).get(objectclass, {}))
return retval
obj_type: str = objectclass.split(".", 1)[0]
styles = {
**STYLES["__GLOBAL__"].get(obj_type, {}),
**STYLES.get(diagramclass or "", {}).get(obj_type, {}),
**STYLES["__GLOBAL__"].get(objectclass, {}),
**STYLES.get(diagramclass or "", {}).get(objectclass, {}),
}
return styles


#: This dict maps the color names used by Capella to RGB tuples.
Expand Down Expand Up @@ -295,6 +290,8 @@ class in the form::
},
"Edge": {
"stroke-width": 1,
"fill": None,
"stroke": COLORS["black"],
},
"Edge.Connector": {
"stroke": RGB(176, 176, 176),
Expand Down
15 changes: 13 additions & 2 deletions capellambse/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def __init__(
| None
) = None,
diagram_cache_subdir: str | pathlib.PurePosixPath | None = None,
jupyter_untrusted: bool = False,
jupyter_untrusted: bool | None = None,
fallback_render_aird: bool = False,
**kwargs: t.Any,
) -> None:
Expand Down Expand Up @@ -234,12 +234,23 @@ def __init__(
capellambse.filehandler.http.HTTPFileHandler :
A simple ``http(s)://`` file handler.
"""
import warnings

if jupyter_untrusted is not None:
warnings.warn(
(
"The 'jupyter_untrusted' argument is no longer needed and"
" will be removed soon. Please remove it from your calls."
),
DeprecationWarning,
stacklevel=2,
)

capellambse.load_model_extensions()

self._constructed = False
self._loader = loader.MelodyLoader(path, **kwargs)
self._fallback_render_aird = fallback_render_aird
self.jupyter_untrusted = jupyter_untrusted

try:
self._pvext = capellambse.pvmt.load_pvmt_from_model(self._loader)
Expand Down
4 changes: 0 additions & 4 deletions capellambse/model/diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,6 @@ def _repr_mimebundle_(
if not mime or mime not in include or mime in exclude:
continue

# XXX Hack to fix diagram previews on Github-rendered notebooks
if self._model.jupyter_untrusted and mime != "image/png":
continue

formats[mime] = conv
if not formats:
return None
Expand Down
144 changes: 49 additions & 95 deletions capellambse/svg/drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from capellambse import diagram
from capellambse import helpers as chelpers
from capellambse.diagram import capstyle

from . import decorations, generate, helpers, style, symbols

Expand Down Expand Up @@ -44,17 +45,24 @@ class Drawing:
"""The main container that stores all svg elements."""

def __init__(self, metadata: generate.DiagramMetadata):
base_css = (
"shape-rendering: geometricPrecision;"
" font-family: 'Segoe UI';"
" font-size: 8pt;"
" cursor: pointer;"
)
superparams = {
"filename": f"{metadata.name}.svg",
"size": metadata.size,
"viewBox": metadata.viewbox,
"style": base_css,
}
if metadata.class_:
superparams["class_"] = re.sub(r"\s+", "", metadata.class_)

self.__drawing = drawing.Drawing(**superparams)
self.diagram_class = metadata.class_
self.stylesheet = self.make_stylesheet()
self.add_static_decorations()
self.add_backdrop(pos=metadata.pos, size=metadata.size)

self.obj_cache: dict[str | None, t.Any] = {}
Expand Down Expand Up @@ -93,17 +101,13 @@ def add_backdrop(
)
self.__drawing.add(self.__backdrop)

def make_stylesheet(self) -> style.SVGStylesheet:
"""Return created stylesheet and add sheet and decorations to defs."""
stylesheet = style.SVGStylesheet(class_=self.diagram_class or "")
self.__drawing.defs.add(stylesheet.sheet)
for name in stylesheet.static_deco:
self.__drawing.defs.add(decorations.deco_factories[name]())

for grad in stylesheet.yield_gradients():
self.__drawing.defs.add(grad)
def add_static_decorations(self) -> None:
static_deco = style.STATIC_DECORATIONS[
"__GLOBAL__"
] + style.STATIC_DECORATIONS.get(self.diagram_class or "", ())

return stylesheet
for name in static_deco:
self.__drawing.defs.add(decorations.deco_factories[name]())

def __repr__(self) -> str:
return self.__drawing._repr_svg_()
Expand Down Expand Up @@ -249,45 +253,12 @@ def _draw_feature_text(

def _draw_box_label(self, builder: LabelBuilder) -> container.Group:
"""Draw label text on given object and return label position."""
x, text_height, _, y_margin = self._draw_label(builder)
_, _, _, y_margin = self._draw_label(builder)

if DEBUG:
assert builder.label is not None
assert y_margin is not None
debug_y = builder.label["y"] + y_margin
debug_y1 = (
builder.label["y"]
+ (builder.label["height"] - decorations.icon_size) / 2
)
x = (
builder.label["x"]
+ decorations.icon_size
+ 2 * decorations.icon_padding
)
if text_height >= decorations.icon_size:
debug_height = text_height
else:
debug_height = decorations.icon_size

bbox: LabelDict = {
"x": (
x
if builder.text_anchor == "start"
else x - builder.label["width"] / 2
),
"y": debug_y if debug_y <= debug_y1 else debug_y1,
"width": builder.label["width"],
"height": debug_height,
}
labelstyle = style.Styling(
self.diagram_class,
"Box",
stroke="rgb(239, 41, 41)",
fill="none",
)
self._draw_label_bbox(
bbox, builder.group, "Box", obj_style=labelstyle
)

self._draw_circle(
center_=(builder.label["x"], builder.label["y"]),
radius_=3,
Expand Down Expand Up @@ -390,11 +361,10 @@ def add_port(
parent_id: str | None,
*,
class_: str,
obj_style: style.Styling | None = None,
obj_style: style.Styling,
label: LabelDict | None = None,
id_: str | None = None,
) -> container.Group:
styles = None if obj_style is None else obj_style[""]
grp = self.__drawing.g(class_=f"Box {class_}", id_=id_)
if class_ in decorations.all_directed_ports:
port_id = "#ErrorSymbol"
Expand All @@ -403,7 +373,6 @@ def add_port(
elif class_ in decorations.component_ports:
port_id = "#ComponentPortSymbol"

grp.style = styles
grp.add(
self.__drawing.use(
href=port_id,
Expand All @@ -413,6 +382,7 @@ def add_port(
pos, size, class_, parent_id
),
class_=class_,
style=obj_style,
)
)
else:
Expand All @@ -424,7 +394,7 @@ def add_port(
transform=self.get_port_transformation(
pos, size, class_, parent_id
),
style=styles,
style=obj_style,
)
)

Expand All @@ -451,38 +421,49 @@ def draw_object(self, obj: cabc.Mapping[str, t.Any]) -> None:
obj
The (decoded) JSON-dict of a single diagram object.
"""
mobj = copy.deepcopy(obj)
del obj
type_mapping: dict[str, tuple[t.Any, str]] = {
"box": (self._draw_box, "Box"),
"edge": (self._draw_edge, "Edge"),
"circle": (self._draw_circle, "Edge"),
"symbol": (self._draw_symbol, "Box"),
"box_symbol": (self._draw_box_symbol, "Box"),
}

try:
drawfunc: t.Any = getattr(self, f'_draw_{obj["type"]}')
except AttributeError:
raise ValueError(f'Invalid object type: {obj["type"]}') from None
drawfunc, style_type = type_mapping[mobj["type"]]
except KeyError:
raise ValueError(f"Invalid object type: {mobj['type']}") from None

mobj = copy.deepcopy(obj)
objparams = {
f"{k}_": v for k, v in obj.items() if k not in {"type", "style"}
f"{k}_": v for k, v in mobj.items() if k not in {"type", "style"}
}
if obj["class"] in decorations.all_ports:
if mobj["class"] in decorations.all_ports:
mobj["type"] = "box" # type: ignore[index]

class_: str = mobj["type"].capitalize() + (
f".{mobj['class']}" if "class" in obj else ""
class_: str = style_type + (
f".{mobj['class']}" if "class" in mobj else ""
)
my_styles: dict[str, t.Any] = {
**capstyle.get_style(self.diagram_class, class_),
**mobj.get("style", {}),
}
obj_style = style.Styling(
self.diagram_class,
class_,
**{k: v for k, v in obj.get("style", {}).items() if "_" not in k},
**{k: v for k, v in my_styles.items() if "_" not in k},
)
text_style = style.Styling(
self.diagram_class,
class_,
"text",
**{
k[len("text_") :]: v
for k, v in obj.get("style", {}).items()
if k.startswith("text_")
k[5:]: v for k, v in my_styles.items() if k.startswith("text_")
},
)

self.obj_cache[mobj["id"]] = obj
self.obj_cache[mobj["id"]] = mobj

drawfunc(**objparams, obj_style=obj_style, text_style=text_style)

Expand Down Expand Up @@ -553,7 +534,7 @@ def _draw_symbol(
text_style: style.Styling,
**kw: t.Any,
):
del obj_style, context_ # FIXME Add context to SVG
del context_ # FIXME Add context to SVG
del kw # Dismiss additional info from json
assert isinstance(label_, (dict, type(None)))
pos = (x_ + 0.5, y_ + 0.5)
Expand All @@ -566,6 +547,7 @@ def _draw_symbol(
text_style,
parent_,
class_=class_,
obj_style=obj_style,
label=label_,
id_=id_,
)
Expand All @@ -577,6 +559,7 @@ def _draw_symbol(
insert=pos,
size=size,
class_=class_,
style=obj_style,
)
)

Expand Down Expand Up @@ -687,7 +670,8 @@ def _draw_circle(
):
del text_style # No label for circles
center_ = tuple(i + 0.5 for i in center_)

obj_style.fill = obj_style.stroke or diagram.RGB(0, 0, 0)
del obj_style.stroke
grp = self.__drawing.g(class_=f"Circle {class_}", id_=id_)
grp.add(
self.__drawing.circle(
Expand Down Expand Up @@ -719,7 +703,6 @@ def _draw_edge(
# Received text space doesn't allow for anything else than the text
for label_ in labels_:
label_["class"] = "Annotation"
self._draw_label_bbox(label_, grp, "AnnotationBB")
self._draw_edge_label(
label_,
grp,
Expand Down Expand Up @@ -764,35 +747,6 @@ def _draw_edge_label(

return group

def _draw_label_bbox(
self,
label: LabelDict,
group: container.Group | None = None,
class_: str | None = None,
obj_style: style.Styling | None = None,
) -> None:
"""Draw a bounding box for given label."""
if DEBUG:
if obj_style is not None:
setattr(obj_style, "stroke", "rgb(239, 41, 41);")
else:
obj_style = style.Styling(
self.diagram_class,
"AnnotationBB",
stroke="rgb(239, 41, 41);",
)

bbox = self.__drawing.rect(
insert=(label["x"], label["y"]),
size=(label["width"], label["height"]),
class_=class_,
style=obj_style,
)
if group is None:
return bbox

return group.add(bbox)

def _draw_line(
self,
data: dict[str, float | int],
Expand Down
Loading

0 comments on commit ce0c7cc

Please sign in to comment.