use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
use ttf_parser::LazyArray16;
use crate::diag::SourceResult;
use crate::foundations::{elem, Content, Packed, Smart, StyleChain};
use crate::layout::{Abs, Axis, Frame, Length, Point, Rel, Size, VAlignment};
use crate::math::{
scaled_font_size, GlyphFragment, LayoutMath, MathContext, MathFragment, Scaled,
VariantFragment,
};
use crate::utils::Get;
const MAX_REPEATS: usize = 1024;
#[elem(LayoutMath)]
pub struct StretchElem {
#[required]
pub body: Content,
pub size: Smart<Rel<Length>>,
}
impl LayoutMath for Packed<StretchElem> {
#[typst_macros::time(name = "math.stretch", span = self.span())]
fn layout_math(&self, ctx: &mut MathContext, styles: StyleChain) -> SourceResult<()> {
let mut fragment = ctx.layout_into_fragment(self.body(), styles)?;
stretch_fragment(
ctx,
styles,
&mut fragment,
None,
None,
self.size(styles),
Abs::zero(),
);
ctx.push(fragment);
Ok(())
}
}
pub(super) fn stretch_fragment(
ctx: &mut MathContext,
styles: StyleChain,
fragment: &mut MathFragment,
axis: Option<Axis>,
relative_to: Option<Abs>,
stretch: Smart<Rel<Length>>,
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 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
.unwrap_or(Rel::one())
.at(scaled_font_size(ctx, styles))
.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);
}
pub(super) fn delimiter_alignment(delimiter: char) -> VAlignment {
match delimiter {
'\u{231c}' | '\u{231d}' => VAlignment::Top,
'\u{231e}' | '\u{231f}' => VAlignment::Bottom,
_ => VAlignment::Horizon,
}
}
fn stretch_axis(ctx: &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,
_ => {
panic!("glyph {:?} has both vertical and horizontal constructions", base.c);
}
}
}
impl GlyphFragment {
pub fn stretch_vertical(
self,
ctx: &MathContext,
height: Abs,
short_fall: Abs,
) -> VariantFragment {
stretch_glyph(ctx, self, height, short_fall, Axis::Y)
}
pub fn stretch_horizontal(
self,
ctx: &MathContext,
width: Abs,
short_fall: Abs,
) -> VariantFragment {
stretch_glyph(ctx, self, width, short_fall, Axis::X)
}
}
fn stretch_glyph(
ctx: &MathContext,
mut base: GlyphFragment,
target: Abs,
short_fall: Abs,
axis: Axis,
) -> VariantFragment {
let advance = match axis {
Axis::X => base.width,
Axis::Y => base.height(),
};
let short_target = target - short_fall;
if short_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(&[]) });
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 short_target <= best_advance {
break;
}
}
if short_target <= best_advance || construction.assembly.is_none() {
base.set_id(ctx, best_id);
return base.into_variant();
}
let assembly = construction.assembly.unwrap();
assemble(ctx, base, assembly, min_overlap, target, axis)
}
fn assemble(
ctx: &MathContext,
base: GlyphFragment,
assembly: GlyphAssembly,
min_overlap: Abs,
target: Abs,
axis: Axis,
) -> VariantFragment {
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);
advance -= max_overlap;
growable += max_overlap - min_overlap;
}
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.post_process_raw(base.dests, base.hidden);
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;
}
let accent_attach = match axis {
Axis::X => frame.width() / 2.0,
Axis::Y => base.accent_attach,
};
VariantFragment {
c: base.c,
id: None,
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,
}
}
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(part).take(count)
})
}