| 1 | /*! |
| 2 | A collection of [Apple Advanced Typography]( |
| 3 | https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html) |
| 4 | related types. |
| 5 | */ |
| 6 | |
| 7 | use core::num::NonZeroU16; |
| 8 | |
| 9 | use crate::parser::{FromData, LazyArray16, NumFrom, Offset, Offset16, Offset32, Stream}; |
| 10 | use crate::GlyphId; |
| 11 | |
| 12 | /// Predefined states. |
| 13 | pub mod state { |
| 14 | #![allow (missing_docs)] |
| 15 | pub const START_OF_TEXT: u16 = 0; |
| 16 | } |
| 17 | |
| 18 | /// Predefined classes. |
| 19 | /// |
| 20 | /// Search for _Class Code_ in [Apple Advanced Typography Font Tables]( |
| 21 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
| 22 | pub mod class { |
| 23 | #![allow (missing_docs)] |
| 24 | pub const END_OF_TEXT: u8 = 0; |
| 25 | pub const OUT_OF_BOUNDS: u8 = 1; |
| 26 | pub const DELETED_GLYPH: u8 = 2; |
| 27 | } |
| 28 | |
| 29 | /// A State Table entry. |
| 30 | /// |
| 31 | /// Used by legacy and extended tables. |
| 32 | #[derive (Clone, Copy, Debug)] |
| 33 | pub struct GenericStateEntry<T: FromData> { |
| 34 | /// A new state. |
| 35 | pub new_state: u16, |
| 36 | /// Entry flags. |
| 37 | pub flags: u16, |
| 38 | /// Additional data. |
| 39 | /// |
| 40 | /// Use `()` if no data expected. |
| 41 | pub extra: T, |
| 42 | } |
| 43 | |
| 44 | impl<T: FromData> FromData for GenericStateEntry<T> { |
| 45 | const SIZE: usize = 4 + T::SIZE; |
| 46 | |
| 47 | #[inline ] |
| 48 | fn parse(data: &[u8]) -> Option<Self> { |
| 49 | let mut s: Stream<'_> = Stream::new(data); |
| 50 | Some(GenericStateEntry { |
| 51 | new_state: s.read::<u16>()?, |
| 52 | flags: s.read::<u16>()?, |
| 53 | extra: s.read::<T>()?, |
| 54 | }) |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | impl<T: FromData> GenericStateEntry<T> { |
| 59 | /// Checks that entry has an offset. |
| 60 | #[inline ] |
| 61 | pub fn has_offset(&self) -> bool { |
| 62 | self.flags & 0x3FFF != 0 |
| 63 | } |
| 64 | |
| 65 | /// Returns a value offset. |
| 66 | /// |
| 67 | /// Used by kern::format1 subtable. |
| 68 | #[inline ] |
| 69 | pub fn value_offset(&self) -> ValueOffset { |
| 70 | ValueOffset(self.flags & 0x3FFF) |
| 71 | } |
| 72 | |
| 73 | /// If set, reset the kerning data (clear the stack). |
| 74 | #[inline ] |
| 75 | pub fn has_reset(&self) -> bool { |
| 76 | self.flags & 0x2000 != 0 |
| 77 | } |
| 78 | |
| 79 | /// If set, advance to the next glyph before going to the new state. |
| 80 | #[inline ] |
| 81 | pub fn has_advance(&self) -> bool { |
| 82 | self.flags & 0x4000 == 0 |
| 83 | } |
| 84 | |
| 85 | /// If set, push this glyph on the kerning stack. |
| 86 | #[inline ] |
| 87 | pub fn has_push(&self) -> bool { |
| 88 | self.flags & 0x8000 != 0 |
| 89 | } |
| 90 | |
| 91 | /// If set, remember this glyph as the marked glyph. |
| 92 | /// |
| 93 | /// Used by kerx::format4 subtable. |
| 94 | /// |
| 95 | /// Yes, the same as [`has_push`](Self::has_push). |
| 96 | #[inline ] |
| 97 | pub fn has_mark(&self) -> bool { |
| 98 | self.flags & 0x8000 != 0 |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /// A legacy state entry used by [StateTable]. |
| 103 | pub type StateEntry = GenericStateEntry<()>; |
| 104 | |
| 105 | /// A type-safe wrapper for a kerning value offset. |
| 106 | #[derive (Clone, Copy, Debug)] |
| 107 | pub struct ValueOffset(u16); |
| 108 | |
| 109 | impl ValueOffset { |
| 110 | /// Returns the next offset. |
| 111 | /// |
| 112 | /// After reaching u16::MAX will start from 0. |
| 113 | #[inline ] |
| 114 | pub fn next(self) -> Self { |
| 115 | ValueOffset(self.0.wrapping_add(u16::SIZE as u16)) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /// A [State Table]( |
| 120 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
| 121 | /// |
| 122 | /// Also called `STHeader`. |
| 123 | /// |
| 124 | /// Currently used by `kern` table. |
| 125 | #[derive (Clone)] |
| 126 | pub struct StateTable<'a> { |
| 127 | number_of_classes: u16, |
| 128 | first_glyph: GlyphId, |
| 129 | class_table: &'a [u8], |
| 130 | state_array_offset: u16, |
| 131 | state_array: &'a [u8], |
| 132 | entry_table: &'a [u8], |
| 133 | actions: &'a [u8], |
| 134 | } |
| 135 | |
| 136 | impl<'a> StateTable<'a> { |
| 137 | pub(crate) fn parse(data: &'a [u8]) -> Option<Self> { |
| 138 | let mut s = Stream::new(data); |
| 139 | |
| 140 | let number_of_classes: u16 = s.read()?; |
| 141 | // Note that in format1 subtable, offsets are not from the subtable start, |
| 142 | // but from subtable start + `header_size`. |
| 143 | // So there is not need to subtract the `header_size`. |
| 144 | let class_table_offset = s.read::<Offset16>()?.to_usize(); |
| 145 | let state_array_offset = s.read::<Offset16>()?.to_usize(); |
| 146 | let entry_table_offset = s.read::<Offset16>()?.to_usize(); |
| 147 | // Ignore `values_offset` since we don't use it. |
| 148 | |
| 149 | // Parse class subtable. |
| 150 | let mut s = Stream::new_at(data, class_table_offset)?; |
| 151 | let first_glyph: GlyphId = s.read()?; |
| 152 | let number_of_glyphs: u16 = s.read()?; |
| 153 | // The class table contains u8, so it's easier to use just a slice |
| 154 | // instead of a LazyArray. |
| 155 | let class_table = s.read_bytes(usize::from(number_of_glyphs))?; |
| 156 | |
| 157 | Some(StateTable { |
| 158 | number_of_classes, |
| 159 | first_glyph, |
| 160 | class_table, |
| 161 | state_array_offset: state_array_offset as u16, |
| 162 | // We don't know the actual data size and it's kinda expensive to calculate. |
| 163 | // So we are simply storing all the data past the offset. |
| 164 | // Despite the fact that they may overlap. |
| 165 | state_array: data.get(state_array_offset..)?, |
| 166 | entry_table: data.get(entry_table_offset..)?, |
| 167 | // `ValueOffset` defines an offset from the start of the subtable data. |
| 168 | // We do not check that the provided offset is actually after `values_offset`. |
| 169 | actions: data, |
| 170 | }) |
| 171 | } |
| 172 | |
| 173 | /// Returns a glyph class. |
| 174 | #[inline ] |
| 175 | pub fn class(&self, glyph_id: GlyphId) -> Option<u8> { |
| 176 | if glyph_id.0 == 0xFFFF { |
| 177 | return Some(class::DELETED_GLYPH); |
| 178 | } |
| 179 | |
| 180 | let idx = glyph_id.0.checked_sub(self.first_glyph.0)?; |
| 181 | self.class_table.get(usize::from(idx)).copied() |
| 182 | } |
| 183 | |
| 184 | /// Returns a class entry. |
| 185 | #[inline ] |
| 186 | pub fn entry(&self, state: u16, mut class: u8) -> Option<StateEntry> { |
| 187 | if u16::from(class) >= self.number_of_classes { |
| 188 | class = class::OUT_OF_BOUNDS; |
| 189 | } |
| 190 | |
| 191 | let entry_idx = self |
| 192 | .state_array |
| 193 | .get(usize::from(state) * usize::from(self.number_of_classes) + usize::from(class))?; |
| 194 | |
| 195 | Stream::read_at(self.entry_table, usize::from(*entry_idx) * StateEntry::SIZE) |
| 196 | } |
| 197 | |
| 198 | /// Returns kerning at offset. |
| 199 | #[inline ] |
| 200 | pub fn kerning(&self, offset: ValueOffset) -> Option<i16> { |
| 201 | Stream::read_at(self.actions, usize::from(offset.0)) |
| 202 | } |
| 203 | |
| 204 | /// Produces a new state. |
| 205 | #[inline ] |
| 206 | pub fn new_state(&self, state: u16) -> u16 { |
| 207 | let n = (i32::from(state) - i32::from(self.state_array_offset)) |
| 208 | / i32::from(self.number_of_classes); |
| 209 | |
| 210 | use core::convert::TryFrom; |
| 211 | u16::try_from(n).unwrap_or(0) |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | impl core::fmt::Debug for StateTable<'_> { |
| 216 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
| 217 | write!(f, "StateTable {{ ... }}" ) |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | /// An [Extended State Table]( |
| 222 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
| 223 | /// |
| 224 | /// Also called `STXHeader`. |
| 225 | /// |
| 226 | /// Currently used by `kerx` and `morx` tables. |
| 227 | #[derive (Clone)] |
| 228 | pub struct ExtendedStateTable<'a, T> { |
| 229 | number_of_classes: u32, |
| 230 | lookup: Lookup<'a>, |
| 231 | state_array: &'a [u8], |
| 232 | entry_table: &'a [u8], |
| 233 | entry_type: core::marker::PhantomData<T>, |
| 234 | } |
| 235 | |
| 236 | impl<'a, T: FromData> ExtendedStateTable<'a, T> { |
| 237 | // TODO: make private |
| 238 | /// Parses an Extended State Table from a stream. |
| 239 | /// |
| 240 | /// `number_of_glyphs` is from the `maxp` table. |
| 241 | pub fn parse(number_of_glyphs: NonZeroU16, s: &mut Stream<'a>) -> Option<Self> { |
| 242 | let data = s.tail()?; |
| 243 | |
| 244 | let number_of_classes = s.read::<u32>()?; |
| 245 | // Note that offsets are not from the subtable start, |
| 246 | // but from subtable start + `header_size`. |
| 247 | // So there is not need to subtract the `header_size`. |
| 248 | let lookup_table_offset = s.read::<Offset32>()?.to_usize(); |
| 249 | let state_array_offset = s.read::<Offset32>()?.to_usize(); |
| 250 | let entry_table_offset = s.read::<Offset32>()?.to_usize(); |
| 251 | |
| 252 | Some(ExtendedStateTable { |
| 253 | number_of_classes, |
| 254 | lookup: Lookup::parse(number_of_glyphs, data.get(lookup_table_offset..)?)?, |
| 255 | // We don't know the actual data size and it's kinda expensive to calculate. |
| 256 | // So we are simply storing all the data past the offset. |
| 257 | // Despite the fact that they may overlap. |
| 258 | state_array: data.get(state_array_offset..)?, |
| 259 | entry_table: data.get(entry_table_offset..)?, |
| 260 | entry_type: core::marker::PhantomData, |
| 261 | }) |
| 262 | } |
| 263 | |
| 264 | /// Returns a glyph class. |
| 265 | #[inline ] |
| 266 | pub fn class(&self, glyph_id: GlyphId) -> Option<u16> { |
| 267 | if glyph_id.0 == 0xFFFF { |
| 268 | return Some(u16::from(class::DELETED_GLYPH)); |
| 269 | } |
| 270 | |
| 271 | self.lookup.value(glyph_id) |
| 272 | } |
| 273 | |
| 274 | /// Returns a class entry. |
| 275 | #[inline ] |
| 276 | pub fn entry(&self, state: u16, mut class: u16) -> Option<GenericStateEntry<T>> { |
| 277 | if u32::from(class) >= self.number_of_classes { |
| 278 | class = u16::from(class::OUT_OF_BOUNDS); |
| 279 | } |
| 280 | |
| 281 | let state_idx = |
| 282 | usize::from(state) * usize::num_from(self.number_of_classes) + usize::from(class); |
| 283 | |
| 284 | let entry_idx: u16 = Stream::read_at(self.state_array, state_idx * u16::SIZE)?; |
| 285 | Stream::read_at( |
| 286 | self.entry_table, |
| 287 | usize::from(entry_idx) * GenericStateEntry::<T>::SIZE, |
| 288 | ) |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | impl<T> core::fmt::Debug for ExtendedStateTable<'_, T> { |
| 293 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
| 294 | write!(f, "ExtendedStateTable {{ ... }}" ) |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | /// A [lookup table]( |
| 299 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
| 300 | /// |
| 301 | /// u32 values in Format10 tables will be truncated to u16. |
| 302 | /// u64 values in Format10 tables are not supported. |
| 303 | #[derive (Clone)] |
| 304 | pub struct Lookup<'a> { |
| 305 | data: LookupInner<'a>, |
| 306 | } |
| 307 | |
| 308 | impl<'a> Lookup<'a> { |
| 309 | /// Parses a lookup table from raw data. |
| 310 | /// |
| 311 | /// `number_of_glyphs` is from the `maxp` table. |
| 312 | #[inline ] |
| 313 | pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> { |
| 314 | LookupInner::parse(number_of_glyphs, data).map(|data: LookupInner<'_>| Self { data }) |
| 315 | } |
| 316 | |
| 317 | /// Returns a value associated with the specified glyph. |
| 318 | #[inline ] |
| 319 | pub fn value(&self, glyph_id: GlyphId) -> Option<u16> { |
| 320 | self.data.value(glyph_id) |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | impl core::fmt::Debug for Lookup<'_> { |
| 325 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
| 326 | write!(f, "Lookup {{ ... }}" ) |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | #[derive (Clone)] |
| 331 | enum LookupInner<'a> { |
| 332 | Format1(LazyArray16<'a, u16>), |
| 333 | Format2(BinarySearchTable<'a, LookupSegment>), |
| 334 | Format4(BinarySearchTable<'a, LookupSegment>, &'a [u8]), |
| 335 | Format6(BinarySearchTable<'a, LookupSingle>), |
| 336 | Format8 { |
| 337 | first_glyph: u16, |
| 338 | values: LazyArray16<'a, u16>, |
| 339 | }, |
| 340 | Format10 { |
| 341 | value_size: u16, |
| 342 | first_glyph: u16, |
| 343 | glyph_count: u16, |
| 344 | data: &'a [u8], |
| 345 | }, |
| 346 | } |
| 347 | |
| 348 | impl<'a> LookupInner<'a> { |
| 349 | fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> { |
| 350 | let mut s = Stream::new(data); |
| 351 | let format = s.read::<u16>()?; |
| 352 | match format { |
| 353 | 0 => { |
| 354 | let values = s.read_array16::<u16>(number_of_glyphs.get())?; |
| 355 | Some(Self::Format1(values)) |
| 356 | } |
| 357 | 2 => { |
| 358 | let bsearch = BinarySearchTable::<LookupSegment>::parse(s.tail()?)?; |
| 359 | Some(Self::Format2(bsearch)) |
| 360 | } |
| 361 | 4 => { |
| 362 | let bsearch = BinarySearchTable::<LookupSegment>::parse(s.tail()?)?; |
| 363 | Some(Self::Format4(bsearch, data)) |
| 364 | } |
| 365 | 6 => { |
| 366 | let bsearch = BinarySearchTable::<LookupSingle>::parse(s.tail()?)?; |
| 367 | Some(Self::Format6(bsearch)) |
| 368 | } |
| 369 | 8 => { |
| 370 | let first_glyph = s.read::<u16>()?; |
| 371 | let glyph_count = s.read::<u16>()?; |
| 372 | let values = s.read_array16::<u16>(glyph_count)?; |
| 373 | Some(Self::Format8 { |
| 374 | first_glyph, |
| 375 | values, |
| 376 | }) |
| 377 | } |
| 378 | 10 => { |
| 379 | let value_size = s.read::<u16>()?; |
| 380 | let first_glyph = s.read::<u16>()?; |
| 381 | let glyph_count = s.read::<u16>()?; |
| 382 | Some(Self::Format10 { |
| 383 | value_size, |
| 384 | first_glyph, |
| 385 | glyph_count, |
| 386 | data: s.tail()?, |
| 387 | }) |
| 388 | } |
| 389 | _ => None, |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | fn value(&self, glyph_id: GlyphId) -> Option<u16> { |
| 394 | match self { |
| 395 | Self::Format1(values) => values.get(glyph_id.0), |
| 396 | Self::Format2(ref bsearch) => bsearch.get(glyph_id).map(|v| v.value), |
| 397 | Self::Format4(ref bsearch, data) => { |
| 398 | // In format 4, LookupSegment contains an offset to a list of u16 values. |
| 399 | // One value for each glyph in the LookupSegment range. |
| 400 | let segment = bsearch.get(glyph_id)?; |
| 401 | let index = glyph_id.0.checked_sub(segment.first_glyph)?; |
| 402 | let offset = usize::from(segment.value) + u16::SIZE * usize::from(index); |
| 403 | Stream::read_at::<u16>(data, offset) |
| 404 | } |
| 405 | Self::Format6(ref bsearch) => bsearch.get(glyph_id).map(|v| v.value), |
| 406 | Self::Format8 { |
| 407 | first_glyph, |
| 408 | values, |
| 409 | } => { |
| 410 | let idx = glyph_id.0.checked_sub(*first_glyph)?; |
| 411 | values.get(idx) |
| 412 | } |
| 413 | Self::Format10 { |
| 414 | value_size, |
| 415 | first_glyph, |
| 416 | glyph_count, |
| 417 | data, |
| 418 | } => { |
| 419 | let idx = glyph_id.0.checked_sub(*first_glyph)?; |
| 420 | let mut s = Stream::new(data); |
| 421 | match value_size { |
| 422 | 1 => s.read_array16::<u8>(*glyph_count)?.get(idx).map(u16::from), |
| 423 | 2 => s.read_array16::<u16>(*glyph_count)?.get(idx), |
| 424 | // TODO: we should return u32 here, but this is not supported yet |
| 425 | 4 => s |
| 426 | .read_array16::<u32>(*glyph_count)? |
| 427 | .get(idx) |
| 428 | .map(|n| n as u16), |
| 429 | _ => None, // 8 is also supported |
| 430 | } |
| 431 | } |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | /// A binary searching table as defined at |
| 437 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html |
| 438 | #[derive (Clone)] |
| 439 | struct BinarySearchTable<'a, T: BinarySearchValue> { |
| 440 | values: LazyArray16<'a, T>, |
| 441 | len: NonZeroU16, // values length excluding termination segment |
| 442 | } |
| 443 | |
| 444 | impl<'a, T: BinarySearchValue + core::fmt::Debug> BinarySearchTable<'a, T> { |
| 445 | #[inline (never)] |
| 446 | fn parse(data: &'a [u8]) -> Option<Self> { |
| 447 | let mut s = Stream::new(data); |
| 448 | let segment_size = s.read::<u16>()?; |
| 449 | let number_of_segments = s.read::<u16>()?; |
| 450 | s.advance(6); // search_range + entry_selector + range_shift |
| 451 | |
| 452 | if usize::from(segment_size) != T::SIZE { |
| 453 | return None; |
| 454 | } |
| 455 | |
| 456 | if number_of_segments == 0 { |
| 457 | return None; |
| 458 | } |
| 459 | |
| 460 | let values = s.read_array16::<T>(number_of_segments)?; |
| 461 | |
| 462 | // 'The number of termination values that need to be included is table-specific. |
| 463 | // The value that indicates binary search termination is 0xFFFF.' |
| 464 | let mut len = number_of_segments; |
| 465 | if values.last()?.is_termination() { |
| 466 | len = len.checked_sub(1)?; |
| 467 | } |
| 468 | |
| 469 | Some(BinarySearchTable { |
| 470 | len: NonZeroU16::new(len)?, |
| 471 | values, |
| 472 | }) |
| 473 | } |
| 474 | |
| 475 | fn get(&self, key: GlyphId) -> Option<T> { |
| 476 | let mut min = 0; |
| 477 | let mut max = (self.len.get() as isize) - 1; |
| 478 | while min <= max { |
| 479 | let mid = (min + max) / 2; |
| 480 | let v = self.values.get(mid as u16)?; |
| 481 | match v.contains(key) { |
| 482 | core::cmp::Ordering::Less => max = mid - 1, |
| 483 | core::cmp::Ordering::Greater => min = mid + 1, |
| 484 | core::cmp::Ordering::Equal => return Some(v), |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | None |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | trait BinarySearchValue: FromData { |
| 493 | fn is_termination(&self) -> bool; |
| 494 | fn contains(&self, glyph_id: GlyphId) -> core::cmp::Ordering; |
| 495 | } |
| 496 | |
| 497 | #[derive (Clone, Copy, Debug)] |
| 498 | struct LookupSegment { |
| 499 | last_glyph: u16, |
| 500 | first_glyph: u16, |
| 501 | value: u16, |
| 502 | } |
| 503 | |
| 504 | impl FromData for LookupSegment { |
| 505 | const SIZE: usize = 6; |
| 506 | |
| 507 | #[inline ] |
| 508 | fn parse(data: &[u8]) -> Option<Self> { |
| 509 | let mut s: Stream<'_> = Stream::new(data); |
| 510 | Some(LookupSegment { |
| 511 | last_glyph: s.read::<u16>()?, |
| 512 | first_glyph: s.read::<u16>()?, |
| 513 | value: s.read::<u16>()?, |
| 514 | }) |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | impl BinarySearchValue for LookupSegment { |
| 519 | #[inline ] |
| 520 | fn is_termination(&self) -> bool { |
| 521 | self.last_glyph == 0xFFFF && self.first_glyph == 0xFFFF |
| 522 | } |
| 523 | |
| 524 | #[inline ] |
| 525 | fn contains(&self, id: GlyphId) -> core::cmp::Ordering { |
| 526 | if id.0 < self.first_glyph { |
| 527 | core::cmp::Ordering::Less |
| 528 | } else if id.0 <= self.last_glyph { |
| 529 | core::cmp::Ordering::Equal |
| 530 | } else { |
| 531 | core::cmp::Ordering::Greater |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | #[derive (Clone, Copy, Debug)] |
| 537 | struct LookupSingle { |
| 538 | glyph: u16, |
| 539 | value: u16, |
| 540 | } |
| 541 | |
| 542 | impl FromData for LookupSingle { |
| 543 | const SIZE: usize = 4; |
| 544 | |
| 545 | #[inline ] |
| 546 | fn parse(data: &[u8]) -> Option<Self> { |
| 547 | let mut s: Stream<'_> = Stream::new(data); |
| 548 | Some(LookupSingle { |
| 549 | glyph: s.read::<u16>()?, |
| 550 | value: s.read::<u16>()?, |
| 551 | }) |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | impl BinarySearchValue for LookupSingle { |
| 556 | #[inline ] |
| 557 | fn is_termination(&self) -> bool { |
| 558 | self.glyph == 0xFFFF |
| 559 | } |
| 560 | |
| 561 | #[inline ] |
| 562 | fn contains(&self, id: GlyphId) -> core::cmp::Ordering { |
| 563 | id.0.cmp(&self.glyph) |
| 564 | } |
| 565 | } |
| 566 | |