computeHighlightRects static method

List<MapEntry<HighlightRect, HighlightRange>> computeHighlightRects(
  1. List<LatexNode> children,
  2. List<HighlightRange> ranges,
  3. RenderContext context,
  4. NodeLayout groupLayout, {
  5. LayoutCache? cache,
})

Calculates the bounding rectangles of highlight areas.

Implementation

static List<MapEntry<HighlightRect, HighlightRange>> computeHighlightRects(
  List<LatexNode> children,
  List<HighlightRange> ranges,
  RenderContext context,
  NodeLayout groupLayout, {
  LayoutCache? cache,
}) {
  if (children.isEmpty) return const [];

  // Pre-measure each child node's size to calculate x offsets.
  // If a cache exists, these nodes will hit the cache from the main measurement phase.
  final childLayouts = children
      .map((it) => measureNode(it, context, cache: cache))
      .toList();

  final fontSizePx = context.fontSize;

  // Calculate baseline alignment parameters
  double maxAscent = 0.0;
  double maxDescent = 0.0;
  for (final layout in childLayouts) {
    final ascent = layout.baseline;
    final descent = layout.height - layout.baseline;
    if (ascent > maxAscent) maxAscent = ascent;
    if (descent > maxDescent) maxDescent = descent;
  }

  // Calculate TeX standard atom spacing (matches the logic in measureGroup)
  final isScript =
      context.mathStyle == MathStyle.script ||
      context.mathStyle == MathStyle.scriptScript;

  final spacings = List<double>.filled(children.length, 0.0);
  for (int i = 0; i < children.length - 1; i++) {
    final leftNode = children[i];
    final rightNode = children[i + 1];

    if (leftNode is SpaceNode ||
        leftNode is HSpaceNode ||
        rightNode is SpaceNode ||
        rightNode is HSpaceNode) {
      continue;
    }

    final leftType = MathSpacing.classifyNode(leftNode);
    final rightType = MathSpacing.classifyNode(rightNode);
    final spacingFactor = MathSpacing.spaceBetween(
      leftType,
      rightType,
      isScript,
    );
    spacings[i] = spacingFactor * fontSizePx;
  }

  // Calculate x offset for each child node
  final xOffsets = List<double>.filled(children.length, 0.0);
  double currentX = 0.0;
  for (int i = 0; i < children.length; i++) {
    xOffsets[i] = currentX;
    currentX += childLayouts[i].width;
    if (i < spacings.length) {
      currentX += spacings[i];
    }
  }

  final result = <MapEntry<HighlightRect, HighlightRange>>[];

  for (final range in ranges) {
    if (range.nodeIndices != null) {
      // clamp start and end indices to bounds using your Dart IntRange properties
      final startIdx = range.nodeIndices!.start.clamp(0, children.length - 1);
      final endIdx = range.nodeIndices!.endInclusive.clamp(
        0,
        children.length - 1,
      );

      final x = xOffsets[startIdx];
      final endX = xOffsets[endIdx] + childLayouts[endIdx].width;

      final rect = HighlightRect(
        x: x,
        y: 0.0,
        width: endX - x,
        height: groupLayout.height,
      );
      result.add(MapEntry(rect, range));
    } else if (range.pattern != null) {
      for (int i = 0; i < children.length; i++) {
        if (_nodeContainsText(children[i], range.pattern!)) {
          final rect = HighlightRect(
            x: xOffsets[i],
            y: maxAscent - childLayouts[i].baseline,
            width: childLayouts[i].width,
            height: childLayouts[i].height,
          );
          result.add(MapEntry(rect, range));
        }
      }
    }
  }

  return result;
}