1use std::sync::{PoisonError, RwLock};
33
34use crate::atom::{AtomClass, classify_list, spacing_in_style};
35use crate::error::MathError;
36use crate::faces::{
37 FACE_BOLD, FACE_ITALIC, FACE_REGULAR, FACE_SYMBOLS, FaceSet, GlyphMetrics,
38 accent_spacing_fallback, alphabet_map, default_math_chain, glyph_metrics, kern_em,
39};
40use crate::mbox::{BoxKind, FaceId, GlueSpec, Layout, MBox, MNode, Positioned};
41use crate::metrics::MathConstants;
42use crate::node::{
43 AccentKind, Delim, DelimSize, FracSpec, FragmentKind, Limits, LineAlign, MathFont, Node,
44 NodeKind, PhantomKind, Span, StackKind, TextStyle,
45};
46use crate::style::{Style, StyleCtx};
47
48pub struct Engine {
50 faces: FaceSet,
51 consts: MathConstants,
52 glyph_memo: RwLock<Vec<Vec<Option<GlyphMetrics>>>>,
63}
64
65impl Engine {
66 #[must_use]
68 pub fn new(faces: FaceSet) -> Self {
69 Self {
70 faces,
71 consts: crate::metrics::CM,
72 glyph_memo: RwLock::new(Vec::new()),
73 }
74 }
75
76 #[cfg(feature = "bundled-faces")]
82 pub fn bundled() -> Result<Self, fmd_font::FontError> {
83 Ok(Self::new(FaceSet::bundled()?))
84 }
85
86 #[must_use]
88 pub fn faces(&self) -> &FaceSet {
89 &self.faces
90 }
91
92 #[must_use]
94 pub fn constants(&self) -> &MathConstants {
95 &self.consts
96 }
97
98 pub fn typeset(&self, source: &str, style: Style) -> Result<Layout, MathError> {
106 let root = crate::parse(source)?;
107 self.finish(&root, StyleCtx::new(style))
108 }
109
110 pub fn typeset_text(&self, source: &str) -> Result<Layout, MathError> {
116 let root = crate::parse_text(source)?;
117 self.finish(&root, StyleCtx::new(Style::Text))
118 }
119
120 pub fn typeset_with_macros(
127 &self,
128 source: &str,
129 style: Style,
130 macros: &crate::macros::MacroSet,
131 ) -> Result<Layout, MathError> {
132 let root = crate::parse_with_macros(source, macros)?;
133 self.finish(&root, StyleCtx::new(style))
134 }
135
136 pub fn typeset_text_with_macros(
142 &self,
143 source: &str,
144 macros: &crate::macros::MacroSet,
145 ) -> Result<Layout, MathError> {
146 let root = crate::parse_text_with_macros(source, macros)?;
147 self.finish(&root, StyleCtx::new(Style::Text))
148 }
149
150 fn finish(&self, root: &Node, ctx: StyleCtx) -> Result<Layout, MathError> {
151 let items = match &root.kind {
152 NodeKind::List(items) => items.as_slice(),
153 _ => std::slice::from_ref(root),
154 };
155 let boxx = self.hlist(
156 items,
157 LayCtx {
158 style: ctx,
159 alphabet: None,
160 text_mode: false,
161 decl_size: 1.0,
162 line_stretch: 1.0,
163 },
164 )?;
165 let mut layout = Layout {
166 width: boxx.width,
167 height: boxx.height,
168 depth: boxx.depth,
169 ..Layout::default()
170 };
171 boxx.flatten_into(0.0, 0.0, &mut layout);
172 Ok(layout)
173 }
174}
175
176#[derive(Clone, Copy)]
178struct LayCtx {
179 style: StyleCtx,
180 alphabet: Option<MathFont>,
181 text_mode: bool,
183 decl_size: f64,
187 line_stretch: f64,
189}
190
191impl LayCtx {
192 fn size(&self) -> f64 {
193 self.style.size_factor() * self.decl_size
194 }
195 fn map(self, f: impl FnOnce(StyleCtx) -> StyleCtx) -> Self {
196 Self {
197 style: f(self.style),
198 ..self
199 }
200 }
201}
202
203struct Laid {
205 boxx: MBox,
206 italic: f64,
208 char_glyph: Option<(FaceId, u16)>,
211}
212
213enum LineItem {
214 Atom { laid: Laid, class: AtomClass },
215 Glue(GlueSpec),
216}
217
218impl Engine {
219 fn hlist(&self, items: &[Node], ctx: LayCtx) -> Result<MBox, MathError> {
226 let mut lines: Vec<(MBox, f64, f64)> = Vec::new();
227 let mut ctx = ctx;
228 for line in items.split(|n| matches!(n.kind, NodeKind::Linebreak)) {
229 let (boxx, after) = self.line(line, ctx)?;
230 ctx = after;
231 lines.push((boxx, ctx.size(), ctx.line_stretch));
232 }
233 if lines.len() == 1 {
234 return lines
235 .pop()
236 .map(|(boxx, _, _)| boxx)
237 .ok_or(MathError::Malformed {
238 what: "internal: empty line vector".to_owned(),
239 at: 0,
240 });
241 }
242 let mut children = Vec::new();
246 let mut baseline = 0.0_f64;
247 let mut prev_depth = 0.0_f64;
248 let mut width = 0.0_f64;
249 let mut depth = 0.0_f64;
250 let mut height = 0.0_f64;
251 for (i, (line, size, stretch)) in lines.into_iter().enumerate() {
252 if i == 0 {
253 height = line.height;
254 } else {
255 let natural = self.consts.baseline_skip * size * stretch;
258 let min_gap = prev_depth + line.height + self.consts.line_skip * size;
259 baseline -= natural.max(min_gap);
260 }
261 width = width.max(line.width);
262 depth = (-baseline) + line.depth;
263 prev_depth = line.depth;
264 children.push(Positioned {
265 dx: 0.0,
266 dy: baseline,
267 node: MNode::Box(line),
268 });
269 }
270 Ok(MBox {
271 kind: BoxKind::Vertical,
272 width,
273 height,
274 depth,
275 children,
276 })
277 }
278
279 fn line(&self, items: &[Node], outer: LayCtx) -> Result<(MBox, LayCtx), MathError> {
282 let classes = classify_list(items);
283 let mut ctx = outer;
284 let mut laid_items: Vec<LineItem> = Vec::new();
285 for (node, class) in items.iter().zip(classes) {
286 match &node.kind {
287 NodeKind::StyleChange(style) => {
288 ctx = ctx.map(|s| StyleCtx {
289 style: *style,
290 cramped: s.cramped,
291 });
292 }
293 NodeKind::SizeChange(factor) => {
294 ctx.decl_size = *factor;
295 }
296 NodeKind::LineSpacing(stretch) => {
297 ctx.line_stretch = *stretch;
298 }
299 NodeKind::ColorChange(_)
304 | NodeKind::AlignChange(_)
305 | NodeKind::AlignTab
306 | NodeKind::Fragment(
307 FragmentKind::UnmatchedClose | FragmentKind::RedundantMathShift,
308 ) => {}
309 NodeKind::Space(kind) => {
310 let em = f64::from(kind.mu()) / 18.0 * ctx.size();
311 laid_items.push(LineItem::Glue(GlueSpec {
312 natural: em,
313 stretch: 0.0,
314 shrink: 0.0,
315 }));
316 }
317 NodeKind::Tie => {
318 laid_items.push(LineItem::Glue(GlueSpec {
319 natural: self.space_width(ctx),
320 stretch: 0.0,
321 shrink: 0.0,
322 }));
323 }
324 _ => {
325 let laid = self.lay_node(node, ctx)?;
326 laid_items.push(LineItem::Atom {
327 laid,
328 class: class.unwrap_or(AtomClass::Ord),
329 });
330 }
331 }
332 }
333 let mut children = Vec::new();
335 let mut x = 0.0_f64;
336 let mut height = 0.0_f64;
337 let mut depth = 0.0_f64;
338 let mut prev: Option<(AtomClass, Option<(FaceId, u16)>)> = None;
339 for item in laid_items {
340 match item {
341 LineItem::Glue(glue) => {
342 x += glue.natural;
343 children.push(Positioned {
344 dx: x,
345 dy: 0.0,
346 node: MNode::Glue(glue),
347 });
348 prev = None;
349 }
350 LineItem::Atom { laid, class } => {
351 if let Some((prev_class, prev_glyph)) = prev {
352 let mu = spacing_in_style(prev_class, class, ctx.style.style).mu();
353 x += f64::from(mu) / 18.0 * ctx.size();
354 if mu == 0 && prev_class == AtomClass::Ord && class == AtomClass::Ord {
355 if let (Some((pf, pg)), Some((cf, cg))) = (prev_glyph, laid.char_glyph)
356 {
357 if pf == cf {
358 if let Some(font) = self.faces.font(pf) {
359 x += kern_em(font, pg, cg) * ctx.size();
360 }
361 let _ = cg;
362 }
363 }
364 }
365 }
366 height = height.max(laid.boxx.height);
367 depth = depth.max(laid.boxx.depth);
368 let advance = laid.boxx.width;
369 let glyph = laid.char_glyph;
370 children.push(Positioned {
371 dx: x,
372 dy: 0.0,
373 node: MNode::Box(laid.boxx),
374 });
375 x += advance;
376 prev = Some((class, glyph));
377 }
378 }
379 }
380 Ok((
381 MBox {
382 kind: BoxKind::Horizontal,
383 width: x,
384 height,
385 depth,
386 children,
387 },
388 ctx,
389 ))
390 }
391
392 #[allow(clippy::too_many_lines)]
394 fn lay_node(&self, node: &Node, ctx: LayCtx) -> Result<Laid, MathError> {
395 match &node.kind {
396 NodeKind::List(items) => Ok(Laid {
397 boxx: self.hlist(items, ctx)?,
398 italic: 0.0,
399 char_glyph: None,
400 }),
401 NodeKind::Symbol { ch, .. } => self.char_atom(*ch, node.span, ctx),
402 NodeKind::BigOp { ch, .. } => {
403 let scale = if ctx.style.style == Style::Display {
404 self.consts.display_op_scale
405 } else {
406 1.0
407 };
408 self.op_glyph(*ch, node.span, ctx, scale)
409 }
410 NodeKind::OpName { name, .. } => {
411 self.word_box(name, node.span, None, ctx, FACE_REGULAR)
412 }
413 NodeKind::TextRun { text, char_spans } => {
414 self.word_box(text, node.span, Some(char_spans), ctx, text_face(ctx))
415 }
416 NodeKind::Text { body } => Ok(Laid {
417 boxx: self.hlist(
418 body,
419 LayCtx {
420 text_mode: true,
421 ..ctx
422 },
423 )?,
424 italic: 0.0,
425 char_glyph: None,
426 }),
427 NodeKind::TextStyled { style, body } => {
428 let styled = LayCtx {
429 text_mode: true,
430 alphabet: Some(match style {
431 TextStyle::Bold => MathFont::Bold,
432 TextStyle::Emph => MathFont::Italic,
433 TextStyle::Underline => MathFont::Roman,
434 }),
435 ..ctx
436 };
437 let inner = self.hlist(body, styled)?;
438 if matches!(style, TextStyle::Underline) {
439 Ok(Laid {
440 boxx: self.underline_box(inner, ctx, node.span),
441 italic: 0.0,
442 char_glyph: None,
443 })
444 } else {
445 Ok(Laid {
446 boxx: inner,
447 italic: 0.0,
448 char_glyph: None,
449 })
450 }
451 }
452 NodeKind::MathIsland { body, display } => {
453 let nominal = if *display {
460 Style::Display
461 } else {
462 Style::Text
463 };
464 let style = if ctx.style.style.size_factor() < nominal.size_factor() {
465 ctx.style.style
466 } else {
467 nominal
468 };
469 Ok(Laid {
470 boxx: self.hlist(
471 body,
472 LayCtx {
473 style: StyleCtx {
474 style,
475 cramped: ctx.style.cramped,
476 },
477 alphabet: None,
478 text_mode: false,
479 ..ctx
480 },
481 )?,
482 italic: 0.0,
483 char_glyph: None,
484 })
485 }
486 NodeKind::MathFont { font, body } => self.lay_node(
487 body,
488 LayCtx {
489 alphabet: Some(*font),
490 ..ctx
491 },
492 ),
493 NodeKind::Scripts {
494 base,
495 sub,
496 sup,
497 primes,
498 } => self.scripts(base.as_deref(), sub.as_deref(), sup.as_deref(), primes, ctx),
499 NodeKind::Frac { num, den, spec } => self.fraction(num, den, *spec, ctx, node.span),
500 NodeKind::Radical { index, radicand } => {
501 self.radical(index.as_deref(), radicand, ctx, node.span)
502 }
503 NodeKind::Accent { accent, base } => self.accent(*accent, base, ctx, node.span),
504 NodeKind::LeftRight { left, right, body } => self.left_right(left, right, body, ctx),
505 NodeKind::SizedDelim { size, delim, .. } => {
506 let target = fixed_delim_target(*size) * ctx.size();
507 let boxx = self.delimiter_box(delim, target, ctx)?;
508 Ok(Laid {
509 boxx,
510 italic: 0.0,
511 char_glyph: None,
512 })
513 }
514 NodeKind::Phantom { kind, body } => {
515 let inner = self.lay_node(body, ctx)?;
516 let (w, h, d) = match kind {
517 PhantomKind::Full => (inner.boxx.width, inner.boxx.height, inner.boxx.depth),
518 PhantomKind::Horizontal => (inner.boxx.width, 0.0, 0.0),
519 PhantomKind::Vertical => (0.0, inner.boxx.height, inner.boxx.depth),
520 };
521 Ok(Laid {
522 boxx: MBox {
523 kind: BoxKind::Horizontal,
524 width: w,
525 height: h,
526 depth: d,
527 children: Vec::new(),
528 },
529 italic: 0.0,
530 char_glyph: None,
531 })
532 }
533 NodeKind::Stack {
534 kind,
535 annotation,
536 base,
537 } => {
538 let base_laid = self.lay_node(base, ctx)?;
539 let ann_ctx = match kind {
540 StackKind::Underset => ctx.map(StyleCtx::sub),
541 StackKind::Stackrel | StackKind::Overset => ctx.map(StyleCtx::sup),
542 };
543 let ann = self.lay_node(annotation, ann_ctx)?;
544 let (upper, lower) = match kind {
545 StackKind::Underset => (None, Some(ann.boxx)),
546 StackKind::Stackrel | StackKind::Overset => (Some(ann.boxx), None),
547 };
548 Ok(Laid {
549 boxx: self.with_limits(base_laid.boxx, upper, lower, ctx, base_laid.italic),
550 italic: 0.0,
551 char_glyph: None,
552 })
553 }
554 NodeKind::XArrow {
555 mapsto,
556 above,
557 below,
558 } => {
559 let size = ctx.size();
564 let above_laid = self.lay_node(above, ctx.map(StyleCtx::sup))?;
565 let below_laid = below
566 .as_deref()
567 .map(|b| self.lay_node(b, ctx.map(StyleCtx::sub)))
568 .transpose()?;
569 let label_w = above_laid
570 .boxx
571 .width
572 .max(below_laid.as_ref().map_or(0.0, |b| b.boxx.width));
573 let width = (label_w + 0.8 * size).max(1.2 * size);
574 let stretch_kind = if *mapsto {
575 crate::drawn::Stretch::MapstoArrow
576 } else {
577 crate::drawn::Stretch::RightArrow
578 };
579 let band = crate::drawn::stretch(stretch_kind, width, size);
580 let axis = self.consts.axis_height * size;
581 let band_dy = axis - band.height / 2.0;
582 let band_box = MBox {
583 kind: BoxKind::Horizontal,
584 width,
585 height: axis + band.height / 2.0,
586 depth: (band.height / 2.0 - axis).max(0.0),
587 children: vec![Positioned {
588 dx: 0.0,
589 dy: band_dy,
590 node: MNode::Path {
591 contours: band.contours,
592 span: node.span,
593 },
594 }],
595 };
596 Ok(Laid {
597 boxx: self.with_limits(
598 band_box,
599 Some(above_laid.boxx),
600 below_laid.map(|b| b.boxx),
601 ctx,
602 0.0,
603 ),
604 italic: 0.0,
605 char_glyph: None,
606 })
607 }
608 NodeKind::Fragment(FragmentKind::StrayRight(delim)) => {
609 if let Some(ch) = delim.ch {
610 self.char_atom(ch, delim.span, ctx)
611 } else {
612 Ok(Laid {
613 boxx: kern_box(self.consts.null_delimiter_space * ctx.size()),
614 italic: 0.0,
615 char_glyph: None,
616 })
617 }
618 }
619 NodeKind::Environment { name, spec, rows } => {
620 self.environment(name, spec.as_deref(), rows, ctx, node.span)
621 }
622 NodeKind::AlignBlock { align, lines } => {
623 let cell_align = match align {
629 LineAlign::Left => CellAlign::Left,
630 LineAlign::Center => CellAlign::Center,
631 LineAlign::Right => CellAlign::Right,
632 };
633 let rows: Vec<Vec<Node>> = lines.iter().map(|line| vec![line.clone()]).collect();
636 let boxx = self.grid(
637 &rows,
638 ctx,
639 &Grid {
640 align: AlignRule::Columns(vec![cell_align]),
641 ..Grid::centered(1.0)
642 },
643 node.span,
644 )?;
645 Ok(Laid {
646 boxx,
647 italic: 0.0,
648 char_glyph: None,
649 })
650 }
651 NodeKind::Fragment(_)
652 | NodeKind::StyleChange(_)
653 | NodeKind::SizeChange(_)
654 | NodeKind::ColorChange(_)
655 | NodeKind::AlignChange(_)
656 | NodeKind::LineSpacing(_)
657 | NodeKind::Space(_)
658 | NodeKind::Tie
659 | NodeKind::Linebreak
660 | NodeKind::AlignTab => Ok(Laid {
661 boxx: kern_box(0.0),
662 italic: 0.0,
663 char_glyph: None,
664 }),
665 }
666 }
667
668 fn resolve_math_char(
671 &self,
672 ch: char,
673 span: Span,
674 ctx: LayCtx,
675 ) -> Result<(FaceId, u16, char), MathError> {
676 let (mapped, chain): (char, &[FaceId]) = match ctx.alphabet {
677 Some(font) => alphabet_map(font, ch),
678 None if ctx.text_mode => (ch, &[FACE_REGULAR, FACE_ITALIC, FACE_SYMBOLS]),
679 None => (ch, default_math_chain(ch)),
680 };
681 match self.faces.resolve(mapped, chain) {
682 Some((face, gid)) => Ok((face, gid, mapped)),
683 None => Err(MathError::UnmappedChar { ch: mapped, span }),
684 }
685 }
686
687 fn char_atom(&self, ch: char, span: Span, ctx: LayCtx) -> Result<Laid, MathError> {
689 let (face, gid, mapped) = match self.resolve_math_char(ch, span, ctx) {
690 Ok(hit) => hit,
691 Err(err) => {
692 if let Some(d) = crate::drawn::symbol(ch, ctx.size()) {
703 return Ok(Laid {
704 boxx: MBox {
705 kind: BoxKind::Horizontal,
706 width: d.width,
707 height: d.height,
708 depth: d.depth,
709 children: vec![Positioned {
710 dx: 0.0,
711 dy: 0.0,
712 node: MNode::Path {
713 contours: d.contours,
714 span,
715 },
716 }],
717 },
718 italic: 0.0,
719 char_glyph: None,
720 });
721 }
722 return Err(err);
723 }
724 };
725 let metrics = self.metrics_of(face, gid);
726 let size = ctx.size();
727 Ok(Laid {
728 boxx: glyph_box(face, gid, mapped, span, size, metrics),
729 italic: metrics.italic * size,
730 char_glyph: Some((face, gid)),
731 })
732 }
733
734 fn op_glyph(&self, ch: char, span: Span, ctx: LayCtx, scale: f64) -> Result<Laid, MathError> {
736 let (face, gid, mapped) = self.resolve_math_char(ch, span, ctx)?;
737 let metrics = self.metrics_of(face, gid);
738 let size = ctx.size() * scale;
739 let gh = metrics.height * size;
740 let gd = metrics.depth * size;
741 let axis = self.consts.axis_height * ctx.size();
742 let dy = axis - (gh - gd) / 2.0;
744 let boxx = MBox {
745 kind: BoxKind::Horizontal,
746 width: metrics.advance * size,
747 height: gh + dy,
748 depth: (gd - dy).max(0.0),
749 children: vec![Positioned {
750 dx: 0.0,
751 dy,
752 node: MNode::Glyph {
753 face,
754 gid,
755 ch: mapped,
756 size,
757 span,
758 },
759 }],
760 };
761 Ok(Laid {
762 boxx,
763 italic: metrics.italic * size,
764 char_glyph: None,
765 })
766 }
767
768 fn word_box(
774 &self,
775 text: &str,
776 span: Span,
777 char_spans: Option<&[Span]>,
778 ctx: LayCtx,
779 prefer: FaceId,
780 ) -> Result<Laid, MathError> {
781 let size = ctx.size();
782 let mut children = Vec::new();
783 let mut x = 0.0_f64;
784 let mut height = 0.0_f64;
785 let mut depth = 0.0_f64;
786 let mut prev: Option<(FaceId, u16)> = None;
787 let mut last_italic = 0.0;
788 for (i, ch) in text.chars().enumerate() {
789 let ch_span = char_spans
790 .and_then(|spans| spans.get(i))
791 .copied()
792 .unwrap_or(span);
793 if ch == ' ' {
794 x += self.space_width(ctx);
795 prev = None;
796 continue;
797 }
798 let chain = [prefer, FACE_REGULAR, FACE_SYMBOLS];
799 let mapped = match ctx.alphabet {
800 Some(font) => alphabet_map(font, ch).0,
801 None => ch,
802 };
803 let Some((face, gid)) = self.faces.resolve(mapped, &chain) else {
804 return Err(MathError::UnmappedChar {
805 ch: mapped,
806 span: ch_span,
807 });
808 };
809 if let Some((pf, pg)) = prev {
810 if pf == face {
811 if let Some(font) = self.faces.font(face) {
812 x += kern_em(font, pg, gid) * size;
813 }
814 }
815 }
816 let m = self.metrics_of(face, gid);
817 children.push(Positioned {
818 dx: x,
819 dy: 0.0,
820 node: MNode::Glyph {
821 face,
822 gid,
823 ch: mapped,
824 size,
825 span: ch_span,
826 },
827 });
828 x += m.advance * size;
829 height = height.max(m.height * size);
830 depth = depth.max(m.depth * size);
831 last_italic = m.italic * size;
832 prev = Some((face, gid));
833 }
834 Ok(Laid {
835 boxx: MBox {
836 kind: BoxKind::Horizontal,
837 width: x,
838 height,
839 depth,
840 children,
841 },
842 italic: last_italic,
843 char_glyph: None,
844 })
845 }
846
847 #[inline(always)]
848 fn metrics_of(&self, face: FaceId, gid: u16) -> GlyphMetrics {
849 if let Some(hit) = self
854 .glyph_memo
855 .read()
856 .unwrap_or_else(PoisonError::into_inner)
857 .get(face.0)
858 .and_then(|per_face| per_face.get(usize::from(gid)))
859 .and_then(|slot| *slot)
860 {
861 return hit;
862 }
863 self.metrics_of_miss(face, gid)
864 }
865
866 fn metrics_of_miss(&self, face: FaceId, gid: u16) -> GlyphMetrics {
867 let Some(font) = self.faces.font(face) else {
868 return GlyphMetrics::default();
871 };
872 let m = glyph_metrics(font, gid);
873 let mut memo = self
875 .glyph_memo
876 .write()
877 .unwrap_or_else(PoisonError::into_inner);
878 if memo.len() <= face.0 {
879 memo.resize(face.0 + 1, Vec::new());
880 }
881 let per_face = &mut memo[face.0];
882 if per_face.len() <= usize::from(gid) {
883 per_face.resize(usize::from(gid) + 1, None);
884 }
885 per_face[usize::from(gid)] = Some(m);
886 m
887 }
888
889 fn space_width(&self, ctx: LayCtx) -> f64 {
890 let width = self
891 .faces
892 .font(FACE_REGULAR)
893 .map(|font| {
894 let gid = font.glyph_index(' ');
895 if gid == 0 {
896 self.consts.fallback_space
897 } else {
898 self.metrics_of(FACE_REGULAR, gid).advance
899 }
900 })
901 .unwrap_or(self.consts.fallback_space);
902 width * ctx.size()
903 }
904
905 #[allow(clippy::too_many_lines)]
911 fn scripts(
912 &self,
913 base: Option<&Node>,
914 sub: Option<&Node>,
915 sup: Option<&Node>,
916 primes: &[Span],
917 ctx: LayCtx,
918 ) -> Result<Laid, MathError> {
919 if let Some(base_node) = base {
921 if self.limits_active(base_node, ctx) {
922 let base_laid = self.lay_node(base_node, ctx)?;
923 let upper = sup
924 .map(|n| self.lay_node(n, ctx.map(StyleCtx::sup)))
925 .transpose()?
926 .map(|l| l.boxx);
927 let lower = sub
928 .map(|n| self.lay_node(n, ctx.map(StyleCtx::sub)))
929 .transpose()?
930 .map(|l| l.boxx);
931 return Ok(Laid {
932 boxx: self.with_limits(base_laid.boxx, upper, lower, ctx, base_laid.italic),
933 italic: 0.0,
934 char_glyph: None,
935 });
936 }
937 }
938 let size = ctx.size();
939 let c = &self.consts;
940 let base_laid = match base {
941 Some(node) => self.lay_node(node, ctx)?,
942 None => Laid {
943 boxx: kern_box(0.0),
944 italic: 0.0,
945 char_glyph: None,
946 },
947 };
948 let base_is_char = base_laid.char_glyph.is_some() || base.is_none();
949 let delta = base_laid.italic;
950 let (u, v) = if base_is_char {
953 (0.0, 0.0)
954 } else {
955 (
956 base_laid.boxx.height - c.sup_drop * size,
957 base_laid.boxx.depth + c.sub_drop * size,
958 )
959 };
960 let sup_box = if primes.is_empty() {
962 match sup {
963 Some(node) => Some(self.lay_node(node, ctx.map(StyleCtx::sup))?.boxx),
964 None => None,
965 }
966 } else {
967 Some(self.prime_run(primes, ctx)?)
968 };
969 let sub_box = match sub {
970 Some(node) => Some(self.lay_node(node, ctx.map(StyleCtx::sub))?.boxx),
971 None => None,
972 };
973 let base_w = base_laid.boxx.width;
974 let mut children = vec![Positioned {
975 dx: 0.0,
976 dy: 0.0,
977 node: MNode::Box(base_laid.boxx),
978 }];
979 let mut width = base_w;
980 let mut height = children[0].node.box_height();
981 let mut depth = children[0].node.box_depth();
982 match (sup_box, sub_box) {
983 (None, None) => {}
984 (None, Some(sb)) => {
985 let shift = v
987 .max(c.sub1 * size)
988 .max(sb.height - 0.8 * c.x_height * size);
989 width = width.max(base_w + sb.width);
990 height = height.max(sb.height - shift);
991 depth = depth.max(sb.depth + shift);
992 children.push(Positioned {
993 dx: base_w,
994 dy: -shift,
995 node: MNode::Box(sb),
996 });
997 }
998 (Some(sp), None) => {
999 let p = script_p(c, ctx.style) * size;
1001 let shift = u.max(p).max(sp.depth + 0.25 * c.x_height * size);
1002 width = width.max(base_w + delta + sp.width);
1003 height = height.max(sp.height + shift);
1004 depth = depth.max(sp.depth - shift);
1005 children.push(Positioned {
1006 dx: base_w + delta,
1007 dy: shift,
1008 node: MNode::Box(sp),
1009 });
1010 }
1011 (Some(sp), Some(sb)) => {
1012 let p = script_p(c, ctx.style) * size;
1015 let mut shift_up = u.max(p).max(sp.depth + 0.25 * c.x_height * size);
1016 let mut shift_down = v.max(c.sub2 * size);
1017 let theta = c.rule_thickness * size;
1018 let gap = (shift_up - sp.depth) - (sb.height - shift_down);
1019 if gap < 4.0 * theta {
1020 shift_down += 4.0 * theta - gap;
1021 let psi = 0.8 * c.x_height * size - (shift_up - sp.depth);
1022 if psi > 0.0 {
1023 shift_up += psi;
1024 shift_down -= psi;
1025 }
1026 }
1027 width = width.max(base_w + delta + sp.width).max(base_w + sb.width);
1028 height = height.max(sp.height + shift_up);
1029 depth = depth.max(sb.depth + shift_down);
1030 children.push(Positioned {
1031 dx: base_w + delta,
1032 dy: shift_up,
1033 node: MNode::Box(sp),
1034 });
1035 children.push(Positioned {
1036 dx: base_w,
1037 dy: -shift_down,
1038 node: MNode::Box(sb),
1039 });
1040 }
1041 }
1042 Ok(Laid {
1043 boxx: MBox {
1044 kind: BoxKind::Horizontal,
1045 width,
1046 height,
1047 depth,
1048 children,
1049 },
1050 italic: 0.0,
1051 char_glyph: None,
1052 })
1053 }
1054
1055 fn limits_active(&self, base: &Node, ctx: LayCtx) -> bool {
1056 match &base.kind {
1057 NodeKind::BigOp {
1058 limits, integral, ..
1059 } => match limits {
1060 Limits::Limits => true,
1061 Limits::NoLimits => false,
1062 Limits::Default => ctx.style.style == Style::Display && !integral,
1063 },
1064 NodeKind::OpName { limits, .. } => *limits && ctx.style.style == Style::Display,
1065 NodeKind::Accent { accent, .. } => {
1069 matches!(accent, AccentKind::OverBrace | AccentKind::UnderBrace)
1070 }
1071 _ => false,
1072 }
1073 }
1074
1075 fn prime_run(&self, primes: &[Span], ctx: LayCtx) -> Result<MBox, MathError> {
1078 let sup_ctx = ctx.map(StyleCtx::sup);
1079 let size = sup_ctx.size();
1080 let fallback = primes.first().copied().unwrap_or(Span::new(0, 0));
1081 let Some((face, gid)) = self.faces.resolve('′', &[FACE_REGULAR, FACE_SYMBOLS]) else {
1082 return Err(MathError::UnmappedChar {
1083 ch: '′',
1084 span: fallback,
1085 });
1086 };
1087 let m = self.metrics_of(face, gid);
1088 let mut children = Vec::new();
1089 let mut x = 0.0;
1090 for span in primes {
1091 children.push(Positioned {
1092 dx: x,
1093 dy: 0.0,
1094 node: MNode::Glyph {
1095 face,
1096 gid,
1097 ch: '′',
1098 size,
1099 span: *span,
1100 },
1101 });
1102 x += m.advance * size;
1103 }
1104 Ok(MBox {
1105 kind: BoxKind::Horizontal,
1106 width: x,
1107 height: m.height * size,
1108 depth: m.depth * size,
1109 children,
1110 })
1111 }
1112
1113 fn with_limits(
1116 &self,
1117 op: MBox,
1118 upper: Option<MBox>,
1119 lower: Option<MBox>,
1120 ctx: LayCtx,
1121 delta: f64,
1122 ) -> MBox {
1123 let size = ctx.size();
1124 let c = &self.consts;
1125 let width = op
1126 .width
1127 .max(upper.as_ref().map_or(0.0, |b| b.width))
1128 .max(lower.as_ref().map_or(0.0, |b| b.width));
1129 let op_dx = (width - op.width) / 2.0;
1130 let mut height = op.height;
1131 let mut depth = op.depth;
1132 let mut children = Vec::new();
1133 if let Some(up) = upper {
1134 let gap = (c.big_op_spacing1 * size).max(c.big_op_spacing3 * size - up.depth);
1135 let dy = op.height + gap + up.depth;
1136 height = dy + up.height + c.big_op_spacing5 * size;
1137 children.push(Positioned {
1138 dx: (width - up.width) / 2.0 + delta / 2.0,
1139 dy,
1140 node: MNode::Box(up),
1141 });
1142 }
1143 if let Some(low) = lower {
1144 let gap = (c.big_op_spacing2 * size).max(c.big_op_spacing4 * size - low.height);
1145 let dy = -(op.depth + gap + low.height);
1146 depth = -dy + low.depth + c.big_op_spacing5 * size;
1147 children.push(Positioned {
1148 dx: (width - low.width) / 2.0 - delta / 2.0,
1149 dy,
1150 node: MNode::Box(low),
1151 });
1152 }
1153 children.push(Positioned {
1154 dx: op_dx,
1155 dy: 0.0,
1156 node: MNode::Box(op),
1157 });
1158 MBox {
1159 kind: BoxKind::Horizontal,
1160 width,
1161 height,
1162 depth,
1163 children,
1164 }
1165 }
1166
1167 fn fraction(
1170 &self,
1171 num: &Node,
1172 den: &Node,
1173 spec: FracSpec,
1174 ctx: LayCtx,
1175 span: Span,
1176 ) -> Result<Laid, MathError> {
1177 let c = &self.consts;
1178 let eff = match spec.forced_style {
1179 Some(forced) => ctx.map(|s| StyleCtx {
1180 style: forced,
1181 cramped: s.cramped,
1182 }),
1183 None => ctx,
1184 };
1185 let display = eff.style.style == Style::Display;
1186 let size = eff.size();
1187 let num_box = self.lay_node(num, eff.map(StyleCtx::num))?.boxx;
1188 let den_box = self.lay_node(den, eff.map(StyleCtx::den))?.boxx;
1189 let theta = c.rule_thickness * size;
1190 let axis = c.axis_height * size;
1191 let mut u = if display {
1193 c.num1
1194 } else if spec.bar {
1195 c.num2
1196 } else {
1197 c.num3
1198 } * size;
1199 let mut v = if display { c.denom1 } else { c.denom2 } * size;
1200 if spec.bar {
1201 let phi = if display { 3.0 * theta } else { theta };
1203 let num_gap = (u - num_box.depth) - (axis + theta / 2.0);
1204 if num_gap < phi {
1205 u += phi - num_gap;
1206 }
1207 let den_gap = (axis - theta / 2.0) - (den_box.height - v);
1208 if den_gap < phi {
1209 v += phi - den_gap;
1210 }
1211 } else {
1212 let phi = if display { 7.0 * theta } else { 3.0 * theta };
1214 let gap = (u - num_box.depth) - (den_box.height - v);
1215 if gap < phi {
1216 u += (phi - gap) / 2.0;
1217 v += (phi - gap) / 2.0;
1218 }
1219 }
1220 let inner_width = num_box.width.max(den_box.width);
1221 let num_dx = (inner_width - num_box.width) / 2.0;
1222 let den_dx = (inner_width - den_box.width) / 2.0;
1223 let height = u + num_box.height;
1224 let depth = v + den_box.depth;
1225 let mut children = vec![
1226 Positioned {
1227 dx: num_dx,
1228 dy: u,
1229 node: MNode::Box(num_box),
1230 },
1231 Positioned {
1232 dx: den_dx,
1233 dy: -v,
1234 node: MNode::Box(den_box),
1235 },
1236 ];
1237 if spec.bar {
1238 children.push(Positioned {
1239 dx: 0.0,
1240 dy: axis - theta / 2.0,
1241 node: MNode::Rule {
1242 width: inner_width,
1243 height: theta,
1244 span,
1245 },
1246 });
1247 }
1248 let core = MBox {
1249 kind: BoxKind::Horizontal,
1250 width: inner_width,
1251 height,
1252 depth,
1253 children,
1254 };
1255 let boxx = if let Some((l, r)) = spec.delims {
1256 let target = if display { c.delim1 } else { c.delim2 } * size;
1258 let left = self.delimiter_box(&Delim { ch: Some(l), span }, target, eff)?;
1259 let right = self.delimiter_box(&Delim { ch: Some(r), span }, target, eff)?;
1260 hcat(vec![left, core, right])
1261 } else {
1262 core
1263 };
1264 Ok(Laid {
1265 boxx,
1266 italic: 0.0,
1267 char_glyph: None,
1268 })
1269 }
1270
1271 fn radical(
1273 &self,
1274 index: Option<&Node>,
1275 radicand: &Node,
1276 ctx: LayCtx,
1277 span: Span,
1278 ) -> Result<Laid, MathError> {
1279 let c = &self.consts;
1280 let size = ctx.size();
1281 let x = self.lay_node(radicand, ctx.map(StyleCtx::cramp))?.boxx;
1282 let theta = c.rule_thickness * size;
1283 let mut psi = if ctx.style.style == Style::Display {
1284 theta + 0.25 * c.x_height * size
1285 } else {
1286 theta + 0.25 * theta
1287 };
1288 let target = x.height + x.depth + psi + theta;
1289 let sign = self.delimiter_box(
1290 &Delim {
1291 ch: Some('√'),
1292 span,
1293 },
1294 target,
1295 ctx,
1296 )?;
1297 let sign_total = sign.height + sign.depth;
1298 let excess = sign_total - target;
1299 if excess > 0.0 {
1300 psi += excess / 2.0;
1301 }
1302 let rule_y = x.height + psi;
1305 let sign_dy = rule_y + theta - sign.height;
1306 let mut pen = 0.0_f64;
1307 let mut children = Vec::new();
1308 let mut total_height = rule_y + theta;
1309 if let Some(ix) = index {
1312 let ix_box = self
1313 .lay_node(
1314 ix,
1315 LayCtx {
1316 style: StyleCtx {
1317 style: Style::ScriptScript,
1318 cramped: ctx.style.cramped,
1319 },
1320 ..ctx
1321 },
1322 )?
1323 .boxx;
1324 let raise = 0.6 * (rule_y + theta + x.depth);
1327 pen += 5.0 / 18.0 * size;
1328 total_height = total_height.max(raise + ix_box.height);
1329 let ix_width = ix_box.width;
1330 children.push(Positioned {
1331 dx: pen,
1332 dy: raise,
1333 node: MNode::Box(ix_box),
1334 });
1335 pen += ix_width - 10.5 / 18.0 * size;
1336 pen = pen.max(0.0);
1337 }
1338 let sign_width = sign.width;
1339 children.push(Positioned {
1340 dx: pen,
1341 dy: sign_dy,
1342 node: MNode::Box(sign),
1343 });
1344 pen += sign_width;
1345 children.push(Positioned {
1346 dx: pen,
1347 dy: rule_y,
1348 node: MNode::Rule {
1349 width: x.width,
1350 height: theta,
1351 span,
1352 },
1353 });
1354 let x_width = x.width;
1355 let x_depth = x.depth;
1356 children.push(Positioned {
1357 dx: pen,
1358 dy: 0.0,
1359 node: MNode::Box(x),
1360 });
1361 Ok(Laid {
1362 boxx: MBox {
1363 kind: BoxKind::Horizontal,
1364 width: pen + x_width,
1365 height: total_height,
1366 depth: x_depth,
1367 children,
1368 },
1369 italic: 0.0,
1370 char_glyph: None,
1371 })
1372 }
1373
1374 fn accent(
1377 &self,
1378 kind: AccentKind,
1379 base: &Node,
1380 ctx: LayCtx,
1381 span: Span,
1382 ) -> Result<Laid, MathError> {
1383 let c = &self.consts;
1384 let size = ctx.size();
1385 match kind {
1386 AccentKind::OverLine => {
1387 let inner = self.lay_node(base, ctx.map(StyleCtx::cramp))?.boxx;
1388 let theta = c.rule_thickness * size;
1389 let rule_y = inner.height + 3.0 * theta;
1390 let width = inner.width;
1391 Ok(Laid {
1392 boxx: MBox {
1393 kind: BoxKind::Horizontal,
1394 width,
1395 height: rule_y + 2.0 * theta,
1396 depth: inner.depth,
1397 children: vec![
1398 Positioned {
1399 dx: 0.0,
1400 dy: rule_y,
1401 node: MNode::Rule {
1402 width,
1403 height: theta,
1404 span,
1405 },
1406 },
1407 Positioned {
1408 dx: 0.0,
1409 dy: 0.0,
1410 node: MNode::Box(inner),
1411 },
1412 ],
1413 },
1414 italic: 0.0,
1415 char_glyph: None,
1416 })
1417 }
1418 AccentKind::UnderLine => {
1419 let inner = self.lay_node(base, ctx)?.boxx;
1420 Ok(Laid {
1421 boxx: self.underline_box(inner, ctx, span),
1422 italic: 0.0,
1423 char_glyph: None,
1424 })
1425 }
1426 AccentKind::OverBrace
1427 | AccentKind::UnderBrace
1428 | AccentKind::OverRightArrow
1429 | AccentKind::OverLeftArrow
1430 | AccentKind::WideHat
1431 | AccentKind::WideTilde => self.stretchy_accent(kind, base, ctx, span),
1432 AccentKind::Dddot | AccentKind::Ddddot => {
1433 let n: u8 = if matches!(kind, AccentKind::Dddot) {
1438 3
1439 } else {
1440 4
1441 };
1442 let base_laid = self.lay_node(base, ctx.map(StyleCtx::cramp))?;
1443 let combining = accent_char(AccentKind::Dot);
1444 let Some((face, gid)) = self
1445 .faces
1446 .resolve(combining, &[FACE_REGULAR, FACE_ITALIC, FACE_SYMBOLS])
1447 .or_else(|| {
1448 accent_spacing_fallback(combining)
1449 .and_then(|alt| self.faces.resolve(alt, &[FACE_REGULAR, FACE_SYMBOLS]))
1450 })
1451 else {
1452 return Err(MathError::UnmappedChar {
1453 ch: combining,
1454 span,
1455 });
1456 };
1457 let Some(font) = self.faces.font(face) else {
1458 return Err(MathError::UnmappedChar {
1459 ch: combining,
1460 span,
1461 });
1462 };
1463 let upm = f64::from(font.units_per_em.max(1));
1464 let bbox = font.glyph_bbox(gid).unwrap_or([0, 0, 0, 0]);
1465 let ink_left = f64::from(bbox[0]) / upm * size;
1466 let ink_right = f64::from(bbox[2]) / upm * size;
1467 let ink_top = f64::from(bbox[3]) / upm * size;
1468 let ink_bottom = f64::from(bbox[1]) / upm * size;
1469 let ink_w = ink_right - ink_left;
1470 let row_w = ink_w * f64::from(n);
1471 let dy = base_laid.boxx.height - c.x_height * size;
1472 let skew = base_laid.italic / 2.0;
1473 let row_left = base_laid.boxx.width / 2.0 + skew - row_w / 2.0;
1474 let width = base_laid.boxx.width;
1475 let height = base_laid.boxx.height.max(dy + ink_top);
1476 let depth = base_laid.boxx.depth.max(-(dy + ink_bottom));
1477 let mut children = Vec::with_capacity(usize::from(n) + 1);
1478 for i in 0..n {
1479 children.push(Positioned {
1480 dx: row_left + ink_w * f64::from(i) - ink_left,
1481 dy,
1482 node: MNode::Glyph {
1483 face,
1484 gid,
1485 ch: combining,
1486 size,
1487 span,
1488 },
1489 });
1490 }
1491 children.push(Positioned {
1492 dx: 0.0,
1493 dy: 0.0,
1494 node: MNode::Box(base_laid.boxx),
1495 });
1496 Ok(Laid {
1497 boxx: MBox {
1498 kind: BoxKind::Horizontal,
1499 width,
1500 height,
1501 depth,
1502 children,
1503 },
1504 italic: 0.0,
1505 char_glyph: None,
1506 })
1507 }
1508 _ => {
1509 let base_laid = self.lay_node(base, ctx.map(StyleCtx::cramp))?;
1510 let combining = accent_char(kind);
1511 let resolved = self
1512 .faces
1513 .resolve(combining, &[FACE_REGULAR, FACE_ITALIC, FACE_SYMBOLS])
1514 .or_else(|| {
1515 accent_spacing_fallback(combining)
1516 .and_then(|alt| self.faces.resolve(alt, &[FACE_REGULAR, FACE_SYMBOLS]))
1517 });
1518 let Some((face, gid)) = resolved else {
1519 return Err(MathError::UnmappedChar {
1520 ch: combining,
1521 span,
1522 });
1523 };
1524 let Some(font) = self.faces.font(face) else {
1525 return Err(MathError::UnmappedChar {
1526 ch: combining,
1527 span,
1528 });
1529 };
1530 let upm = f64::from(font.units_per_em.max(1));
1531 let bbox = font.glyph_bbox(gid).unwrap_or([0, 0, 0, 0]);
1532 let ink_left = f64::from(bbox[0]) / upm * size;
1533 let ink_right = f64::from(bbox[2]) / upm * size;
1534 let ink_top = f64::from(bbox[3]) / upm * size;
1535 let ink_bottom = f64::from(bbox[1]) / upm * size;
1536 let dy = base_laid.boxx.height - c.x_height * size;
1539 let skew = base_laid.italic / 2.0;
1540 let dx = base_laid.boxx.width / 2.0 + skew - (ink_left + ink_right) / 2.0;
1541 let width = base_laid.boxx.width;
1542 let height = base_laid.boxx.height.max(dy + ink_top);
1543 let depth = base_laid.boxx.depth.max(-(dy + ink_bottom));
1544 let base_box = base_laid.boxx;
1545 Ok(Laid {
1546 boxx: MBox {
1547 kind: BoxKind::Horizontal,
1548 width,
1549 height,
1550 depth,
1551 children: vec![
1552 Positioned {
1553 dx,
1554 dy,
1555 node: MNode::Glyph {
1556 face,
1557 gid,
1558 ch: combining,
1559 size,
1560 span,
1561 },
1562 },
1563 Positioned {
1564 dx: 0.0,
1565 dy: 0.0,
1566 node: MNode::Box(base_box),
1567 },
1568 ],
1569 },
1570 italic: 0.0,
1571 char_glyph: None,
1572 })
1573 }
1574 }
1575 }
1576
1577 fn environment(
1586 &self,
1587 name: &str,
1588 spec: Option<&str>,
1589 rows: &[Vec<Node>],
1590 ctx: LayCtx,
1591 span: Span,
1592 ) -> Result<Laid, MathError> {
1593 let text_cells = ctx.map(|s| StyleCtx {
1596 style: if s.style == Style::Display {
1597 Style::Text
1598 } else {
1599 s.style
1600 },
1601 cramped: s.cramped,
1602 });
1603 let boxx = match name {
1604 "matrix" | "pmatrix" | "bmatrix" | "Bmatrix" | "vmatrix" | "Vmatrix" => {
1605 let grid = self.grid(rows, text_cells, &Grid::centered(1.0), span)?;
1606 match name {
1607 "matrix" => grid,
1610 "pmatrix" => self.wrap_delims(grid, Some('('), Some(')'), ctx, span)?,
1611 "bmatrix" => self.wrap_delims(grid, Some('['), Some(']'), ctx, span)?,
1612 "Bmatrix" => self.wrap_delims(grid, Some('{'), Some('}'), ctx, span)?,
1613 "vmatrix" => self.wrap_delims(grid, Some('|'), Some('|'), ctx, span)?,
1614 _ => self.wrap_delims(grid, Some('‖'), Some('‖'), ctx, span)?,
1615 }
1616 }
1617 "smallmatrix" => {
1618 let cells = ctx.map(StyleCtx::sup);
1621 self.grid(
1622 rows,
1623 cells,
1624 &Grid {
1625 col_sep: 0.35,
1626 row_factor: 0.7,
1627 ..Grid::centered(1.0)
1628 },
1629 span,
1630 )?
1631 }
1632 "cases" => {
1633 let grid = self.grid(
1636 rows,
1637 text_cells,
1638 &Grid {
1639 align: AlignRule::AllLeft,
1640 ..Grid::centered(1.0)
1641 },
1642 span,
1643 )?;
1644 self.wrap_delims(grid, Some('{'), None, ctx, span)?
1645 }
1646 "array" => {
1647 let plan = parse_array_spec(spec.unwrap_or(""), span)?;
1648 self.grid(
1649 rows,
1650 text_cells,
1651 &Grid {
1652 align: AlignRule::Columns(plan.aligns),
1653 vrules: plan.vrules,
1654 outer_pad: 0.5,
1655 ..Grid::centered(1.0)
1656 },
1657 span,
1658 )?
1659 }
1660 "align" | "align*" | "aligned" => {
1661 let cells = ctx.map(|s| StyleCtx {
1665 style: Style::Display,
1666 cramped: s.cramped,
1667 });
1668 self.grid(
1669 rows,
1670 cells,
1671 &Grid {
1672 align: AlignRule::AlignPairs,
1673 col_sep: 1.0,
1674 jot: 0.3,
1675 ..Grid::centered(1.0)
1676 },
1677 span,
1678 )?
1679 }
1680 "substack" => {
1681 let cells = ctx.map(|_| StyleCtx::new(Style::Script));
1688 self.grid(rows, cells, &Grid::centered(1.0), span)?
1689 }
1690 other => {
1691 return Err(MathError::UnsupportedCommand {
1694 name: format!("env:{other}"),
1695 span,
1696 });
1697 }
1698 };
1699 Ok(Laid {
1700 boxx,
1701 italic: 0.0,
1702 char_glyph: None,
1703 })
1704 }
1705
1706 fn grid(
1709 &self,
1710 rows: &[Vec<Node>],
1711 cell_ctx: LayCtx,
1712 grid: &Grid,
1713 span: Span,
1714 ) -> Result<MBox, MathError> {
1715 let size = cell_ctx.size();
1716 let c = &self.consts;
1717 let mut cells: Vec<Vec<MBox>> = Vec::new();
1719 for row in rows {
1720 let mut out = Vec::new();
1721 for cell in row {
1722 out.push(self.lay_node(cell, cell_ctx)?.boxx);
1723 }
1724 cells.push(out);
1725 }
1726 let ncols = cells.iter().map(Vec::len).max().unwrap_or(0);
1727 let mut col_w = vec![0.0_f64; ncols];
1729 for row in &cells {
1730 for (j, cell) in row.iter().enumerate() {
1731 col_w[j] = col_w[j].max(cell.width);
1732 }
1733 }
1734 let row_h: Vec<f64> = cells
1735 .iter()
1736 .map(|r| r.iter().map(|b| b.height).fold(0.0, f64::max))
1737 .collect();
1738 let row_d: Vec<f64> = cells
1739 .iter()
1740 .map(|r| r.iter().map(|b| b.depth).fold(0.0, f64::max))
1741 .collect();
1742 let mut col_x = vec![0.0_f64; ncols];
1744 let mut x = grid.outer_pad * size;
1745 for (j, w) in col_w.iter().enumerate() {
1746 if j > 0 {
1747 x += grid.sep_before(j) * size;
1748 }
1749 col_x[j] = x;
1750 x += w;
1751 }
1752 let total_width = x + grid.outer_pad * size;
1753 let mut baselines = vec![0.0_f64; cells.len()];
1756 for i in 1..cells.len() {
1757 let natural = (c.baseline_skip + grid.jot) * grid.row_factor * size;
1758 let min_gap = row_d[i - 1] + row_h[i] + c.line_skip * size;
1759 baselines[i] = baselines[i - 1] - natural.max(min_gap);
1760 }
1761 let top = row_h.first().copied().unwrap_or(0.0);
1763 let bottom =
1764 baselines.last().copied().unwrap_or(0.0) - row_d.last().copied().unwrap_or(0.0);
1765 let total = top - bottom;
1766 let axis = c.axis_height * cell_ctx.size();
1767 let height = total / 2.0 + axis;
1768 let shift = height - top; let depth = (total / 2.0 - axis).max(0.0);
1770 let mut children = Vec::new();
1771 for (i, row) in cells.into_iter().enumerate() {
1772 for (j, cell) in row.into_iter().enumerate() {
1773 let slack = col_w[j] - cell.width;
1774 let dx = col_x[j] + grid.align.factor(j) * slack;
1775 children.push(Positioned {
1776 dx,
1777 dy: baselines[i] + shift,
1778 node: MNode::Box(cell),
1779 });
1780 }
1781 }
1782 for &before_col in &grid.vrules {
1784 let theta = c.rule_thickness * size;
1785 let rule_x = if before_col == 0 {
1786 (grid.outer_pad * size - theta).max(0.0) / 2.0
1787 } else if before_col < ncols {
1788 col_x[before_col] - grid.sep_before(before_col) * size / 2.0 - theta / 2.0
1789 } else {
1790 total_width - (grid.outer_pad * size - theta).max(0.0) / 2.0 - theta
1791 };
1792 children.push(Positioned {
1793 dx: rule_x,
1794 dy: -depth,
1795 node: MNode::Rule {
1796 width: theta,
1797 height: height + depth,
1798 span,
1799 },
1800 });
1801 }
1802 Ok(MBox {
1803 kind: BoxKind::Vertical,
1804 width: total_width,
1805 height,
1806 depth,
1807 children,
1808 })
1809 }
1810
1811 fn wrap_delims(
1813 &self,
1814 inner: MBox,
1815 left: Option<char>,
1816 right: Option<char>,
1817 ctx: LayCtx,
1818 span: Span,
1819 ) -> Result<MBox, MathError> {
1820 let c = &self.consts;
1821 let size = ctx.size();
1822 let axis = c.axis_height * size;
1823 let delta = (inner.height - axis).max(inner.depth + axis);
1824 let target =
1825 (2.0 * delta * c.delimiter_factor).max(2.0 * delta - c.delimiter_shortfall * size);
1826 let left_box = self.delimiter_box(&Delim { ch: left, span }, target, ctx)?;
1827 let right_box = self.delimiter_box(&Delim { ch: right, span }, target, ctx)?;
1828 Ok(hcat(vec![left_box, inner, right_box]))
1829 }
1830
1831 fn stretchy_accent(
1838 &self,
1839 kind: AccentKind,
1840 base: &Node,
1841 ctx: LayCtx,
1842 span: Span,
1843 ) -> Result<Laid, MathError> {
1844 let size = ctx.size();
1845 let theta = self.consts.rule_thickness * size;
1846 let under = matches!(kind, AccentKind::UnderBrace);
1847 let inner = if under {
1850 self.lay_node(base, ctx)?.boxx
1851 } else {
1852 self.lay_node(base, ctx.map(StyleCtx::cramp))?.boxx
1853 };
1854 let stretch_kind = match kind {
1855 AccentKind::WideHat => crate::drawn::Stretch::Hat,
1856 AccentKind::WideTilde => crate::drawn::Stretch::Tilde,
1857 AccentKind::OverBrace => crate::drawn::Stretch::OverBrace,
1858 AccentKind::UnderBrace => crate::drawn::Stretch::UnderBrace,
1859 AccentKind::OverRightArrow => crate::drawn::Stretch::RightArrow,
1860 _ => crate::drawn::Stretch::LeftArrow,
1861 };
1862 let width = inner.width.max(0.25 * size);
1863 let band = crate::drawn::stretch(stretch_kind, width, size);
1864 let gap = match kind {
1865 AccentKind::WideHat | AccentKind::WideTilde => theta,
1866 _ => 2.0 * theta,
1867 };
1868 let (band_dy, height, depth) = if under {
1869 let dy = -(inner.depth + gap + band.height);
1870 (dy, inner.height, inner.depth + gap + band.height)
1871 } else {
1872 let dy = inner.height + gap;
1873 (dy, inner.height + gap + band.height, inner.depth)
1874 };
1875 let inner_width = inner.width;
1876 Ok(Laid {
1877 boxx: MBox {
1878 kind: BoxKind::Horizontal,
1879 width: inner_width.max(width),
1880 height,
1881 depth,
1882 children: vec![
1883 Positioned {
1884 dx: 0.0,
1885 dy: band_dy,
1886 node: MNode::Path {
1887 contours: band.contours,
1888 span,
1889 },
1890 },
1891 Positioned {
1892 dx: 0.0,
1893 dy: 0.0,
1894 node: MNode::Box(inner),
1895 },
1896 ],
1897 },
1898 italic: 0.0,
1899 char_glyph: None,
1900 })
1901 }
1902
1903 fn underline_box(&self, inner: MBox, ctx: LayCtx, span: Span) -> MBox {
1904 let theta = self.consts.rule_thickness * ctx.size();
1905 let rule_y = -(inner.depth + 3.0 * theta) - theta;
1906 let width = inner.width;
1907 MBox {
1908 kind: BoxKind::Horizontal,
1909 width,
1910 height: inner.height,
1911 depth: -rule_y + 2.0 * theta,
1912 children: vec![
1913 Positioned {
1914 dx: 0.0,
1915 dy: rule_y,
1916 node: MNode::Rule {
1917 width,
1918 height: theta,
1919 span,
1920 },
1921 },
1922 Positioned {
1923 dx: 0.0,
1924 dy: 0.0,
1925 node: MNode::Box(inner),
1926 },
1927 ],
1928 }
1929 }
1930
1931 fn left_right(
1933 &self,
1934 left: &Delim,
1935 right: &Delim,
1936 body: &[Node],
1937 ctx: LayCtx,
1938 ) -> Result<Laid, MathError> {
1939 let c = &self.consts;
1940 let size = ctx.size();
1941 let inner = self.hlist(body, ctx)?;
1942 let axis = c.axis_height * size;
1943 let delta = (inner.height - axis).max(inner.depth + axis);
1944 let target =
1945 (2.0 * delta * c.delimiter_factor).max(2.0 * delta - c.delimiter_shortfall * size);
1946 let left_box = self.delimiter_box(left, target, ctx)?;
1947 let right_box = self.delimiter_box(right, target, ctx)?;
1948 Ok(Laid {
1949 boxx: hcat(vec![left_box, inner, right_box]),
1950 italic: 0.0,
1951 char_glyph: None,
1952 })
1953 }
1954
1955 fn delimiter_box(
1964 &self,
1965 delim: &Delim,
1966 target_total: f64,
1967 ctx: LayCtx,
1968 ) -> Result<MBox, MathError> {
1969 let Some(ch) = delim.ch else {
1970 return Ok(kern_box(self.consts.null_delimiter_space * ctx.size()));
1971 };
1972 let Some((face, gid)) = self
1973 .faces
1974 .resolve(ch, &[FACE_REGULAR, FACE_SYMBOLS, FACE_ITALIC])
1975 else {
1976 if let Some(d) =
1980 crate::drawn::delimiter(ch, target_total.max(0.7 * ctx.size()), ctx.size())
1981 {
1982 let total = target_total.max(0.7 * ctx.size());
1983 let axis = self.consts.axis_height * ctx.size();
1984 let dy = axis - total / 2.0;
1985 return Ok(MBox {
1986 kind: BoxKind::Horizontal,
1987 width: d.width,
1988 height: total + dy,
1989 depth: (-dy).max(0.0),
1990 children: vec![Positioned {
1991 dx: 0.0,
1992 dy,
1993 node: MNode::Path {
1994 contours: d.contours,
1995 span: delim.span,
1996 },
1997 }],
1998 });
1999 }
2000 return Err(MathError::UnmappedChar {
2001 ch,
2002 span: delim.span,
2003 });
2004 };
2005 let m = self.metrics_of(face, gid);
2006 let natural_total = (m.height + m.depth) * ctx.size();
2007 let scale = if natural_total >= target_total || natural_total <= 0.0 {
2008 1.0
2009 } else {
2010 target_total / natural_total
2011 };
2012 if scale > self.consts.delimiter_scale_ceiling
2013 && let Some(d) = crate::drawn::delimiter(ch, target_total, ctx.size())
2014 {
2015 let axis = self.consts.axis_height * ctx.size();
2018 let dy = axis - target_total / 2.0;
2019 return Ok(MBox {
2020 kind: BoxKind::Horizontal,
2021 width: d.width,
2022 height: target_total + dy,
2023 depth: (-dy).max(0.0),
2024 children: vec![Positioned {
2025 dx: 0.0,
2026 dy,
2027 node: MNode::Path {
2028 contours: d.contours,
2029 span: delim.span,
2030 },
2031 }],
2032 });
2033 }
2034 let size = ctx.size() * scale;
2035 let gh = m.height * size;
2036 let gd = m.depth * size;
2037 let axis = self.consts.axis_height * ctx.size();
2038 let dy = axis - (gh - gd) / 2.0;
2039 Ok(MBox {
2040 kind: BoxKind::Horizontal,
2041 width: m.advance * size,
2042 height: gh + dy,
2043 depth: (gd - dy).max(0.0),
2044 children: vec![Positioned {
2045 dx: 0.0,
2046 dy,
2047 node: MNode::Glyph {
2048 face,
2049 gid,
2050 ch,
2051 size,
2052 span: delim.span,
2053 },
2054 }],
2055 })
2056 }
2057}
2058
2059fn script_p(c: &MathConstants, style: StyleCtx) -> f64 {
2061 if style.cramped {
2062 c.sup3
2063 } else if style.style == Style::Display {
2064 c.sup1
2065 } else {
2066 c.sup2
2067 }
2068}
2069
2070fn fixed_delim_target(size: DelimSize) -> f64 {
2073 match size {
2074 DelimSize::Big => 0.85,
2075 DelimSize::BBig => 1.15,
2076 DelimSize::Bigg => 1.45,
2077 DelimSize::BBigg => 1.75,
2078 }
2079}
2080
2081fn text_face(ctx: LayCtx) -> FaceId {
2082 match ctx.alphabet {
2083 Some(MathFont::Bold) => FACE_BOLD,
2084 Some(MathFont::Italic) => FACE_ITALIC,
2085 _ => FACE_REGULAR,
2086 }
2087}
2088
2089const fn accent_char(kind: AccentKind) -> char {
2090 match kind {
2091 AccentKind::Hat => '\u{0302}',
2092 AccentKind::Check => '\u{030C}',
2093 AccentKind::Tilde => '\u{0303}',
2094 AccentKind::Acute => '\u{0301}',
2095 AccentKind::Grave => '\u{0300}',
2096 AccentKind::Dot => '\u{0307}',
2097 AccentKind::Ddot => '\u{0308}',
2098 AccentKind::Breve => '\u{0306}',
2099 AccentKind::Bar => '\u{0304}',
2100 AccentKind::Vec => '\u{20D7}',
2101 AccentKind::Ring => '\u{030A}',
2102 _ => '\u{0302}',
2103 }
2104}
2105
2106struct Grid {
2108 align: AlignRule,
2110 col_sep: f64,
2113 outer_pad: f64,
2115 jot: f64,
2117 row_factor: f64,
2119 vrules: Vec<usize>,
2122}
2123
2124impl Grid {
2125 fn centered(col_sep: f64) -> Self {
2127 Self {
2128 align: AlignRule::AllCenter,
2129 col_sep,
2130 outer_pad: 0.0,
2131 jot: 0.0,
2132 row_factor: 1.0,
2133 vrules: Vec::new(),
2134 }
2135 }
2136
2137 fn sep_before(&self, j: usize) -> f64 {
2141 if matches!(self.align, AlignRule::AlignPairs) && j % 2 == 1 {
2142 0.0
2143 } else {
2144 self.col_sep
2145 }
2146 }
2147}
2148
2149enum AlignRule {
2151 AllCenter,
2153 AllLeft,
2155 Columns(Vec<CellAlign>),
2157 AlignPairs,
2159}
2160
2161impl AlignRule {
2162 fn factor(&self, j: usize) -> f64 {
2164 match self {
2165 Self::AllCenter => 0.5,
2166 Self::AllLeft => 0.0,
2167 Self::Columns(cols) => match cols.get(j) {
2168 Some(CellAlign::Left) => 0.0,
2169 Some(CellAlign::Right) => 1.0,
2170 Some(CellAlign::Center) | None => 0.5,
2171 },
2172 Self::AlignPairs => {
2173 if j % 2 == 0 {
2174 1.0 } else {
2176 0.0 }
2178 }
2179 }
2180 }
2181}
2182
2183enum CellAlign {
2185 Left,
2187 Center,
2189 Right,
2191}
2192
2193struct ArrayPlan {
2195 aligns: Vec<CellAlign>,
2196 vrules: Vec<usize>,
2197}
2198
2199fn parse_array_spec(spec: &str, span: Span) -> Result<ArrayPlan, MathError> {
2202 let mut aligns = Vec::new();
2203 let mut vrules = Vec::new();
2204 for ch in spec.chars() {
2205 match ch {
2206 'l' => aligns.push(CellAlign::Left),
2207 'c' => aligns.push(CellAlign::Center),
2208 'r' => aligns.push(CellAlign::Right),
2209 '|' => vrules.push(aligns.len()),
2210 c if c.is_whitespace() => {}
2211 other => {
2212 return Err(MathError::Malformed {
2213 what: format!(
2214 "unsupported array column-spec character {other:?} \
2215 (the tier-1 surface covers l, c, r, |)"
2216 ),
2217 at: span.start,
2218 });
2219 }
2220 }
2221 }
2222 Ok(ArrayPlan { aligns, vrules })
2223}
2224
2225fn kern_box(width: f64) -> MBox {
2227 MBox {
2228 kind: BoxKind::Horizontal,
2229 width,
2230 height: 0.0,
2231 depth: 0.0,
2232 children: Vec::new(),
2233 }
2234}
2235
2236fn hcat(boxes: Vec<MBox>) -> MBox {
2238 let mut x = 0.0_f64;
2239 let mut height = 0.0_f64;
2240 let mut depth = 0.0_f64;
2241 let mut children = Vec::new();
2242 for b in boxes {
2243 height = height.max(b.height);
2244 depth = depth.max(b.depth);
2245 let w = b.width;
2246 children.push(Positioned {
2247 dx: x,
2248 dy: 0.0,
2249 node: MNode::Box(b),
2250 });
2251 x += w;
2252 }
2253 MBox {
2254 kind: BoxKind::Horizontal,
2255 width: x,
2256 height,
2257 depth,
2258 children,
2259 }
2260}
2261
2262impl MNode {
2263 fn box_height(&self) -> f64 {
2264 match self {
2265 Self::Box(b) => b.height,
2266 _ => 0.0,
2267 }
2268 }
2269 fn box_depth(&self) -> f64 {
2270 match self {
2271 Self::Box(b) => b.depth,
2272 _ => 0.0,
2273 }
2274 }
2275}
2276
2277fn glyph_box(face: FaceId, gid: u16, ch: char, span: Span, size: f64, m: GlyphMetrics) -> MBox {
2279 MBox {
2280 kind: BoxKind::Horizontal,
2281 width: m.advance * size,
2282 height: m.height * size,
2283 depth: m.depth * size,
2284 children: vec![Positioned {
2285 dx: 0.0,
2286 dy: 0.0,
2287 node: MNode::Glyph {
2288 face,
2289 gid,
2290 ch,
2291 size,
2292 span,
2293 },
2294 }],
2295 }
2296}