Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't use a stylesheet in SVGs #341

Merged
merged 2 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Wuestengecko marked this conversation as resolved.
Show resolved Hide resolved

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