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 if let MathKind::Glyph(glyph) = &comp.kind
244 && class == MathClass::Large
245 && comp.props.size == MathSize::Display
246 && !glyph.stretch.get().is_explicit(Axis::Y)
247 {
248 let info = StretchInfo::default();
249 glyph.stretch.update(|stretch| stretch.with_y(info));
250 }
251 }
252 }
253
254 pub(crate) fn set_lspace(&mut self, lspace: Option<Em>) {
256 if let Self::Component(comp) = self
257 && comp.props.lspace.is_none()
258 {
259 comp.props.lspace = lspace;
260 }
261 }
262
263 pub(crate) fn set_rspace(&mut self, rspace: Option<Em>) {
265 if let Self::Component(comp) = self
266 && comp.props.rspace.is_none()
267 {
268 comp.props.rspace = rspace;
269 }
270 }
271
272 pub(crate) fn with_multiline_centering(mut self) -> Self {
274 if let Self::Component(comp) = &mut self
275 && let MathKind::Multiline(multiline) = &mut comp.kind
276 {
277 multiline.centered = true;
278 }
279 self
280 }
281
282 pub(crate) fn set_mid_stretched(&self, mid_stretched: Option<bool>) {
284 if let Self::Component(comp) = self
285 && let MathKind::Glyph(glyph) = &comp.kind
286 {
287 glyph.mid_stretched.set(mid_stretched);
288 }
289 }
290
291 pub(crate) fn set_stretch(&self, mut stretch: Stretch) {
293 if let Some(info) = &mut stretch.0.x {
294 info.explicit = true;
295 }
296 if let Some(info) = &mut stretch.0.y {
297 info.explicit = true;
298 }
299 self.replace_stretch(stretch);
300 }
301
302 pub(crate) fn replace_stretch(&self, stretch: Stretch) {
304 if let Self::Component(comp) = self
305 && let MathKind::Glyph(glyph) = &comp.kind
306 {
307 glyph.stretch.replace(stretch);
308 }
309 }
310
311 pub(crate) fn set_y_stretch(&self, mut info: StretchInfo) {
313 if let Self::Component(comp) = self
314 && let MathKind::Glyph(glyph) = &comp.kind
315 {
316 info.explicit = true;
317 glyph.stretch.update(|stretch| stretch.with_y(info));
318 }
319 }
320
321 pub(crate) fn update_stretch(&self, info: StretchInfo) {
323 if let Self::Component(comp) = self
324 && let MathKind::Glyph(glyph) = &comp.kind
325 {
326 glyph.stretch.update(|stretch| stretch.update(info));
327 }
328 }
329
330 pub fn set_stretch_relative_to(&self, relative_to: Abs, axis: Axis) {
332 if let Self::Component(comp) = self
333 && let MathKind::Glyph(glyph) = &comp.kind
334 {
335 glyph.stretch.update(|stretch| stretch.relative_to(relative_to, axis));
336 }
337 }
338
339 pub fn set_stretch_font_size(&self, font_size: Abs, axis: Axis) {
341 if let Self::Component(comp) = self
342 && let MathKind::Glyph(glyph) = &comp.kind
343 {
344 glyph.stretch.update(|stretch| stretch.font_size(font_size, axis));
345 }
346 }
347
348 pub fn set_flac(&self) {
350 if let Self::Component(comp) = self
351 && let MathKind::Glyph(glyph) = &comp.kind
352 {
353 glyph.flac.set(true);
354 }
355 }
356}
357
358#[derive(Debug)]
361pub struct MathComponent<'a> {
362 pub kind: MathKind<'a>,
364 pub props: MathProperties,
366 pub styles: StyleChain<'a>,
368}
369
370#[derive(Debug)]
374pub enum MathKind<'a> {
375 Group(GroupItem<'a>),
377 Multiline(MultilineItem<'a>),
379 Radical(Box<RadicalItem<'a>>),
381 Fenced(Box<FencedItem<'a>>),
383 Fraction(Box<FractionItem<'a>>),
385 SkewedFraction(Box<SkewedFractionItem<'a>>),
387 Table(Box<TableItem<'a>>),
389 Scripts(Box<ScriptsItem<'a>>),
391 Accent(Box<AccentItem<'a>>),
393 Cancel(Box<CancelItem<'a>>),
395 Line(Box<LineItem<'a>>),
397 Primes(Box<PrimesItem>),
399 Text(TextItem<'a>),
401 Number(NumberItem),
403 Glyph(Box<GlyphItem>),
405 Box(BoxItem<'a>),
407 Mathml(Box<MathmlItem<'a>>),
409 External(ExternalItem<'a>),
411}
412
413#[derive(Debug, Copy, Clone)]
415pub struct MathProperties {
416 pub(crate) limits: Limits,
418 pub class: Option<MathClass>,
420 pub size: MathSize,
422 pub cramped: bool,
424 pub(crate) ignorant: bool,
426 pub(crate) spaced: bool,
428 pub lspace: Option<Em>,
430 pub rspace: Option<Em>,
432 pub align_form_infix: bool,
435 pub span: Span,
437}
438
439impl MathProperties {
440 fn new(styles: StyleChain, class: Option<MathClass>, span: Span) -> MathProperties {
442 Self {
443 limits: Limits::Never,
444 class,
445 size: styles.get(EquationElem::size),
446 cramped: styles.get(EquationElem::cramped),
447 ignorant: false,
448 spaced: false,
449 lspace: None,
450 rspace: None,
451 align_form_infix: false,
452 span,
453 }
454 }
455
456 pub fn default(styles: StyleChain, span: Span) -> MathProperties {
460 Self::new(styles, None, span)
461 }
462
463 pub fn class(&self) -> MathClass {
465 self.class.unwrap_or(MathClass::Normal)
466 }
467
468 fn with_limits(mut self, limits: Limits) -> Self {
470 self.limits = limits;
471 self
472 }
473
474 fn with_ignorant(mut self, ignorant: bool) -> Self {
476 self.ignorant = ignorant;
477 self
478 }
479
480 fn with_spaced(mut self, spaced: bool) -> Self {
482 self.spaced = spaced;
483 self
484 }
485}
486
487#[derive(Debug)]
489pub struct GroupItem<'a> {
490 pub items: Vec<MathItem<'a>>,
492}
493
494impl<'a> GroupItem<'a> {
495 pub(crate) fn create(
497 items: Vec<MathItem<'a>>,
498 styles: StyleChain<'a>,
499 ) -> MathItem<'a> {
500 let props = MathProperties::default(styles, Span::detached());
501 let kind = MathKind::Group(Self { items });
502 MathComponent { kind, props, styles }.into()
503 }
504}
505
506#[derive(Debug)]
508pub struct MultilineItem<'a> {
509 pub rows: Vec<AlignedRow<'a>>,
515 pub centered: bool,
519}
520
521impl<'a> MultilineItem<'a> {
522 pub(crate) fn create(
524 rows: Vec<AlignedRow<'a>>,
525 styles: StyleChain<'a>,
526 ) -> MathItem<'a> {
527 let kind = MathKind::Multiline(Self { rows, centered: false });
528 let props = MathProperties::default(styles, Span::detached());
529 MathComponent { kind, props, styles }.into()
530 }
531}
532
533#[derive(Debug)]
535pub struct RadicalItem<'a> {
536 pub radicand: MathItem<'a>,
538 pub index: Option<MathItem<'a>>,
540 pub sqrt: MathItem<'a>,
544}
545
546impl<'a> RadicalItem<'a> {
547 pub(crate) fn create(
549 radicand: MathItem<'a>,
550 index: Option<MathItem<'a>>,
551 sqrt: MathItem<'a>,
552 styles: StyleChain<'a>,
553 span: Span,
554 ) -> MathItem<'a> {
555 let kind = MathKind::Radical(Box::new(Self { radicand, index, sqrt }));
556 let props = MathProperties::default(styles, span);
557 MathComponent { kind, props, styles }.into()
558 }
559}
560
561#[derive(Debug)]
563pub struct FencedItem<'a> {
564 pub open: Option<MathItem<'a>>,
566 pub close: Option<MathItem<'a>>,
568 pub body: FencedBody<'a>,
570 pub balanced: bool,
578}
579
580impl<'a> FencedItem<'a> {
581 pub(crate) fn create(
583 open: Option<MathItem<'a>>,
584 close: Option<MathItem<'a>>,
585 body: impl Into<FencedBody<'a>>,
586 balanced: bool,
587 styles: StyleChain<'a>,
588 span: Span,
589 ) -> MathItem<'a> {
590 let kind =
591 MathKind::Fenced(Box::new(Self { open, close, body: body.into(), balanced }));
592 let props = MathProperties::default(styles, span);
593 MathComponent { kind, props, styles }.into()
594 }
595}
596
597#[derive(Debug)]
599pub struct FractionItem<'a> {
600 pub numerator: MathItem<'a>,
602 pub denominator: MathItem<'a>,
604 pub line: bool,
606 pub padding: Em,
608}
609
610impl<'a> FractionItem<'a> {
611 pub(crate) fn create(
613 numerator: MathItem<'a>,
614 denominator: MathItem<'a>,
615 line: bool,
616 padding: Em,
617 styles: StyleChain<'a>,
618 span: Span,
619 ) -> MathItem<'a> {
620 let kind =
621 MathKind::Fraction(Box::new(Self { numerator, denominator, line, padding }));
622 let props = MathProperties::default(styles, span);
623 MathComponent { kind, props, styles }.into()
624 }
625}
626
627#[derive(Debug)]
629pub struct SkewedFractionItem<'a> {
630 pub numerator: MathItem<'a>,
632 pub denominator: MathItem<'a>,
634 pub slash: MathItem<'a>,
638}
639
640impl<'a> SkewedFractionItem<'a> {
641 pub(crate) fn create(
643 numerator: MathItem<'a>,
644 denominator: MathItem<'a>,
645 slash: MathItem<'a>,
646 styles: StyleChain<'a>,
647 span: Span,
648 ) -> MathItem<'a> {
649 let kind =
650 MathKind::SkewedFraction(Box::new(Self { numerator, denominator, slash }));
651 let props = MathProperties::default(styles, span);
652 MathComponent { kind, props, styles }.into()
653 }
654}
655
656#[derive(Debug)]
658pub struct TableItem<'a> {
659 pub cells: Vec<Vec<AlignedRow<'a>>>,
661 pub gap: Axes<Rel<Abs>>,
663 pub augment: Option<Augment<Abs>>,
665 pub align: FixedAlignment,
667 pub alternator: LeftRightAlternator,
669}
670
671impl<'a> TableItem<'a> {
672 pub(crate) fn create(
674 cells: Vec<Vec<AlignedRow<'a>>>,
675 gap: Axes<Rel<Abs>>,
676 augment: Option<Augment<Abs>>,
677 align: FixedAlignment,
678 alternator: LeftRightAlternator,
679 styles: StyleChain<'a>,
680 span: Span,
681 ) -> MathItem<'a> {
682 let kind =
683 MathKind::Table(Box::new(Self { cells, gap, augment, align, alternator }));
684 let props = MathProperties::default(styles, span);
685 MathComponent { kind, props, styles }.into()
686 }
687}
688
689#[derive(Debug)]
691pub struct ScriptsItem<'a> {
692 pub base: MathItem<'a>,
694 pub top: Option<MathItem<'a>>,
696 pub bottom: Option<MathItem<'a>>,
698 pub top_left: Option<MathItem<'a>>,
700 pub bottom_left: Option<MathItem<'a>>,
702 pub top_right: Option<MathItem<'a>>,
704 pub bottom_right: Option<MathItem<'a>>,
706}
707
708impl<'a> ScriptsItem<'a> {
709 pub(crate) fn create(
713 base: MathItem<'a>,
714 top: Option<MathItem<'a>>,
715 bottom: Option<MathItem<'a>>,
716 top_left: Option<MathItem<'a>>,
717 bottom_left: Option<MathItem<'a>>,
718 top_right: Option<MathItem<'a>>,
719 bottom_right: Option<MathItem<'a>>,
720 styles: StyleChain<'a>,
721 ) -> MathItem<'a> {
722 let props = MathProperties::new(styles, base.raw_class(), Span::detached());
723 let kind = MathKind::Scripts(Box::new(Self {
724 base,
725 top,
726 bottom,
727 top_left,
728 bottom_left,
729 top_right,
730 bottom_right,
731 }));
732 MathComponent { kind, props, styles }.into()
733 }
734}
735
736#[derive(Debug)]
738pub struct AccentItem<'a> {
739 pub base: MathItem<'a>,
741 pub accent: MathItem<'a>,
743 pub position: Position,
745 pub dotless: bool,
747 pub exact_frame_width: bool,
751}
752
753impl<'a> AccentItem<'a> {
754 pub(crate) fn create(
758 base: MathItem<'a>,
759 accent: MathItem<'a>,
760 position: Position,
761 dotless: bool,
762 exact_frame_width: bool,
763 styles: StyleChain<'a>,
764 ) -> MathItem<'a> {
765 let props = MathProperties::new(styles, base.raw_class(), Span::detached());
766 let kind = MathKind::Accent(Box::new(Self {
767 base,
768 accent,
769 position,
770 dotless,
771 exact_frame_width,
772 }));
773 MathComponent { kind, props, styles }.into()
774 }
775}
776
777#[derive(Debug)]
779pub struct CancelItem<'a> {
780 pub base: MathItem<'a>,
782 pub length: Rel<Abs>,
784 pub stroke: FixedStroke,
786 pub cross: bool,
788 pub invert_first_line: bool,
790 pub angle: Smart<CancelAngle>,
792}
793
794impl<'a> CancelItem<'a> {
795 pub(crate) fn create(
799 base: MathItem<'a>,
800 length: Rel<Abs>,
801 stroke: FixedStroke,
802 cross: bool,
803 invert_first_line: bool,
804 angle: Smart<CancelAngle>,
805 styles: StyleChain<'a>,
806 span: Span,
807 ) -> MathItem<'a> {
808 let props = MathProperties::new(styles, base.raw_class(), span);
809 let kind = MathKind::Cancel(Box::new(Self {
810 base,
811 length,
812 stroke,
813 cross,
814 invert_first_line,
815 angle,
816 }));
817 MathComponent { kind, props, styles }.into()
818 }
819}
820
821#[derive(Debug)]
823pub struct LineItem<'a> {
824 pub base: MathItem<'a>,
826 pub position: Position,
828}
829
830impl<'a> LineItem<'a> {
831 pub(crate) fn create(
835 base: MathItem<'a>,
836 position: Position,
837 styles: StyleChain<'a>,
838 span: Span,
839 ) -> MathItem<'a> {
840 let props = MathProperties::new(styles, base.raw_class(), span);
841 let kind = MathKind::Line(Box::new(Self { base, position }));
842 MathComponent { kind, props, styles }.into()
843 }
844}
845
846pub const PRIME_CHAR: char = '′';
848
849#[derive(Debug)]
854pub struct PrimesItem {
855 pub count: usize,
857}
858
859impl PrimesItem {
860 pub(crate) fn create<'a>(count: usize, styles: StyleChain<'a>) -> MathItem<'a> {
862 let kind = MathKind::Primes(Box::new(Self { count }));
863 let props = MathProperties::default(styles, Span::detached());
864 MathComponent { kind, props, styles }.into()
865 }
866}
867
868#[derive(Debug)]
870pub struct TextItem<'a> {
871 pub text: EcoString,
873 pub locator: Locator<'a>,
875}
876
877impl<'a> TextItem<'a> {
878 pub(crate) fn create(
882 text: EcoString,
883 styles: StyleChain<'a>,
884 span: Span,
885 locator: Locator<'a>,
886 ) -> MathItem<'a> {
887 let kind = MathKind::Text(Self { text, locator });
888 let props = MathProperties::new(styles, Some(MathClass::Alphabetic), span)
889 .with_spaced(true);
890 MathComponent { kind, props, styles }.into()
891 }
892}
893
894#[derive(Debug)]
896pub struct NumberItem {
897 pub text: EcoString,
899}
900
901impl NumberItem {
902 pub(crate) fn create<'a>(
904 text: EcoString,
905 styles: StyleChain<'a>,
906 span: Span,
907 ) -> MathItem<'a> {
908 let kind = MathKind::Number(Self { text });
909 let props = MathProperties::default(styles, span);
910 MathComponent { kind, props, styles }.into()
911 }
912}
913
914#[derive(Debug)]
916pub struct GlyphItem {
917 pub text: EcoString,
919 pub stretch: Cell<Stretch>,
921 pub mid_stretched: Cell<Option<bool>>,
923 pub flac: Cell<bool>,
925}
926
927impl GlyphItem {
928 pub(crate) fn create<'a>(
933 text: EcoString,
934 styles: StyleChain<'a>,
935 span: Span,
936 ) -> MathItem<'a> {
937 assert!(text.graphemes(true).count() == 1);
938
939 let c = text.chars().next().unwrap();
940
941 let class = default_math_class(c);
942 let limits = Limits::for_char_with_class(c, class);
943
944 let kind = MathKind::Glyph(Box::new(Self {
945 text,
946 stretch: Cell::new(Stretch::new()),
947 mid_stretched: Cell::new(None),
948 flac: Cell::new(false),
949 }));
950 let props = MathProperties::new(styles, class, span).with_limits(limits);
951 MathComponent { kind, props, styles }.into()
952 }
953}
954
955#[derive(Debug)]
957pub struct BoxItem<'a> {
958 pub elem: &'a Packed<BoxElem>,
960 pub locator: Locator<'a>,
962}
963
964impl<'a> BoxItem<'a> {
965 pub(crate) fn create(
969 elem: &'a Packed<BoxElem>,
970 styles: StyleChain<'a>,
971 locator: Locator<'a>,
972 ) -> MathItem<'a> {
973 let kind = MathKind::Box(Self { elem, locator });
974 let props = MathProperties::default(styles, elem.span()).with_spaced(true);
975 MathComponent { kind, props, styles }.into()
976 }
977}
978
979#[derive(Debug)]
981pub struct MathmlItem<'a> {
982 pub elem: &'a Content,
986 pub body: Option<MathItem<'a>>,
988}
989
990impl<'a> MathmlItem<'a> {
991 pub(crate) fn create(
993 elem: &'a Content,
994 body: Option<MathItem<'a>>,
995 styles: StyleChain<'a>,
996 ) -> MathItem<'a> {
997 let kind = MathKind::Mathml(Box::new(Self { elem, body }));
998 let props = MathProperties::default(styles, elem.span());
999 MathComponent { kind, props, styles }.into()
1000 }
1001}
1002
1003#[derive(Debug)]
1005pub struct ExternalItem<'a> {
1006 pub content: &'a Content,
1008 pub locator: Locator<'a>,
1010}
1011
1012impl<'a> ExternalItem<'a> {
1013 pub(crate) fn create(
1018 content: &'a Content,
1019 styles: StyleChain<'a>,
1020 locator: Locator<'a>,
1021 ) -> MathItem<'a> {
1022 let kind = MathKind::External(Self { content, locator });
1023 let props = MathProperties::default(styles, content.span())
1024 .with_spaced(true)
1025 .with_ignorant(content.is::<PlaceElem>());
1026 MathComponent { kind, props, styles }.into()
1027 }
1028}
1029
1030#[derive(Debug)]
1032pub struct SharedFenceSizing<'a> {
1033 items: Vec<MathItem<'a>>,
1035 relative_to: Cell<Option<Abs>>,
1037 styles: StyleChain<'a>,
1039}
1040
1041impl<'a> SharedFenceSizing<'a> {
1042 pub(crate) fn new(items: Vec<MathItem<'a>>, styles: StyleChain<'a>) -> Rc<Self> {
1044 Rc::new(Self { items, relative_to: Cell::new(None), styles })
1045 }
1046
1047 pub fn try_get_or_update(
1050 &self,
1051 f: impl FnOnce(&[MathItem<'a>], StyleChain<'a>) -> SourceResult<Abs>,
1052 ) -> SourceResult<Abs> {
1053 Ok(if let Some(relative_to) = self.relative_to.get() {
1054 relative_to
1055 } else {
1056 let relative_to = f(&self.items, self.styles)?;
1057 self.relative_to.set(Some(relative_to));
1058 relative_to
1059 })
1060 }
1061}
1062
1063#[derive(Debug)]
1065pub enum FencedBody<'a> {
1066 Owned(MathItem<'a>),
1068 Shared { index: usize, sizing: Rc<SharedFenceSizing<'a>> },
1070}
1071
1072impl<'a> FencedBody<'a> {
1073 pub(crate) fn shared(index: usize, sizing: Rc<SharedFenceSizing<'a>>) -> Self {
1074 Self::Shared { index, sizing }
1075 }
1076
1077 pub fn sizing(&self) -> Option<&SharedFenceSizing<'a>> {
1079 match self {
1080 Self::Owned(_) => None,
1081 Self::Shared { sizing, .. } => Some(sizing),
1082 }
1083 }
1084}
1085
1086impl<'a> From<MathItem<'a>> for FencedBody<'a> {
1087 fn from(item: MathItem<'a>) -> Self {
1088 Self::Owned(item)
1089 }
1090}
1091
1092impl<'a> Deref for FencedBody<'a> {
1093 type Target = MathItem<'a>;
1094
1095 fn deref(&self) -> &Self::Target {
1096 match self {
1097 Self::Owned(item) => item,
1098 Self::Shared { index, sizing } => &sizing.items[*index],
1099 }
1100 }
1101}
1102
1103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1105pub struct Stretch(Axes<Option<StretchInfo>>);
1106
1107impl Stretch {
1108 pub(crate) fn new() -> Self {
1110 Self(Axes::splat(None))
1111 }
1112
1113 pub(crate) fn with_x(mut self, info: StretchInfo) -> Self {
1115 self.0.x = Some(info);
1116 self
1117 }
1118
1119 pub(crate) fn with_y(mut self, info: StretchInfo) -> Self {
1121 self.0.y = Some(info);
1122 self
1123 }
1124
1125 pub(crate) fn update(mut self, mut info: StretchInfo) -> Self {
1128 info.explicit = true;
1129 match &mut self.0.x {
1130 Some(val) => *val *= info,
1131 None => self.0.x = Some(info),
1132 }
1133 match &mut self.0.y {
1134 Some(val) => *val *= info,
1135 None => self.0.y = Some(info),
1136 }
1137 self
1138 }
1139
1140 pub(crate) fn relative_to(mut self, relative_to: Abs, axis: Axis) -> Self {
1144 if let Some(info) = self.0.get_mut(axis)
1145 && info.relative_to.is_none()
1146 {
1147 info.relative_to = Some(relative_to);
1148 }
1149 self
1150 }
1151
1152 pub(crate) fn font_size(mut self, font_size: Abs, axis: Axis) -> Self {
1156 if let Some(info) = self.0.get_mut(axis)
1157 && info.font_size.is_none()
1158 {
1159 info.font_size = Some(font_size);
1160 }
1161 self
1162 }
1163
1164 pub fn resolve(mut self, axis: Axis) -> Option<StretchInfo> {
1166 if let Some(info) = self.0.get_mut(axis)
1167 && let Some(buffer) = info.buffer
1168 {
1169 if info.relative_to.is_some() {
1171 info.target = buffer;
1172 } else {
1173 info.target = Rel::new(
1174 info.target.rel * buffer.rel,
1175 buffer.rel.of(info.target.abs) + buffer.abs,
1176 );
1177 }
1178 }
1179 self.0.get(axis)
1180 }
1181
1182 pub fn resolve_requested(self, axis: Axis) -> Option<Rel<Length>> {
1184 self.0
1185 .get(axis)
1186 .and_then(|info| info.requested_target)
1187 .filter(|target| !target.is_one())
1188 }
1189
1190 pub fn is_explicit(self, axis: Axis) -> bool {
1193 self.0.get(axis).is_some_and(|info| info.explicit)
1194 }
1195}
1196
1197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1199pub struct StretchInfo {
1200 pub target: Rel<Abs>,
1202 buffer: Option<Rel<Abs>>,
1205 pub(crate) explicit: bool,
1208 pub(crate) requested_target: Option<Rel<Length>>,
1210 pub short_fall: Em,
1212 pub relative_to: Option<Abs>,
1216 pub font_size: Option<Abs>,
1220}
1221
1222impl StretchInfo {
1223 pub(crate) fn new(target: Rel<Abs>, short_fall: Em) -> Self {
1225 Self {
1226 target,
1227 buffer: None,
1228 explicit: false,
1229 requested_target: None,
1230 short_fall,
1231 relative_to: None,
1232 font_size: None,
1233 }
1234 }
1235
1236 pub(crate) fn from_size(size: Rel<Length>, short_fall: Em, font_size: Abs) -> Self {
1238 Self {
1239 target: size.map(|l| l.at(font_size)),
1240 buffer: None,
1241 explicit: false,
1242 requested_target: (!size.is_one()).then_some(size),
1243 short_fall,
1244 relative_to: None,
1245 font_size: None,
1246 }
1247 }
1248}
1249
1250impl Default for StretchInfo {
1251 fn default() -> Self {
1252 let target = Rel::new(Ratio::one(), Abs::zero());
1253 Self::new(target, Em::zero())
1254 }
1255}
1256
1257impl MulAssign for StretchInfo {
1258 fn mul_assign(&mut self, rhs: Self) {
1259 if let Some(buffer) = self.buffer {
1260 self.target = Rel::new(
1261 self.target.rel * buffer.rel,
1262 buffer.rel.of(self.target.abs) + buffer.abs,
1263 );
1264 }
1265 self.buffer = Some(rhs.target);
1266
1267 if let Some(requested) = rhs.requested_target {
1268 self.requested_target =
1269 Some(self.requested_target.map_or(requested, |target| {
1270 Rel::new(
1271 target.rel * requested.rel,
1272 requested.rel.of(target.abs) + requested.abs,
1273 )
1274 }));
1275 }
1276
1277 self.explicit = self.explicit || rhs.explicit;
1278 self.short_fall = rhs.short_fall;
1279 }
1280}
1281
1282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1284pub enum Position {
1285 Above,
1287 Below,
1289}