1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::ops::Range;
6
7use crate::model::{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 }
259 if wrap_math {
260 self.text = true;
261 self.push("}");
262 }
263 let start = self.start_after_sep(start);
264 if let Some(spans) = &mut self.spans {
265 spans.push_node(node, start..self.out.len());
266 }
267 start
268 }
269
270 fn emit_under_over(&mut self, node: NodeId, index: usize, base: SeqId, labels: [(Option<SeqId>, Deco, bool); 2]) {
272 let shown = |ex: &Self, l: Option<SeqId>| l.filter(|&s| ex.placeholders || !ex.tree.is_empty(s));
273 let [over, under] = labels;
274 let mut closers: Vec<(Option<SeqId>, Deco, bool)> = Vec::new();
276 for (label, deco, is_over) in [over, under] {
277 if label.is_none() {
278 continue;
279 }
280 let label = shown(self, label);
281 match (deco, label) {
282 (Deco::Brace, _) => self.push(if is_over { "\\overbrace{" } else { "\\underbrace{" }),
283 (_, Some(l)) => {
284 self.push(if is_over { "\\overset" } else { "\\underset" });
285 self.emit_braced(node, index, l);
286 self.push("{");
287 }
288 (_, None) => {}
289 }
290 match deco {
291 Deco::Arrow => self.push(if is_over { "\\overrightarrow{" } else { "\\underrightarrow{" }),
292 Deco::Line => self.push(if is_over { "\\overline{" } else { "\\underline{" }),
293 Deco::None | Deco::Brace => {}
294 }
295 closers.push((label, deco, is_over));
296 }
297 self.emit_slot(node, index, base);
298 for (label, deco, is_over) in closers.into_iter().rev() {
299 if matches!(deco, Deco::Arrow | Deco::Line) {
300 self.push("}");
301 }
302 match (deco, label) {
303 (Deco::Brace, Some(l)) => {
304 self.push(if is_over { "}^" } else { "}_" });
305 self.emit_braced(node, index, l);
306 }
307 (Deco::Brace, None) | (_, Some(_)) => self.push("}"),
308 (_, None) => {}
309 }
310 }
311 }
312
313 fn emit_matrix(&mut self, node: NodeId, index: usize, env: MatrixEnv, rows: &[Vec<SeqId>]) {
314 let name = matrix_env_name(env);
315 self.push(&format!("\\begin{{{name}}}"));
316 if env == MatrixEnv::Array {
317 let cols = rows.first().map_or(0, Vec::len);
318 self.push(&format!("{{{}}}", "c".repeat(cols)));
319 }
320 for (ri, row) in rows.iter().enumerate() {
321 if ri > 0 {
322 self.push(" \\\\ ");
323 }
324 for (ci, &cell) in row.iter().enumerate() {
325 if ci > 0 {
326 self.push(" & ");
327 }
328 self.emit_slot(node, index, cell);
329 }
330 }
331 self.push(&format!("\\end{{{name}}}"));
332 }
333}
334
335fn ends_in_control_word(s: &str) -> bool {
337 let letters = s.chars().rev().take_while(|c| c.is_alphabetic()).map(char::len_utf8).sum::<usize>();
338 if letters == 0 {
339 return false;
340 }
341 let slashes = s[..s.len() - letters].chars().rev().take_while(|&c| c == '\\').count();
342 slashes % 2 == 1
343}
344
345fn text_latex(latex: &str) -> Cow<'_, str> {
347 let mut chars = latex.chars();
348 if let (Some(c), None) = (chars.next(), chars.next()) {
349 return match c {
350 '_' => Cow::Borrowed("\\_"),
351 '^' => Cow::Borrowed("\\textasciicircum{}"),
352 '~' => Cow::Borrowed("\\textasciitilde{}"),
353 _ => Cow::Borrowed(latex),
354 };
355 }
356 match latex {
357 "\\%" | "\\#" | "\\&" | "\\$" | "\\_" | "\\{" | "\\}" | "\\ " => Cow::Borrowed(latex),
358 "\\sim" => Cow::Borrowed("\\textasciitilde{}"),
359 "\\backslash" => Cow::Borrowed("\\textbackslash{}"),
360 "\\prime" => Cow::Borrowed("'"),
361 _ => match latex.strip_prefix("\\text{").and_then(|l| l.strip_suffix('}')) {
362 Some(inner) => Cow::Owned(inner.to_string()),
363 None => Cow::Owned(format!("\\ensuremath{{{latex}}}")),
364 },
365 }
366}
367
368fn frac_cmd(style: FracStyle) -> &'static str {
369 match style {
370 FracStyle::Bar => "\\frac",
371 FracStyle::Display => "\\dfrac",
372 FracStyle::Text => "\\tfrac",
373 FracStyle::Binom => "\\binom",
374 FracStyle::Atop => "\\genfrac{}{}{0pt}{}",
376 }
377}
378
379fn delim_tex(c: char) -> &'static str {
380 match c {
381 '(' => "(",
382 ')' => ")",
383 '[' => "[",
384 ']' => "]",
385 '{' => "\\{",
386 '}' => "\\}",
387 '|' => "|",
388 '‖' => "\\|",
389 '/' => "/",
390 '⌈' => "\\lceil",
391 '⌉' => "\\rceil",
392 '⌊' => "\\lfloor",
393 '⌋' => "\\rfloor",
394 '⟨' => "\\langle",
395 '⟩' => "\\rangle",
396 _ => ".",
397 }
398}
399
400fn accent_cmd(mark: Mark) -> &'static str {
401 match mark {
402 Mark::Hat => "\\hat",
403 Mark::Vec => "\\vec",
404 Mark::Bar => "\\bar",
405 Mark::Tilde => "\\tilde",
406 Mark::Dot => "\\dot",
407 Mark::Ddot => "\\ddot",
408 Mark::Widehat => "\\widehat",
409 Mark::Widetilde => "\\widetilde",
410 Mark::Overline => "\\overline",
411 Mark::Underline => "\\underline",
412 Mark::Check => "\\check",
413 Mark::Breve => "\\breve",
414 }
415}
416
417fn variant_cmd(v: Variant) -> &'static str {
418 match v {
419 Variant::Normal => "\\mathnormal",
420 Variant::Bold => "\\mathbf",
421 Variant::Blackboard => "\\mathbb",
422 Variant::Calligraphic => "\\mathcal",
423 Variant::Fraktur => "\\mathfrak",
424 Variant::Roman => "\\mathrm",
425 Variant::SansSerif => "\\mathsf",
426 Variant::Typewriter => "\\mathtt",
427 Variant::Text => "\\text",
428 Variant::OperatorName => "\\operatorname",
429 }
430}
431
432fn matrix_env_name(env: MatrixEnv) -> &'static str {
433 match env {
434 MatrixEnv::Matrix => "matrix",
435 MatrixEnv::Pmatrix => "pmatrix",
436 MatrixEnv::Bmatrix => "bmatrix",
437 MatrixEnv::Vmatrix => "vmatrix",
438 MatrixEnv::Cases => "cases",
439 MatrixEnv::Aligned => "aligned",
440 MatrixEnv::Array => "array",
441 }
442}