1#![allow(clippy::too_many_arguments)]
2use std::cell::Cell;
3use std::ops::{Deref, MulAssign};
4use std::rc::Rc;
5
6use ecow::EcoString;
7use typst_syntax::Span;
8use typst_utils::{Get, default_math_class};
9use unicode_math_class::MathClass;
10use unicode_segmentation::UnicodeSegmentation;
11
12use super::multiline::AlignedRow;
13use crate::diag::SourceResult;
14use crate::foundations::{Content, Packed, Smart, StyleChain};
15use crate::introspection::{Locator, Tag};
16use crate::layout::{
17 Abs, Axes, Axis, BoxElem, Em, FixedAlignment, Length, PlaceElem, Ratio, Rel,
18};
19use crate::math::{
20 Augment, CancelAngle, EquationElem, LeftRightAlternator, Limits, MathSize,
21};
22use crate::visualize::FixedStroke;
23
24#[derive(Debug)]
27pub(crate) enum RawMathItem<'a> {
28 Item(MathItem<'a>),
30 Linebreak,
32 Align,
34}
35
36impl<'a> From<MathItem<'a>> for RawMathItem<'a> {
37 fn from(item: MathItem<'a>) -> Self {
38 Self::Item(item)
39 }
40}
41
42impl<'a> RawMathItem<'a> {
43 pub(crate) fn is_ignorant(&self) -> bool {
45 match self {
46 Self::Item(item) => item.is_ignorant(),
47 Self::Linebreak | Self::Align => false,
48 }
49 }
50
51 pub(crate) fn into_item(self) -> Option<MathItem<'a>> {
56 match self {
57 Self::Item(item) => Some(item),
58 Self::Linebreak | Self::Align => None,
59 }
60 }
61}
62
63#[derive(Debug)]
65pub enum MathItem<'a> {
66 Component(MathComponent<'a>),
68 Spacing(Length, Abs, bool),
71 Space,
73 Tag(Tag),
75}
76
77impl<'a> From<MathComponent<'a>> for MathItem<'a> {
78 fn from(comp: MathComponent<'a>) -> Self {
79 Self::Component(comp)
80 }
81}
82
83impl<'a> MathItem<'a> {
84 pub(crate) fn wrap(
87 mut items: Vec<MathItem<'a>>,
88 styles: StyleChain<'a>,
89 ) -> MathItem<'a> {
90 if items.len() == 1 {
91 items.pop().unwrap()
92 } else {
93 GroupItem::create(items, styles)
94 }
95 }
96
97 pub(crate) fn limits(&self) -> Limits {
99 match self {
100 Self::Component(comp) => comp.props.limits,
101 _ => Limits::Never,
102 }
103 }
104
105 pub(crate) fn class(&self) -> MathClass {
107 self.raw_class().unwrap_or(MathClass::Normal)
108 }
109
110 pub(crate) fn raw_class(&self) -> Option<MathClass> {
111 match self {
112 Self::Component(comp) => comp.props.class,
113 Self::Spacing(..) | Self::Space => Some(MathClass::Space),
114 Self::Tag(_) => Some(MathClass::Special),
115 }
116 }
117
118 pub(crate) fn rclass(&self) -> MathClass {
123 match self {
124 Self::Component(MathComponent {
125 kind: MathKind::Fenced(fence),
126 props: MathProperties { class: None, .. },
127 ..
128 }) if fence.close.is_some() => MathClass::Closing,
129 _ => self.class(),
130 }
131 }
132
133 pub(crate) fn lclass(&self) -> MathClass {
138 match self {
139 Self::Component(MathComponent {
140 kind: MathKind::Fenced(fence),
141 props: MathProperties { class: None, .. },
142 ..
143 }) if fence.open.is_some() => MathClass::Opening,
144 _ => self.class(),
145 }
146 }
147
148 pub(crate) fn size(&self) -> Option<MathSize> {
150 match self {
151 Self::Component(comp) => Some(comp.props.size),
152 _ => None,
153 }
154 }
155
156 pub(crate) fn is_spaced(&self) -> bool {
158 if self.class() == MathClass::Fence {
159 return true;
160 }
161
162 if let Self::Component(comp) = self
163 && comp.props.spaced
164 && matches!(comp.props.class(), MathClass::Normal | MathClass::Alphabetic)
165 {
166 true
167 } else {
168 false
169 }
170 }
171
172 pub fn is_ignorant(&self) -> bool {
174 match self {
175 Self::Component(comp) => comp.props.ignorant,
176 Self::Tag(_) => true,
177 _ => false,
178 }
179 }
180
181 pub fn span(&self) -> Span {
183 match self {
184 Self::Component(comp) => comp.props.span,
185 _ => Span::detached(),
186 }
187 }
188
189 pub fn styles(&self) -> Option<StyleChain<'a>> {
191 match self {
192 Self::Component(comp) => Some(comp.styles),
193 _ => None,
194 }
195 }
196
197 pub fn mid_stretched(&self) -> Option<bool> {
199 if let Self::Component(comp) = self
200 && let MathKind::Glyph(glyph) = &comp.kind
201 {
202 glyph.mid_stretched.get()
203 } else {
204 None
205 }
206 }
207
208 pub fn is_multiline(&self) -> bool {
210 matches!(
211 self,
212 MathItem::Component(MathComponent { kind: MathKind::Multiline(_), .. })
213 )
214 }
215
216 pub fn as_slice(&self) -> &[MathItem<'a>] {
219 if let MathItem::Component(comp) = self
220 && let MathKind::Group(group) = &comp.kind
221 {
222 &group.items
223 } else {
224 core::slice::from_ref(self)
225 }
226 }
227
228 pub(crate) fn set_limits(&mut self, limits: Limits) {
230 if let Self::Component(comp) = self {
231 comp.props.limits = limits;
232 }
233 }
234
235 pub(crate) fn set_class(&mut self, class: MathClass) {
237 if let Self::Component(comp) = self {
238 comp.props.class = Some(class);
239 }
240 }
241
242 pub(crate) fn set_explicit_class(&mut self, class: MathClass) {
244 self.set_class(class);
245 if let Self::Component(comp) = self
246 && let MathKind::Glyph(glyph) = &mut comp.kind
247 {
248 glyph.class = class;
249
250 if class == MathClass::Large
254 && comp.props.size == MathSize::Display
255 && !glyph.stretch.get().is_explicit(Axis::Y)
256 {
257 let info = StretchInfo::default();
258 glyph.stretch.update(|stretch| stretch.with_y(info));
259 }
260 }
261 }
262
263 pub(crate) fn set_lspace(&mut self, lspace: Option<Em>) {
265 if let Self::Component(comp) = self
266 && comp.props.lspace.is_none()
267 {
268 comp.props.lspace = lspace;
269 }
270 }
271
272 pub(crate) fn set_rspace(&mut self, rspace: Option<Em>) {
274 if let Self::Component(comp) = self
275 && comp.props.rspace.is_none()
276 {
277 comp.props.rspace = rspace;
278 }
279 }
280
281 pub(crate) fn with_multiline_centering(mut self) -> Self {
283 if let Self::Component(comp) = &mut self
284 && let MathKind::Multiline(multiline) = &mut comp.kind
285 {
286 multiline.centered = true;
287 }
288 self
289 }
290
291 pub(crate) fn set_mid_stretched(&self, mid_stretched: Option<bool>) {
293 if let Self::Component(comp) = self
294 && let MathKind::Glyph(glyph) = &comp.kind
295 {
296 glyph.mid_stretched.set(mid_stretched);
297 }
298 }
299
300 pub(crate) fn set_stretch(&self, mut stretch: Stretch) {
302 if let Some(info) = &mut stretch.0.x {
303 info.explicit = true;
304 }
305 if let Some(info) = &mut stretch.0.y {
306 info.explicit = true;
307 }
308 self.replace_stretch(stretch);
309 }
310
311 pub(crate) fn replace_stretch(&self, stretch: Stretch) {
313 if let Self::Component(comp) = self
314 && let MathKind::Glyph(glyph) = &comp.kind
315 {
316 glyph.stretch.replace(stretch);
317 }
318 }
319
320 pub(crate) fn set_y_stretch(&self, mut info: StretchInfo) {
322 if let Self::Component(comp) = self
323 && let MathKind::Glyph(glyph) = &comp.kind
324 {
325 info.explicit = true;
326 glyph.stretch.update(|stretch| stretch.with_y(info));
327 }
328 }
329
330 pub(crate) fn update_stretch(&self, info: StretchInfo) {
332 if let Self::Component(comp) = self
333 && let MathKind::Glyph(glyph) = &comp.kind
334 {
335 glyph.stretch.update(|stretch| stretch.update(info));
336 }
337 }
338
339 pub fn set_stretch_relative_to(&self, relative_to: Abs, axis: Axis) {
341 if let Self::Component(comp) = self
342 && let MathKind::Glyph(glyph) = &comp.kind
343 {
344 glyph.stretch.update(|stretch| stretch.relative_to(relative_to, axis));
345 }
346 }
347
348 pub fn set_stretch_font_size(&self, font_size: Abs, axis: Axis) {
350 if let Self::Component(comp) = self
351 && let MathKind::Glyph(glyph) = &comp.kind
352 {
353 glyph.stretch.update(|stretch| stretch.font_size(font_size, axis));
354 }
355 }
356
357 pub fn set_flac(&self) {
359 if let Self::Component(comp) = self
360 && let MathKind::Glyph(glyph) = &comp.kind
361 {
362 glyph.flac.set(true);
363 }
364 }
365}
366
367#[derive(Debug)]
370pub struct MathComponent<'a> {
371 pub kind: MathKind<'a>,
373 pub props: MathProperties,
375 pub styles: StyleChain<'a>,
377}
378
379#[derive(Debug)]
383pub enum MathKind<'a> {
384 Group(GroupItem<'a>),
386 Multiline(MultilineItem<'a>),
388 Radical(Box<RadicalItem<'a>>),
390 Fenced(Box<FencedItem<'a>>),
392 Fraction(Box<FractionItem<'a>>),
394 SkewedFraction(Box<SkewedFractionItem<'a>>),
396 Table(Box<TableItem<'a>>),
398 Scripts(Box<ScriptsItem<'a>>),
400 Accent(Box<AccentItem<'a>>),
402 Cancel(Box<CancelItem<'a>>),
404 Line(Box<LineItem<'a>>),
406 Primes(Box<PrimesItem>),
408 Text(TextItem<'a>),
410 Number(NumberItem),
412 Glyph(Box<GlyphItem>),
414 Box(BoxItem<'a>),
416 Mathml(Box<MathmlItem<'a>>),
418 External(ExternalItem<'a>),
420}
421
422#[derive(Debug, Copy, Clone)]
424pub struct MathProperties {
425 pub(crate) limits: Limits,
427 pub class: Option<MathClass>,
429 pub size: MathSize,
431 pub cramped: bool,
433 pub(crate) ignorant: bool,
435 pub(crate) spaced: bool,
437 pub lspace: Option<Em>,
439 pub rspace: Option<Em>,
441 pub align_form_infix: bool,
444 pub span: Span,
446}
447
448impl MathProperties {
449 fn new(styles: StyleChain, class: Option<MathClass>, span: Span) -> MathProperties {
451 Self {
452 limits: Limits::Never,
453 class,
454 size: styles.get(EquationElem::size),
455 cramped: styles.get(EquationElem::cramped),
456 ignorant: false,
457 spaced: false,
458 lspace: None,
459 rspace: None,
460 align_form_infix: false,
461 span,
462 }
463 }
464
465 pub fn default(styles: StyleChain, span: Span) -> MathProperties {
469 Self::new(styles, None, span)
470 }
471
472 pub fn class(&self) -> MathClass {
474 self.class.unwrap_or(MathClass::Normal)
475 }
476
477 fn with_limits(mut self, limits: Limits) -> Self {
479 self.limits = limits;
480 self
481 }
482
483 fn with_ignorant(mut self, ignorant: bool) -> Self {
485 self.ignorant = ignorant;
486 self
487 }
488
489 fn with_spaced(mut self, spaced: bool) -> Self {
491 self.spaced = spaced;
492 self
493 }
494}
495
496#[derive(Debug)]
498pub struct GroupItem<'a> {
499 pub items: Vec<MathItem<'a>>,
501}
502
503impl<'a> GroupItem<'a> {
504 pub(crate) fn create(
506 items: Vec<MathItem<'a>>,
507 styles: StyleChain<'a>,
508 ) -> MathItem<'a> {
509 let props = MathProperties::default(styles, Span::detached());
510 let kind = MathKind::Group(Self { items });
511 MathComponent { kind, props, styles }.into()
512 }
513}
514
515#[derive(Debug)]
517pub struct MultilineItem<'a> {
518 pub rows: Vec<AlignedRow<'a>>,
524 pub centered: bool,
528}
529
530impl<'a> MultilineItem<'a> {
531 pub(crate) fn create(
533 rows: Vec<AlignedRow<'a>>,
534 styles: StyleChain<'a>,
535 ) -> MathItem<'a> {
536 let kind = MathKind::Multiline(Self { rows, centered: false });
537 let props = MathProperties::default(styles, Span::detached());
538 MathComponent { kind, props, styles }.into()
539 }
540}
541
542#[derive(Debug)]
544pub struct RadicalItem<'a> {
545 pub radicand: MathItem<'a>,
547 pub index: Option<MathItem<'a>>,
549 pub sqrt: MathItem<'a>,
553}
554
555impl<'a> RadicalItem<'a> {
556 pub(crate) fn create(
558 radicand: MathItem<'a>,
559 index: Option<MathItem<'a>>,
560 sqrt: MathItem<'a>,
561 styles: StyleChain<'a>,
562 span: Span,
563 ) -> MathItem<'a> {
564 let kind = MathKind::Radical(Box::new(Self { radicand, index, sqrt }));
565 let props = MathProperties::default(styles, span);
566 MathComponent { kind, props, styles }.into()
567 }
568}
569
570#[derive(Debug)]
572pub struct FencedItem<'a> {
573 pub open: Option<MathItem<'a>>,
575 pub close: Option<MathItem<'a>>,
577 pub body: FencedBody<'a>,
579 pub balanced: bool,
587}
588
589impl<'a> FencedItem<'a> {
590 pub(crate) fn create(
592 open: Option<MathItem<'a>>,
593 close: Option<MathItem<'a>>,
594 body: impl Into<FencedBody<'a>>,
595 balanced: bool,
596 styles: StyleChain<'a>,
597 span: Span,
598 ) -> MathItem<'a> {
599 let kind =
600 MathKind::Fenced(Box::new(Self { open, close, body: body.into(), balanced }));
601 let props = MathProperties::default(styles, span);
602 MathComponent { kind, props, styles }.into()
603 }
604}
605
606#[derive(Debug)]
608pub struct FractionItem<'a> {
609 pub numerator: MathItem<'a>,
611 pub denominator: MathItem<'a>,
613 pub line: bool,
615 pub padding: Em,
617}
618
619impl<'a> FractionItem<'a> {
620 pub(crate) fn create(
622 numerator: MathItem<'a>,
623 denominator: MathItem<'a>,
624 line: bool,
625 padding: Em,
626 styles: StyleChain<'a>,
627 span: Span,
628 ) -> MathItem<'a> {
629 let kind =
630 MathKind::Fraction(Box::new(Self { numerator, denominator, line, padding }));
631 let props = MathProperties::default(styles, span);
632 MathComponent { kind, props, styles }.into()
633 }
634}
635
636#[derive(Debug)]
638pub struct SkewedFractionItem<'a> {
639 pub numerator: MathItem<'a>,
641 pub denominator: MathItem<'a>,
643 pub slash: MathItem<'a>,
647}
648
649impl<'a> SkewedFractionItem<'a> {
650 pub(crate) fn create(
652 numerator: MathItem<'a>,
653 denominator: MathItem<'a>,
654 slash: MathItem<'a>,
655 styles: StyleChain<'a>,
656 span: Span,
657 ) -> MathItem<'a> {
658 let kind =
659 MathKind::SkewedFraction(Box::new(Self { numerator, denominator, slash }));
660 let props = MathProperties::default(styles, span);
661 MathComponent { kind, props, styles }.into()
662 }
663}
664
665#[derive(Debug)]
667pub struct TableItem<'a> {
668 pub cells: Vec<Vec<AlignedRow<'a>>>,
670 pub gap: Axes<Rel<Abs>>,
672 pub augment: Option<Augment<Abs>>,
674 pub align: FixedAlignment,
676 pub alternator: LeftRightAlternator,
678}
679
680impl<'a> TableItem<'a> {
681 pub(crate) fn create(
683 cells: Vec<Vec<AlignedRow<'a>>>,
684 gap: Axes<Rel<Abs>>,
685 augment: Option<Augment<Abs>>,
686 align: FixedAlignment,
687 alternator: LeftRightAlternator,
688 styles: StyleChain<'a>,
689 span: Span,
690 ) -> MathItem<'a> {
691 let kind =
692 MathKind::Table(Box::new(Self { cells, gap, augment, align, alternator }));
693 let props = MathProperties::default(styles, span);
694 MathComponent { kind, props, styles }.into()
695 }
696}
697
698#[derive(Debug)]
700pub struct ScriptsItem<'a> {
701 pub base: MathItem<'a>,
703 pub top: Option<MathItem<'a>>,
705 pub bottom: Option<MathItem<'a>>,
707 pub top_left: Option<MathItem<'a>>,
709 pub bottom_left: Option<MathItem<'a>>,
711 pub top_right: Option<MathItem<'a>>,
713 pub bottom_right: Option<MathItem<'a>>,
715}
716
717impl<'a> ScriptsItem<'a> {
718 pub(crate) fn create(
722 base: MathItem<'a>,
723 top: Option<MathItem<'a>>,
724 bottom: Option<MathItem<'a>>,
725 top_left: Option<MathItem<'a>>,
726 bottom_left: Option<MathItem<'a>>,
727 top_right: Option<MathItem<'a>>,
728 bottom_right: Option<MathItem<'a>>,
729 styles: StyleChain<'a>,
730 ) -> MathItem<'a> {
731 let props = MathProperties::new(styles, base.raw_class(), Span::detached());
732 let kind = MathKind::Scripts(Box::new(Self {
733 base,
734 top,
735 bottom,
736 top_left,
737 bottom_left,
738 top_right,
739 bottom_right,
740 }));
741 MathComponent { kind, props, styles }.into()
742 }
743}
744
745#[derive(Debug)]
747pub struct AccentItem<'a> {
748 pub base: MathItem<'a>,
750 pub accent: MathItem<'a>,
752 pub position: Position,
754 pub dotless: bool,
756 pub exact_frame_width: bool,
760}
761
762impl<'a> AccentItem<'a> {
763 pub(crate) fn create(
767 base: MathItem<'a>,
768 accent: MathItem<'a>,
769 position: Position,
770 dotless: bool,
771 exact_frame_width: bool,
772 styles: StyleChain<'a>,
773 ) -> MathItem<'a> {
774 let props = MathProperties::new(styles, base.raw_class(), Span::detached());
775 let kind = MathKind::Accent(Box::new(Self {
776 base,
777 accent,
778 position,
779 dotless,
780 exact_frame_width,
781 }));
782 MathComponent { kind, props, styles }.into()
783 }
784}
785
786#[derive(Debug)]
788pub struct CancelItem<'a> {
789 pub base: MathItem<'a>,
791 pub length: Rel<Abs>,
793 pub stroke: FixedStroke,
795 pub cross: bool,
797 pub invert_first_line: bool,
799 pub angle: Smart<CancelAngle>,
801}
802
803impl<'a> CancelItem<'a> {
804 pub(crate) fn create(
808 base: MathItem<'a>,
809 length: Rel<Abs>,
810 stroke: FixedStroke,
811 cross: bool,
812 invert_first_line: bool,
813 angle: Smart<CancelAngle>,
814 styles: StyleChain<'a>,
815 span: Span,
816 ) -> MathItem<'a> {
817 let props = MathProperties::new(styles, base.raw_class(), span);
818 let kind = MathKind::Cancel(Box::new(Self {
819 base,
820 length,
821 stroke,
822 cross,
823 invert_first_line,
824 angle,
825 }));
826 MathComponent { kind, props, styles }.into()
827 }
828}
829
830#[derive(Debug)]
832pub struct LineItem<'a> {
833 pub base: MathItem<'a>,
835 pub position: Position,
837}
838
839impl<'a> LineItem<'a> {
840 pub(crate) fn create(
844 base: MathItem<'a>,
845 position: Position,
846 styles: StyleChain<'a>,
847 span: Span,
848 ) -> MathItem<'a> {
849 let props = MathProperties::new(styles, base.raw_class(), span);
850 let kind = MathKind::Line(Box::new(Self { base, position }));
851 MathComponent { kind, props, styles }.into()
852 }
853}
854
855pub const PRIME_CHAR: char = '′';
857
858#[derive(Debug)]
863pub struct PrimesItem {
864 pub count: usize,
866}
867
868impl PrimesItem {
869 pub(crate) fn create<'a>(count: usize, styles: StyleChain<'a>) -> MathItem<'a> {
871 let kind = MathKind::Primes(Box::new(Self { count }));
872 let props = MathProperties::default(styles, Span::detached());
873 MathComponent { kind, props, styles }.into()
874 }
875}
876
877#[derive(Debug)]
879pub struct TextItem<'a> {
880 pub text: EcoString,
882 pub locator: Locator<'a>,
884}
885
886impl<'a> TextItem<'a> {
887 pub(crate) fn create(
891 text: EcoString,
892 styles: StyleChain<'a>,
893 span: Span,
894 locator: Locator<'a>,
895 ) -> MathItem<'a> {
896 let kind = MathKind::Text(Self { text, locator });
897 let props = MathProperties::new(styles, Some(MathClass::Alphabetic), span)
898 .with_spaced(true);
899 MathComponent { kind, props, styles }.into()
900 }
901}
902
903#[derive(Debug)]
905pub struct NumberItem {
906 pub text: EcoString,
908}
909
910impl NumberItem {
911 pub(crate) fn create<'a>(
913 text: EcoString,
914 styles: StyleChain<'a>,
915 span: Span,
916 ) -> MathItem<'a> {
917 let kind = MathKind::Number(Self { text });
918 let props = MathProperties::default(styles, span);
919 MathComponent { kind, props, styles }.into()
920 }
921}
922
923#[derive(Debug)]
925pub struct GlyphItem {
926 pub text: EcoString,
928 pub class: MathClass,
935 pub stretch: Cell<Stretch>,
937 pub mid_stretched: Cell<Option<bool>>,
939 pub flac: Cell<bool>,
941}
942
943impl GlyphItem {
944 pub(crate) fn create<'a>(
949 text: EcoString,
950 styles: StyleChain<'a>,
951 span: Span,
952 ) -> MathItem<'a> {
953 assert!(text.graphemes(true).count() == 1);
954
955 let c = text.chars().next().unwrap();
956
957 let class = default_math_class(c);
958 let limits = Limits::for_char_with_class(c, class);
959
960 let kind = MathKind::Glyph(Box::new(Self {
961 text,
962 class: class.unwrap_or(MathClass::Normal),
963 stretch: Cell::new(Stretch::new()),
964 mid_stretched: Cell::new(None),
965 flac: Cell::new(false),
966 }));
967 let props = MathProperties::new(styles, class, span).with_limits(limits);
968 MathComponent { kind, props, styles }.into()
969 }
970}
971
972#[derive(Debug)]
974pub struct BoxItem<'a> {
975 pub elem: &'a Packed<BoxElem>,
977 pub locator: Locator<'a>,
979}
980
981impl<'a> BoxItem<'a> {
982 pub(crate) fn create(
986 elem: &'a Packed<BoxElem>,
987 styles: StyleChain<'a>,
988 locator: Locator<'a>,
989 ) -> MathItem<'a> {
990 let kind = MathKind::Box(Self { elem, locator });
991 let props = MathProperties::default(styles, elem.span()).with_spaced(true);
992 MathComponent { kind, props, styles }.into()
993 }
994}
995
996#[derive(Debug)]
998pub struct MathmlItem<'a> {
999 pub elem: &'a Content,
1003 pub body: Option<MathItem<'a>>,
1005}
1006
1007impl<'a> MathmlItem<'a> {
1008 pub(crate) fn create(
1010 elem: &'a Content,
1011 body: Option<MathItem<'a>>,
1012 styles: StyleChain<'a>,
1013 ) -> MathItem<'a> {
1014 let kind = MathKind::Mathml(Box::new(Self { elem, body }));
1015 let props = MathProperties::default(styles, elem.span());
1016 MathComponent { kind, props, styles }.into()
1017 }
1018}
1019
1020#[derive(Debug)]
1022pub struct ExternalItem<'a> {
1023 pub content: &'a Content,
1025 pub locator: Locator<'a>,
1027}
1028
1029impl<'a> ExternalItem<'a> {
1030 pub(crate) fn create(
1035 content: &'a Content,
1036 styles: StyleChain<'a>,
1037 locator: Locator<'a>,
1038 ) -> MathItem<'a> {
1039 let kind = MathKind::External(Self { content, locator });
1040 let props = MathProperties::default(styles, content.span())
1041 .with_spaced(true)
1042 .with_ignorant(content.is::<PlaceElem>());
1043 MathComponent { kind, props, styles }.into()
1044 }
1045}
1046
1047#[derive(Debug)]
1049pub struct SharedFenceSizing<'a> {
1050 items: Vec<MathItem<'a>>,
1052 relative_to: Cell<Option<Abs>>,
1054 styles: StyleChain<'a>,
1056}
1057
1058impl<'a> SharedFenceSizing<'a> {
1059 pub(crate) fn new(items: Vec<MathItem<'a>>, styles: StyleChain<'a>) -> Rc<Self> {
1061 Rc::new(Self { items, relative_to: Cell::new(None), styles })
1062 }
1063
1064 pub fn try_get_or_update(
1067 &self,
1068 f: impl FnOnce(&[MathItem<'a>], StyleChain<'a>) -> SourceResult<Abs>,
1069 ) -> SourceResult<Abs> {
1070 Ok(if let Some(relative_to) = self.relative_to.get() {
1071 relative_to
1072 } else {
1073 let relative_to = f(&self.items, self.styles)?;
1074 self.relative_to.set(Some(relative_to));
1075 relative_to
1076 })
1077 }
1078}
1079
1080#[derive(Debug)]
1082pub enum FencedBody<'a> {
1083 Owned(MathItem<'a>),
1085 Shared { index: usize, sizing: Rc<SharedFenceSizing<'a>> },
1087}
1088
1089impl<'a> FencedBody<'a> {
1090 pub(crate) fn shared(index: usize, sizing: Rc<SharedFenceSizing<'a>>) -> Self {
1091 Self::Shared { index, sizing }
1092 }
1093
1094 pub fn sizing(&self) -> Option<&SharedFenceSizing<'a>> {
1096 match self {
1097 Self::Owned(_) => None,
1098 Self::Shared { sizing, .. } => Some(sizing),
1099 }
1100 }
1101}
1102
1103impl<'a> From<MathItem<'a>> for FencedBody<'a> {
1104 fn from(item: MathItem<'a>) -> Self {
1105 Self::Owned(item)
1106 }
1107}
1108
1109impl<'a> Deref for FencedBody<'a> {
1110 type Target = MathItem<'a>;
1111
1112 fn deref(&self) -> &Self::Target {
1113 match self {
1114 Self::Owned(item) => item,
1115 Self::Shared { index, sizing } => &sizing.items[*index],
1116 }
1117 }
1118}
1119
1120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1122pub struct Stretch(Axes<Option<StretchInfo>>);
1123
1124impl Stretch {
1125 pub(crate) fn new() -> Self {
1127 Self(Axes::splat(None))
1128 }
1129
1130 pub(crate) fn with_x(mut self, info: StretchInfo) -> Self {
1132 self.0.x = Some(info);
1133 self
1134 }
1135
1136 pub(crate) fn with_y(mut self, info: StretchInfo) -> Self {
1138 self.0.y = Some(info);
1139 self
1140 }
1141
1142 pub(crate) fn update(mut self, mut info: StretchInfo) -> Self {
1145 info.explicit = true;
1146 match &mut self.0.x {
1147 Some(val) => *val *= info,
1148 None => self.0.x = Some(info),
1149 }
1150 match &mut self.0.y {
1151 Some(val) => *val *= info,
1152 None => self.0.y = Some(info),
1153 }
1154 self
1155 }
1156
1157 pub(crate) fn relative_to(mut self, relative_to: Abs, axis: Axis) -> Self {
1161 if let Some(info) = self.0.get_mut(axis)
1162 && info.relative_to.is_none()
1163 {
1164 info.relative_to = Some(relative_to);
1165 }
1166 self
1167 }
1168
1169 pub(crate) fn font_size(mut self, font_size: Abs, axis: Axis) -> Self {
1173 if let Some(info) = self.0.get_mut(axis)
1174 && info.font_size.is_none()
1175 {
1176 info.font_size = Some(font_size);
1177 }
1178 self
1179 }
1180
1181 pub fn resolve(mut self, axis: Axis) -> Option<StretchInfo> {
1183 if let Some(info) = self.0.get_mut(axis)
1184 && let Some(buffer) = info.buffer
1185 {
1186 if info.relative_to.is_some() {
1188 info.target = buffer;
1189 } else {
1190 info.target = Rel::new(
1191 info.target.rel * buffer.rel,
1192 buffer.rel.of(info.target.abs) + buffer.abs,
1193 );
1194 }
1195 }
1196 self.0.get(axis)
1197 }
1198
1199 pub fn resolve_requested(self, axis: Axis) -> Option<Rel<Length>> {
1201 self.0
1202 .get(axis)
1203 .and_then(|info| info.requested_target)
1204 .filter(|target| !target.is_one())
1205 }
1206
1207 pub fn is_explicit(self, axis: Axis) -> bool {
1210 self.0.get(axis).is_some_and(|info| info.explicit)
1211 }
1212}
1213
1214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1216pub struct StretchInfo {
1217 pub target: Rel<Abs>,
1219 buffer: Option<Rel<Abs>>,
1222 pub(crate) explicit: bool,
1225 pub(crate) requested_target: Option<Rel<Length>>,
1227 pub short_fall: Em,
1229 pub relative_to: Option<Abs>,
1233 pub font_size: Option<Abs>,
1237}
1238
1239impl StretchInfo {
1240 pub(crate) fn new(target: Rel<Abs>, short_fall: Em) -> Self {
1242 Self {
1243 target,
1244 buffer: None,
1245 explicit: false,
1246 requested_target: None,
1247 short_fall,
1248 relative_to: None,
1249 font_size: None,
1250 }
1251 }
1252
1253 pub(crate) fn from_size(size: Rel<Length>, short_fall: Em, font_size: Abs) -> Self {
1255 Self {
1256 target: size.map(|l| l.at(font_size)),
1257 buffer: None,
1258 explicit: false,
1259 requested_target: (!size.is_one()).then_some(size),
1260 short_fall,
1261 relative_to: None,
1262 font_size: None,
1263 }
1264 }
1265}
1266
1267impl Default for StretchInfo {
1268 fn default() -> Self {
1269 let target = Rel::new(Ratio::one(), Abs::zero());
1270 Self::new(target, Em::zero())
1271 }
1272}
1273
1274impl MulAssign for StretchInfo {
1275 fn mul_assign(&mut self, rhs: Self) {
1276 if let Some(buffer) = self.buffer {
1277 self.target = Rel::new(
1278 self.target.rel * buffer.rel,
1279 buffer.rel.of(self.target.abs) + buffer.abs,
1280 );
1281 }
1282 self.buffer = Some(rhs.target);
1283
1284 if let Some(requested) = rhs.requested_target {
1285 self.requested_target =
1286 Some(self.requested_target.map_or(requested, |target| {
1287 Rel::new(
1288 target.rel * requested.rel,
1289 requested.rel.of(target.abs) + requested.abs,
1290 )
1291 }));
1292 }
1293
1294 self.explicit = self.explicit || rhs.explicit;
1295 self.short_fall = rhs.short_fall;
1296 }
1297}
1298
1299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1301pub enum Position {
1302 Above,
1304 Below,
1306}