Skip to main content

read_fonts/model/glyph/outline/
glyf.rs

1//! Outlines from the `glyf` table.
2
3use crate::model::Error;
4use crate::{
5    model::{
6        glyph::outline::{DrawOptions, DrawnMetrics, PathSink, UnscaledOutlineSink},
7        Font,
8    },
9    tables::{
10        glyf::Glyf,
11        glyf::{
12            outline::{
13                Buffers, OutlineContext, OutlinePlan, Scale, Scale26Dot6, ScaleF32, Unscaled,
14            },
15            PointCoord, PHANTOM_POINT_COUNT,
16        },
17        gvar::{GlyphVariationData, SelectedGvar},
18        hmtx::GlyphMetricRecords,
19        loca::{Loca, LocaGlyph},
20    },
21    types::{F2Dot14, Fixed, GlyphId, Point},
22};
23
24/// The arithmetic used to scale an outline.
25///
26/// Drawing uses fixed point. Floating point is used for measurements that
27/// match HarfBuzz, which keeps every fraction.
28#[derive(Copy, Clone, PartialEq, Eq, Debug)]
29pub(crate) enum Precision {
30    /// 26.6 fixed point, as used by FreeType.
31    ///
32    /// Values are rounded to 1/64 of a pixel during scaling. Without a size,
33    /// this preserves implied midpoints that fall on half units.
34    Fixed,
35    /// Single precision floating point.
36    ///
37    /// This keeps the fraction of every coordinate and variation delta.
38    Float,
39}
40
41/// A TrueType outline from a [`Font`], ready to load.
42///
43/// Contains the [`OutlinePlan`] used for both sizing and loading, so the
44/// glyph is only walked once no matter how many times it is drawn. The font
45/// is the [`OutlineContext`] used for loading. To hint, pass a
46/// [`Hinter`](crate::tables::glyf::outline::Hinter) to [`OutlinePlan::load`]
47/// with the same context.
48#[derive(Clone)]
49pub struct GlyfGlyph<'a> {
50    context: GlyfContext<'a>,
51    plan: OutlinePlan<'a>,
52}
53
54impl<'a> GlyfGlyph<'a> {
55    pub(crate) fn new(context: GlyfContext<'a>, glyph: GlyphId) -> Option<Self> {
56        let plan = OutlinePlan::new(&context, glyph).ok()?;
57        Some(Self { context, plan })
58    }
59
60    /// Returns the glyph identifier.
61    pub fn glyph(&self) -> GlyphId {
62        self.plan.glyph
63    }
64
65    /// Returns the font, which is the context used to load the plan.
66    pub fn font(&self) -> &'a Font {
67        self.context.font
68    }
69
70    /// Returns the plan that describes the outline's structure and memory
71    /// requirements.
72    pub fn plan(&self) -> &OutlinePlan<'a> {
73        &self.plan
74    }
75
76    /// Returns the size in bytes of the working memory needed for drawing.
77    pub fn required_memory_size(&self) -> usize {
78        plan_buffer_size(&self.plan)
79    }
80
81    pub(crate) fn draw(
82        &self,
83        options: &DrawOptions,
84        precision: Precision,
85        buf: &mut [u8],
86        sink: &mut dyn PathSink,
87    ) -> Result<DrawnMetrics, Error> {
88        draw_plan(&self.plan, &self.context, options, precision, buf, sink)
89    }
90
91    /// Returns the size in bytes of the working memory needed to load the
92    /// outline in font units.
93    pub fn required_memory_size_unscaled(&self) -> usize {
94        self.plan
95            .buffer_lengths::<Unscaled>()
96            .packed_len::<Unscaled>()
97    }
98
99    /// Loads the outline in font units and sends its points to `sink`.
100    pub(crate) fn draw_unscaled(
101        &self,
102        buf: &mut [u8],
103        sink: &mut dyn UnscaledOutlineSink,
104    ) -> Result<DrawnMetrics, Error> {
105        let lengths = self.plan.buffer_lengths::<Unscaled>();
106        let buffers = Buffers::from_bytes(buf, &lengths)?;
107        let outline = self.plan.load(&self.context, &Unscaled, buffers, None)?;
108        let (points, flags) = (outline.points(), outline.flags());
109        let mut start = 0;
110        for end in outline.contours() {
111            let end = *end as usize;
112            if end < start || end >= points.len() {
113                return Err(Error::Malformed);
114            }
115            sink.start_contour();
116            for (point, flags) in points[start..=end].iter().zip(&flags[start..=end]) {
117                sink.point(point.x, point.y, flags.without_markers());
118            }
119            sink.end_contour();
120            start = end + 1;
121        }
122        Ok(DrawnMetrics {
123            h_advance: Some(outline.adjusted_advance_width() as f32),
124            x_bearing: Some(outline.adjusted_lsb() as f32),
125            has_overlaps: self.plan.has_overlaps,
126            ..Default::default()
127        })
128    }
129}
130
131/// Returns the size in bytes of the working memory needed to draw `plan`.
132///
133/// The size is computed for fixed point, which has the larger layout
134/// because it keeps a copy of the unscaled points. A floating point draw
135/// fits in the same buffer.
136pub(crate) fn plan_buffer_size(plan: &OutlinePlan<'_>) -> usize {
137    plan.buffer_lengths::<Scale26Dot6>()
138        .packed_len::<Scale26Dot6>()
139}
140
141/// Loads `plan` into `buf` with the given options and precision, and draws
142/// it into `sink`.
143pub(crate) fn draw_plan<'a>(
144    plan: &OutlinePlan<'a>,
145    context: &'a dyn OutlineContext<'a>,
146    options: &DrawOptions,
147    precision: Precision,
148    buf: &mut [u8],
149    sink: &mut dyn PathSink,
150) -> Result<DrawnMetrics, Error> {
151    let units_per_em = context.units_per_em();
152    match precision {
153        Precision::Fixed => load_and_draw(
154            plan,
155            context,
156            &Scale26Dot6::new(options.ppem, units_per_em),
157            options,
158            buf,
159            sink,
160        ),
161        Precision::Float => load_and_draw(
162            plan,
163            context,
164            &ScaleF32::new(options.ppem, units_per_em),
165            options,
166            buf,
167            sink,
168        ),
169    }
170}
171
172fn load_and_draw<'a, S: Scale>(
173    plan: &OutlinePlan<'a>,
174    context: &'a dyn OutlineContext<'a>,
175    scale: &S,
176    options: &DrawOptions,
177    buf: &mut [u8],
178    mut sink: &mut dyn PathSink,
179) -> Result<DrawnMetrics, Error> {
180    let lengths = plan.buffer_lengths::<S>();
181    let buffers = Buffers::from_bytes(buf, &lengths)?;
182    let outline = plan.load(context, scale, buffers, None)?;
183    let metrics = DrawnMetrics {
184        h_advance: Some(outline.adjusted_advance_width().to_f32()),
185        x_bearing: Some(outline.adjusted_lsb().to_f32()),
186        has_overlaps: plan.has_overlaps,
187        ..Default::default()
188    };
189    if !outline.to_path(options.contour_start, &mut sink) {
190        return Err(Error::Malformed);
191    }
192    Ok(metrics)
193}
194
195/// The tables, location and scalars a `glyf` outline loads from, looked up
196/// once for a font.
197///
198/// Loading asks for these for every glyph, so a draw reads them from here
199/// rather than from the font's caches each time.
200#[derive(Clone, Copy)]
201pub(crate) struct GlyfContext<'a> {
202    pub(crate) font: &'a Font,
203    tables: &'a (Glyf<'a>, Loca<'a>),
204    gvar: Option<&'a SelectedGvar<'a>>,
205    h_metrics: &'a GlyphMetricRecords<'a>,
206    v_metrics: &'a GlyphMetricRecords<'a>,
207    coords: &'a [F2Dot14],
208    gvar_scalars: &'a [Fixed],
209    /// The ascender and descender that place vertical phantom points for a
210    /// font with no vertical metrics.
211    h_line: (i32, i32),
212    num_glyphs: u32,
213    units_per_em: u16,
214    has_hvar: bool,
215}
216
217impl<'a> GlyfContext<'a> {
218    /// Returns the context for `font`, or `None` if it has no `glyf` table.
219    pub(crate) fn new(font: &'a Font) -> Option<Self> {
220        let tables = font.selected_glyf_loca()?;
221        let v_metrics = font.v_metric_records();
222        let h_line = if v_metrics.is_empty() {
223            OutlineContext::h_line_metrics(&font)
224        } else {
225            (0, 0)
226        };
227        Some(Self {
228            font,
229            tables,
230            gvar: font.selected_gvar(),
231            h_metrics: font.h_metric_records(),
232            v_metrics,
233            coords: font.normalized_coords(),
234            gvar_scalars: font.gvar_scalars(),
235            h_line,
236            num_glyphs: font.num_glyphs(),
237            units_per_em: font.units_per_em(),
238            has_hvar: font.hvar().is_some(),
239        })
240    }
241
242    /// Returns the bearing and advance in `records` for `glyph`.
243    fn metrics(&self, records: &GlyphMetricRecords<'_>, glyph: GlyphId) -> (i32, i32) {
244        (
245            records
246                .stated_side_bearing(glyph)
247                .unwrap_or_default()
248                .to_i32(),
249            records.stated_advance(self.num_glyphs, glyph).to_i32(),
250        )
251    }
252}
253
254impl<'a> OutlineContext<'a> for GlyfContext<'a> {
255    fn glyph(&self, glyph: GlyphId) -> Option<LocaGlyph<'a>> {
256        let (glyf, loca) = self.tables;
257        loca.get(glyph, glyf)
258    }
259
260    fn glyph_variation_data(&self, glyph: GlyphId) -> Option<GlyphVariationData<'a>> {
261        self.gvar?.glyph_variation_data(glyph).ok()?
262    }
263
264    fn has_gvar(&self) -> bool {
265        self.gvar.is_some()
266    }
267
268    fn phantom_point_deltas(&self, glyph: GlyphId) -> Option<[Point<Fixed>; PHANTOM_POINT_COUNT]> {
269        let (glyf, loca) = self.tables;
270        self.gvar?.phantom_point_deltas_with_scalars(
271            glyf,
272            loca,
273            self.coords,
274            self.gvar_scalars,
275            glyph,
276        )
277    }
278
279    fn gvar_scalars(&self) -> &[Fixed] {
280        self.gvar_scalars
281    }
282
283    fn coords(&self) -> &[F2Dot14] {
284        self.coords
285    }
286
287    fn units_per_em(&self) -> u16 {
288        self.units_per_em
289    }
290
291    fn h_metrics(&self, glyph: GlyphId) -> (i32, i32) {
292        self.metrics(self.h_metrics, glyph)
293    }
294
295    fn v_metrics(&self, glyph: GlyphId) -> Option<(i32, i32)> {
296        (!self.v_metrics.is_empty()).then(|| self.metrics(self.v_metrics, glyph))
297    }
298
299    fn h_line_metrics(&self) -> (i32, i32) {
300        self.h_line
301    }
302
303    fn has_hvar(&self) -> bool {
304        self.has_hvar
305    }
306}
307
308/// A font read at a location other than its own.
309///
310/// A `VARC` component defines its own coordinates, so its glyph is loaded
311/// through this type rather than the font. The font's cached scalars only
312/// apply to its own location, so this type does not provide them.
313#[derive(Clone, Copy)]
314pub(crate) struct FontAt<'a> {
315    pub(crate) context: GlyfContext<'a>,
316    pub(crate) coords: &'a [F2Dot14],
317}
318
319impl<'a> OutlineContext<'a> for FontAt<'a> {
320    fn glyph(&self, glyph: GlyphId) -> Option<LocaGlyph<'a>> {
321        self.context.glyph(glyph)
322    }
323
324    fn glyph_variation_data(&self, glyph: GlyphId) -> Option<GlyphVariationData<'a>> {
325        self.context.glyph_variation_data(glyph)
326    }
327
328    fn has_gvar(&self) -> bool {
329        self.context.has_gvar()
330    }
331
332    fn phantom_point_deltas(&self, glyph: GlyphId) -> Option<[Point<Fixed>; PHANTOM_POINT_COUNT]> {
333        let (glyf, loca) = self.context.tables;
334        self.context
335            .gvar?
336            .phantom_point_deltas_with_scalars(glyf, loca, self.coords, &[], glyph)
337    }
338
339    fn coords(&self) -> &[F2Dot14] {
340        self.coords
341    }
342
343    fn units_per_em(&self) -> u16 {
344        self.context.units_per_em
345    }
346
347    fn h_metrics(&self, glyph: GlyphId) -> (i32, i32) {
348        self.context.h_metrics(glyph)
349    }
350
351    fn v_metrics(&self, glyph: GlyphId) -> Option<(i32, i32)> {
352        self.context.v_metrics(glyph)
353    }
354
355    fn h_line_metrics(&self) -> (i32, i32) {
356        self.context.h_line
357    }
358
359    fn has_hvar(&self) -> bool {
360        self.context.has_hvar
361    }
362}