Skip to main content

harfrust/hb/
face.rs

1use alloc::boxed::Box;
2use read_fonts::types::{F2Dot14, Fixed};
3use read_fonts::{FontRef, TableProvider};
4use smallvec::SmallVec;
5
6// libm used for f32::floor() and f32::ceil()
7#[cfg(not(feature = "std"))]
8#[allow(unused_imports)]
9use core_maths::CoreFloat as _;
10
11use super::aat::AatTables;
12use super::charmap::{cache_t as cmap_cache_t, Charmap};
13use super::font_funcs::FontFuncsDispatch;
14use super::glyph_metrics::GlyphMetrics;
15use super::glyph_names::GlyphNames;
16use super::ot::{LayoutTable, OtCache, OtTables};
17use super::ot_layout::TableIndex;
18use super::ot_shape::OtShapeContext;
19use crate::hb::aat::AatCache;
20use crate::hb::tables::TableRanges;
21use crate::{script, Feature, GlyphBuffer, NormalizedCoord, ShapePlan, UnicodeBuffer, Variation};
22
23pub use super::font_funcs::{AdvanceWidthBatch, BuiltinFontFuncs, FontFuncs, RawAdvanceWidthBatch};
24
25/// Data required for shaping with a single font.
26pub struct ShaperData {
27    table_ranges: TableRanges,
28    ot_cache: OtCache,
29    aat_cache: AatCache,
30    cmap_cache: cmap_cache_t,
31    // True if a font has both trak and STAT tables.
32    apply_trak: bool,
33}
34
35impl ShaperData {
36    /// Creates new cached shaper data for the given font.
37    pub fn new(font: &FontRef) -> Self {
38        let ot_cache = OtCache::new(font);
39        let aat_cache = AatCache::new(font);
40        let table_ranges = TableRanges::new(font);
41        let cmap_cache = cmap_cache_t::new();
42        let apply_trak = font.trak().is_ok() && font.stat().is_ok();
43        Self {
44            table_ranges,
45            ot_cache,
46            aat_cache,
47            cmap_cache,
48            apply_trak,
49        }
50    }
51
52    fn from_font(font: &crate::font::Font) -> Self {
53        let tables = font.tables();
54        let ot_cache = OtCache::new(&tables);
55        let aat_cache = AatCache::new(&tables);
56        let table_ranges = TableRanges::from_tables(&tables);
57        let cmap_cache = cmap_cache_t::new();
58        let apply_trak = tables.trak_data().is_some() && tables.stat_data().is_some();
59        Self {
60            table_ranges,
61            ot_cache,
62            aat_cache,
63            cmap_cache,
64            apply_trak,
65        }
66    }
67
68    /// Returns a builder for constructing a new shaper with the given
69    /// font.
70    pub fn shaper<'a>(&'a self, font: &FontRef<'a>) -> ShaperBuilder<'a> {
71        ShaperBuilder {
72            data: self,
73            font: font.clone(),
74            instance: None,
75        }
76    }
77}
78
79// Maximum number of coordinates to store inline before spilling to the
80// heap.
81//
82// Any value between 5 and 11 yields a SmallVec footprint of 32 bytes.
83const MAX_INLINE_COORDS: usize = 11;
84
85/// An instance of a variable font.
86#[derive(Clone, Default, Debug)]
87pub struct ShaperInstance {
88    coords: SmallVec<[F2Dot14; MAX_INLINE_COORDS]>,
89    pub(crate) feature_variations: [Option<u32>; 2],
90    // TODO: this is a good place to hang variation specific caches
91}
92
93impl ShaperInstance {
94    /// Creates a new shaper instance for the given font from the specified
95    /// list of variation settings.
96    ///
97    /// The setting values are in user space and the order is insignificant.
98    pub fn from_variations<V>(font: &FontRef, variations: V) -> Self
99    where
100        V: IntoIterator,
101        V::Item: Into<Variation>,
102    {
103        let mut this = Self::default();
104        this.set_variations(font, variations);
105        this
106    }
107
108    /// Creates a new shaper instance for the given font from the specified
109    /// set of normalized coordinates.
110    ///
111    /// The sequence of coordinates is expected to be in axis order.
112    pub fn from_coords(font: &FontRef, coords: impl IntoIterator<Item = NormalizedCoord>) -> Self {
113        let mut this = Self::default();
114        this.set_coords(font, coords);
115        this
116    }
117
118    /// Creates a new shaper instance for the given font using the variation
119    /// position from the named instance at the specified index.
120    pub fn from_named_instance(font: &FontRef, index: usize) -> Self {
121        let mut this = Self::default();
122        this.set_named_instance(font, index);
123        this
124    }
125
126    /// Returns the underlying set of normalized coordinates.
127    pub fn coords(&self) -> &[F2Dot14] {
128        &self.coords
129    }
130
131    /// Resets the instance for the given font and variation settings.
132    pub fn set_variations<V>(&mut self, font: &FontRef, variations: V)
133    where
134        V: IntoIterator,
135        V::Item: Into<Variation>,
136    {
137        self.coords.clear();
138        if let Ok(fvar) = font.fvar() {
139            self.coords
140                .resize(fvar.axis_count() as usize, F2Dot14::ZERO);
141            fvar.user_to_normalized(
142                font.avar().ok().as_ref(),
143                variations
144                    .into_iter()
145                    .map(Into::into)
146                    .map(|var| (var.tag, Fixed::from_f64(var.value as _))),
147                self.coords.as_mut_slice(),
148            );
149            self.check_default();
150            self.set_feature_variations(font);
151        }
152    }
153
154    /// Resets the instance for the given font and normalized coordinates.
155    pub fn set_coords(&mut self, font: &FontRef, coords: impl IntoIterator<Item = F2Dot14>) {
156        self.coords.clear();
157        if let Ok(fvar) = font.fvar() {
158            let count = fvar.axis_count() as usize;
159            self.coords.reserve(count);
160            self.coords.extend(coords.into_iter().take(count));
161            self.check_default();
162            self.set_feature_variations(font);
163        }
164    }
165
166    /// Resets the instance for the given font using the variation
167    /// position from the named instance at the specified index.
168    pub fn set_named_instance(&mut self, font: &FontRef, index: usize) {
169        self.coords.clear();
170        if let Ok(fvar) = font.fvar() {
171            if let Ok((axes, instance)) = fvar
172                .axis_instance_arrays()
173                .and_then(|arrays| Ok((arrays.axes(), arrays.instances().get(index)?)))
174            {
175                self.set_variations(
176                    font,
177                    axes.iter()
178                        .zip(instance.coordinates)
179                        .map(|(axis, coord)| (axis.axis_tag(), coord.get().to_f32())),
180                );
181            }
182        }
183    }
184
185    fn set_feature_variations(&mut self, font: &FontRef) {
186        self.feature_variations = [None; 2];
187        if self.coords.is_empty() {
188            return;
189        }
190        self.feature_variations[0] = font
191            .gsub()
192            .ok()
193            .and_then(|t| LayoutTable::Gsub(t).feature_variation_index(&self.coords));
194        self.feature_variations[1] = font
195            .gpos()
196            .ok()
197            .and_then(|t| LayoutTable::Gpos(t).feature_variation_index(&self.coords));
198    }
199
200    fn check_default(&mut self) {
201        if self.coords.iter().all(|coord| *coord == F2Dot14::ZERO) {
202            self.coords.clear();
203        }
204    }
205}
206
207/// Builder type for constructing a [`Shaper`](crate::Shaper).
208pub struct ShaperBuilder<'a> {
209    data: &'a ShaperData,
210    font: FontRef<'a>,
211    instance: Option<&'a ShaperInstance>,
212}
213
214impl<'a> ShaperBuilder<'a> {
215    /// Sets an optional instance for the shaper.
216    ///
217    /// This defines the variable font configuration.
218    pub fn instance(mut self, instance: Option<&'a ShaperInstance>) -> Self {
219        self.instance = instance;
220        self
221    }
222
223    /// Builds the shaper with the current configuration.
224    pub fn build(self) -> crate::Shaper<'a> {
225        let font = self.font;
226        let units_per_em = self.data.table_ranges.units_per_em;
227        let charmap = Charmap::new(&font, &self.data.table_ranges);
228        let glyph_metrics = GlyphMetrics::new(&font, &self.data.table_ranges);
229        let (coords, feature_variations) = self
230            .instance
231            .map(|instance| (instance.coords(), instance.feature_variations))
232            .unwrap_or_default();
233        let ot_tables = OtTables::new(
234            &font,
235            &self.data.ot_cache,
236            &self.data.table_ranges,
237            coords,
238            feature_variations,
239        );
240        let aat_tables = AatTables::new(&font, &self.data.aat_cache, &self.data.table_ranges);
241        let font = FontKind::FontRef(FontRefData {
242            font,
243            glyph_metrics,
244            charmap,
245        });
246        hb_font_t {
247            font,
248            units_per_em,
249            cmap_cache: &self.data.cmap_cache,
250            ot_tables,
251            aat_tables,
252            apply_trak: self.data.apply_trak,
253        }
254    }
255}
256
257/// Options which can be used to configure shaping.
258#[derive(Default)]
259pub struct ShapeOptions<'a> {
260    plan: Option<&'a ShapePlan>,
261    scale: Option<(i32, i32)>,
262    point_size: Option<f32>,
263    features: &'a [Feature],
264    font_funcs: Option<&'a mut (dyn FontFuncs + 'a)>,
265}
266
267impl<'a> ShapeOptions<'a> {
268    /// Creates a default set of shape options ready for configuration.
269    pub fn new() -> Self {
270        Self::default()
271    }
272
273    /// Sets the plan to use for shaping.
274    ///
275    /// The shape plan must be compatible with the properties of the buffer
276    /// passed to shaping.
277    pub fn plan(mut self, plan: Option<&'a ShapePlan>) -> Self {
278        self.plan = plan;
279        self
280    }
281
282    /// Sets the scale factor to use during shaping.
283    ///
284    /// The font scale is a number related to, but not the same as, font size.
285    /// Typically the client establishes a scale factor to be used between the
286    /// two. For example, 64, or 256, which would be the fractional-precision
287    /// part of the font scale. This is necessary because position and metric
288    /// values are integer types and you need to leave room for fractional
289    /// values in there.
290    ///
291    /// For example, to set the font size to 20, with 64 levels of fractional
292    /// precision you would call provide a scale of `20 * 64`.
293    ///
294    /// In the example above, even what font size 20 means is up to you. It
295    /// might be 20 pixels, or 20 points, or 20 millimeters. HarfRust does
296    /// not care about that.
297    ///
298    /// The choice of scale is yours but needs to be consistent between what
299    /// you set here, and what you expect as output as well as the values
300    /// returned by [font functions](FontFuncs).
301    ///
302    /// This defaults to `None` which means that no scale is applied-- positions
303    /// and metrics will be returned in font units.
304    pub fn scale(mut self, scale: Option<i32>) -> Self {
305        self.scale = scale.map(|s| (s, s));
306        self
307    }
308
309    /// Sets separate x- and y-scale factors to use during shaping.
310    ///
311    /// Each axis uses the same semantics as [`scale`](Self::scale).
312    pub fn scale_separate(mut self, scale: Option<(i32, i32)>) -> Self {
313        self.scale = scale;
314        self
315    }
316
317    /// Sets the size used for application of the tracking table.
318    pub fn point_size(mut self, point_size: Option<f32>) -> Self {
319        self.point_size = point_size;
320        self
321    }
322
323    /// Sets the features to apply during shaping.
324    pub fn features(mut self, features: &'a [Feature]) -> Self {
325        self.features = features;
326        self
327    }
328
329    /// Sets optional font functions used for shaping.
330    pub fn font_funcs(mut self, funcs: Option<&'a mut (dyn FontFuncs + 'a)>) -> Self {
331        self.font_funcs = funcs;
332        self
333    }
334}
335
336#[derive(Copy, Clone)]
337pub(crate) struct Scale {
338    x_mult: i64,
339    y_mult: i64,
340    x_multf: f32,
341    y_multf: f32,
342}
343
344impl Default for Scale {
345    fn default() -> Self {
346        Self {
347            x_mult: 1 << 16,
348            y_mult: 1 << 16,
349            x_multf: 1.0,
350            y_multf: 1.0,
351        }
352    }
353}
354
355// Various conversions between f32 and i32
356#[allow(clippy::cast_precision_loss)]
357impl Scale {
358    pub(crate) fn new(scale: Option<(i32, i32)>, upem: i32) -> Self {
359        let (Some((x_scale, y_scale)), true) = (scale, upem != 0) else {
360            // When scale is not configured, or upem is zero, return results
361            // in font units.
362            return Self::default();
363        };
364        let [x_mult, y_mult] = [x_scale, y_scale].map(|s| Self::mult_from_scale(s, upem));
365        let upem = upem as f32;
366        Self {
367            x_mult,
368            y_mult,
369            x_multf: x_scale as f32 / upem,
370            y_multf: y_scale as f32 / upem,
371        }
372    }
373
374    #[inline(always)]
375    pub(crate) fn scale_x(&self, x: i32) -> i32 {
376        Self::scale_by_mult(x, self.x_mult)
377    }
378
379    #[inline(always)]
380    pub(crate) fn scale_y(&self, y: i32) -> i32 {
381        Self::scale_by_mult(y, self.y_mult)
382    }
383
384    /// Scales a fractional (font-unit) value, matching HarfBuzz's `em_scalef`
385    /// (`roundf(v * scale / upem)`).
386    #[inline(always)]
387    pub(crate) fn scale_x_f(&self, x: f32) -> i32 {
388        (x * self.x_multf).round() as i32
389    }
390
391    #[inline(always)]
392    pub(crate) fn scale_y_f(&self, y: f32) -> i32 {
393        (y * self.y_multf).round() as i32
394    }
395
396    /// Scales glyph extents using HarfBuzz's corner-based float arithmetic:
397    /// floor the origin corners and ceil the far corners before deriving the
398    /// final width/height.
399    /// hb_font_t::scale_glyph_extents: <https://github.com/harfbuzz/harfbuzz/blob/88adc6437ef561486a5adf1822410297ef4a852b/src/hb-font.hh#L201>'
400    pub(crate) fn scale_extents(&self, mut extents: GlyphExtents) -> GlyphExtents {
401        let x1 = extents.x_bearing as f32 * self.x_multf;
402        let y1 = extents.y_bearing as f32 * self.y_multf;
403        let x2 = (extents.x_bearing + extents.width) as f32 * self.x_multf;
404        let y2 = (extents.y_bearing + extents.height) as f32 * self.y_multf;
405        extents.x_bearing = x1.floor() as i32;
406        extents.y_bearing = y1.floor() as i32;
407        extents.width = x2.ceil() as i32 - extents.x_bearing;
408        extents.height = y2.ceil() as i32 - extents.y_bearing;
409        extents
410    }
411
412    #[inline(always)]
413    fn mult_from_scale(scale: i32, upem: i32) -> i64 {
414        if scale < 0 {
415            -((-(scale as i64)) << 16) / upem as i64
416        } else {
417            ((scale as i64) << 16) / upem as i64
418        }
419    }
420
421    #[inline(always)]
422    fn scale_by_mult(value: i32, mult: i64) -> i32 {
423        (((value as i64) * mult + 32768) >> 16) as i32
424    }
425}
426
427/// Shapes the buffer content using provided options.
428///
429/// Consumes the buffer. You can then run [`GlyphBuffer::clear`] to get the [`UnicodeBuffer`] back
430/// without allocating a new one.
431///
432/// If a plan is provided, it is up to the caller to ensure that the shape plan matches the
433/// properties of the provided buffer, otherwise the shaping result will likely be incorrect.
434///
435/// # Panics
436///
437/// Will panic when debugging assertions are enabled if the buffer and plan have mismatched
438/// properties.
439pub fn shape(
440    font: &crate::font::FontInstance,
441    mut buffer: UnicodeBuffer,
442    mut options: ShapeOptions<'_>,
443) -> GlyphBuffer {
444    let Some(hb_font) = hb_font_t::from_font(font) else {
445        buffer.clear();
446        return GlyphBuffer(buffer.0);
447    };
448    // If the user didn't request an explicit scale but the font instance
449    // has a size, set the scale to that size with 16 fractional bits.
450    if options.scale.is_none() {
451        if let Some(ppem) = font.size() {
452            options = options.scale(Some((ppem * 65536.0) as i32));
453        }
454    }
455    hb_font.shape(buffer, options)
456}
457
458// This will go away completely when we drop the old API.
459#[allow(clippy::large_enum_variant)]
460#[derive(Clone)]
461pub enum FontKind<'a> {
462    FontRef(FontRefData<'a>),
463    FontInstance(&'a crate::font::FontInstance, BasicFontMetrics),
464}
465
466#[derive(Clone)]
467pub struct FontRefData<'a> {
468    pub(crate) font: FontRef<'a>,
469    pub(crate) glyph_metrics: GlyphMetrics<'a>,
470    pub(crate) charmap: Charmap<'a>,
471}
472
473#[derive(Copy, Clone, Debug)]
474pub struct BasicFontMetrics {
475    pub units_per_em: u16,
476    pub num_glyphs: u32,
477    pub ascent: i16,
478    pub descent: i16,
479}
480
481/// A configured shaper.
482#[derive(Clone)]
483pub struct hb_font_t<'a> {
484    pub(crate) font: FontKind<'a>,
485    pub(crate) units_per_em: u16,
486    pub(crate) cmap_cache: &'a cmap_cache_t,
487    pub(crate) ot_tables: OtTables<'a>,
488    pub(crate) aat_tables: AatTables<'a>,
489    pub(crate) apply_trak: bool,
490}
491
492impl<'a> crate::Shaper<'a> {
493    pub(crate) fn from_font(font: &'a crate::font::FontInstance) -> Option<Self> {
494        let data = crate::font::_font_interop::_get_or_init_shaping_data(font, || {
495            Box::new(ShaperData::from_font(font))
496        })
497        .downcast_ref::<ShaperData>()?;
498        let metrics = BasicFontMetrics {
499            units_per_em: data.table_ranges.units_per_em,
500            num_glyphs: data.table_ranges.num_glyphs,
501            ascent: data.table_ranges.ascent,
502            descent: data.table_ranges.descent,
503        };
504        let coords = font.normalized_coords();
505        let feature_variations = font.feature_variations();
506        let feature_variations = [feature_variations.gsub(), feature_variations.gpos()];
507        let tables = font.tables();
508        let ot_tables = OtTables::from_tables(&tables, &data.ot_cache, coords, feature_variations);
509        let aat_tables = AatTables::from_tables(&tables, &ot_tables, &data.aat_cache);
510        Some(Self {
511            font: FontKind::FontInstance(font, metrics),
512            units_per_em: data.table_ranges.units_per_em,
513            cmap_cache: &data.cmap_cache,
514            ot_tables,
515            aat_tables,
516            apply_trak: data.apply_trak,
517        })
518    }
519
520    /// Returns font's units per EM.
521    #[inline]
522    pub fn units_per_em(&self) -> i32 {
523        self.units_per_em as i32
524    }
525
526    /// Returns the currently active normalized coordinates.
527    pub fn coords(&self) -> &'a [NormalizedCoord] {
528        self.ot_tables.coords
529    }
530
531    /// Shapes the buffer content using provided options.
532    ///
533    /// Consumes the buffer. You can then run [`GlyphBuffer::clear`] to get the [`UnicodeBuffer`] back
534    /// without allocating a new one.
535    ///
536    /// If a plan is provided, it is up to the caller to ensure that the shape plan matches the
537    /// properties of the provided buffer, otherwise the shaping result will likely be incorrect.
538    ///
539    /// # Panics
540    ///
541    /// Will panic when debugging assertions are enabled if the buffer and plan have mismatched
542    /// properties.    
543    pub fn shape(&self, buffer: UnicodeBuffer, options: ShapeOptions<'_>) -> GlyphBuffer {
544        if let Some(plan) = options.plan {
545            self.shape_with_plan(plan, buffer, options)
546        } else {
547            let plan = ShapePlan::new(
548                self,
549                buffer.0.direction,
550                buffer.0.script,
551                buffer.0.language.as_ref(),
552                options.features,
553            );
554            self.shape_with_plan(&plan, buffer, options)
555        }
556    }
557
558    fn shape_with_plan(
559        &self,
560        plan: &ShapePlan,
561        buffer: UnicodeBuffer,
562        options: ShapeOptions<'_>,
563    ) -> GlyphBuffer {
564        let mut buffer = buffer.0;
565        buffer.enter();
566
567        assert_eq!(
568            buffer.direction, plan.direction,
569            "Buffer direction does not match plan direction: {:?} != {:?}",
570            buffer.direction, plan.direction
571        );
572        assert_eq!(
573            buffer.script.unwrap_or(script::UNKNOWN),
574            plan.script.unwrap_or(script::UNKNOWN),
575            "Buffer script does not match plan script: {:?} != {:?}",
576            buffer.script.unwrap_or(script::UNKNOWN),
577            plan.script.unwrap_or(script::UNKNOWN)
578        );
579
580        if buffer.len > 0 {
581            // Save the original direction, we use it later.
582            let target_direction = buffer.direction;
583            let scale = Scale::new(options.scale, self.units_per_em as i32);
584            let mut font_funcs = FontFuncsDispatch::new(self, scale, options.font_funcs);
585            OtShapeContext {
586                plan,
587                face: self,
588                buffer: &mut buffer,
589                target_direction,
590                features: options.features,
591                point_size: options.point_size,
592                font_funcs: &mut font_funcs,
593            }
594            .shape_internal();
595        }
596
597        buffer.leave();
598
599        GlyphBuffer(buffer)
600    }
601
602    pub(crate) fn glyph_names(&self) -> GlyphNames<'a> {
603        GlyphNames::new(&self.font)
604    }
605
606    pub(crate) fn glyph_metrics(&self) -> GlyphMetrics<'a> {
607        match &self.font {
608            FontKind::FontRef(data) => data.glyph_metrics.clone(),
609            FontKind::FontInstance(instance, metrics) => {
610                GlyphMetrics::from_tables(&instance.tables(), metrics)
611            }
612        }
613    }
614
615    pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<LayoutTable<'a>> {
616        match table_index {
617            TableIndex::GSUB => self
618                .ot_tables
619                .gsub
620                .as_ref()
621                .map(|table| LayoutTable::Gsub(table.table.clone())),
622            TableIndex::GPOS => self
623                .ot_tables
624                .gpos
625                .as_ref()
626                .map(|table| LayoutTable::Gpos(table.table.clone())),
627        }
628    }
629
630    pub(crate) fn layout_tables(&self) -> impl Iterator<Item = (TableIndex, LayoutTable<'a>)> + '_ {
631        TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table)))
632    }
633}
634
635/// Glyph ink extents in font units.
636///
637/// This matches HarfBuzz's glyph extents layout and semantics.
638#[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
639#[repr(C)]
640pub struct GlyphExtents {
641    /// Horizontal bearing from glyph origin to the left side of the ink box.
642    pub x_bearing: i32,
643    /// Vertical bearing from glyph origin to the top of the ink box.
644    pub y_bearing: i32,
645    /// Width of the glyph ink box.
646    pub width: i32,
647    /// Height of the glyph ink box.
648    pub height: i32,
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654
655    #[test]
656    fn extents_scale_from_corners_like_harfbuzz() {
657        // HarfBuzz scales corners in floating point, floors the bearings,
658        // ceils the far corners, and then derives width/height from them.
659        let scale = Scale::new(Some((1500, 1500)), 1000);
660        let extents = GlyphExtents {
661            x_bearing: 1,
662            y_bearing: 4,
663            width: 3,
664            height: -2,
665        };
666        let scaled = scale.scale_extents(extents);
667        assert_eq!(scaled.x_bearing, 1);
668        assert_eq!(scaled.y_bearing, 6);
669        assert_eq!(scaled.width, 5);
670        assert_eq!(scaled.height, -3);
671    }
672}