zng_ext_font/
ligature_util.rs

1/*
2https://learn.microsoft.com/en-us/typography/opentype/spec/otff
3All OpenType fonts use Motorola-style byte ordering (Big Endian)
4
5Fixed 	 = 32-bit signed fixed-point number (16.16)
6Offset32 = uint32
7NULL     = 0
8 */
9
10use core::cmp;
11
12use byteorder::{BigEndian, ReadBytesExt};
13use zng_view_api::font::GlyphIndex;
14
15const GDEF: u32 = u32::from_be_bytes(*b"GDEF");
16
17#[derive(Clone)]
18pub struct LigatureCaretList {
19    coverage: Coverage,
20    lig_caret_start: Box<[u32]>,
21    lig_carets: Box<[LigatureCaret]>,
22}
23impl LigatureCaretList {
24    pub fn empty() -> Self {
25        Self {
26            coverage: Coverage::Format1 { glyphs: Box::new([]) },
27            lig_caret_start: Box::new([]),
28            lig_carets: Box::new([]),
29        }
30    }
31
32    pub fn load(font: &ttf_parser::RawFace) -> std::io::Result<Self> {
33        let table = match font.table(ttf_parser::Tag(GDEF)) {
34            Some(d) => d,
35            None => return Ok(Self::empty()),
36        };
37
38        /*
39        https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#gdef-header
40
41        GDEF Header
42
43        Type     Name                     Description
44        uint16   majorVersion             Major version of the GDEF table, = 1
45        uint16   minorVersion             Minor version of the GDEF table, = 0
46        Offset16 glyphClassDefOffset      Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL)
47        Offset16 attachListOffset         Offset to attachment point list table, from beginning of GDEF header (may be NULL)
48        Offset16 ligCaretListOffset       Offset to ligature caret list table, from beginning of GDEF header (may be NULL)
49        ..
50        */
51
52        let mut cursor = std::io::Cursor::new(&table);
53
54        let major_version = cursor.read_u16::<BigEndian>()?;
55        let _minor_version = cursor.read_u16::<BigEndian>()?;
56        if major_version != 1 {
57            return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unknown GDEF version"));
58        }
59
60        let _glyph_class_def_offset = cursor.read_u16::<BigEndian>()?;
61        let _attach_list_offset = cursor.read_u16::<BigEndian>()?;
62
63        let lig_caret_list_offset = cursor.read_u16::<BigEndian>()? as u64;
64
65        if lig_caret_list_offset == 0 {
66            return Ok(Self::empty());
67        }
68
69        cursor.set_position(lig_caret_list_offset);
70
71        /*
72        https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#ligature-caret-list-table
73
74        Ligature Caret List table (LigCaretList)
75
76        Type     Name                           Description
77        Offset16 coverageOffset                 Offset to Coverage table - from beginning of LigCaretList table
78        uint16   ligGlyphCount                  Number of ligature glyphs
79        Offset16 ligGlyphOffsets[ligGlyphCount] Array of offsets to LigGlyph tables, from beginning of LigCaretList table —in Coverage Index order
80        */
81
82        let coverage_offset = cursor.read_u16::<BigEndian>()? as u64;
83        /*
84        https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverageTbl
85
86        Coverage Table
87
88        Coverage Format 1: Individual glyph indices
89        Type   Name                   Description
90        uint16 coverageFormat         Format identifier — format = 1
91        uint16 glyphCount             Number of glyphs in the glyph array
92        uint16 glyphArray[glyphCount] Array of glyph IDs — in numerical order
93
94        Coverage Format 2: Range of glyphs
95        Type        Name                     Description
96        uint16      coverageFormat           Format identifier — format = 2
97        uint16      rangeCount               Number of RangeRecords
98        RangeRecord rangeRecords[rangeCount] Array of glyph ranges — ordered by startGlyphID.
99
100        RangeRecord:
101        Type   Name               Description
102        uint16 startGlyphID       First glyph ID in the range
103        uint16 endGlyphID         Last glyph ID in the range
104        uint16 startCoverageIndex Coverage Index of first glyph ID in range
105        */
106        let return_offset = cursor.position();
107        cursor.set_position(lig_caret_list_offset + coverage_offset);
108        let coverage_format = cursor.read_u16::<BigEndian>()?;
109        let coverage = match coverage_format {
110            1 => {
111                let glyph_count = cursor.read_u16::<BigEndian>()?;
112                let mut glyphs = Vec::with_capacity(glyph_count as usize);
113                for _ in 0..glyph_count {
114                    glyphs.push(cursor.read_u16::<BigEndian>()?);
115                }
116
117                Coverage::Format1 {
118                    glyphs: glyphs.into_boxed_slice(),
119                }
120            }
121            2 => {
122                let range_count = cursor.read_u16::<BigEndian>()?;
123                let mut glyph_ranges = Vec::with_capacity(range_count as usize);
124                for _ in 0..range_count {
125                    glyph_ranges.push(RangeRecord {
126                        start_glyph_id: cursor.read_u16::<BigEndian>()?,
127                        end_glyph_id: cursor.read_u16::<BigEndian>()?,
128                        start_coverage_index: cursor.read_u16::<BigEndian>()?,
129                    });
130                }
131                Coverage::Format2 {
132                    glyph_ranges: glyph_ranges.into_boxed_slice(),
133                }
134            }
135            _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unknown coverage format")),
136        };
137        cursor.set_position(return_offset);
138
139        let lig_glyph_count = cursor.read_u16::<BigEndian>()?;
140        let lig_glyph_offsets = cursor.read_u16::<BigEndian>()? as u64;
141
142        cursor.set_position(lig_caret_list_offset + lig_glyph_offsets);
143
144        let mut lig_caret_start = Vec::with_capacity(lig_glyph_count as usize);
145        let mut lig_carets = vec![];
146
147        for _ in 0..lig_glyph_count {
148            /*
149            https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#ligature-glyph-table
150
151            Ligature Glyph table (LigGlyph)
152
153            Type     Name                          Description
154            uint16   caretCount                    Number of CaretValue tables for this ligature (components - 1)
155            Offset16 caretValueOffsets[caretCount] Array of offsets to CaretValue tables, from beginning of LigGlyph table — in increasing coordinate order
156            */
157
158            let caret_count = cursor.read_u16::<BigEndian>()?;
159            let caret_value_offsets = cursor.read_u16::<BigEndian>()? as u64;
160
161            if caret_count == 0 {
162                continue;
163            }
164            lig_caret_start.push(lig_carets.len() as u32);
165            lig_carets.reserve(caret_count as usize);
166
167            let return_offset = cursor.position();
168            cursor.set_position(caret_value_offsets);
169            for _ in 0..caret_count {
170                /*
171                https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#caret-value-tables
172
173                Caret Values table (CaretValues)
174
175                CaretValue Format 1
176                Type   Name             Description
177                uint16 CaretValueFormat Format identifier-format = 1
178                int16  Coordinate       X or Y value, in design units
179
180                CaretValue Format 2
181                Type   Name                 Description
182                uint16 CaretValueFormat     Format identifier-format = 2
183                uint16 caretValuePointIndex Contour point index on glyph
184
185                CaretValue Format 3
186                Type     Name             Description
187                uint16   CaretValueFormat Format identifier-format = 3
188                int16    Coordinate       X or Y value, in design units
189                Offset16 DeviceOffset     Offset to Device table for X or Y value-from beginning of CaretValue table
190                */
191
192                let caret_value_format = cursor.read_u16::<BigEndian>()?;
193
194                match caret_value_format {
195                    1 => {
196                        let coordinate = cursor.read_i16::<BigEndian>()?;
197                        lig_carets.push(LigatureCaret::Coordinate(coordinate));
198                    }
199                    2 => {
200                        let caret_value_point_index = cursor.read_u16::<BigEndian>()?;
201                        lig_carets.push(LigatureCaret::GlyphContourPoint(caret_value_point_index));
202                    }
203                    3 => {
204                        let coordinate = cursor.read_i16::<BigEndian>()?;
205                        lig_carets.push(LigatureCaret::Coordinate(coordinate));
206                        let _device_table = cursor.read_u32::<BigEndian>()? as u64;
207                    }
208                    _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unknown CaretValue format")),
209                }
210            }
211            cursor.set_position(return_offset);
212        }
213
214        Ok(Self {
215            coverage,
216            lig_caret_start: lig_caret_start.into_boxed_slice(),
217            lig_carets: lig_carets.into_boxed_slice(),
218        })
219    }
220
221    /// Gets the caret offsets for clusters in the `lig` glyph, except the first cluster.
222    ///
223    /// The caret position for the first cluster is the ligature glyph position, the returned
224    /// slice contains the carets for subsequent clusters that form the ligature.
225    ///
226    /// Returns an empty slice if the font does not provide caret positions for `lig`, in this
227    /// case app must divide the glyph advance in equal parts to find caret positions.
228    pub fn carets(&self, lig: GlyphIndex) -> &[LigatureCaret] {
229        if let Some(p) = self.coverage.position(lig) {
230            if let Some(&start) = self.lig_caret_start.get(p) {
231                let start = start as usize;
232                let next_p = p + 1;
233                return if next_p < self.lig_carets.len() {
234                    let end = self.lig_caret_start[next_p] as usize;
235                    &self.lig_carets[start..end]
236                } else {
237                    &self.lig_carets[start..]
238                };
239            }
240        }
241        &[]
242    }
243
244    /// If the font provides not ligature caret positions.
245    ///
246    /// If `true` the [`carets`] method always returns an empty slice.
247    ///
248    /// [`carets`]: Self::carets
249    pub fn is_empty(&self) -> bool {
250        match &self.coverage {
251            Coverage::Format1 { glyphs } => glyphs.is_empty(),
252            Coverage::Format2 { glyph_ranges } => glyph_ranges.is_empty(),
253        }
254    }
255}
256
257#[derive(Clone)]
258enum Coverage {
259    Format1 {
260        /// Sorted glyph IDs, position is coverage index.
261        glyphs: Box<[u16]>,
262    },
263    Format2 {
264        /// Sorted glyph ranges.
265        glyph_ranges: Box<[RangeRecord]>,
266    },
267}
268impl Coverage {
269    fn position(&self, glyph: GlyphIndex) -> Option<usize> {
270        let glyph = glyph as u16;
271        match self {
272            Coverage::Format1 { glyphs } => glyphs.binary_search(&glyph).ok(),
273            Coverage::Format2 { glyph_ranges } => {
274                // see: https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2
275
276                let i = glyph_ranges
277                    .binary_search_by(|r| {
278                        if glyph < r.start_glyph_id {
279                            cmp::Ordering::Greater
280                        } else if glyph <= r.end_glyph_id {
281                            cmp::Ordering::Equal
282                        } else {
283                            cmp::Ordering::Less
284                        }
285                    })
286                    .ok()?;
287                let r = &glyph_ranges[i];
288
289                Some((r.start_coverage_index + glyph - r.start_glyph_id) as usize)
290            }
291        }
292    }
293}
294
295#[derive(Clone, Copy)]
296struct RangeRecord {
297    start_glyph_id: u16,
298    /// Inclusive.
299    end_glyph_id: u16,
300    start_coverage_index: u16,
301}
302
303#[derive(Clone, Copy, Debug)]
304pub enum LigatureCaret {
305    /// Offset in font units.
306    Coordinate(i16),
307    /// Index of a point in the glyph outline.
308    GlyphContourPoint(u16),
309}