deepSelectionUnits function
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),
];
}