1use std::collections::HashMap;
4
5use crate::command::Point;
6use crate::export::SpanMap;
7use crate::host::{HostObject, Metrics, Rect, RenderOutput};
8use crate::menu::{Menu, MenuItem, MenuView};
9use crate::model::{Cursor, Kind, NodeId, Selection, SeqId, Tree};
10
11use mathtex_ir::{Fragment, LayoutNode, LayoutNodeKind, NodeId as IrId, Point as IrPoint};
12
13#[derive(Debug, Clone, Default)]
15pub struct BoxMap {
16 pub node: HashMap<NodeId, Rect>,
18 pub seq: HashMap<SeqId, Rect>,
20}
21
22pub(crate) fn render(
24 tree: &Tree,
25 cursor: Cursor,
26 sel: Option<Selection>,
27 spans: &SpanMap,
28 ir: Fragment,
29 menu: Option<&Menu>,
30) -> RenderOutput {
31 let boxes = match_boxes(spans, &ir);
32 let caret = caret_rect(&boxes, tree, cursor);
33 let selection = sel
34 .map(|s| selection_rects(&boxes, tree, s))
35 .unwrap_or_default();
36 let placeholders: Vec<Rect> = spans
38 .seq
39 .keys()
40 .filter(|&&s| tree.is_empty(s))
41 .filter_map(|s| boxes.seq.get(s).copied())
42 .collect();
43 let metrics = Metrics {
44 width: pt(ir.surface.width),
45 height: pt(ir.surface.height),
46 baseline: pt(ir.surface.baseline),
47 };
48 let menu = menu.map(|m| MenuView {
50 anchor: boxes.node.get(&m.anchor).copied().unwrap_or(ZERO),
51 items: m
52 .visible()
53 .iter()
54 .map(|row| MenuItem { label: row.label() })
55 .collect(),
56 selected: m.selected,
57 query: m.query.clone(),
58 });
59 let host_objects: Vec<HostObject> = spans
61 .node
62 .keys()
63 .filter_map(|&n| match tree.kind(n) {
64 Some(Kind::HostBox { token }) => boxes
65 .node
66 .get(&n)
67 .map(|&rect| HostObject { token: *token, rect }),
68 _ => None,
69 })
70 .collect();
71 RenderOutput {
72 ir,
73 caret,
74 selection,
75 placeholders,
76 metrics,
77 menu,
78 host_objects,
79 }
80}
81
82pub fn match_boxes(spans: &SpanMap, fragment: &Fragment) -> BoxMap {
84 let abs = absolute_origins(fragment);
85 let mut parent: HashMap<IrId, IrId> = HashMap::new();
87 for n in &fragment.nodes {
88 for c in children_of(n) {
89 parent.insert(c, n.id);
90 }
91 }
92 let mut box_metrics: Vec<(usize, usize, f64, f64)> = Vec::new();
94 for n in &fragment.nodes {
95 let LayoutNodeKind::Box(bx) = &n.kind else { continue };
96 let Some(s) = n.primary_source else { continue };
97 let (a, b) = (s.span.start as usize, s.span.end as usize);
98 box_metrics.push((a, b, pt(bx.metrics.height), pt(bx.metrics.depth)));
99 }
100 let own_box_split = |a: usize, b: usize, glyph_total: f64| -> Option<(f64, f64)> {
102 box_metrics
103 .iter()
104 .filter(|(ba, bb, _, _)| *ba == a && *bb == b)
105 .min_by(|(_, _, h1, d1), (_, _, h2, d2)| {
106 let e1 = (h1 + d1 - glyph_total).abs();
107 let e2 = (h2 + d2 - glyph_total).abs();
108 e1.total_cmp(&e2)
109 })
110 .map(|(_, _, h, d)| (*h, *d))
111 };
112 let mut leaves: Vec<(usize, usize, Rect)> = Vec::new();
113 let mut containers: Vec<(IrId, usize, usize, Rect)> = Vec::new();
114 for n in &fragment.nodes {
115 let Some(s) = n.primary_source else { continue };
116 let (a, b) = (s.span.start as usize, s.span.end as usize);
117 if a >= b {
118 continue;
119 }
120 if let LayoutNodeKind::Rule(r) = &n.kind {
122 if r.size.width.0 == 0 || r.size.height.0 == 0 {
123 continue;
124 }
125 }
126 if matches!(n.kind, LayoutNodeKind::Glue(_) | LayoutNodeKind::Kern(_)) {
128 continue;
129 }
130 let mut rect = rect_of(fragment, &abs, n.id);
131 if matches!(
132 n.kind,
133 LayoutNodeKind::GlyphRun(_) | LayoutNodeKind::Rule(_) | LayoutNodeKind::Drawing(_)
134 ) {
135 if let LayoutNodeKind::GlyphRun(_) = &n.kind {
137 let glyph_total = pt(n.bounds.size.height);
138 if let (Some((h, d)), Some(o)) = (own_box_split(a, b, glyph_total), abs.get(&n.id))
139 {
140 rect.y = pt(o.y) - h;
141 rect.height = h + d;
142 }
143 }
144 leaves.push((a, b, rect));
145 } else {
146 if let LayoutNodeKind::Box(bx) = &n.kind {
148 if let Some(o) = abs.get(&n.id) {
149 rect.y = pt(o.y) - pt(bx.metrics.height);
150 rect.height = pt(bx.metrics.height) + pt(bx.metrics.depth);
151 rect.width = rect.width.max(pt(bx.metrics.width));
153 }
154 }
155 containers.push((n.id, a, b, rect));
156 }
157 }
158 let is_leafless =
160 |ca: usize, cb: usize| !leaves.iter().any(|(la, lb, _)| *la >= ca && *lb <= cb);
161 let union_in = |range: &std::ops::Range<usize>| -> Option<Rect> {
162 let leaf_rects: Vec<Rect> = leaves
163 .iter()
164 .filter(|(a, b, _)| *a >= range.start && *b <= range.end)
165 .map(|(_, _, r)| *r)
166 .collect();
167 let leafless: Vec<(IrId, Rect)> = containers
169 .iter()
170 .filter(|(_, a, b, _)| *a >= range.start && *b <= range.end)
171 .filter(|(_, a, b, _)| is_leafless(*a, *b))
172 .map(|(id, _, _, r)| (*id, *r))
173 .collect();
174 let mut has_leafless_descendant: std::collections::HashSet<IrId> =
175 std::collections::HashSet::new();
176 for (id, _) in &leafless {
177 let mut cur = *id;
178 while let Some(&p) = parent.get(&cur) {
179 if leafless.iter().any(|(cid, _)| *cid == p) {
180 has_leafless_descendant.insert(p);
181 }
182 cur = p;
183 }
184 }
185 let leafless_deepest: Vec<Rect> = leafless
186 .iter()
187 .filter(|(id, _)| !has_leafless_descendant.contains(id))
188 .map(|(_, r)| *r)
189 .collect();
190 union(&[leaf_rects, leafless_deepest].concat())
191 };
192 let mut map = BoxMap::default();
193 for (&node, range) in &spans.node {
194 if let Some(r) = union_in(range) {
195 map.node.insert(node, r);
196 }
197 }
198 for (&seq, range) in &spans.seq {
199 if let Some(r) = union_in(range) {
200 map.seq.insert(seq, r);
201 }
202 }
203 map
204}
205
206fn absolute_origins(fragment: &Fragment) -> HashMap<IrId, IrPoint> {
208 let mut map = HashMap::new();
209 if let Some(root) = select_root(fragment) {
210 let base = IrPoint {
212 x: mathtex_ir::Length(0),
213 y: fragment.surface.baseline,
214 };
215 walk_origins(fragment, root, base, &mut map);
216 }
217 for n in &fragment.nodes {
219 map.entry(n.id).or_insert(n.origin);
220 }
221 map
222}
223
224fn walk_origins(fragment: &Fragment, id: IrId, parent_abs: IrPoint, map: &mut HashMap<IrId, IrPoint>) {
225 let Some(node) = fragment.node(id) else { return };
226 let abs = IrPoint {
227 x: mathtex_ir::Length(parent_abs.x.0.saturating_add(node.origin.x.0)),
228 y: mathtex_ir::Length(parent_abs.y.0.saturating_add(node.origin.y.0)),
229 };
230 map.insert(id, abs);
231 for child in children_of(node) {
232 walk_origins(fragment, child, abs, map);
233 }
234}
235
236fn children_of(node: &LayoutNode) -> Vec<IrId> {
237 match &node.kind {
238 LayoutNodeKind::Box(b) => b.children.clone(),
239 LayoutNodeKind::List(l) => l.children.clone(),
240 LayoutNodeKind::Group { children } => children.clone(),
241 _ => Vec::new(),
242 }
243}
244
245fn select_root(fragment: &Fragment) -> Option<IrId> {
247 let mut is_child = std::collections::HashSet::new();
248 for n in &fragment.nodes {
249 for c in children_of(n) {
250 is_child.insert(c);
251 }
252 }
253 fragment.nodes.iter().find_map(|n| match n.kind {
254 LayoutNodeKind::Box(_) if !is_child.contains(&n.id) => Some(n.id),
255 _ => None,
256 })
257}
258
259fn rect_of(fragment: &Fragment, abs: &HashMap<IrId, IrPoint>, id: IrId) -> Rect {
260 let Some(n) = fragment.node(id) else {
261 return ZERO;
262 };
263 let o = abs.get(&id).copied().unwrap_or(n.origin);
264 Rect {
266 x: pt(mathtex_ir::Length(o.x.0.saturating_add(n.bounds.origin.x.0))),
267 y: pt(mathtex_ir::Length(
268 o.y.0
269 .saturating_sub(n.bounds.origin.y.0)
270 .saturating_sub(n.bounds.size.height.0),
271 )),
272 width: pt(n.bounds.size.width),
273 height: pt(n.bounds.size.height),
274 }
275}
276
277pub fn caret_rect(boxes: &BoxMap, tree: &Tree, cursor: Cursor) -> Rect {
279 if tree.is_empty(cursor.seq) {
281 return boxes
282 .seq
283 .get(&cursor.seq)
284 .map(|v| caret_at(v.x, v))
285 .unwrap_or(ZERO);
286 }
287 let items = tree.items(cursor.seq);
288 let placement = if cursor.index > 0 {
290 Some((items[cursor.index - 1], true))
291 } else if cursor.index < items.len() {
292 Some((items[cursor.index], false))
293 } else {
294 None
295 };
296 if let Some((node, right_edge)) = placement {
297 if let Some(v) = boxes.node.get(&node) {
298 return caret_at(if right_edge { v.x + v.width } else { v.x }, v);
299 }
300 }
301 boxes
302 .seq
303 .get(&cursor.seq)
304 .map(|v| caret_at(v.x, v))
305 .unwrap_or(ZERO)
306}
307
308fn caret_at(x: f64, v: &Rect) -> Rect {
310 Rect {
311 x,
312 y: v.y,
313 width: 0.0,
314 height: v.height,
315 }
316}
317
318pub fn selection_rects(boxes: &BoxMap, tree: &Tree, sel: Selection) -> Vec<Rect> {
320 let lo = sel.anchor.min(sel.focus);
321 let hi = sel.anchor.max(sel.focus).min(tree.len(sel.seq));
322 let rects: Vec<Rect> = tree.items(sel.seq)[lo..hi]
323 .iter()
324 .filter_map(|n| boxes.node.get(n).copied())
325 .collect();
326 union(&rects).into_iter().collect()
327}
328
329pub fn hit_test(fragment: &Fragment, spans: &SpanMap, tree: &Tree, point: Point) -> Cursor {
331 hit_test_boxes(&match_boxes(spans, fragment), spans, tree, point)
332}
333
334#[derive(Clone, Copy)]
336enum Target {
337 Node(NodeId),
338 EmptySeq(SeqId),
339}
340
341fn hit_test_boxes(boxes: &BoxMap, spans: &SpanMap, tree: &Tree, point: Point) -> Cursor {
343 let mut best: Option<(Target, Rect, usize)> = None;
344 for (&node, &rect) in &boxes.node {
345 if contains(&rect, point) {
346 let span = spans.node.get(&node).map_or(usize::MAX, |r| r.end - r.start);
347 if best.as_ref().is_none_or(|(_, _, s)| span < *s) {
348 best = Some((Target::Node(node), rect, span));
349 }
350 }
351 }
352 for (&seq, &rect) in &boxes.seq {
354 if tree.is_empty(seq) && contains(&rect, point) {
355 let span = spans.seq.get(&seq).map_or(usize::MAX, |r| r.end - r.start);
356 if best.as_ref().is_none_or(|(_, _, s)| span < *s) {
357 best = Some((Target::EmptySeq(seq), rect, span));
358 }
359 }
360 }
361 let chosen = best
363 .map(|(t, r, _)| (t, r))
364 .or_else(|| nearest_target(boxes, tree, point));
365 if let Some((target, rect)) = chosen {
366 if let Some(c) = resolve_target(tree, target, rect, point) {
367 return c;
368 }
369 }
370 Cursor {
371 seq: tree.root(),
372 index: 0,
373 }
374}
375
376fn resolve_target(tree: &Tree, target: Target, rect: Rect, point: Point) -> Option<Cursor> {
377 match target {
378 Target::Node(node) => {
379 let (seq, idx) = tree.index_in_parent(node)?;
380 let mid = rect.x + rect.width / 2.0;
381 let index = if point.x > mid { idx + 1 } else { idx };
382 Some(Cursor { seq, index })
383 }
384 Target::EmptySeq(seq) => Some(Cursor { seq, index: 0 }),
386 }
387}
388
389fn nearest_target(boxes: &BoxMap, tree: &Tree, point: Point) -> Option<(Target, Rect)> {
391 let nodes = boxes
392 .node
393 .iter()
394 .map(|(&n, &r)| (Target::Node(n), r, rect_dist2(&r, point)));
395 let empty_seqs = boxes
396 .seq
397 .iter()
398 .filter(|&(&s, _)| tree.is_empty(s))
399 .map(|(&s, &r)| (Target::EmptySeq(s), r, rect_dist2(&r, point)));
400 nodes
401 .chain(empty_seqs)
402 .min_by(|(_, _, a), (_, _, b)| a.total_cmp(b))
403 .map(|(t, r, _)| (t, r))
404}
405
406fn rect_dist2(r: &Rect, p: Point) -> f64 {
408 let cx = p.x.clamp(r.x, r.x + r.width);
409 let cy = p.y.clamp(r.y, r.y + r.height);
410 let (dx, dy) = (p.x - cx, p.y - cy);
411 dx * dx + dy * dy
412}
413
414const ZERO: Rect = Rect {
415 x: 0.0,
416 y: 0.0,
417 width: 0.0,
418 height: 0.0,
419};
420
421fn contains(r: &Rect, p: Point) -> bool {
422 p.x >= r.x && p.x <= r.x + r.width && p.y >= r.y && p.y <= r.y + r.height
423}
424fn union(rects: &[Rect]) -> Option<Rect> {
425 let first = rects.first()?;
426 let (mut x0, mut y0) = (first.x, first.y);
427 let (mut x1, mut y1) = (first.x + first.width, first.y + first.height);
428 for r in &rects[1..] {
429 x0 = x0.min(r.x);
430 y0 = y0.min(r.y);
431 x1 = x1.max(r.x + r.width);
432 y1 = y1.max(r.y + r.height);
433 }
434 Some(Rect {
435 x: x0,
436 y: y0,
437 width: x1 - x0,
438 height: y1 - y0,
439 })
440}
441
442fn pt(len: mathtex_ir::Length) -> f64 {
444 len.0 as f64 / 65536.0
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::model::{MathClass, Symbol};
451
452 fn atom(c: &str) -> Symbol {
453 Symbol { latex: c.into(), class: MathClass::Ord }
454 }
455
456 #[test]
458 fn hit_test_falls_back_to_nearest_box_on_a_miss() {
459 let mut t = Tree::new();
460 let root = t.root();
461 t.insert_atom(Cursor { seq: root, index: 0 }, atom("a"));
462 t.insert_atom(Cursor { seq: root, index: 1 }, atom("b"));
463 let a = t.items(root)[0];
464 let b = t.items(root)[1];
465
466 let mut boxes = BoxMap::default();
467 boxes.node.insert(a, Rect { x: 0.0, y: 0.0, width: 1.0, height: 1.0 });
468 boxes.node.insert(b, Rect { x: 5.0, y: 0.0, width: 1.0, height: 1.0 });
469 let spans = SpanMap::default();
470
471 let c = hit_test_boxes(&boxes, &spans, &t, Point { x: 1.5, y: 0.5 });
473 assert_eq!(c, Cursor { seq: root, index: 1 });
474
475 let c = hit_test_boxes(&boxes, &spans, &t, Point { x: 4.0, y: 0.5 });
477 assert_eq!(c, Cursor { seq: root, index: 1 });
478
479 let c = hit_test_boxes(&boxes, &spans, &t, Point { x: 0.8, y: -5.0 });
481 assert_eq!(c, Cursor { seq: root, index: 1 });
482 }
483
484 #[test]
485 fn hit_test_on_a_truly_empty_box_map_defaults_to_document_start() {
486 let t = Tree::new();
487 let root = t.root();
488 let c = hit_test_boxes(&BoxMap::default(), &SpanMap::default(), &t, Point { x: 3.0, y: 3.0 });
489 assert_eq!(c, Cursor { seq: root, index: 0 });
490 }
491
492 #[test]
494 fn hit_test_lands_inside_empty_matrix_cells() {
495 let mut t = Tree::new();
496 let root = t.root();
497 let c = t.insert_matrix(Cursor { seq: root, index: 0 }, crate::model::MatrixEnv::Pmatrix, 2, 2);
498 let matrix = t.items(root)[0];
499 let cells = t.child_seqs(matrix); assert_eq!(cells.len(), 4);
501 assert_eq!(c.seq, cells[0]); let mut boxes = BoxMap::default();
504 boxes.node.insert(matrix, Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
505 boxes.seq.insert(cells[0], Rect { x: 1.0, y: 1.0, width: 3.0, height: 3.0 }); boxes.seq.insert(cells[1], Rect { x: 6.0, y: 1.0, width: 3.0, height: 3.0 }); boxes.seq.insert(cells[2], Rect { x: 1.0, y: 6.0, width: 3.0, height: 3.0 }); boxes.seq.insert(cells[3], Rect { x: 6.0, y: 6.0, width: 3.0, height: 3.0 }); let mut spans = SpanMap::default();
512 spans.node.insert(matrix, 0..40);
513 for (i, &cell) in cells.iter().enumerate() {
514 spans.seq.insert(cell, i * 11..i * 11 + 11);
515 }
516
517 let hit = |x: f64, y: f64| hit_test_boxes(&boxes, &spans, &t, Point { x, y });
518 assert_eq!(hit(2.5, 2.5), Cursor { seq: cells[0], index: 0 });
519 assert_eq!(hit(7.5, 2.5), Cursor { seq: cells[1], index: 0 });
520 assert_eq!(hit(2.5, 7.5), Cursor { seq: cells[2], index: 0 });
521 assert_eq!(hit(7.5, 7.5), Cursor { seq: cells[3], index: 0 });
522 }
523
524 #[test]
526 fn hit_test_prefers_node_over_a_non_empty_seqs_own_box() {
527 let mut t = Tree::new();
528 let root = t.root();
529 t.insert_atom(Cursor { seq: root, index: 0 }, atom("a"));
530 let a = t.items(root)[0];
531
532 let mut boxes = BoxMap::default();
533 boxes.node.insert(a, Rect { x: 0.0, y: 0.0, width: 2.0, height: 2.0 });
534 boxes.seq.insert(root, Rect { x: 0.0, y: 0.0, width: 2.0, height: 2.0 });
535 let spans = SpanMap::default();
536
537 let c = hit_test_boxes(&boxes, &spans, &t, Point { x: 0.5, y: 0.5 });
539 assert_eq!(c, Cursor { seq: root, index: 0 });
540 }
541}