leafLocalBounds method

Rect2D? leafLocalBounds(
  1. Node n, {
  2. Map<ElementId, PathIR>? pathIRById,
  3. Map<ElementId, ImagePlacement>? imagePlacementById,
  4. Map<ElementId, ResolvedIconText>? iconTextById,
  5. Map<ElementId, PathIR>? iconPathIRById,
})

Implementation

Rect2D? leafLocalBounds(
  Node n, {
  Map<ElementId, PathIR>? pathIRById,
  Map<ElementId, ImagePlacement>? imagePlacementById,
  Map<ElementId, ResolvedIconText>? iconTextById,
  Map<ElementId, PathIR>? iconPathIRById,
}) {
  final s = services;

  switch (n) {
    case TextNode(:final data):
      final m = s.textMeasurer.measure(
        text: data.text,
        fontFamily: data.fontFamily,
        fontWeight: data.fontWeight,
        fontSize: data.fontSize,
        letterSpacing: data.letterSpacing,
      );
      return Rect2D.fromLTWH(-m.w / 2, -m.h / 2, m.w, m.h);

    case IconNode(id: final id, data: final d):
      // Icon bounds must remain deterministic across different icon
      // implementations such as font glyphs and vector paths.
      //
      // Glyph metrics and path bounds vary per icon, which would otherwise shift
      // layout and picking geometry. We still resolve the icon for rendering and
      // caching, but layout uses a stable centered square based on sizePx.
      final resolved = s.icons?.resolve(d.iconRef);
      switch (resolved) {
        case ResolvedIconText():
          iconTextById?[id] = resolved;
          // Return stable bounds (do NOT use measured glyph bounds for layout).
          final sz = d.sizePx;
          return Rect2D.fromLTWH(-sz / 2, -sz / 2, sz, sz);

        case ResolvedIconPath(:final path):
          // Keep the compiled path for rendering/caching, but still return stable bounds.
          final ir = compilePath(path);
          iconPathIRById?[id] = ir;
          final sz = d.sizePx;
          return Rect2D.fromLTWH(-sz / 2, -sz / 2, sz, sz);

        default:
          final sz = d.sizePx;
          return Rect2D.fromLTWH(-sz / 2, -sz / 2, sz, sz);
      }

    case ImageNode(id: final id, data: final d):
      final intrinsic = s.images?.intrinsicSize(id);
      final layout = d.size;

      if (layout.w <= 0 || layout.h <= 0) {
        // Not measurable yet (prevents degenerate bounds affecting snap/pick/fit)
        return null;
      }

      final placement = imageSrcDst(
        intrinsic: intrinsic ?? layout,
        layout: layout,
        fit: d.fit,
        align: d.align,
      );

      imagePlacementById?[id] = placement;
      return placement.dst;

    case PathNode(id: final id, data: final d):
      final ir = compilePath(d);
      pathIRById?[id] = ir;
      return ir.localBounds(includeStroke: true);

    default:
      return null;
  }
}