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