mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Glyph stretching
This commit is contained in:
parent
83b6858146
commit
a50cb58823
@ -9,6 +9,7 @@ mod group;
|
|||||||
mod matrix;
|
mod matrix;
|
||||||
mod root;
|
mod root;
|
||||||
mod script;
|
mod script;
|
||||||
|
mod stretch;
|
||||||
mod style;
|
mod style;
|
||||||
|
|
||||||
pub use self::accent::*;
|
pub use self::accent::*;
|
||||||
|
191
library/src/math/stretch.rs
Normal file
191
library/src/math/stretch.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
use ttf_parser::math::{GlyphAssembly, GlyphConstruction, GlyphPart};
|
||||||
|
use ttf_parser::LazyArray16;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Maximum number of times extenders can be repeated.
|
||||||
|
const MAX_REPEATS: usize = 1024;
|
||||||
|
|
||||||
|
impl GlyphFragment {
|
||||||
|
/// Try to stretch a glyph to a desired height.
|
||||||
|
pub fn stretch_vertical(
|
||||||
|
self,
|
||||||
|
ctx: &MathContext,
|
||||||
|
height: Abs,
|
||||||
|
short_fall: Abs,
|
||||||
|
) -> VariantFragment {
|
||||||
|
stretch_glyph(ctx, self, height, short_fall, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to stretch a glyph to a desired width.
|
||||||
|
pub fn stretch_horizontal(
|
||||||
|
self,
|
||||||
|
ctx: &MathContext,
|
||||||
|
width: Abs,
|
||||||
|
short_fall: Abs,
|
||||||
|
) -> VariantFragment {
|
||||||
|
stretch_glyph(ctx, self, width, short_fall, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to stretch a glyph to a desired width or height.
|
||||||
|
///
|
||||||
|
/// The resulting frame may not have the exact desired width.
|
||||||
|
fn stretch_glyph(
|
||||||
|
ctx: &MathContext,
|
||||||
|
base: GlyphFragment,
|
||||||
|
target: Abs,
|
||||||
|
short_fall: Abs,
|
||||||
|
horizontal: bool,
|
||||||
|
) -> VariantFragment {
|
||||||
|
let short_target = target - short_fall;
|
||||||
|
let mut min_overlap = Abs::zero();
|
||||||
|
let construction = ctx
|
||||||
|
.table
|
||||||
|
.variants
|
||||||
|
.and_then(|variants| {
|
||||||
|
min_overlap = variants.min_connector_overlap.scaled(ctx);
|
||||||
|
if horizontal {
|
||||||
|
variants.horizontal_constructions
|
||||||
|
} else {
|
||||||
|
variants.vertical_constructions
|
||||||
|
}
|
||||||
|
.get(base.id)
|
||||||
|
})
|
||||||
|
.unwrap_or(GlyphConstruction { assembly: None, variants: LazyArray16::new(&[]) });
|
||||||
|
|
||||||
|
// If the base glyph is good enough, use it.
|
||||||
|
let advance = if horizontal { base.width } else { base.height() };
|
||||||
|
if short_target <= advance {
|
||||||
|
return base.to_variant(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = variant.advance_measurement.scaled(ctx);
|
||||||
|
if short_target <= best_advance {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is either good or the best we've got.
|
||||||
|
if short_target <= best_advance || construction.assembly.is_none() {
|
||||||
|
return GlyphFragment::with_id(ctx, base.c, best_id).to_variant(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble from parts.
|
||||||
|
let assembly = construction.assembly.unwrap();
|
||||||
|
assemble(ctx, base, assembly, min_overlap, target, horizontal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assemble a glyph from parts.
|
||||||
|
fn assemble(
|
||||||
|
ctx: &MathContext,
|
||||||
|
base: GlyphFragment,
|
||||||
|
assembly: GlyphAssembly,
|
||||||
|
min_overlap: Abs,
|
||||||
|
target: Abs,
|
||||||
|
horizontal: bool,
|
||||||
|
) -> 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);
|
||||||
|
if let Some(next) = parts.peek() {
|
||||||
|
let max_overlap = part
|
||||||
|
.end_connector_length
|
||||||
|
.min(next.start_connector_length)
|
||||||
|
.scaled(ctx);
|
||||||
|
|
||||||
|
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);
|
||||||
|
if let Some(next) = parts.peek() {
|
||||||
|
let max_overlap =
|
||||||
|
part.end_connector_length.min(next.start_connector_length).scaled(ctx);
|
||||||
|
advance -= max_overlap;
|
||||||
|
advance += ratio * (max_overlap - min_overlap);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fragment = GlyphFragment::with_id(ctx, base.c, part.glyph_id);
|
||||||
|
selected.push((fragment, advance));
|
||||||
|
}
|
||||||
|
|
||||||
|
let size;
|
||||||
|
let baseline;
|
||||||
|
if horizontal {
|
||||||
|
let height = base.ascent + base.descent;
|
||||||
|
size = Size::new(full, height);
|
||||||
|
baseline = base.ascent;
|
||||||
|
} else {
|
||||||
|
let axis = scaled!(ctx, axis_height);
|
||||||
|
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::new(size);
|
||||||
|
let mut offset = Abs::zero();
|
||||||
|
frame.set_baseline(baseline);
|
||||||
|
|
||||||
|
for (fragment, advance) in selected {
|
||||||
|
let pos = if horizontal {
|
||||||
|
Point::new(offset, frame.baseline() - fragment.ascent)
|
||||||
|
} else {
|
||||||
|
Point::with_y(full - offset - fragment.height())
|
||||||
|
};
|
||||||
|
frame.push_frame(pos, fragment.to_frame(ctx));
|
||||||
|
offset += advance;
|
||||||
|
}
|
||||||
|
|
||||||
|
VariantFragment {
|
||||||
|
c: base.c,
|
||||||
|
id: None,
|
||||||
|
frame,
|
||||||
|
italics_correction: Abs::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(part).take(count)
|
||||||
|
})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user