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}