zng_ext_font/
ligature_util.rs

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
https://learn.microsoft.com/en-us/typography/opentype/spec/otff
All OpenType fonts use Motorola-style byte ordering (Big Endian)

Fixed 	 = 32-bit signed fixed-point number (16.16)
Offset32 = uint32
NULL     = 0
 */

use core::cmp;

use byteorder::{BigEndian, ReadBytesExt};
use zng_view_api::font::GlyphIndex;

const GDEF: u32 = u32::from_be_bytes(*b"GDEF");

#[derive(Clone)]
pub struct LigatureCaretList {
    coverage: Coverage,
    lig_caret_start: Box<[u32]>,
    lig_carets: Box<[LigatureCaret]>,
}
impl LigatureCaretList {
    pub fn empty() -> Self {
        Self {
            coverage: Coverage::Format1 { glyphs: Box::new([]) },
            lig_caret_start: Box::new([]),
            lig_carets: Box::new([]),
        }
    }

    pub fn load(font: &ttf_parser::RawFace) -> std::io::Result<Self> {
        let table = match font.table(ttf_parser::Tag(GDEF)) {
            Some(d) => d,
            None => return Ok(Self::empty()),
        };

        /*
        https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#gdef-header

        GDEF Header

        Type     Name                     Description
        uint16   majorVersion             Major version of the GDEF table, = 1
        uint16   minorVersion             Minor version of the GDEF table, = 0
        Offset16 glyphClassDefOffset      Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL)
        Offset16 attachListOffset         Offset to attachment point list table, from beginning of GDEF header (may be NULL)
        Offset16 ligCaretListOffset       Offset to ligature caret list table, from beginning of GDEF header (may be NULL)
        ..
        */

        let mut cursor = std::io::Cursor::new(&table);

        let major_version = cursor.read_u16::<BigEndian>()?;
        let _minor_version = cursor.read_u16::<BigEndian>()?;
        if major_version != 1 {
            return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unknown GDEF version"));
        }

        let _glyph_class_def_offset = cursor.read_u16::<BigEndian>()?;
        let _attach_list_offset = cursor.read_u16::<BigEndian>()?;

        let lig_caret_list_offset = cursor.read_u16::<BigEndian>()? as u64;

        if lig_caret_list_offset == 0 {
            return Ok(Self::empty());
        }

        cursor.set_position(lig_caret_list_offset);

        /*
        https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#ligature-caret-list-table

        Ligature Caret List table (LigCaretList)

        Type     Name                           Description
        Offset16 coverageOffset                 Offset to Coverage table - from beginning of LigCaretList table
        uint16   ligGlyphCount                  Number of ligature glyphs
        Offset16 ligGlyphOffsets[ligGlyphCount] Array of offsets to LigGlyph tables, from beginning of LigCaretList table —in Coverage Index order
        */

        let coverage_offset = cursor.read_u16::<BigEndian>()? as u64;
        /*
        https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverageTbl

        Coverage Table

        Coverage Format 1: Individual glyph indices
        Type   Name                   Description
        uint16 coverageFormat         Format identifier — format = 1
        uint16 glyphCount             Number of glyphs in the glyph array
        uint16 glyphArray[glyphCount] Array of glyph IDs — in numerical order

        Coverage Format 2: Range of glyphs
        Type        Name                     Description
        uint16      coverageFormat           Format identifier — format = 2
        uint16      rangeCount               Number of RangeRecords
        RangeRecord rangeRecords[rangeCount] Array of glyph ranges — ordered by startGlyphID.

        RangeRecord:
        Type   Name               Description
        uint16 startGlyphID       First glyph ID in the range
        uint16 endGlyphID         Last glyph ID in the range
        uint16 startCoverageIndex Coverage Index of first glyph ID in range
        */
        let return_offset = cursor.position();
        cursor.set_position(lig_caret_list_offset + coverage_offset);
        let coverage_format = cursor.read_u16::<BigEndian>()?;
        let coverage = match coverage_format {
            1 => {
                let glyph_count = cursor.read_u16::<BigEndian>()?;
                let mut glyphs = Vec::with_capacity(glyph_count as usize);
                for _ in 0..glyph_count {
                    glyphs.push(cursor.read_u16::<BigEndian>()?);
                }

                Coverage::Format1 {
                    glyphs: glyphs.into_boxed_slice(),
                }
            }
            2 => {
                let range_count = cursor.read_u16::<BigEndian>()?;
                let mut glyph_ranges = Vec::with_capacity(range_count as usize);
                for _ in 0..range_count {
                    glyph_ranges.push(RangeRecord {
                        start_glyph_id: cursor.read_u16::<BigEndian>()?,
                        end_glyph_id: cursor.read_u16::<BigEndian>()?,
                        start_coverage_index: cursor.read_u16::<BigEndian>()?,
                    });
                }
                Coverage::Format2 {
                    glyph_ranges: glyph_ranges.into_boxed_slice(),
                }
            }
            _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unknown coverage format")),
        };
        cursor.set_position(return_offset);

        let lig_glyph_count = cursor.read_u16::<BigEndian>()?;
        let lig_glyph_offsets = cursor.read_u16::<BigEndian>()? as u64;

        cursor.set_position(lig_caret_list_offset + lig_glyph_offsets);

        let mut lig_caret_start = Vec::with_capacity(lig_glyph_count as usize);
        let mut lig_carets = vec![];

        for _ in 0..lig_glyph_count {
            /*
            https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#ligature-glyph-table

            Ligature Glyph table (LigGlyph)

            Type     Name                          Description
            uint16   caretCount                    Number of CaretValue tables for this ligature (components - 1)
            Offset16 caretValueOffsets[caretCount] Array of offsets to CaretValue tables, from beginning of LigGlyph table — in increasing coordinate order
            */

            let caret_count = cursor.read_u16::<BigEndian>()?;
            let caret_value_offsets = cursor.read_u16::<BigEndian>()? as u64;

            if caret_count == 0 {
                continue;
            }
            lig_caret_start.push(lig_carets.len() as u32);
            lig_carets.reserve(caret_count as usize);

            let return_offset = cursor.position();
            cursor.set_position(caret_value_offsets);
            for _ in 0..caret_count {
                /*
                https://learn.microsoft.com/en-us/typography/opentype/spec/gdef#caret-value-tables

                Caret Values table (CaretValues)

                CaretValue Format 1
                Type   Name             Description
                uint16 CaretValueFormat Format identifier-format = 1
                int16  Coordinate       X or Y value, in design units

                CaretValue Format 2
                Type   Name                 Description
                uint16 CaretValueFormat     Format identifier-format = 2
                uint16 caretValuePointIndex Contour point index on glyph

                CaretValue Format 3
                Type     Name             Description
                uint16   CaretValueFormat Format identifier-format = 3
                int16    Coordinate       X or Y value, in design units
                Offset16 DeviceOffset     Offset to Device table for X or Y value-from beginning of CaretValue table
                */

                let caret_value_format = cursor.read_u16::<BigEndian>()?;

                match caret_value_format {
                    1 => {
                        let coordinate = cursor.read_i16::<BigEndian>()?;
                        lig_carets.push(LigatureCaret::Coordinate(coordinate));
                    }
                    2 => {
                        let caret_value_point_index = cursor.read_u16::<BigEndian>()?;
                        lig_carets.push(LigatureCaret::GlyphContourPoint(caret_value_point_index));
                    }
                    3 => {
                        let coordinate = cursor.read_i16::<BigEndian>()?;
                        lig_carets.push(LigatureCaret::Coordinate(coordinate));
                        let _device_table = cursor.read_u32::<BigEndian>()? as u64;
                    }
                    _ => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unknown CaretValue format")),
                }
            }
            cursor.set_position(return_offset);
        }

        Ok(Self {
            coverage,
            lig_caret_start: lig_caret_start.into_boxed_slice(),
            lig_carets: lig_carets.into_boxed_slice(),
        })
    }

    /// Gets the caret offsets for clusters in the `lig` glyph, except the first cluster.
    ///
    /// The caret position for the first cluster is the ligature glyph position, the returned
    /// slice contains the carets for subsequent clusters that form the ligature.
    ///
    /// Returns an empty slice if the font does not provide caret positions for `lig`, in this
    /// case app must divide the glyph advance in equal parts to find caret positions.
    pub fn carets(&self, lig: GlyphIndex) -> &[LigatureCaret] {
        if let Some(p) = self.coverage.position(lig) {
            if let Some(&start) = self.lig_caret_start.get(p) {
                let start = start as usize;
                let next_p = p + 1;
                return if next_p < self.lig_carets.len() {
                    let end = self.lig_caret_start[next_p] as usize;
                    &self.lig_carets[start..end]
                } else {
                    &self.lig_carets[start..]
                };
            }
        }
        &[]
    }

    /// If the font provides not ligature caret positions.
    ///
    /// If `true` the [`carets`] method always returns an empty slice.
    ///
    /// [`carets`]: Self::carets
    pub fn is_empty(&self) -> bool {
        match &self.coverage {
            Coverage::Format1 { glyphs } => glyphs.is_empty(),
            Coverage::Format2 { glyph_ranges } => glyph_ranges.is_empty(),
        }
    }
}

#[derive(Clone)]
enum Coverage {
    Format1 {
        /// Sorted glyph IDs, position is coverage index.
        glyphs: Box<[u16]>,
    },
    Format2 {
        /// Sorted glyph ranges.
        glyph_ranges: Box<[RangeRecord]>,
    },
}
impl Coverage {
    fn position(&self, glyph: GlyphIndex) -> Option<usize> {
        let glyph = glyph as u16;
        match self {
            Coverage::Format1 { glyphs } => glyphs.binary_search(&glyph).ok(),
            Coverage::Format2 { glyph_ranges } => {
                // see: https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2

                let i = glyph_ranges
                    .binary_search_by(|r| {
                        if glyph < r.start_glyph_id {
                            cmp::Ordering::Greater
                        } else if glyph <= r.end_glyph_id {
                            cmp::Ordering::Equal
                        } else {
                            cmp::Ordering::Less
                        }
                    })
                    .ok()?;
                let r = &glyph_ranges[i];

                Some((r.start_coverage_index + glyph - r.start_glyph_id) as usize)
            }
        }
    }
}

#[derive(Clone, Copy)]
struct RangeRecord {
    start_glyph_id: u16,
    /// Inclusive.
    end_glyph_id: u16,
    start_coverage_index: u16,
}

#[derive(Clone, Copy, Debug)]
pub enum LigatureCaret {
    /// Offset in font units.
    Coordinate(i16),
    /// Index of a point in the glyph outline.
    GlyphContourPoint(u16),
}