archgraph-layout 1.0.1__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {archgraph_layout-1.0.1/archgraph_layout.egg-info → archgraph_layout-2.0.0}/PKG-INFO +4 -4
  2. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/README.md +3 -3
  3. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/__init__.py +2 -2
  4. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/model.py +1 -10
  5. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/parser.py +20 -24
  6. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/hierarchy.py +6 -29
  7. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/ports.py +370 -359
  8. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/sizing.py +60 -54
  9. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/portlabels.py +12 -1
  10. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/render/html.py +3 -6
  11. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/render/jsonout.py +4 -4
  12. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/render/shapes.py +129 -221
  13. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/render/svg.py +35 -79
  14. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/routing/__init__.py +6 -23
  15. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/routing/grid.py +2 -6
  16. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/routing/router.py +1127 -1122
  17. archgraph_layout-2.0.0/archgraph/shapegeometry.py +261 -0
  18. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/styles.py +6 -10
  19. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/textmetrics.py +0 -13
  20. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0/archgraph_layout.egg-info}/PKG-INFO +4 -4
  21. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/SOURCES.txt +1 -1
  22. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/docs/xml-reference.md +19 -14
  23. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/automotive.xml +1 -1
  24. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/basic.xml +3 -3
  25. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/datacenter.xml +1 -1
  26. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/fpga_pipeline.xml +1 -1
  27. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/halcyon7.xml +54 -54
  28. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/halcyon7_lib.xml +14 -14
  29. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/halcyon7_styles.xml +2 -4
  30. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/soc.xml +5 -5
  31. archgraph_layout-1.0.1/archgraph/routing/labels.py +0 -156
  32. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/LICENSE +0 -0
  33. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/MANIFEST.in +0 -0
  34. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/__main__.py +0 -0
  35. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/cli.py +0 -0
  36. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/geometry.py +0 -0
  37. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/__init__.py +0 -0
  38. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/congestion.py +0 -0
  39. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/detail.py +0 -0
  40. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/engine.py +0 -0
  41. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/floorplan.py +0 -0
  42. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/graphalgo.py +0 -0
  43. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/layered.py +0 -0
  44. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/placement/vpsc.py +0 -0
  45. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/progress.py +0 -0
  46. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/quality.py +0 -0
  47. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/render/__init__.py +0 -0
  48. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/render/png.py +0 -0
  49. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph/routing/nudge.py +0 -0
  50. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/dependency_links.txt +0 -0
  51. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/entry_points.txt +0 -0
  52. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/requires.txt +0 -0
  53. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/archgraph_layout.egg-info/top_level.txt +0 -0
  54. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/accelerator_mesh.xml +0 -0
  55. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/examples/flat_network.xml +0 -0
  56. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/pyproject.toml +0 -0
  57. {archgraph_layout-1.0.1 → archgraph_layout-2.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archgraph-layout
3
- Version: 1.0.1
3
+ Version: 2.0.0
4
4
  Summary: Hierarchical architecture diagrams from XML with floorplanning and orthogonal routing
5
5
  Author: Ellie Hartung
6
6
  License-Expression: MIT
@@ -96,7 +96,7 @@ XML ──► parse/expand ──► size blocks ──► auto-cluster huge lev
96
96
  ──► hierarchical floorplanning (bottom-up, several passes)
97
97
  ──► detailed QP placement + edge alignment (bottom-up, then top-down)
98
98
  ──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
99
- ──► nudging ──► labels ──► SVG / HTML / JSON / PNG
99
+ ──► nudging ──► SVG / HTML / JSON / PNG
100
100
  ```
101
101
 
102
102
  **Floorplanning (placement).** Each subsystem level is a floorplanning problem over
@@ -151,8 +151,8 @@ the border of a subsystem that contains neither end is almost forbidden. Specifi
151
151
  painted over a hop. Crossings closer together than one hop share a single flat bridge.
152
152
  Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
153
153
  corners and neighbouring parallel wires). A crossing right next to a corner of the
154
- horizontal wire hops sideways on the vertical wire instead. Bit-width markers and edge
155
- labels keep clear of crossings.
154
+ horizontal wire hops sideways on the vertical wire instead. Bit-width markers
155
+ keep clear of crossings.
156
156
 
157
157
  ---
158
158
 
@@ -71,7 +71,7 @@ XML ──► parse/expand ──► size blocks ──► auto-cluster huge lev
71
71
  ──► hierarchical floorplanning (bottom-up, several passes)
72
72
  ──► detailed QP placement + edge alignment (bottom-up, then top-down)
73
73
  ──► port sides (router-chosen) ──► orthogonal routing ──► congestion-driven spacing
74
- ──► nudging ──► labels ──► SVG / HTML / JSON / PNG
74
+ ──► nudging ──► SVG / HTML / JSON / PNG
75
75
  ```
76
76
 
77
77
  **Floorplanning (placement).** Each subsystem level is a floorplanning problem over
@@ -126,8 +126,8 @@ the border of a subsystem that contains neither end is almost forbidden. Specifi
126
126
  painted over a hop. Crossings closer together than one hop share a single flat bridge.
127
127
  Each hop bumps to the side where the crossed wire has room (clear of its arrowheads,
128
128
  corners and neighbouring parallel wires). A crossing right next to a corner of the
129
- horizontal wire hops sideways on the vertical wire instead. Bit-width markers and edge
130
- labels keep clear of crossings.
129
+ horizontal wire hops sideways on the vertical wire instead. Bit-width markers
130
+ keep clear of crossings.
131
131
 
132
132
  ---
133
133
 
@@ -23,7 +23,7 @@ from .progress import report
23
23
  from .placement import engine as _engine # noqa: E402
24
24
  from . import routing as _routing # noqa: E402
25
25
 
26
- __version__ = "1.0.1"
26
+ __version__ = "2.0.0"
27
27
  __all__ = ["load", "loads", "layout", "route", "build", "to_svg", "to_html", "to_json", "to_png", "save",
28
28
  "ArchGraphError", "Graph", "__version__"]
29
29
 
@@ -45,7 +45,7 @@ def layout(g: Graph) -> Graph:
45
45
 
46
46
 
47
47
  def route(g: Graph) -> Graph:
48
- """Route all connections and buses, nudge them apart and place labels."""
48
+ """Route all connections and buses and nudge them apart."""
49
49
  _routing.route(g)
50
50
  from .geometry import Rect
51
51
  rects = [e.rect for e in g.root.iter_descendants()]
@@ -53,7 +53,6 @@ class Settings:
53
53
  junction_dots: bool = True
54
54
  shadows: bool = True
55
55
  background_grid: bool = False
56
- edge_labels: bool = True
57
56
  default_shape: str = "box"
58
57
  bundle_parallel: bool = False # merge parallel edges between the same endpoints
59
58
  margin: float = 24.0 # outer margin of the drawing
@@ -184,6 +183,7 @@ class Node(Element):
184
183
  min_h: Optional[float] = None
185
184
  virtual: bool = False # internal helper node (not drawn)
186
185
  is_note: bool = False
186
+ layout_direction: str = "LR" # inherited flow used by shape geometry
187
187
 
188
188
  kind = "node"
189
189
 
@@ -246,9 +246,6 @@ class Edge:
246
246
  id: str
247
247
  src: Endpoint
248
248
  dst: Endpoint
249
- label: str = ""
250
- src_label: str = ""
251
- dst_label: str = ""
252
249
  classes: List[str] = field(default_factory=list)
253
250
  attrs: Dict[str, str] = field(default_factory=dict)
254
251
  style: Dict[str, Any] = field(default_factory=dict)
@@ -267,9 +264,6 @@ class Edge:
267
264
  line: Optional[int] = None
268
265
  # results
269
266
  points: List[Point] = field(default_factory=list)
270
- label_pos: Optional[Tuple[float, float, float, float]] = None # x, y, w, h (centre)
271
- src_label_pos: Optional[Tuple[float, float]] = None
272
- dst_label_pos: Optional[Tuple[float, float]] = None
273
267
  routed: bool = False
274
268
  uid: int = field(default_factory=lambda: next(_counter))
275
269
 
@@ -286,14 +280,12 @@ class Edge:
286
280
  @dataclass(eq=False, repr=False)
287
281
  class BusTap:
288
282
  endpoint: Endpoint
289
- label: str = ""
290
283
  direction: Optional[str] = None
291
284
 
292
285
 
293
286
  @dataclass(eq=False, repr=False)
294
287
  class Bus:
295
288
  id: str
296
- label: str = ""
297
289
  taps: List[BusTap] = field(default_factory=list)
298
290
  classes: List[str] = field(default_factory=list)
299
291
  attrs: Dict[str, str] = field(default_factory=dict)
@@ -305,7 +297,6 @@ class Bus:
305
297
  # results
306
298
  segments: List[Tuple[Point, Point]] = field(default_factory=list)
307
299
  junctions: List[Point] = field(default_factory=list)
308
- label_pos: Optional[Tuple[float, float, float, float]] = None
309
300
  routed: bool = False
310
301
  uid: int = field(default_factory=lambda: next(_counter))
311
302
 
@@ -1086,25 +1086,28 @@ class Builder:
1086
1086
  e.ports[pid] = p
1087
1087
  return Endpoint(element=e, port=p, ref=ref)
1088
1088
 
1089
- EDGE_ATTRS = {"from", "to", "src", "dst", "source", "target", "label", "src-label", "tail-label",
1090
- "dst-label", "head-label", "class", "dir", "directed", "arrowhead", "arrowtail", "weight",
1089
+ EDGE_ATTRS = {"from", "to", "src", "dst", "source", "target", "class", "dir", "directed", "arrowhead", "arrowtail", "weight",
1091
1090
  "minlen", "constraint", "layout", "straight", "bits", "width-bits", "bus-width", "net",
1092
1091
  "tooltip", "id", "nodes", "path", "priority", "lanes"}
1093
1092
 
1093
+ def _connection_attrs(self, el: ET.Element, attrs: Dict[str, Any]) -> Dict[str, Any]:
1094
+ """Ignore retired connection labels while still accepting older XML files."""
1095
+ retired = {"label", "src-label", "tail-label", "dst-label", "head-label"}
1096
+ if retired.intersection(attrs) or el.find("label") is not None or (el.text or "").strip():
1097
+ self.graph.warn(f"{_loc(el)}: connection labels are no longer supported; ignored")
1098
+ return {k: v for k, v in attrs.items() if k not in retired}
1099
+
1094
1100
  def _make_edges(self, el: ET.Element, extra: Dict[str, Any], sub: Subsystem):
1095
1101
  g = self.graph
1096
1102
  attrs = {k: v for k, v in el.attrib.items() if not k.startswith("__")}
1097
1103
  attrs.update({k: v for k, v in extra.items() if not k.startswith("__")})
1104
+ attrs = self._connection_attrs(el, attrs)
1098
1105
  where = _loc(el)
1099
1106
  srcs = _split_refs(attrs.get("from") or attrs.get("src") or attrs.get("source"))
1100
1107
  dsts = _split_refs(attrs.get("to") or attrs.get("dst") or attrs.get("target"))
1101
1108
  if not srcs or not dsts:
1102
1109
  self.errors.append(f"{where}: <{el.tag}> needs from= and to=")
1103
1110
  return
1104
- label = attrs.get("label")
1105
- if label is None:
1106
- lab_el = el.find("label")
1107
- label = (lab_el.text or "").strip() if lab_el is not None else (el.text or "").strip()
1108
1111
  classes = _split_refs(attrs.get("class"))
1109
1112
  raw_style = {}
1110
1113
  for k, v in attrs.items():
@@ -1127,11 +1130,9 @@ class Builder:
1127
1130
  de = self._endpoint(d, where, eid, "d")
1128
1131
  if se is None or de is None:
1129
1132
  continue
1130
- e = Edge(id=eid, src=se, dst=de, label=label or "", classes=list(classes), style=dict(style),
1133
+ e = Edge(id=eid, src=se, dst=de, classes=list(classes), style=dict(style),
1131
1134
  line=_line(el))
1132
1135
  e.attrs = attrs
1133
- e.src_label = attrs.get("src-label", attrs.get("tail-label", ""))
1134
- e.dst_label = attrs.get("dst-label", attrs.get("head-label", ""))
1135
1136
  dirv = (attrs.get("dir") or "").lower()
1136
1137
  if attrs.get("directed") is not None:
1137
1138
  e.directed = st.parse_bool(attrs.get("directed"), True)
@@ -1165,36 +1166,38 @@ class Builder:
1165
1166
  g = self.graph
1166
1167
  where = _loc(el)
1167
1168
  bid = el.get("id") or f"bus{len(g.buses) + 1}"
1168
- bus = Bus(id=bid, label=el.get("label", bid), line=_line(el))
1169
+ attrs = self._connection_attrs(el, el.attrib)
1170
+ bus = Bus(id=bid, line=_line(el))
1169
1171
  bus.classes = _split_refs(el.get("class"))
1170
1172
  bus.bits = el.get("bits", el.get("width-bits", ""))
1171
1173
  bus.orientation = el.get("orientation", "auto")
1172
1174
  bus.tooltip = el.get("tooltip", "")
1173
- raw = {k: v for k, v in el.attrib.items()
1174
- if k not in ("id", "label", "class", "bits", "orientation", "taps", "tooltip", "width-bits")
1175
+ raw = {k: v for k, v in attrs.items()
1176
+ if k not in ("id", "class", "bits", "orientation", "taps", "tooltip", "width-bits")
1175
1177
  and not k.startswith("__")}
1176
1178
  if "style" in raw and raw["style"].lower() not in st.DASH_PATTERNS:
1177
1179
  raw.update(st.parse_css_style(raw.pop("style")))
1178
1180
  bus.style = st.normalize_style(raw)
1179
1181
  k = 0
1180
- tap_specs: List[Tuple[str, str, Optional[str], Optional[int]]] = []
1182
+ tap_specs: List[Tuple[str, Optional[str], Optional[int]]] = []
1181
1183
  for t in _split_refs(el.get("taps")):
1182
- tap_specs.append((t, "", None, _line(el)))
1184
+ tap_specs.append((t, None, _line(el)))
1183
1185
  for c in el:
1184
1186
  if c.tag in ("tap", "member", "endpoint", "connect", "pin"):
1187
+ self._connection_attrs(c, c.attrib)
1185
1188
  ref = c.get("node") or c.get("ref") or c.get("to") or c.get("id")
1186
1189
  if not ref:
1187
1190
  self.errors.append(f"{_loc(c)}: <tap> needs node=")
1188
1191
  continue
1189
1192
  if c.get("port") and ":" not in ref:
1190
1193
  ref = f"{ref}:{c.get('port')}"
1191
- tap_specs.append((ref, c.get("label", ""), c.get("dir", c.get("direction")), _line(c)))
1192
- for ref, lab, d, ln in tap_specs:
1194
+ tap_specs.append((ref, c.get("dir", c.get("direction")), _line(c)))
1195
+ for ref, d, ln in tap_specs:
1193
1196
  ep = self._endpoint(ref, f"{_loc(el)}" if ln is None else f"{os.path.basename(el.get(FILE, ''))}:{ln}",
1194
1197
  f"bus~{bid}~{k}", "t")
1195
1198
  k += 1
1196
1199
  if ep is not None:
1197
- bus.taps.append(BusTap(endpoint=ep, label=lab, direction=d))
1200
+ bus.taps.append(BusTap(endpoint=ep, direction=d))
1198
1201
  if len(bus.taps) < 2:
1199
1202
  self.errors.append(f"{where}: bus '{bid}' needs at least two taps")
1200
1203
  return
@@ -1355,11 +1358,6 @@ def _bundle_parallel(g: Graph):
1355
1358
  continue
1356
1359
  first = es[0]
1357
1360
  first.multiplicity = sum(x.multiplicity for x in es)
1358
- labels = [x.label for x in es if x.label]
1359
- if labels:
1360
- uniq = list(dict.fromkeys(labels))
1361
- first.label = ", ".join(uniq[:3]) + (" …" if len(uniq) > 3 else "")
1362
- first.label = (first.label + " " if first.label else "") + f"×{first.multiplicity}"
1363
1361
  for x in es[1:]:
1364
1362
  dropped.add(x)
1365
1363
  for ep in (x.src, x.dst):
@@ -1435,8 +1433,6 @@ def _apply_styles(g: Graph):
1435
1433
  base.update(e.style)
1436
1434
  if "color" in base and "stroke" not in e.style:
1437
1435
  base["stroke"] = base["color"]
1438
- if "label-color" not in cls and "label-color" not in e.style and "color" in (cls.keys() | e.style.keys()):
1439
- base["label-color"] = st.mix(base["color"], theme["text"], 0.35)
1440
1436
  if "stroke-width-scale" in e.style:
1441
1437
  base["stroke-width"] = base.get("stroke-width", 1.3) * e.style["stroke-width-scale"]
1442
1438
  for raw in [st.class_raw(e.classes, g.classes, "edge")]:
@@ -23,7 +23,7 @@ from ..geometry import E, N, OPPOSITE, S, W, Rect, parse_side, side_of_point
23
23
  from ..model import Bus, Edge, Element, Graph, Hint, Node, Port, Subsystem
24
24
  from ..progress import activity, report
25
25
  from ..portlabels import port_content_padding
26
- from ..textmetrics import LABEL_CLEARANCE, label_box_size, text_block_size
26
+ from ..textmetrics import text_block_size
27
27
  from .detail import LevelQP, QPEdge, QPHint, QPItem, QPTerminal, solve_level
28
28
  from .floorplan import (FPEdge, FPHint, FPItem, FPProblem, FPState, FPTerminal, FPVariant, Floorplanner)
29
29
  from .graphalgo import fiedler_order, partition_items
@@ -418,36 +418,13 @@ class HierarchicalPlacer:
418
418
  for le in ledges:
419
419
  self._classify(le)
420
420
  self.auto_pad = self._auto_padding()
421
- self.label_margins, self.label_padding = self._label_spacing()
422
421
  self.deadline = (time.time() + self.s.time_budget) if self.s.time_budget > 0 else None
423
422
 
424
- def _label_spacing(self):
425
- """Reserve channels for text before floorplanning, including draft layouts."""
426
- margins, padding = {}, {}
427
- for le in self.ledges:
428
- source = le.source
429
- if not isinstance(source, (Edge, Bus)) or not source.label:
430
- continue
431
- if isinstance(source, Edge) and not self.s.edge_labels:
432
- continue
433
- w, h = label_box_size(source.label, source.style, self.s.font_family, bus=isinstance(source, Bus))
434
- need = max(w, h) + 2 * LABEL_CLEARANCE
435
- scope = le.scope
436
- children = [scope.child_containing(el) for el in (le.a, le.b) if el is not scope]
437
- for child in children:
438
- if child is not None:
439
- key = (scope.uid, child.uid)
440
- margins[key] = max(margins.get(key, 0.0), need / 2)
441
- if le.a is scope or le.b is scope:
442
- padding[scope.uid] = max(padding.get(scope.uid, 0.0), need)
443
- return margins, padding
444
-
445
423
  def _text_margin(self, sub, child):
446
- margin = self.label_margins.get((sub.uid, child.uid), 0.0)
447
424
  if isinstance(child, Subsystem) and child.visible:
448
425
  bands = port_content_padding(estimate_port_sides(child, flow_of(child, self.s)), self.s)
449
- margin += max(bands.values())
450
- return margin
426
+ return max(bands.values())
427
+ return 0.0
451
428
 
452
429
  def _auto_padding(self) -> Dict[int, float]:
453
430
  """Extra inner padding for subsystems that many wires leave: every wire needs a straight
@@ -567,8 +544,7 @@ class HierarchicalPlacer:
567
544
  self.margin_bonus[(x.uid, c.uid)] = role_max[role]
568
545
 
569
546
  def _pad(self, sub: Subsystem) -> float:
570
- return max(subsystem_padding(sub, self.s) + self.auto_pad.get(sub.uid, 0.0),
571
- self.label_padding.get(sub.uid, 0.0)) + self.pad_bonus.get(sub.uid, 0.0)
547
+ return subsystem_padding(sub, self.s) + self.auto_pad.get(sub.uid, 0.0) + self.pad_bonus.get(sub.uid, 0.0)
572
548
 
573
549
  # ------------------------------------------------------------------------------------
574
550
  def node_variant(self, n: Node) -> FPVariant:
@@ -907,7 +883,8 @@ class HierarchicalPlacer:
907
883
  box_w = cw + 2 * pad
908
884
  box_h = ch + 2 * pad + header
909
885
  if not is_root and sub.visible and sub.label:
910
- lw, _ = text_block_size(sub.label, float(sub.style.get("font-size", 12.5)), True,
886
+ bold = str(sub.style.get("font-weight", "700")) in ("bold", "600", "700", "800", "900")
887
+ lw, _ = text_block_size(sub.label, float(sub.style.get("font-size", s.font_size+.5)), bold,
911
888
  sub.style.get("font-family", s.font_family))
912
889
  lw += 2 * pad + 8
913
890
  if box_w < lw: