1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::ops::Range;
6
7use crate::model::{Arrow, Deco, FracStyle, Kind, Mark, MatrixEnv, NodeId, SeqId, SeqRange, Tree, Variant};
8use crate::path::{CaretPath, Step};
9
10#[derive(Debug, Clone)]
12pub struct Source {
13 pub tex: String,
15 pub spans: SpanMap,
17 pub revision: u64,
19}
20
21impl Source {
22 pub fn caret_offset(&self, at: &CaretPath) -> Option<usize> {
24 self.spans.gaps.get(&at.steps)?.get(at.index).copied()
25 }
26}
27
28#[derive(Debug, Clone, Default)]
30pub struct SpanMap {
31 pub(crate) owner: u64,
32 pub(crate) nodes: Vec<(NodeId, Range<usize>)>,
33 pub(crate) seqs: Vec<(SeqId, Range<usize>)>,
34 pub(crate) gaps: HashMap<Vec<Step>, Vec<usize>>,
35}
36
37impl SpanMap {
38 pub(crate) fn push_node(&mut self, id: NodeId, range: Range<usize>) {
39 self.nodes.push((id, range));
40 }
41
42 pub(crate) fn push_seq(&mut self, id: SeqId, range: Range<usize>) {
43 self.seqs.push((id, range));
44 }
45}
46
47pub(crate) fn source(tree: &Tree, owner: u64, revision: u64, placeholders: bool) -> Source {
49 let mut ex = Exporter::new(tree, placeholders, None);
50 ex.spans = Some(SpanMap { owner, ..SpanMap::default() });
51 ex.emit_seq(tree.root());
52 Source { tex: ex.out, spans: ex.spans.unwrap_or_default(), revision }
53}
54
55pub(crate) fn clean_tex<'a>(tree: &'a Tree, host_box: Option<&'a mut dyn FnMut(u32) -> Option<String>>) -> String {
57 let mut ex = Exporter::new(tree, false, host_box);
58 ex.emit_seq(tree.root());
59 ex.out
60}
61
62pub(crate) fn range_tex(tree: &Tree, sel: SeqRange) -> String {
64 let mut ex = Exporter::new(tree, false, None);
65 ex.text = tree.is_text_slot(sel.seq);
66 if ex.text {
67 ex.push("\\text{");
68 }
69 let items = tree.items(sel.seq);
70 let hi = sel.hi().min(items.len());
71 for (i, &n) in items.iter().enumerate().take(hi).skip(sel.lo()) {
72 ex.emit_node(n, i);
73 }
74 if ex.text {
75 ex.push("}");
76 }
77 ex.out
78}
79
80struct Exporter<'a> {
81 tree: &'a Tree,
82 out: String,
83 placeholders: bool,
85 text: bool,
87 spans: Option<SpanMap>,
88 path: Vec<Step>,
89 seps: Vec<usize>,
91 host_box: Option<&'a mut dyn FnMut(u32) -> Option<String>>,
92}
93
94impl<'a> Exporter<'a> {
95 fn new(tree: &'a Tree, placeholders: bool, host_box: Option<&'a mut dyn FnMut(u32) -> Option<String>>) -> Self {
96 Self { tree, out: String::new(), placeholders, text: false, spans: None, path: Vec::new(), seps: Vec::new(), host_box }
97 }
98
99 fn push(&mut self, s: &str) {
101 if s.starts_with(char::is_alphabetic) && ends_in_control_word(&self.out) {
102 self.seps.push(self.out.len());
103 self.out.push(' ');
104 }
105 self.out.push_str(s);
106 }
107
108 fn start_after_sep(&self, start: usize) -> usize {
110 if self.seps.binary_search(&start).is_ok() { start + 1 } else { start }
111 }
112
113 fn emit_seq(&mut self, seq: SeqId) {
114 let start = self.out.len();
115 let items = self.tree.items(seq);
116 let mut gaps = Vec::with_capacity(items.len() + 1);
117 if items.is_empty() && self.placeholders {
118 self.push("\\phantom{x}");
119 }
120 for (i, &n) in items.iter().enumerate() {
121 gaps.push(self.emit_node(n, i));
122 }
123 let end = self.out.len();
124 let start = self.start_after_sep(start).min(end);
125 gaps.push(if items.is_empty() { start } else { end });
126 if let Some(spans) = &mut self.spans {
127 spans.push_seq(seq, start..end);
128 spans.gaps.insert(self.path.clone(), gaps);
129 }
130 }
131
132 fn emit_slot(&mut self, node: NodeId, index: usize, seq: SeqId) {
134 let slot = self.tree.slot_of(node, seq);
135 if let Some(slot) = slot {
136 self.path.push(Step { node: index, slot });
137 }
138 self.emit_seq(seq);
139 if slot.is_some() {
140 self.path.pop();
141 }
142 }
143
144 fn emit_braced(&mut self, node: NodeId, index: usize, seq: SeqId) {
145 self.push("{");
146 self.emit_slot(node, index, seq);
147 self.push("}");
148 }
149
150 fn emit_arg(&mut self, node: NodeId, index: usize, seq: SeqId) {
152 let items = self.tree.items(seq);
153 let bare = items.len() == 1
154 && matches!(self.tree.kind(items[0]), Some(Kind::Atom(s)) if s.latex.chars().count() == 1);
155 if bare { self.emit_slot(node, index, seq) } else { self.emit_braced(node, index, seq) }
156 }
157
158 fn emit_attachment(&mut self, node: NodeId, index: usize, marker: &str, seq: Option<SeqId>) {
160 let Some(seq) = seq else { return };
161 if self.placeholders || !self.tree.is_empty(seq) {
162 self.push(marker);
163 self.emit_arg(node, index, seq);
164 }
165 }
166
167 fn script_safe_base(&self, seq: SeqId) -> bool {
169 match self.tree.items(seq) {
170 [only] => matches!(
171 self.tree.kind(*only),
172 Some(Kind::Atom(_) | Kind::Frac { .. } | Kind::Sqrt { .. } | Kind::Delim { .. })
173 | Some(Kind::Accent { .. } | Kind::Styled { .. })
174 ),
175 _ => false,
176 }
177 }
178
179 fn emit_node(&mut self, node: NodeId, index: usize) -> usize {
181 let start = self.out.len();
182 let Some(kind) = self.tree.kind(node).cloned() else {
183 return start;
184 };
185 let wrap_math = self.text && !matches!(kind, Kind::Atom(_) | Kind::HostBox { .. });
187 if wrap_math {
188 self.push("\\ensuremath{");
189 self.text = false;
190 }
191 match kind {
192 Kind::Atom(s) => {
193 let latex = if self.text { text_latex(&s.latex) } else { Cow::Borrowed(s.latex.as_str()) };
194 self.push(&latex);
195 }
196 Kind::HostBox { token } => {
198 let content = self.host_box.as_mut().and_then(|f| f(token));
199 match content {
200 Some(c) => self.push(&c),
201 None => self.push(&format!("\\hostbox{{{token}}}")),
202 }
203 }
204 Kind::Frac { num, den, style } => {
205 self.push(frac_cmd(style));
206 self.emit_braced(node, index, num);
207 self.emit_braced(node, index, den);
208 }
209 Kind::Script { base, sub, sup } => {
210 if self.script_safe_base(base) {
211 self.emit_slot(node, index, base);
212 } else {
213 self.emit_braced(node, index, base);
214 }
215 self.emit_attachment(node, index, "_", sub);
216 self.emit_attachment(node, index, "^", sup);
217 }
218 Kind::BigOp { op, lower, upper } => {
219 self.push(&op.latex);
220 self.emit_attachment(node, index, "_", Some(lower));
221 self.emit_attachment(node, index, "^", Some(upper));
222 }
223 Kind::Sqrt { index: degree, radicand } => {
224 self.push("\\sqrt");
225 if self.placeholders || !self.tree.is_empty(degree) {
226 self.push("[{");
227 self.emit_slot(node, index, degree);
228 self.push("}]");
229 }
230 self.emit_braced(node, index, radicand);
231 }
232 Kind::Delim { open, close, body } => {
233 self.push("\\left");
234 self.push(delim_tex(open));
235 self.emit_slot(node, index, body);
236 self.push("\\right");
237 self.push(delim_tex(close));
238 }
239 Kind::Accent { mark, base } => {
240 self.push(accent_cmd(mark));
241 self.emit_braced(node, index, base);
242 }
243 Kind::UnderOver { base, over, under, over_deco, under_deco } => {
244 self.emit_under_over(node, index, base, [(over, over_deco, true), (under, under_deco, false)]);
245 }
246 Kind::Styled { variant: Variant::Text, content } => {
247 self.push("\\text{");
248 self.text = true;
249 self.emit_slot(node, index, content);
250 self.text = false;
251 self.push("}");
252 }
253 Kind::Styled { variant, content } => {
254 self.push(variant_cmd(variant));
255 self.emit_braced(node, index, content);
256 }
257 Kind::Matrix { env, rows } => self.emit_matrix(node, index, env, &rows),
258 Kind::Ratio { left, right } => {
260 self.emit_braced(node, index, left);
261 self.push("\\mathrel{:}");
262 self.emit_braced(node, index, right);
263 }
264 Kind::EvalBar { body, lower, upper } => {
265 self.push("\\left.");
266 self.emit_slot(node, index, body);
267 self.push("\\right|");
268 self.emit_attachment(node, index, "_", Some(lower));
269 self.emit_attachment(node, index, "^", Some(upper));
270 }
271 Kind::LabeledArrow { arrow, over, under } => {
272 self.push(arrow_cmd(arrow));
273 if self.placeholders || !self.tree.is_empty(under) {
274 self.push("[{");
275 self.emit_slot(node, index, under);
276 self.push("}]");
277 }
278 self.emit_braced(node, index, over);
279 }
280 }
281 if wrap_math {
282 self.text = true;
283 self.push("}");
284 }
285 let start = self.start_after_sep(start);
286 if let Some(spans) = &mut self.spans {
287 spans.push_node(node, start..self.out.len());
288 }
289 start
290 }
291
292 fn emit_under_over(&mut self, node: NodeId, index: usize, base: SeqId, labels: [(Option<SeqId>, Deco, bool); 2]) {
294 let shown = |ex: &Self, l: Option<SeqId>| l.filter(|&s| ex.placeholders || !ex.tree.is_empty(s));
295 let [over, under] = labels;
296 let mut closers: Vec<(Option<SeqId>, Deco, bool)> = Vec::new();
298 for (label, deco, is_over) in [over, under] {
299 if label.is_none() {
300 continue;
301 }
302 let label = shown(self, label);
303 match (deco, label) {
304 (Deco::Brace, _) => self.push(if is_over { "\\overbrace{" } else { "\\underbrace{" }),
305 (_, Some(l)) => {
306 self.push(if is_over { "\\overset" } else { "\\underset" });
307 self.emit_braced(node, index, l);
308 self.push("{");
309 }
310 (_, None) => {}
311 }
312 match deco {
313 Deco::Arrow => self.push(if is_over { "\\overrightarrow{" } else { "\\underrightarrow{" }),
314 Deco::Line => self.push(if is_over { "\\overline{" } else { "\\underline{" }),
315 Deco::None | Deco::Brace => {}
316 }
317 closers.push((label, deco, is_over));
318 }
319 self.emit_slot(node, index, base);
320 for (label, deco, is_over) in closers.into_iter().rev() {
321 if matches!(deco, Deco::Arrow | Deco::Line) {
322 self.push("}");
323 }
324 match (deco, label) {
325 (Deco::Brace, Some(l)) => {
326 self.push(if is_over { "}^" } else { "}_" });
327 self.emit_braced(node, index, l);
328 }
329 (Deco::Brace, None) | (_, Some(_)) => self.push("}"),
330 (_, None) => {}
331 }
332 }
333 }
334
335 fn emit_matrix(&mut self, node: NodeId, index: usize, env: MatrixEnv, rows: &[Vec<SeqId>]) {
336 let name = matrix_env_name(env);
337 self.push(&format!("\\begin{{{name}}}"));
338 if env == MatrixEnv::Array {
339 let cols = rows.first().map_or(0, Vec::len);
340 self.push(&format!("{{{}}}", "c".repeat(cols)));
341 }
342 for (ri, row) in rows.iter().enumerate() {
343 if ri > 0 {
344 self.push(" \\\\ ");
345 }
346 for (ci, &cell) in row.iter().enumerate() {
347 if ci > 0 {
348 self.push(" & ");
349 }
350 self.emit_slot(node, index, cell);
351 }
352 }
353 self.push(&format!("\\end{{{name}}}"));
354 }
355}
356
357fn ends_in_control_word(s: &str) -> bool {
359 let letters = s.chars().rev().take_while(|c| c.is_alphabetic()).map(char::len_utf8).sum::<usize>();
360 if letters == 0 {
361 return false;
362 }
363 let slashes = s[..s.len() - letters].chars().rev().take_while(|&c| c == '\\').count();
364 slashes % 2 == 1
365}
366
367fn text_latex(latex: &str) -> Cow<'_, str> {
369 let mut chars = latex.chars();
370 if let (Some(c), None) = (chars.next(), chars.next()) {
371 return match c {
372 '_' => Cow::Borrowed("\\_"),
373 '^' => Cow::Borrowed("\\textasciicircum{}"),
374 '~' => Cow::Borrowed("\\textasciitilde{}"),
375 _ => Cow::Borrowed(latex),
376 };
377 }
378 match latex {
379 "\\%" | "\\#" | "\\&" | "\\$" | "\\_" | "\\{" | "\\}" | "\\ " => Cow::Borrowed(latex),
380 "\\sim" => Cow::Borrowed("\\textasciitilde{}"),
381 "\\backslash" => Cow::Borrowed("\\textbackslash{}"),
382 "\\prime" => Cow::Borrowed("'"),
383 _ => match latex.strip_prefix("\\text{").and_then(|l| l.strip_suffix('}')) {
384 Some(inner) => Cow::Owned(inner.to_string()),
385 None => Cow::Owned(format!("\\ensuremath{{{latex}}}")),
386 },
387 }
388}
389
390fn frac_cmd(style: FracStyle) -> &'static str {
391 match style {
392 FracStyle::Bar => "\\frac",
393 FracStyle::Display => "\\dfrac",
394 FracStyle::Text => "\\tfrac",
395 FracStyle::Binom => "\\binom",
396 FracStyle::Atop => "\\genfrac{}{}{0pt}{}",
398 }
399}
400
401fn delim_tex(c: char) -> &'static str {
402 match c {
403 '(' => "(",
404 ')' => ")",
405 '[' => "[",
406 ']' => "]",
407 '{' => "\\{",
408 '}' => "\\}",
409 '|' => "|",
410 '‖' => "\\|",
411 '/' => "/",
412 '⌈' => "\\lceil",
413 '⌉' => "\\rceil",
414 '⌊' => "\\lfloor",
415 '⌋' => "\\rfloor",
416 '⟨' => "\\langle",
417 '⟩' => "\\rangle",
418 _ => ".",
419 }
420}
421
422fn accent_cmd(mark: Mark) -> &'static str {
423 match mark {
424 Mark::Hat => "\\hat",
425 Mark::Vec => "\\vec",
426 Mark::Bar => "\\bar",
427 Mark::Tilde => "\\tilde",
428 Mark::Dot => "\\dot",
429 Mark::Ddot => "\\ddot",
430 Mark::Widehat => "\\widehat",
431 Mark::Widetilde => "\\widetilde",
432 Mark::Overline => "\\overline",
433 Mark::Underline => "\\underline",
434 Mark::Check => "\\check",
435 Mark::Breve => "\\breve",
436 Mark::Ring => "\\mathring",
437 }
438}
439
440fn arrow_cmd(arrow: Arrow) -> &'static str {
441 match arrow {
442 Arrow::Right => "\\xrightarrow",
443 Arrow::Left => "\\xleftarrow",
444 }
445}
446
447fn variant_cmd(v: Variant) -> &'static str {
448 match v {
449 Variant::Normal => "\\mathnormal",
450 Variant::Bold => "\\mathbf",
451 Variant::Blackboard => "\\mathbb",
452 Variant::Calligraphic => "\\mathcal",
453 Variant::Fraktur => "\\mathfrak",
454 Variant::Roman => "\\mathrm",
455 Variant::SansSerif => "\\mathsf",
456 Variant::Typewriter => "\\mathtt",
457 Variant::Text => "\\text",
458 Variant::OperatorName => "\\operatorname",
459 }
460}
461
462fn matrix_env_name(env: MatrixEnv) -> &'static str {
463 match env {
464 MatrixEnv::Matrix => "matrix",
465 MatrixEnv::Pmatrix => "pmatrix",
466 MatrixEnv::Bmatrix => "bmatrix",
467 MatrixEnv::Vmatrix => "vmatrix",
468 MatrixEnv::Cases => "cases",
469 MatrixEnv::Aligned => "aligned",
470 MatrixEnv::Array => "array",
471 }
472}