summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src/math/shared.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-10-27 19:04:55 +0100
committerGitHub <noreply@github.com>2024-10-27 18:04:55 +0000
commitbe7cfc85d08c545abfac08098b7b33b4bd71f37e (patch)
treef4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-layout/src/math/shared.rs
parentb8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff)
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-layout/src/math/shared.rs')
-rw-r--r--crates/typst-layout/src/math/shared.rs207
1 files changed, 207 insertions, 0 deletions
diff --git a/crates/typst-layout/src/math/shared.rs b/crates/typst-layout/src/math/shared.rs
new file mode 100644
index 00000000..13477c10
--- /dev/null
+++ b/crates/typst-layout/src/math/shared.rs
@@ -0,0 +1,207 @@
+use ttf_parser::math::MathValue;
+use typst_library::foundations::{Style, StyleChain};
+use typst_library::layout::{Abs, Em, FixedAlignment, Frame, Point, Size, VAlignment};
+use typst_library::math::{EquationElem, MathSize};
+use typst_library::text::TextElem;
+use typst_utils::LazyHash;
+
+use super::{LeftRightAlternator, MathContext, MathFragment, MathRun};
+
+macro_rules! scaled {
+ ($ctx:expr, $styles:expr, text: $text:ident, display: $display:ident $(,)?) => {
+ match typst_library::math::EquationElem::size_in($styles) {
+ typst_library::math::MathSize::Display => scaled!($ctx, $styles, $display),
+ _ => scaled!($ctx, $styles, $text),
+ }
+ };
+ ($ctx:expr, $styles:expr, $name:ident) => {
+ $crate::math::Scaled::scaled(
+ $ctx.constants.$name(),
+ $ctx,
+ $crate::math::scaled_font_size($ctx, $styles),
+ )
+ };
+}
+
+macro_rules! percent {
+ ($ctx:expr, $name:ident) => {
+ $ctx.constants.$name() as f64 / 100.0
+ };
+}
+
+/// How much less high scaled delimiters can be than what they wrap.
+pub const DELIM_SHORT_FALL: Em = Em::new(0.1);
+
+/// Converts some unit to an absolute length with the current font & font size.
+pub trait Scaled {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs;
+}
+
+impl Scaled for i16 {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ ctx.font.to_em(self).at(font_size)
+ }
+}
+
+impl Scaled for u16 {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ ctx.font.to_em(self).at(font_size)
+ }
+}
+
+impl Scaled for MathValue<'_> {
+ fn scaled(self, ctx: &MathContext, font_size: Abs) -> Abs {
+ self.value.scaled(ctx, font_size)
+ }
+}
+
+/// Get the font size scaled with the `MathSize`.
+pub fn scaled_font_size(ctx: &MathContext, styles: StyleChain) -> Abs {
+ let factor = match EquationElem::size_in(styles) {
+ MathSize::Display | MathSize::Text => 1.0,
+ MathSize::Script => percent!(ctx, script_percent_scale_down),
+ MathSize::ScriptScript => percent!(ctx, script_script_percent_scale_down),
+ };
+ factor * TextElem::size_in(styles)
+}
+
+/// Styles something as cramped.
+pub fn style_cramped() -> LazyHash<Style> {
+ EquationElem::set_cramped(true).wrap()
+}
+
+/// The style for subscripts in the current style.
+pub fn style_for_subscript(styles: StyleChain) -> [LazyHash<Style>; 2] {
+ [style_for_superscript(styles), EquationElem::set_cramped(true).wrap()]
+}
+
+/// The style for superscripts in the current style.
+pub fn style_for_superscript(styles: StyleChain) -> LazyHash<Style> {
+ EquationElem::set_size(match EquationElem::size_in(styles) {
+ MathSize::Display | MathSize::Text => MathSize::Script,
+ MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
+ })
+ .wrap()
+}
+
+/// The style for numerators in the current style.
+pub fn style_for_numerator(styles: StyleChain) -> LazyHash<Style> {
+ EquationElem::set_size(match EquationElem::size_in(styles) {
+ MathSize::Display => MathSize::Text,
+ MathSize::Text => MathSize::Script,
+ MathSize::Script | MathSize::ScriptScript => MathSize::ScriptScript,
+ })
+ .wrap()
+}
+
+/// The style for denominators in the current style.
+pub fn style_for_denominator(styles: StyleChain) -> [LazyHash<Style>; 2] {
+ [style_for_numerator(styles), EquationElem::set_cramped(true).wrap()]
+}
+
+/// How a delimieter should be aligned when scaling.
+pub fn delimiter_alignment(delimiter: char) -> VAlignment {
+ match delimiter {
+ '⌜' | '⌝' => VAlignment::Top,
+ '⌞' | '⌟' => VAlignment::Bottom,
+ _ => VAlignment::Horizon,
+ }
+}
+
+/// Stack rows on top of each other.
+///
+/// Add a `gap` between each row and uses the baseline of the `baseline`-th
+/// row for the whole frame. `alternator` controls the left/right alternating
+/// alignment behavior of `AlignPointElem` in the rows.
+pub fn stack(
+ rows: Vec<MathRun>,
+ align: FixedAlignment,
+ gap: Abs,
+ baseline: usize,
+ alternator: LeftRightAlternator,
+ minimum_ascent_descent: Option<(Abs, Abs)>,
+) -> Frame {
+ let rows: Vec<_> = rows.into_iter().flat_map(|r| r.rows()).collect();
+ let AlignmentResult { points, width } = alignments(&rows);
+ let rows: Vec<_> = rows
+ .into_iter()
+ .map(|row| row.into_line_frame(&points, alternator))
+ .collect();
+
+ let padded_height = |height: Abs| {
+ height.max(minimum_ascent_descent.map_or(Abs::zero(), |(a, d)| a + d))
+ };
+
+ let mut frame = Frame::soft(Size::new(
+ width,
+ rows.iter().map(|row| padded_height(row.height())).sum::<Abs>()
+ + rows.len().saturating_sub(1) as f64 * gap,
+ ));
+
+ let mut y = Abs::zero();
+ for (i, row) in rows.into_iter().enumerate() {
+ let x = if points.is_empty() {
+ align.position(width - row.width())
+ } else {
+ Abs::zero()
+ };
+ let ascent_padded_part = minimum_ascent_descent
+ .map_or(Abs::zero(), |(a, _)| (a - row.ascent()))
+ .max(Abs::zero());
+ let pos = Point::new(x, y + ascent_padded_part);
+ if i == baseline {
+ frame.set_baseline(y + row.baseline() + ascent_padded_part);
+ }
+ y += padded_height(row.height()) + gap;
+ frame.push_frame(pos, row);
+ }
+
+ frame
+}
+
+/// Determine the positions of the alignment points, according to the input rows combined.
+pub fn alignments(rows: &[MathRun]) -> AlignmentResult {
+ let mut widths = Vec::<Abs>::new();
+
+ let mut pending_width = Abs::zero();
+ for row in rows {
+ let mut width = Abs::zero();
+ let mut alignment_index = 0;
+
+ for fragment in row.iter() {
+ if matches!(fragment, MathFragment::Align) {
+ if alignment_index < widths.len() {
+ widths[alignment_index].set_max(width);
+ } else {
+ widths.push(width.max(pending_width));
+ }
+ width = Abs::zero();
+ alignment_index += 1;
+ } else {
+ width += fragment.width();
+ }
+ }
+ if widths.is_empty() {
+ pending_width.set_max(width);
+ } else if alignment_index < widths.len() {
+ widths[alignment_index].set_max(width);
+ } else {
+ widths.push(width.max(pending_width));
+ }
+ }
+
+ let mut points = widths;
+ for i in 1..points.len() {
+ let prev = points[i - 1];
+ points[i] += prev;
+ }
+ AlignmentResult {
+ width: points.last().copied().unwrap_or(pending_width),
+ points,
+ }
+}
+
+pub struct AlignmentResult {
+ pub points: Vec<Abs>,
+ pub width: Abs,
+}