summaryrefslogtreecommitdiff
path: root/library/src/math/stretch.rs
blob: 7ba6e5c1ad74c2b8a2f695ca1ca6ab035348f646 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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();
    }

    // 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 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, base.span).to_variant();
    }

    // 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, base.span);
        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());
        offset += advance;
    }

    VariantFragment {
        c: base.c,
        id: None,
        frame,
        style: base.style,
        font_size: base.font_size,
        italics_correction: Abs::zero(),
        class: base.class,
        span: base.span,
    }
}

/// 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)
    })
}