summaryrefslogtreecommitdiff
path: root/crates/typst-layout/src/math/stretch.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-layout/src/math/stretch.rs')
-rw-r--r--crates/typst-layout/src/math/stretch.rs298
1 files changed, 32 insertions, 266 deletions
diff --git a/crates/typst-layout/src/math/stretch.rs b/crates/typst-layout/src/math/stretch.rs
index 40f76da5..d4370e7b 100644
--- a/crates/typst-layout/src/math/stretch.rs
+++ b/crates/typst-layout/src/math/stretch.rs
@@ -1,19 +1,10 @@
-use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
-use ttf_parser::LazyArray16;
use typst_library::diag::{warning, SourceResult};
use typst_library::foundations::{Packed, StyleChain};
-use typst_library::layout::{Abs, Axis, Frame, Point, Rel, Size};
+use typst_library::layout::{Abs, Axis, Rel};
use typst_library::math::StretchElem;
use typst_utils::Get;
-use super::{
- delimiter_alignment, GlyphFragment, MathContext, MathFragment, Scaled,
- VariantFragment,
-};
-use crate::modifiers::FrameModify;
-
-/// Maximum number of times extenders can be repeated.
-const MAX_REPEATS: usize = 1024;
+use super::{stretch_axes, MathContext, MathFragment};
/// Lays out a [`StretchElem`].
#[typst_macros::time(name = "math.stretch", span = elem.span())]
@@ -23,15 +14,7 @@ pub fn layout_stretch(
styles: StyleChain,
) -> SourceResult<()> {
let mut fragment = ctx.layout_into_fragment(&elem.body, styles)?;
- stretch_fragment(
- ctx,
- styles,
- &mut fragment,
- None,
- None,
- elem.size(styles),
- Abs::zero(),
- );
+ stretch_fragment(ctx, &mut fragment, None, None, elem.size(styles), Abs::zero());
ctx.push(fragment);
Ok(())
}
@@ -39,266 +22,49 @@ pub fn layout_stretch(
/// Attempts to stretch the given fragment by/to the amount given in stretch.
pub fn stretch_fragment(
ctx: &mut MathContext,
- styles: StyleChain,
fragment: &mut MathFragment,
axis: Option<Axis>,
relative_to: Option<Abs>,
stretch: Rel<Abs>,
short_fall: Abs,
) {
- let glyph = match fragment {
- MathFragment::Glyph(glyph) => glyph.clone(),
- MathFragment::Variant(variant) => {
- GlyphFragment::new(ctx, styles, variant.c, variant.span)
- }
- _ => return,
- };
+ let size = fragment.size();
+
+ let MathFragment::Glyph(ref mut glyph) = fragment else { return };
// Return if we attempt to stretch along an axis which isn't stretchable,
// so that the original fragment isn't modified.
- let Some(stretch_axis) = stretch_axis(ctx, &glyph) else { return };
- let axis = axis.unwrap_or(stretch_axis);
- if axis != stretch_axis {
- return;
- }
-
- let relative_to_size = relative_to.unwrap_or_else(|| fragment.size().get(axis));
-
- let mut variant = stretch_glyph(
- ctx,
- glyph,
- stretch.relative_to(relative_to_size) - short_fall,
- axis,
- );
-
- if axis == Axis::Y {
- variant.align_on_axis(ctx, delimiter_alignment(variant.c));
- }
-
- *fragment = MathFragment::Variant(variant);
-}
-
-/// Return whether the glyph is stretchable and if it is, along which axis it
-/// can be stretched.
-fn stretch_axis(ctx: &mut MathContext, base: &GlyphFragment) -> Option<Axis> {
- let base_id = base.id;
- let vertical = ctx
- .table
- .variants
- .and_then(|variants| variants.vertical_constructions.get(base_id))
- .map(|_| Axis::Y);
- let horizontal = ctx
- .table
- .variants
- .and_then(|variants| variants.horizontal_constructions.get(base_id))
- .map(|_| Axis::X);
-
- match (vertical, horizontal) {
- (vertical, None) => vertical,
- (None, horizontal) => horizontal,
- _ => {
- // As far as we know, there aren't any glyphs that have both
- // vertical and horizontal constructions. So for the time being, we
- // will assume that a glyph cannot have both.
- ctx.engine.sink.warn(warning!(
- base.span,
- "glyph has both vertical and horizontal constructions";
- hint: "this is probably a font bug";
- hint: "please file an issue at https://github.com/typst/typst/issues"
- ));
-
- None
- }
- }
-}
-
-/// Try to stretch a glyph to a desired width or height.
-///
-/// The resulting frame may not have the exact desired width.
-pub fn stretch_glyph(
- ctx: &mut MathContext,
- mut base: GlyphFragment,
- target: Abs,
- axis: Axis,
-) -> VariantFragment {
- // If the base glyph is good enough, use it.
- let advance = match axis {
- Axis::X => base.width,
- Axis::Y => base.height(),
- };
- if target <= advance {
- return base.into_variant();
- }
-
- let mut min_overlap = Abs::zero();
- let construction = ctx
- .table
- .variants
- .and_then(|variants| {
- min_overlap = variants.min_connector_overlap.scaled(ctx, base.font_size);
- match axis {
- Axis::X => variants.horizontal_constructions,
- Axis::Y => variants.vertical_constructions,
- }
- .get(base.id)
- })
- .unwrap_or(GlyphConstruction { assembly: None, variants: LazyArray16::new(&[]) });
-
- // Search for a pre-made variant with a good advance.
- let mut best_id = base.id;
- let mut best_advance = base.width;
- for variant in construction.variants {
- best_id = variant.variant_glyph;
- best_advance = base.font.to_em(variant.advance_measurement).at(base.font_size);
- if target <= best_advance {
- break;
+ let axes = stretch_axes(&glyph.item.font, glyph.base_glyph.id);
+ let stretch_axis = if let Some(axis) = axis {
+ if !axes.get(axis) {
+ return;
}
- }
-
- // This is either good or the best we've got.
- if target <= best_advance || construction.assembly.is_none() {
- base.set_id(ctx, best_id);
- return base.into_variant();
- }
-
- // Assemble from parts.
- let assembly = construction.assembly.unwrap();
- assemble(ctx, base, assembly, min_overlap, target, axis)
-}
-
-/// Assemble a glyph from parts.
-fn assemble(
- ctx: &mut MathContext,
- base: GlyphFragment,
- assembly: GlyphAssembly,
- min_overlap: Abs,
- target: Abs,
- axis: Axis,
-) -> VariantFragment {
- // Determine the number of times the extenders need to be repeated as well
- // as a ratio specifying how much to spread the parts apart
- // (0 = maximal overlap, 1 = minimal overlap).
- let mut full;
- let mut ratio;
- let mut repeat = 0;
- loop {
- full = Abs::zero();
- ratio = 0.0;
-
- let mut parts = parts(assembly, repeat).peekable();
- let mut growable = Abs::zero();
-
- while let Some(part) = parts.next() {
- let mut advance = part.full_advance.scaled(ctx, base.font_size);
- if let Some(next) = parts.peek() {
- let max_overlap = part
- .end_connector_length
- .min(next.start_connector_length)
- .scaled(ctx, base.font_size);
- if max_overlap < min_overlap {
- // This condition happening is indicative of a bug in the
- // font.
- ctx.engine.sink.warn(warning!(
- base.span,
- "glyph has assembly parts with overlap less than minConnectorOverlap";
- hint: "its rendering may appear broken - this is probably a font bug";
- hint: "please file an issue at https://github.com/typst/typst/issues"
- ));
- }
-
- advance -= max_overlap;
- growable += max_overlap - min_overlap;
+ axis
+ } else {
+ match (axes.x, axes.y) {
+ (true, false) => Axis::X,
+ (false, true) => Axis::Y,
+ (false, false) => return,
+ (true, true) => {
+ // As far as we know, there aren't any glyphs that have both
+ // vertical and horizontal constructions. So for the time being, we
+ // will assume that a glyph cannot have both.
+ ctx.engine.sink.warn(warning!(
+ glyph.item.glyphs[0].span.0,
+ "glyph has both vertical and horizontal constructions";
+ hint: "this is probably a font bug";
+ hint: "please file an issue at https://github.com/typst/typst/issues"
+ ));
+ return;
}
-
- full += advance;
- }
-
- if full < target {
- let delta = target - full;
- ratio = (delta / growable).min(1.0);
- full += ratio * growable;
- }
-
- if target <= full || repeat >= MAX_REPEATS {
- break;
- }
-
- repeat += 1;
- }
-
- let mut selected = vec![];
- let mut parts = parts(assembly, repeat).peekable();
- while let Some(part) = parts.next() {
- let mut advance = part.full_advance.scaled(ctx, base.font_size);
- if let Some(next) = parts.peek() {
- let max_overlap = part
- .end_connector_length
- .min(next.start_connector_length)
- .scaled(ctx, base.font_size);
- advance -= max_overlap;
- advance += ratio * (max_overlap - min_overlap);
- }
-
- let mut fragment = base.clone();
- fragment.set_id(ctx, part.glyph_id);
- selected.push((fragment, advance));
- }
-
- let size;
- let baseline;
- match axis {
- Axis::X => {
- let height = base.ascent + base.descent;
- size = Size::new(full, height);
- baseline = base.ascent;
- }
- Axis::Y => {
- let axis = ctx.constants.axis_height().scaled(ctx, base.font_size);
- let width = selected.iter().map(|(f, _)| f.width).max().unwrap_or_default();
- size = Size::new(width, full);
- baseline = full / 2.0 + axis;
}
- }
+ };
- let mut frame = Frame::soft(size);
- let mut offset = Abs::zero();
- frame.set_baseline(baseline);
- frame.modify(&base.modifiers);
+ let relative_to_size = relative_to.unwrap_or_else(|| size.get(stretch_axis));
- for (fragment, advance) in selected {
- let pos = match axis {
- Axis::X => Point::new(offset, frame.baseline() - fragment.ascent),
- Axis::Y => Point::with_y(full - offset - fragment.height()),
- };
- frame.push_frame(pos, fragment.into_frame());
- offset += advance;
- }
+ glyph.stretch(ctx, stretch.relative_to(relative_to_size) - short_fall, stretch_axis);
- let accent_attach = match axis {
- Axis::X => (frame.width() / 2.0, frame.width() / 2.0),
- Axis::Y => base.accent_attach,
- };
-
- VariantFragment {
- c: base.c,
- frame,
- font_size: base.font_size,
- italics_correction: Abs::zero(),
- accent_attach,
- class: base.class,
- math_size: base.math_size,
- span: base.span,
- limits: base.limits,
- mid_stretched: None,
- extended_shape: true,
+ if stretch_axis == Axis::Y {
+ glyph.center_on_axis();
}
}
-
-/// Return an iterator over the assembly's parts with extenders repeated the
-/// specified number of times.
-fn parts(assembly: GlyphAssembly, repeat: usize) -> impl Iterator<Item = GlyphPart> + '_ {
- assembly.parts.into_iter().flat_map(move |part| {
- let count = if part.part_flags.extender() { repeat } else { 1 };
- std::iter::repeat_n(part, count)
- })
-}