Skip to main content

write_fonts/tables/
layout.rs

1//! OpenType layout.
2
3use std::{collections::HashSet, hash::Hash};
4
5pub use read_fonts::tables::layout::LookupFlag;
6use read_fonts::FontRead;
7
8pub mod builders;
9#[cfg(test)]
10mod spec_tests;
11
12include!("../../generated/generated_layout.rs");
13
14/// A macro to implement the [LookupSubtable] trait.
15macro_rules! lookup_type {
16    (gpos, $ty:ty, $val:expr) => {
17        impl LookupSubtable for $ty {
18            const TYPE: LookupType = LookupType::Gpos($val);
19        }
20    };
21
22    (gsub, $ty:ty, $val:expr) => {
23        impl LookupSubtable for $ty {
24            const TYPE: LookupType = LookupType::Gsub($val);
25        }
26    };
27}
28
29/// A macro to define a newtype around an existing table, that defers all
30/// impls to that table.
31///
32/// We use this to ensure that shared lookup types (Sequence/Chain
33/// lookups) can be given different lookup ids for each of GSUB/GPOS.
34macro_rules! table_newtype {
35    ($name:ident, $inner:ident, $read_type:path) => {
36        /// A typed wrapper around a shared table.
37        ///
38        /// This is used so that we can associate the correct lookup ids for
39        /// lookups that are shared between GPOS/GSUB.
40        ///
41        /// You can access the inner type via `Deref` or the `as_inner` method.
42        #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
43        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44        pub struct $name($inner);
45
46        impl $name {
47            /// Return a reference to the inner type.
48            pub fn as_inner(&self) -> &$inner {
49                &self.0
50            }
51        }
52
53        impl std::ops::Deref for $name {
54            type Target = $inner;
55            fn deref(&self) -> &Self::Target {
56                &self.0
57            }
58        }
59
60        impl std::ops::DerefMut for $name {
61            fn deref_mut(&mut self) -> &mut Self::Target {
62                &mut self.0
63            }
64        }
65
66        impl FontWrite for $name {
67            fn write_into(&self, writer: &mut TableWriter) {
68                self.0.write_into(writer)
69            }
70
71            fn table_type(&self) -> crate::table_type::TableType {
72                self.0.table_type()
73            }
74        }
75
76        impl Validate for $name {
77            fn validate_impl(&self, ctx: &mut ValidationCtx) {
78                self.0.validate_impl(ctx)
79            }
80        }
81
82        impl<'a> FromObjRef<$read_type> for $name {
83            fn from_obj_ref(obj: &$read_type, _data: FontData) -> Self {
84                Self(FromObjRef::from_obj_ref(obj, _data))
85            }
86        }
87
88        impl<'a> FromTableRef<$read_type> for $name {}
89
90        impl From<$inner> for $name {
91            fn from(src: $inner) -> $name {
92                $name(src)
93            }
94        }
95    };
96}
97
98pub(crate) use lookup_type;
99pub(crate) use table_newtype;
100
101impl FontWrite for LookupFlag {
102    fn write_into(&self, writer: &mut TableWriter) {
103        self.to_bits().write_into(writer)
104    }
105}
106
107impl<T: LookupSubtable + FontWrite> FontWrite for Lookup<T> {
108    fn write_into(&self, writer: &mut TableWriter) {
109        T::TYPE.write_into(writer);
110        self.lookup_flag.write_into(writer);
111        u16::try_from(self.subtables.len())
112            .unwrap()
113            .write_into(writer);
114        self.subtables.write_into(writer);
115        self.mark_filtering_set.write_into(writer);
116    }
117
118    fn table_type(&self) -> crate::table_type::TableType {
119        T::TYPE.into()
120    }
121}
122
123impl Lookup<SequenceContext> {
124    /// Convert this untyped SequenceContext into its GSUB or GPOS specific version
125    pub fn into_concrete<T: From<SequenceContext>>(self) -> Lookup<T> {
126        let Lookup {
127            lookup_flag,
128            subtables,
129            mark_filtering_set,
130        } = self;
131        let subtables = subtables
132            .into_iter()
133            .map(|offset| OffsetMarker::new(offset.into_inner().into()))
134            .collect();
135        Lookup {
136            lookup_flag,
137            subtables,
138            mark_filtering_set,
139        }
140    }
141}
142
143impl Lookup<ChainedSequenceContext> {
144    /// Convert this untyped SequenceContext into its GSUB or GPOS specific version
145    pub fn into_concrete<T: From<ChainedSequenceContext>>(self) -> Lookup<T> {
146        let Lookup {
147            lookup_flag,
148            subtables,
149            mark_filtering_set,
150        } = self;
151        let subtables = subtables
152            .into_iter()
153            .map(|offset| OffsetMarker::new(offset.into_inner().into()))
154            .collect();
155        Lookup {
156            lookup_flag,
157            subtables,
158            mark_filtering_set,
159        }
160    }
161}
162
163/// A utility trait for writing lookup tables.
164///
165/// This allows us to attach the numerical lookup type to the appropriate concrete
166/// types, so that we can write it as needed without passing it around.
167pub trait LookupSubtable {
168    /// The lookup type of this layout subtable.
169    const TYPE: LookupType;
170}
171
172/// Raw values for the different layout subtables
173#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174pub enum LookupType {
175    Gpos(u16),
176    Gsub(u16),
177}
178
179impl LookupType {
180    pub(crate) const GSUB_EXT_TYPE: u16 = 7;
181    pub(crate) const GPOS_EXT_TYPE: u16 = 9;
182    pub(crate) const PAIR_POS: u16 = 2;
183    pub(crate) const MARK_TO_BASE: u16 = 4;
184
185    pub(crate) fn to_raw(self) -> u16 {
186        match self {
187            LookupType::Gpos(val) => val,
188            LookupType::Gsub(val) => val,
189        }
190    }
191
192    pub(crate) fn promote(self) -> Self {
193        match self {
194            LookupType::Gpos(Self::GPOS_EXT_TYPE) | LookupType::Gsub(Self::GSUB_EXT_TYPE) => {
195                panic!("should never be promoting an extension subtable")
196            }
197            LookupType::Gpos(_) => LookupType::Gpos(Self::GPOS_EXT_TYPE),
198            LookupType::Gsub(_) => LookupType::Gsub(Self::GSUB_EXT_TYPE),
199        }
200    }
201}
202
203impl FontWrite for LookupType {
204    fn write_into(&self, writer: &mut TableWriter) {
205        self.to_raw().write_into(writer)
206    }
207}
208
209#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211pub enum FeatureParams {
212    StylisticSet(StylisticSetParams),
213    Size(SizeParams),
214    CharacterVariant(CharacterVariantParams),
215}
216
217impl FontWrite for FeatureParams {
218    fn write_into(&self, writer: &mut TableWriter) {
219        match self {
220            FeatureParams::StylisticSet(table) => table.write_into(writer),
221            FeatureParams::Size(table) => table.write_into(writer),
222            FeatureParams::CharacterVariant(table) => table.write_into(writer),
223        }
224    }
225}
226
227impl Validate for FeatureParams {
228    fn validate_impl(&self, ctx: &mut ValidationCtx) {
229        match self {
230            Self::StylisticSet(table) => table.validate_impl(ctx),
231            Self::Size(table) => table.validate_impl(ctx),
232            Self::CharacterVariant(table) => table.validate_impl(ctx),
233        }
234    }
235}
236
237impl FromObjRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {
238    fn from_obj_ref(from: &read_fonts::tables::layout::FeatureParams, data: FontData) -> Self {
239        use read_fonts::tables::layout::FeatureParams as FromType;
240        match from {
241            FromType::Size(thing) => Self::Size(SizeParams::from_obj_ref(thing, data)),
242            FromType::StylisticSet(thing) => {
243                Self::StylisticSet(FromObjRef::from_obj_ref(thing, data))
244            }
245            FromType::CharacterVariant(thing) => {
246                Self::CharacterVariant(FromObjRef::from_obj_ref(thing, data))
247            }
248        }
249    }
250}
251
252impl FromTableRef<read_fonts::tables::layout::FeatureParams<'_>> for FeatureParams {}
253
254impl ClassDefFormat1 {
255    fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
256        self.class_value_array.iter().enumerate().map(|(i, cls)| {
257            (
258                GlyphId16::new(self.start_glyph_id.to_u16().saturating_add(i as u16)),
259                *cls,
260            )
261        })
262    }
263}
264
265impl ClassRangeRecord {
266    fn validate_glyph_range(&self, ctx: &mut ValidationCtx) {
267        if self.start_glyph_id > self.end_glyph_id {
268            ctx.report(format!(
269                "start_glyph_id {} larger than end_glyph_id {}",
270                self.start_glyph_id, self.end_glyph_id
271            ));
272        }
273    }
274
275    fn contains(&self, gid: GlyphId16) -> bool {
276        (self.start_glyph_id..=self.end_glyph_id).contains(&gid)
277    }
278}
279
280impl ClassDefFormat2 {
281    fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
282        self.class_range_records.iter().flat_map(|rcd| {
283            (rcd.start_glyph_id.to_u16()..=rcd.end_glyph_id.to_u16())
284                .map(|gid| (GlyphId16::new(gid), rcd.class))
285        })
286    }
287}
288
289impl ClassDef {
290    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, u16)> + '_ {
291        let (one, two) = match self {
292            Self::Format1(table) => (Some(table.iter()), None),
293            Self::Format2(table) => (None, Some(table.iter())),
294        };
295
296        one.into_iter().flatten().chain(two.into_iter().flatten())
297    }
298
299    /// Return the glyph class for the provided glyph.
300    ///
301    /// Glyphs which have not been assigned a class are given class 0
302    pub fn get(&self, glyph: GlyphId16) -> u16 {
303        self.get_raw(glyph).unwrap_or(0)
304    }
305
306    // exposed for testing
307    fn get_raw(&self, glyph: GlyphId16) -> Option<u16> {
308        match self {
309            ClassDef::Format1(table) => glyph
310                .to_u16()
311                .checked_sub(table.start_glyph_id.to_u16())
312                .and_then(|idx| table.class_value_array.get(idx as usize))
313                .copied(),
314            ClassDef::Format2(table) => table
315                .class_range_records
316                .iter()
317                .find_map(|rec| rec.contains(glyph).then_some(rec.class)),
318        }
319    }
320
321    pub fn class_count(&self) -> u16 {
322        //TODO: implement a good integer set!!
323        self.iter()
324            .map(|(_gid, cls)| cls)
325            .chain(std::iter::once(0))
326            .collect::<HashSet<_>>()
327            .len()
328            .try_into()
329            .unwrap()
330    }
331
332    /// Returns `true` if no glyphs are explicitly assigned to a class in this table
333    pub fn is_empty(&self) -> bool {
334        match self {
335            Self::Format1(table) => table.class_value_array.is_empty(),
336            Self::Format2(table) => table.class_range_records.is_empty(),
337        }
338    }
339}
340
341impl CoverageFormat1 {
342    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
343        self.glyph_array.iter().copied()
344    }
345
346    fn len(&self) -> usize {
347        self.glyph_array.len()
348    }
349}
350
351impl CoverageFormat2 {
352    fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
353        self.range_records
354            .iter()
355            .flat_map(|rcd| iter_gids(rcd.start_glyph_id, rcd.end_glyph_id))
356    }
357
358    fn len(&self) -> usize {
359        self.range_records
360            .iter()
361            .map(|rcd| {
362                rcd.end_glyph_id
363                    .to_u16()
364                    .saturating_sub(rcd.start_glyph_id.to_u16()) as usize
365                    + 1
366            })
367            .sum()
368    }
369}
370
371impl CoverageTable {
372    pub fn iter(&self) -> impl Iterator<Item = GlyphId16> + '_ {
373        let (one, two) = match self {
374            Self::Format1(table) => (Some(table.iter()), None),
375            Self::Format2(table) => (None, Some(table.iter())),
376        };
377
378        one.into_iter().flatten().chain(two.into_iter().flatten())
379    }
380
381    pub fn len(&self) -> usize {
382        match self {
383            Self::Format1(table) => table.len(),
384            Self::Format2(table) => table.len(),
385        }
386    }
387
388    pub fn is_empty(&self) -> bool {
389        self.len() == 0
390    }
391}
392
393impl FromIterator<GlyphId16> for CoverageTable {
394    fn from_iter<T: IntoIterator<Item = GlyphId16>>(iter: T) -> Self {
395        let glyphs = iter.into_iter().collect::<Vec<_>>();
396        builders::CoverageTableBuilder::from_glyphs(glyphs).build()
397    }
398}
399
400impl From<Vec<GlyphId16>> for CoverageTable {
401    fn from(value: Vec<GlyphId16>) -> Self {
402        builders::CoverageTableBuilder::from_glyphs(value).build()
403    }
404}
405
406impl FromIterator<(GlyphId16, u16)> for ClassDef {
407    fn from_iter<T: IntoIterator<Item = (GlyphId16, u16)>>(iter: T) -> Self {
408        builders::ClassDefBuilderImpl::from_iter(iter).build()
409    }
410}
411
412impl RangeRecord {
413    /// An iterator over records for this array of glyphs.
414    ///
415    /// # Note
416    ///
417    /// this function expects that glyphs are already sorted.
418    pub fn iter_for_glyphs(glyphs: &[GlyphId16]) -> impl Iterator<Item = RangeRecord> + '_ {
419        let mut cur_range = glyphs.first().copied().map(|g| (g, g));
420        let mut len = 0u16;
421        let mut iter = glyphs.iter().skip(1).copied();
422
423        #[allow(clippy::while_let_on_iterator)]
424        std::iter::from_fn(move || {
425            while let Some(glyph) = iter.next() {
426                match cur_range {
427                    None => return None,
428                    Some((a, b)) if are_sequential(b, glyph) => cur_range = Some((a, glyph)),
429                    Some((a, b)) => {
430                        let result = RangeRecord {
431                            start_glyph_id: a,
432                            end_glyph_id: b,
433                            start_coverage_index: len,
434                        };
435                        cur_range = Some((glyph, glyph));
436                        len += 1 + b.to_u16().saturating_sub(a.to_u16());
437                        return Some(result);
438                    }
439                }
440            }
441            cur_range
442                .take()
443                .map(|(start_glyph_id, end_glyph_id)| RangeRecord {
444                    start_glyph_id,
445                    end_glyph_id,
446                    start_coverage_index: len,
447                })
448        })
449    }
450}
451
452fn iter_gids(gid1: GlyphId16, gid2: GlyphId16) -> impl Iterator<Item = GlyphId16> {
453    (gid1.to_u16()..=gid2.to_u16()).map(GlyphId16::new)
454}
455
456fn are_sequential(gid1: GlyphId16, gid2: GlyphId16) -> bool {
457    gid2.to_u16().saturating_sub(gid1.to_u16()) == 1
458}
459
460impl Device {
461    pub fn new(start_size: u16, end_size: u16, values: &[i8]) -> Self {
462        debug_assert_eq!(
463            (start_size..=end_size).count(),
464            values.len(),
465            "device range and values must match"
466        );
467        let delta_format: DeltaFormat = values
468            .iter()
469            .map(|val| match val {
470                -2..=1 => DeltaFormat::Local2BitDeltas,
471                -8..=7 => DeltaFormat::Local4BitDeltas,
472                _ => DeltaFormat::Local8BitDeltas,
473            })
474            .max()
475            .unwrap_or_default();
476        let delta_value = encode_delta(delta_format, values);
477
478        Device {
479            start_size,
480            end_size,
481            delta_format,
482            delta_value,
483        }
484    }
485}
486
487impl DeviceOrVariationIndex {
488    /// Create a new [`Device`] subtable
489    pub fn device(start_size: u16, end_size: u16, values: &[i8]) -> Self {
490        DeviceOrVariationIndex::Device(Device::new(start_size, end_size, values))
491    }
492}
493
494impl FontWrite for PendingVariationIndex {
495    fn write_into(&self, _writer: &mut TableWriter) {
496        panic!(
497            "Attempted to write PendingVariationIndex.\n\
498            VariationIndex tables should always be resolved before compilation.\n\
499            Please report this bug at <https://github.com/googlefonts/fontations/issues>"
500        )
501    }
502}
503
504fn encode_delta(format: DeltaFormat, values: &[i8]) -> Vec<u16> {
505    let (chunk_size, mask, bits) = match format {
506        DeltaFormat::Local2BitDeltas => (8, 0b11, 2),
507        DeltaFormat::Local4BitDeltas => (4, 0b1111, 4),
508        DeltaFormat::Local8BitDeltas => (2, 0b11111111, 8),
509        _ => panic!("invalid format"),
510    };
511    values
512        .chunks(chunk_size)
513        .map(|chunk| encode_chunk(chunk, mask, bits))
514        .collect()
515}
516
517fn encode_chunk(chunk: &[i8], mask: u8, bits: usize) -> u16 {
518    let mut out = 0u16;
519    for (i, val) in chunk.iter().enumerate() {
520        out |= ((val.to_be_bytes()[0] & mask) as u16) << ((16 - bits) - i * bits);
521    }
522    out
523}
524
525impl From<VariationIndex> for u32 {
526    fn from(value: VariationIndex) -> Self {
527        ((value.delta_set_outer_index as u32) << 16) | value.delta_set_inner_index as u32
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534
535    #[test]
536    #[should_panic(expected = "array exceeds max length")]
537    fn array_len_smoke_test() {
538        let table = ScriptList {
539            script_records: vec![ScriptRecord {
540                script_tag: Tag::new(b"hihi"),
541                script: OffsetMarker::new(Script {
542                    default_lang_sys: NullableOffsetMarker::new(None),
543                    lang_sys_records: vec![LangSysRecord {
544                        lang_sys_tag: Tag::new(b"coco"),
545                        lang_sys: OffsetMarker::new(LangSys {
546                            required_feature_index: 0xffff,
547                            feature_indices: vec![69; (u16::MAX) as usize + 5],
548                        }),
549                    }],
550                }),
551            }],
552        };
553
554        table.validate().unwrap();
555    }
556
557    #[test]
558    #[should_panic(expected = "larger than end_glyph_id")]
559    fn validate_classdef_ranges() {
560        let classdef = ClassDefFormat2::new(vec![ClassRangeRecord::new(
561            GlyphId16::new(12),
562            GlyphId16::new(3),
563            7,
564        )]);
565
566        classdef.validate().unwrap();
567    }
568
569    #[test]
570    fn delta_encode() {
571        let inp = [1i8, 2, 3, -1];
572        let result = encode_delta(DeltaFormat::Local4BitDeltas, &inp);
573        assert_eq!(result.len(), 1);
574        assert_eq!(result[0], 0x123f_u16);
575
576        let inp = [1i8, 1, 1, 1, 1];
577        let result = encode_delta(DeltaFormat::Local2BitDeltas, &inp);
578        assert_eq!(result.len(), 1);
579        assert_eq!(result[0], 0x5540_u16);
580    }
581}