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