deepSelectionUnits function

List<SelectionUnit> deepSelectionUnits(
  1. ParseResult result,
  2. MathLayout layout
)

Every atom of the formula in reading order, those inside fractions, scripts and groups too, with their painted rectangles: finer units for selecting than selectionUnits. Nodes that paint nothing are left out.

Implementation

List<SelectionUnit> deepSelectionUnits(ParseResult result, MathLayout layout) {
  final units = <(MathNode, List<MathNode>)>[];
  void atoms(MathNode n, List<MathNode> outer) {
    if (n is RowNode && !n.braced) {
      for (final c in n.nodes) {
        atoms(c, outer);
      }
      return;
    }
    // Fractions, scripts, roots and \left…\right hold units of their
    // own; anything else is one.
    final parts = switch (n) {
      FracNode(:final numerator, :final denominator) => [
        numerator,
        denominator,
      ],
      ScriptsNode(:final base, :final sub, :final sup) => [?base, ?sub, ?sup],
      SqrtNode(:final index, :final body) => [?index, body],
      LeftRightNode(:final body) => [body],
      RowNode(braced: true) => [n],
      _ => null,
    };
    final chain = [...outer, n];
    if (parts == null) {
      units.add((n, chain));
      return;
    }
    for (final part in parts) {
      if (part is RowNode) {
        for (final c in part.nodes) {
          atoms(c, chain);
        }
      } else {
        atoms(part, chain);
      }
    }
  }

  atoms(result.root, const []);
  units.sort((a, b) => a.$1.span.start.compareTo(b.$1.span.start));
  final rects = List<EmRect?>.filled(units.length, null);
  for (final item in layout.items) {
    final span = item.info.span;
    if (span == null) continue;
    // The innermost unit holding the item: the last one in source order
    // that does.
    for (var i = units.length - 1; i >= 0; i--) {
      final s = units[i].$1.span;
      if (span.start >= s.start &&
          span.end <= s.end &&
          !(s.isEmpty && !span.isEmpty)) {
        final b = itemBounds(item);
        rects[i] = rects[i] == null ? b : rects[i]!.union(b);
        break;
      }
    }
  }
  return [
    for (var i = 0; i < units.length; i++)
      if (rects[i] != null)
        SelectionUnit(units[i].$1, rects[i]!, chain: units[i].$2),
  ];
}