1#[macro_use]
2mod shared;
3mod accent;
4mod attach;
5mod cancel;
6mod frac;
7mod fragment;
8mod lr;
9mod mat;
10mod root;
11mod run;
12mod stretch;
13mod text;
14mod underover;
15
16use rustybuzz::Feature;
17use ttf_parser::Tag;
18use typst_library::diag::{bail, SourceResult};
19use typst_library::engine::Engine;
20use typst_library::foundations::{
21 Content, NativeElement, Packed, Resolve, StyleChain, SymbolElem,
22};
23use typst_library::introspection::{Counter, Locator, SplitLocator, TagElem};
24use typst_library::layout::{
25 Abs, AlignElem, Axes, BlockElem, BoxElem, Em, FixedAlignment, Fragment, Frame, HElem,
26 InlineItem, OuterHAlignment, PlaceElem, Point, Region, Regions, Size, Spacing,
27 SpecificAlignment, VAlignment,
28};
29use typst_library::math::*;
30use typst_library::model::ParElem;
31use typst_library::routines::{Arenas, RealizationKind};
32use typst_library::text::{
33 families, features, variant, Font, LinebreakElem, SpaceElem, TextEdgeBounds, TextElem,
34};
35use typst_library::World;
36use typst_syntax::Span;
37use typst_utils::Numeric;
38use unicode_math_class::MathClass;
39
40use self::fragment::{
41 FrameFragment, GlyphFragment, GlyphwiseSubsts, Limits, MathFragment, VariantFragment,
42};
43use self::run::{LeftRightAlternator, MathRun, MathRunFrameBuilder};
44use self::shared::*;
45use self::stretch::{stretch_fragment, stretch_glyph};
46
47#[typst_macros::time(span = elem.span())]
49pub fn layout_equation_inline(
50 elem: &Packed<EquationElem>,
51 engine: &mut Engine,
52 locator: Locator,
53 styles: StyleChain,
54 region: Size,
55) -> SourceResult<Vec<InlineItem>> {
56 assert!(!elem.block(styles));
57
58 let font = find_math_font(engine, styles, elem.span())?;
59
60 let mut locator = locator.split();
61 let mut ctx = MathContext::new(engine, &mut locator, styles, region, &font);
62
63 let scale_style = style_for_script_scale(&ctx);
64 let styles = styles.chain(&scale_style);
65
66 let run = ctx.layout_into_run(&elem.body, styles)?;
67
68 let mut items = if run.row_count() == 1 {
69 run.into_par_items()
70 } else {
71 vec![InlineItem::Frame(run.into_fragment(styles).into_frame())]
72 };
73
74 if items.is_empty() {
77 items.push(InlineItem::Frame(Frame::soft(Size::zero())));
78 }
79
80 for item in &mut items {
81 let InlineItem::Frame(frame) = item else { continue };
82
83 let slack = ParElem::leading_in(styles) * 0.7;
84
85 let (t, b) = font.edges(
86 TextElem::top_edge_in(styles),
87 TextElem::bottom_edge_in(styles),
88 TextElem::size_in(styles),
89 TextEdgeBounds::Frame(frame),
90 );
91
92 let ascent = t.max(frame.ascent() - slack);
93 let descent = b.max(frame.descent() - slack);
94 frame.translate(Point::with_y(ascent - frame.baseline()));
95 frame.size_mut().y = ascent + descent;
96 }
97
98 Ok(items)
99}
100
101#[typst_macros::time(span = elem.span())]
103pub fn layout_equation_block(
104 elem: &Packed<EquationElem>,
105 engine: &mut Engine,
106 locator: Locator,
107 styles: StyleChain,
108 regions: Regions,
109) -> SourceResult<Fragment> {
110 assert!(elem.block(styles));
111
112 let span = elem.span();
113 let font = find_math_font(engine, styles, span)?;
114
115 let mut locator = locator.split();
116 let mut ctx = MathContext::new(engine, &mut locator, styles, regions.base(), &font);
117
118 let scale_style = style_for_script_scale(&ctx);
119 let styles = styles.chain(&scale_style);
120
121 let full_equation_builder = ctx
122 .layout_into_run(&elem.body, styles)?
123 .multiline_frame_builder(styles);
124 let width = full_equation_builder.size.x;
125
126 let equation_builders = if BlockElem::breakable_in(styles) {
127 let mut rows = full_equation_builder.frames.into_iter().peekable();
128 let mut equation_builders = vec![];
129 let mut last_first_pos = Point::zero();
130 let mut regions = regions;
131
132 loop {
133 let Some(&(_, first_pos)) = rows.peek() else { break };
136 last_first_pos = first_pos;
137
138 let mut frames = vec![];
139 let mut height = Abs::zero();
140 while let Some((sub, pos)) = rows.peek() {
141 let mut pos = *pos;
142 pos.y -= first_pos.y;
143
144 if !regions.size.y.fits(sub.height() + pos.y)
149 && (regions.may_progress()
150 || (regions.may_break() && !frames.is_empty()))
151 {
152 break;
153 }
154
155 let (sub, _) = rows.next().unwrap();
156 height = height.max(pos.y + sub.height());
157 frames.push((sub, pos));
158 }
159
160 equation_builders
161 .push(MathRunFrameBuilder { frames, size: Size::new(width, height) });
162 regions.next();
163 }
164
165 if let Some(equation_builder) = equation_builders.last_mut() {
167 equation_builder.frames.extend(rows.map(|(frame, mut pos)| {
168 pos.y -= last_first_pos.y;
169 (frame, pos)
170 }));
171
172 let height = equation_builder
173 .frames
174 .iter()
175 .map(|(frame, pos)| frame.height() + pos.y)
176 .max()
177 .unwrap_or(equation_builder.size.y);
178
179 equation_builder.size.y = height;
180 }
181
182 if equation_builders.is_empty() {
184 equation_builders
185 .push(MathRunFrameBuilder { frames: vec![], size: Size::zero() });
186 }
187
188 equation_builders
189 } else {
190 vec![full_equation_builder]
191 };
192
193 let Some(numbering) = (**elem).numbering(styles) else {
194 let frames = equation_builders
195 .into_iter()
196 .map(MathRunFrameBuilder::build)
197 .collect();
198 return Ok(Fragment::frames(frames));
199 };
200
201 let pod = Region::new(regions.base(), Axes::splat(false));
202 let counter = Counter::of(EquationElem::elem())
203 .display_at_loc(engine, elem.location().unwrap(), styles, numbering)?
204 .spanned(span);
205 let number = crate::layout_frame(engine, &counter, locator.next(&()), styles, pod)?;
206
207 static NUMBER_GUTTER: Em = Em::new(0.5);
208 let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
209
210 let number_align = match elem.number_align(styles) {
211 SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon),
212 SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v),
213 SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
214 };
215
216 let region_count = equation_builders.len();
218 let frames = equation_builders
219 .into_iter()
220 .map(|builder| {
221 if builder.frames.is_empty() && region_count > 1 {
222 return builder.build();
224 }
225 add_equation_number(
226 builder,
227 number.clone(),
228 number_align.resolve(styles),
229 AlignElem::alignment_in(styles).resolve(styles).x,
230 regions.size.x,
231 full_number_width,
232 )
233 })
234 .collect();
235
236 Ok(Fragment::frames(frames))
237}
238
239fn find_math_font(
240 engine: &mut Engine<'_>,
241 styles: StyleChain,
242 span: Span,
243) -> SourceResult<Font> {
244 let variant = variant(styles);
245 let world = engine.world;
246 let Some(font) = families(styles).find_map(|family| {
247 let id = world.book().select(family.as_str(), variant)?;
248 let font = world.font(id)?;
249 let _ = font.ttf().tables().math?.constants?;
250 Some(font)
251 }) else {
252 bail!(span, "current font does not support math");
253 };
254 Ok(font)
255}
256
257fn add_equation_number(
258 equation_builder: MathRunFrameBuilder,
259 number: Frame,
260 number_align: Axes<FixedAlignment>,
261 equation_align: FixedAlignment,
262 region_size_x: Abs,
263 full_number_width: Abs,
264) -> Frame {
265 let first =
266 equation_builder.frames.first().map_or(
267 (equation_builder.size, Point::zero(), Abs::zero()),
268 |(frame, pos)| (frame.size(), *pos, frame.baseline()),
269 );
270 let last =
271 equation_builder.frames.last().map_or(
272 (equation_builder.size, Point::zero(), Abs::zero()),
273 |(frame, pos)| (frame.size(), *pos, frame.baseline()),
274 );
275 let line_count = equation_builder.frames.len();
276 let mut equation = equation_builder.build();
277
278 let width = if region_size_x.is_finite() {
279 region_size_x
280 } else {
281 equation.width() + 2.0 * full_number_width
282 };
283
284 let is_multiline = line_count >= 2;
285 let resizing_offset = resize_equation(
286 &mut equation,
287 &number,
288 number_align,
289 equation_align,
290 width,
291 is_multiline,
292 [first, last],
293 );
294 equation.translate(Point::with_x(match (equation_align, number_align.x) {
295 (FixedAlignment::Start, FixedAlignment::Start) => full_number_width,
296 (FixedAlignment::End, FixedAlignment::End) => -full_number_width,
297 _ => Abs::zero(),
298 }));
299
300 let x = match number_align.x {
301 FixedAlignment::Start => Abs::zero(),
302 FixedAlignment::End => equation.width() - number.width(),
303 _ => unreachable!(),
304 };
305 let y = {
306 let align_baselines = |(_, pos, baseline): (_, Point, Abs), number: &Frame| {
307 resizing_offset.y + pos.y + baseline - number.baseline()
308 };
309 match number_align.y {
310 FixedAlignment::Start => align_baselines(first, &number),
311 FixedAlignment::Center if !is_multiline => align_baselines(first, &number),
312 FixedAlignment::Center => (equation.height() - number.height()) / 2.0,
315 FixedAlignment::End => align_baselines(last, &number),
316 }
317 };
318
319 equation.push_frame(Point::new(x, y), number);
320 equation
321}
322
323fn resize_equation(
325 equation: &mut Frame,
326 number: &Frame,
327 number_align: Axes<FixedAlignment>,
328 equation_align: FixedAlignment,
329 width: Abs,
330 is_multiline: bool,
331 [first, last]: [(Axes<Abs>, Point, Abs); 2],
332) -> Point {
333 if matches!(number_align.y, FixedAlignment::Center if is_multiline) {
334 return equation.resize(
337 Size::new(width, equation.height().max(number.height())),
338 Axes::<FixedAlignment>::new(equation_align, FixedAlignment::Center),
339 );
340 }
341
342 let excess_above = Abs::zero().max({
343 if !is_multiline || matches!(number_align.y, FixedAlignment::Start) {
344 let (.., baseline) = first;
345 number.baseline() - baseline
346 } else {
347 Abs::zero()
348 }
349 });
350 let excess_below = Abs::zero().max({
351 if !is_multiline || matches!(number_align.y, FixedAlignment::End) {
352 let (size, .., baseline) = last;
353 (number.height() - number.baseline()) - (size.y - baseline)
354 } else {
355 Abs::zero()
356 }
357 });
358
359 let resizing_offset = equation.resize(
362 Size::new(width, equation.height() + excess_above + excess_below),
363 Axes::<FixedAlignment>::new(equation_align, FixedAlignment::Start),
364 );
365 equation.translate(Point::with_y(excess_above));
366 resizing_offset + Point::with_y(excess_above)
367}
368
369struct MathContext<'a, 'v, 'e> {
371 engine: &'v mut Engine<'e>,
373 locator: &'v mut SplitLocator<'a>,
374 region: Region,
375 font: &'a Font,
377 ttf: &'a ttf_parser::Face<'a>,
378 table: ttf_parser::math::Table<'a>,
379 constants: ttf_parser::math::Constants<'a>,
380 dtls_table: Option<GlyphwiseSubsts<'a>>,
381 flac_table: Option<GlyphwiseSubsts<'a>>,
382 ssty_table: Option<GlyphwiseSubsts<'a>>,
383 glyphwise_tables: Option<Vec<GlyphwiseSubsts<'a>>>,
384 space_width: Em,
385 fragments: Vec<MathFragment>,
387}
388
389impl<'a, 'v, 'e> MathContext<'a, 'v, 'e> {
390 fn new(
392 engine: &'v mut Engine<'e>,
393 locator: &'v mut SplitLocator<'a>,
394 styles: StyleChain<'a>,
395 base: Size,
396 font: &'a Font,
397 ) -> Self {
398 let math_table = font.ttf().tables().math.unwrap();
399 let gsub_table = font.ttf().tables().gsub;
400 let constants = math_table.constants.unwrap();
401
402 let feat = |tag: &[u8; 4]| {
403 GlyphwiseSubsts::new(gsub_table, Feature::new(Tag::from_bytes(tag), 0, ..))
404 };
405
406 let features = features(styles);
407 let glyphwise_tables = Some(
408 features
409 .into_iter()
410 .filter_map(|feature| GlyphwiseSubsts::new(gsub_table, feature))
411 .collect(),
412 );
413
414 let ttf = font.ttf();
415 let space_width = ttf
416 .glyph_index(' ')
417 .and_then(|id| ttf.glyph_hor_advance(id))
418 .map(|advance| font.to_em(advance))
419 .unwrap_or(THICK);
420
421 Self {
422 engine,
423 locator,
424 region: Region::new(base, Axes::splat(false)),
425 font,
426 ttf,
427 table: math_table,
428 constants,
429 dtls_table: feat(b"dtls"),
430 flac_table: feat(b"flac"),
431 ssty_table: feat(b"ssty"),
432 glyphwise_tables,
433 space_width,
434 fragments: vec![],
435 }
436 }
437
438 fn push(&mut self, fragment: impl Into<MathFragment>) {
440 self.fragments.push(fragment.into());
441 }
442
443 fn extend(&mut self, fragments: impl IntoIterator<Item = MathFragment>) {
445 self.fragments.extend(fragments);
446 }
447
448 fn layout_into_run(
450 &mut self,
451 elem: &Content,
452 styles: StyleChain,
453 ) -> SourceResult<MathRun> {
454 Ok(MathRun::new(self.layout_into_fragments(elem, styles)?))
455 }
456
457 fn layout_into_fragments(
459 &mut self,
460 elem: &Content,
461 styles: StyleChain,
462 ) -> SourceResult<Vec<MathFragment>> {
463 let prev = std::mem::take(&mut self.fragments);
468 self.layout_into_self(elem, styles)?;
469 Ok(std::mem::replace(&mut self.fragments, prev))
470 }
471
472 fn layout_into_fragment(
475 &mut self,
476 elem: &Content,
477 styles: StyleChain,
478 ) -> SourceResult<MathFragment> {
479 Ok(self.layout_into_run(elem, styles)?.into_fragment(styles))
480 }
481
482 fn layout_into_frame(
484 &mut self,
485 elem: &Content,
486 styles: StyleChain,
487 ) -> SourceResult<Frame> {
488 Ok(self.layout_into_fragment(elem, styles)?.into_frame())
489 }
490
491 fn layout_into_self(
493 &mut self,
494 content: &Content,
495 styles: StyleChain,
496 ) -> SourceResult<()> {
497 let arenas = Arenas::default();
498 let pairs = (self.engine.routines.realize)(
499 RealizationKind::Math,
500 self.engine,
501 self.locator,
502 &arenas,
503 content,
504 styles,
505 )?;
506
507 let outer = styles;
508 for (elem, styles) in pairs {
509 if styles != outer && TextElem::font_in(styles) != TextElem::font_in(outer) {
511 let frame = layout_external(elem, self, styles)?;
512 self.push(FrameFragment::new(styles, frame).with_spaced(true));
513 continue;
514 }
515
516 layout_realized(elem, self, styles)?;
517 }
518
519 Ok(())
520 }
521}
522
523fn layout_realized(
525 elem: &Content,
526 ctx: &mut MathContext,
527 styles: StyleChain,
528) -> SourceResult<()> {
529 if let Some(elem) = elem.to_packed::<TagElem>() {
530 ctx.push(MathFragment::Tag(elem.tag.clone()));
531 } else if elem.is::<SpaceElem>() {
532 ctx.push(MathFragment::Space(ctx.space_width.resolve(styles)));
533 } else if elem.is::<LinebreakElem>() {
534 ctx.push(MathFragment::Linebreak);
535 } else if let Some(elem) = elem.to_packed::<HElem>() {
536 layout_h(elem, ctx, styles)?;
537 } else if let Some(elem) = elem.to_packed::<TextElem>() {
538 self::text::layout_text(elem, ctx, styles)?;
539 } else if let Some(elem) = elem.to_packed::<SymbolElem>() {
540 self::text::layout_symbol(elem, ctx, styles)?;
541 } else if let Some(elem) = elem.to_packed::<BoxElem>() {
542 layout_box(elem, ctx, styles)?;
543 } else if elem.is::<AlignPointElem>() {
544 ctx.push(MathFragment::Align);
545 } else if let Some(elem) = elem.to_packed::<ClassElem>() {
546 layout_class(elem, ctx, styles)?;
547 } else if let Some(elem) = elem.to_packed::<AccentElem>() {
548 self::accent::layout_accent(elem, ctx, styles)?;
549 } else if let Some(elem) = elem.to_packed::<AttachElem>() {
550 self::attach::layout_attach(elem, ctx, styles)?;
551 } else if let Some(elem) = elem.to_packed::<PrimesElem>() {
552 self::attach::layout_primes(elem, ctx, styles)?;
553 } else if let Some(elem) = elem.to_packed::<ScriptsElem>() {
554 self::attach::layout_scripts(elem, ctx, styles)?;
555 } else if let Some(elem) = elem.to_packed::<LimitsElem>() {
556 self::attach::layout_limits(elem, ctx, styles)?;
557 } else if let Some(elem) = elem.to_packed::<CancelElem>() {
558 self::cancel::layout_cancel(elem, ctx, styles)?
559 } else if let Some(elem) = elem.to_packed::<FracElem>() {
560 self::frac::layout_frac(elem, ctx, styles)?;
561 } else if let Some(elem) = elem.to_packed::<BinomElem>() {
562 self::frac::layout_binom(elem, ctx, styles)?;
563 } else if let Some(elem) = elem.to_packed::<LrElem>() {
564 self::lr::layout_lr(elem, ctx, styles)?
565 } else if let Some(elem) = elem.to_packed::<MidElem>() {
566 self::lr::layout_mid(elem, ctx, styles)?
567 } else if let Some(elem) = elem.to_packed::<VecElem>() {
568 self::mat::layout_vec(elem, ctx, styles)?
569 } else if let Some(elem) = elem.to_packed::<MatElem>() {
570 self::mat::layout_mat(elem, ctx, styles)?
571 } else if let Some(elem) = elem.to_packed::<CasesElem>() {
572 self::mat::layout_cases(elem, ctx, styles)?
573 } else if let Some(elem) = elem.to_packed::<OpElem>() {
574 layout_op(elem, ctx, styles)?
575 } else if let Some(elem) = elem.to_packed::<RootElem>() {
576 self::root::layout_root(elem, ctx, styles)?
577 } else if let Some(elem) = elem.to_packed::<StretchElem>() {
578 self::stretch::layout_stretch(elem, ctx, styles)?
579 } else if let Some(elem) = elem.to_packed::<UnderlineElem>() {
580 self::underover::layout_underline(elem, ctx, styles)?
581 } else if let Some(elem) = elem.to_packed::<OverlineElem>() {
582 self::underover::layout_overline(elem, ctx, styles)?
583 } else if let Some(elem) = elem.to_packed::<UnderbraceElem>() {
584 self::underover::layout_underbrace(elem, ctx, styles)?
585 } else if let Some(elem) = elem.to_packed::<OverbraceElem>() {
586 self::underover::layout_overbrace(elem, ctx, styles)?
587 } else if let Some(elem) = elem.to_packed::<UnderbracketElem>() {
588 self::underover::layout_underbracket(elem, ctx, styles)?
589 } else if let Some(elem) = elem.to_packed::<OverbracketElem>() {
590 self::underover::layout_overbracket(elem, ctx, styles)?
591 } else if let Some(elem) = elem.to_packed::<UnderparenElem>() {
592 self::underover::layout_underparen(elem, ctx, styles)?
593 } else if let Some(elem) = elem.to_packed::<OverparenElem>() {
594 self::underover::layout_overparen(elem, ctx, styles)?
595 } else if let Some(elem) = elem.to_packed::<UndershellElem>() {
596 self::underover::layout_undershell(elem, ctx, styles)?
597 } else if let Some(elem) = elem.to_packed::<OvershellElem>() {
598 self::underover::layout_overshell(elem, ctx, styles)?
599 } else {
600 let mut frame = layout_external(elem, ctx, styles)?;
601 if !frame.has_baseline() {
602 let axis = scaled!(ctx, styles, axis_height);
603 frame.set_baseline(frame.height() / 2.0 + axis);
604 }
605 ctx.push(
606 FrameFragment::new(styles, frame)
607 .with_spaced(true)
608 .with_ignorant(elem.is::<PlaceElem>()),
609 );
610 }
611
612 Ok(())
613}
614
615fn layout_box(
617 elem: &Packed<BoxElem>,
618 ctx: &mut MathContext,
619 styles: StyleChain,
620) -> SourceResult<()> {
621 let frame = crate::inline::layout_box(
622 elem,
623 ctx.engine,
624 ctx.locator.next(&elem.span()),
625 styles,
626 ctx.region.size,
627 )?;
628 ctx.push(FrameFragment::new(styles, frame).with_spaced(true));
629 Ok(())
630}
631
632fn layout_h(
634 elem: &Packed<HElem>,
635 ctx: &mut MathContext,
636 styles: StyleChain,
637) -> SourceResult<()> {
638 if let Spacing::Rel(rel) = elem.amount {
639 if rel.rel.is_zero() {
640 ctx.push(MathFragment::Spacing(rel.abs.resolve(styles), elem.weak(styles)));
641 }
642 }
643 Ok(())
644}
645
646#[typst_macros::time(name = "math.class", span = elem.span())]
648fn layout_class(
649 elem: &Packed<ClassElem>,
650 ctx: &mut MathContext,
651 styles: StyleChain,
652) -> SourceResult<()> {
653 let style = EquationElem::set_class(Some(elem.class)).wrap();
654 let mut fragment = ctx.layout_into_fragment(&elem.body, styles.chain(&style))?;
655 fragment.set_class(elem.class);
656 fragment.set_limits(Limits::for_class(elem.class));
657 ctx.push(fragment);
658 Ok(())
659}
660
661#[typst_macros::time(name = "math.op", span = elem.span())]
663fn layout_op(
664 elem: &Packed<OpElem>,
665 ctx: &mut MathContext,
666 styles: StyleChain,
667) -> SourceResult<()> {
668 let fragment = ctx.layout_into_fragment(&elem.text, styles)?;
669 let italics = fragment.italics_correction();
670 let accent_attach = fragment.accent_attach();
671 let text_like = fragment.is_text_like();
672
673 ctx.push(
674 FrameFragment::new(styles, fragment.into_frame())
675 .with_class(MathClass::Large)
676 .with_italics_correction(italics)
677 .with_accent_attach(accent_attach)
678 .with_text_like(text_like)
679 .with_limits(if elem.limits(styles) {
680 Limits::Display
681 } else {
682 Limits::Never
683 }),
684 );
685 Ok(())
686}
687
688fn layout_external(
690 content: &Content,
691 ctx: &mut MathContext,
692 styles: StyleChain,
693) -> SourceResult<Frame> {
694 crate::layout_frame(
695 ctx.engine,
696 content,
697 ctx.locator.next(&content.span()),
698 styles,
699 ctx.region,
700 )
701}