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),
}