diff options
Diffstat (limited to 'components/fonts/glyph.rs')
-rw-r--r-- | components/fonts/glyph.rs | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/components/fonts/glyph.rs b/components/fonts/glyph.rs new file mode 100644 index 00000000000..420d43b0774 --- /dev/null +++ b/components/fonts/glyph.rs @@ -0,0 +1,815 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cmp::{Ordering, PartialOrd}; +use std::sync::Arc; +use std::vec::Vec; +use std::{fmt, mem, u16}; + +use app_units::Au; +use euclid::default::Point2D; +pub use fonts_traits::ByteIndex; +use log::debug; +use malloc_size_of_derive::MallocSizeOf; +use range::{self, EachIndex, Range, RangeIndex}; +use serde::{Deserialize, Serialize}; + +/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly. +/// +/// In the common case (reasonable glyph advances, no offsets from the font em-box, and one glyph +/// per character), we pack glyph advance, glyph id, and some flags into a single u32. +/// +/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or +/// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information +/// in DetailedGlyphStore. +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub struct GlyphEntry { + value: u32, +} + +impl GlyphEntry { + fn new(value: u32) -> GlyphEntry { + GlyphEntry { value } + } + + fn initial() -> GlyphEntry { + GlyphEntry::new(0) + } + + // Creates a GlyphEntry for the common case + fn simple(id: GlyphId, advance: Au) -> GlyphEntry { + assert!(is_simple_glyph_id(id)); + assert!(is_simple_advance(advance)); + + let id_mask = id; + let Au(advance) = advance; + let advance_mask = (advance as u32) << GLYPH_ADVANCE_SHIFT; + + GlyphEntry::new(id_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH) + } + + // Create a GlyphEntry for uncommon case; should be accompanied by + // initialization of the actual DetailedGlyph data in DetailedGlyphStore + fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: usize) -> GlyphEntry { + assert!(glyph_count <= u16::MAX as usize); + + debug!( + "creating complex glyph entry: starts_cluster={}, starts_ligature={}, \ + glyph_count={}", + starts_cluster, starts_ligature, glyph_count + ); + + GlyphEntry::new(glyph_count as u32) + } + + fn is_initial(&self) -> bool { + *self == GlyphEntry::initial() + } +} + +/// The id of a particular glyph within a font +pub type GlyphId = u32; + +// TODO: make this more type-safe. + +const FLAG_CHAR_IS_WORD_SEPARATOR: u32 = 0x40000000; +const FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000; + +// glyph advance; in Au's. +const GLYPH_ADVANCE_MASK: u32 = 0x3FFF0000; +const GLYPH_ADVANCE_SHIFT: u32 = 16; +const GLYPH_ID_MASK: u32 = 0x0000FFFF; + +// Non-simple glyphs (more than one glyph per char; missing glyph, +// newline, tab, large advance, or nonzero x/y offsets) may have one +// or more detailed glyphs associated with them. They are stored in a +// side array so that there is a 1:1 mapping of GlyphEntry to +// unicode char. + +// The number of detailed glyphs for this char. +const GLYPH_COUNT_MASK: u32 = 0x0000FFFF; + +fn is_simple_glyph_id(id: GlyphId) -> bool { + (id & GLYPH_ID_MASK) == id +} + +fn is_simple_advance(advance: Au) -> bool { + advance >= Au(0) && { + let unsigned_au = advance.0 as u32; + (unsigned_au & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT)) == unsigned_au + } +} + +// Getters and setters for GlyphEntry. Setter methods are functional, +// because GlyphEntry is immutable and only a u32 in size. +impl GlyphEntry { + #[inline(always)] + fn advance(&self) -> Au { + Au::new(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as i32) + } + + #[inline] + fn id(&self) -> GlyphId { + self.value & GLYPH_ID_MASK + } + + /// True if the original character was a word separator. These include spaces + /// (U+0020), non-breaking spaces (U+00A0), and a few other characters + /// non-exhaustively listed in the specification. Other characters may map to the same + /// glyphs, but this function does not take mapping into account. + /// + /// See <https://drafts.csswg.org/css-text/#word-separator>. + fn char_is_word_separator(&self) -> bool { + self.has_flag(FLAG_CHAR_IS_WORD_SEPARATOR) + } + + #[inline(always)] + fn set_char_is_word_separator(&mut self) { + self.value |= FLAG_CHAR_IS_WORD_SEPARATOR; + } + + fn glyph_count(&self) -> u16 { + assert!(!self.is_simple()); + (self.value & GLYPH_COUNT_MASK) as u16 + } + + #[inline(always)] + fn is_simple(&self) -> bool { + self.has_flag(FLAG_IS_SIMPLE_GLYPH) + } + + #[inline(always)] + fn has_flag(&self, flag: u32) -> bool { + (self.value & flag) != 0 + } +} + +// Stores data for a detailed glyph, in the case that several glyphs +// correspond to one character, or the glyph's data couldn't be packed. +#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] +struct DetailedGlyph { + id: GlyphId, + // glyph's advance, in the text's direction (LTR or RTL) + advance: Au, + // glyph's offset from the font's em-box (from top-left) + offset: Point2D<Au>, +} + +impl DetailedGlyph { + fn new(id: GlyphId, advance: Au, offset: Point2D<Au>) -> DetailedGlyph { + DetailedGlyph { + id, + advance, + offset, + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] +struct DetailedGlyphRecord { + // source string offset/GlyphEntry offset in the TextRun + entry_offset: ByteIndex, + // offset into the detailed glyphs buffer + detail_offset: usize, +} + +impl Ord for DetailedGlyphRecord { + fn cmp(&self, other: &DetailedGlyphRecord) -> Ordering { + self.entry_offset.cmp(&other.entry_offset) + } +} + +impl PartialOrd for DetailedGlyphRecord { + fn partial_cmp(&self, other: &DetailedGlyphRecord) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +// Manages the lookup table for detailed glyphs. Sorting is deferred +// until a lookup is actually performed; this matches the expected +// usage pattern of setting/appending all the detailed glyphs, and +// then querying without setting. +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] +struct DetailedGlyphStore { + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. + detail_buffer: Vec<DetailedGlyph>, + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. + detail_lookup: Vec<DetailedGlyphRecord>, + lookup_is_sorted: bool, +} + +impl<'a> DetailedGlyphStore { + fn new() -> DetailedGlyphStore { + DetailedGlyphStore { + detail_buffer: vec![], // TODO: default size? + detail_lookup: vec![], + lookup_is_sorted: false, + } + } + + fn add_detailed_glyphs_for_entry(&mut self, entry_offset: ByteIndex, glyphs: &[DetailedGlyph]) { + let entry = DetailedGlyphRecord { + entry_offset, + detail_offset: self.detail_buffer.len(), + }; + + debug!( + "Adding entry[off={:?}] for detailed glyphs: {:?}", + entry_offset, glyphs + ); + + debug_assert!(!self.detail_lookup.contains(&entry)); + self.detail_lookup.push(entry); + self.detail_buffer.extend_from_slice(glyphs); + self.lookup_is_sorted = false; + } + + fn detailed_glyphs_for_entry( + &'a self, + entry_offset: ByteIndex, + count: u16, + ) -> &'a [DetailedGlyph] { + debug!( + "Requesting detailed glyphs[n={}] for entry[off={:?}]", + count, entry_offset + ); + + // FIXME: Is this right? --pcwalton + // TODO: should fix this somewhere else + if count == 0 { + return &self.detail_buffer[0..0]; + } + + assert!((count as usize) <= self.detail_buffer.len()); + assert!(self.lookup_is_sorted); + + let key = DetailedGlyphRecord { + entry_offset, + detail_offset: 0, // unused + }; + + let i = self + .detail_lookup + .binary_search(&key) + .expect("Invalid index not found in detailed glyph lookup table!"); + let main_detail_offset = self.detail_lookup[i].detail_offset; + assert!(main_detail_offset + (count as usize) <= self.detail_buffer.len()); + // return a slice into the buffer + &self.detail_buffer[main_detail_offset..main_detail_offset + count as usize] + } + + fn detailed_glyph_with_index( + &'a self, + entry_offset: ByteIndex, + detail_offset: u16, + ) -> &'a DetailedGlyph { + assert!((detail_offset as usize) <= self.detail_buffer.len()); + assert!(self.lookup_is_sorted); + + let key = DetailedGlyphRecord { + entry_offset, + detail_offset: 0, // unused + }; + + let i = self + .detail_lookup + .binary_search(&key) + .expect("Invalid index not found in detailed glyph lookup table!"); + let main_detail_offset = self.detail_lookup[i].detail_offset; + assert!(main_detail_offset + (detail_offset as usize) < self.detail_buffer.len()); + &self.detail_buffer[main_detail_offset + (detail_offset as usize)] + } + + fn ensure_sorted(&mut self) { + if self.lookup_is_sorted { + return; + } + + // Sorting a unique vector is surprisingly hard. The following + // code is a good argument for using DVecs, but they require + // immutable locations thus don't play well with freezing. + + // Thar be dragons here. You have been warned. (Tips accepted.) + let mut unsorted_records: Vec<DetailedGlyphRecord> = vec![]; + mem::swap(&mut self.detail_lookup, &mut unsorted_records); + let mut mut_records: Vec<DetailedGlyphRecord> = unsorted_records; + mut_records.sort_by(|a, b| { + if a < b { + Ordering::Less + } else { + Ordering::Greater + } + }); + let mut sorted_records = mut_records; + mem::swap(&mut self.detail_lookup, &mut sorted_records); + + self.lookup_is_sorted = true; + } +} + +// This struct is used by GlyphStore clients to provide new glyph data. +// It should be allocated on the stack and passed by reference to GlyphStore. +#[derive(Clone, Copy)] +pub struct GlyphData { + id: GlyphId, + advance: Au, + offset: Point2D<Au>, + cluster_start: bool, + ligature_start: bool, +} + +impl GlyphData { + /// Creates a new entry for one glyph. + pub fn new( + id: GlyphId, + advance: Au, + offset: Option<Point2D<Au>>, + cluster_start: bool, + ligature_start: bool, + ) -> GlyphData { + GlyphData { + id, + advance, + offset: offset.unwrap_or(Point2D::zero()), + cluster_start, + ligature_start, + } + } +} + +// This enum is a proxy that's provided to GlyphStore clients when iterating +// through glyphs (either for a particular TextRun offset, or all glyphs). +// Rather than eagerly assembling and copying glyph data, it only retrieves +// values as they are needed from the GlyphStore, using provided offsets. +#[derive(Clone, Copy)] +pub enum GlyphInfo<'a> { + Simple(&'a GlyphStore, ByteIndex), + Detail(&'a GlyphStore, ByteIndex, u16), +} + +impl<'a> GlyphInfo<'a> { + pub fn id(self) -> GlyphId { + match self { + GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].id(), + GlyphInfo::Detail(store, entry_i, detail_j) => { + store + .detail_store + .detailed_glyph_with_index(entry_i, detail_j) + .id + }, + } + } + + #[inline(always)] + // FIXME: Resolution conflicts with IteratorUtil trait so adding trailing _ + pub fn advance(self) -> Au { + match self { + GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].advance(), + GlyphInfo::Detail(store, entry_i, detail_j) => { + store + .detail_store + .detailed_glyph_with_index(entry_i, detail_j) + .advance + }, + } + } + + #[inline] + pub fn offset(self) -> Option<Point2D<Au>> { + match self { + GlyphInfo::Simple(_, _) => None, + GlyphInfo::Detail(store, entry_i, detail_j) => Some( + store + .detail_store + .detailed_glyph_with_index(entry_i, detail_j) + .offset, + ), + } + } + + pub fn char_is_word_separator(self) -> bool { + let (store, entry_i) = match self { + GlyphInfo::Simple(store, entry_i) => (store, entry_i), + GlyphInfo::Detail(store, entry_i, _) => (store, entry_i), + }; + + store.char_is_word_separator(entry_i) + } +} + +/// Stores the glyph data belonging to a text run. +/// +/// Simple glyphs are stored inline in the `entry_buffer`, detailed glyphs are +/// stored as pointers into the `detail_store`. +/// +/// ~~~ascii +/// +- GlyphStore --------------------------------+ +/// | +---+---+---+---+---+---+---+ | +/// | entry_buffer: | | s | | s | | s | s | | d = detailed +/// | +-|-+---+-|-+---+-|-+---+---+ | s = simple +/// | | | | | +/// | | +---+-------+ | +/// | | | | +/// | +-V-+-V-+ | +/// | detail_store: | d | d | | +/// | +---+---+ | +/// +---------------------------------------------+ +/// ~~~ +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] +pub struct GlyphStore { + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. + /// A buffer of glyphs within the text run, in the order in which they + /// appear in the input text. + /// Any changes will also need to be reflected in + /// transmute_entry_buffer_to_u32_buffer(). + entry_buffer: Vec<GlyphEntry>, + /// A store of the detailed glyph data. Detailed glyphs contained in the + /// `entry_buffer` point to locations in this data structure. + detail_store: DetailedGlyphStore, + + /// A cache of the advance of the entire glyph store. + total_advance: Au, + + /// A cache of the number of word separators in the entire glyph store. + /// See <https://drafts.csswg.org/css-text/#word-separator>. + total_word_separators: usize, + + /// Used to check if fast path should be used in glyph iteration. + has_detailed_glyphs: bool, + + /// Whether or not this glyph store contains only glyphs for whitespace. + is_whitespace: bool, + + /// Whether or not this glyph store contains only a single glyph for a single + /// preserved newline. + is_single_preserved_newline: bool, + + is_rtl: bool, +} + +impl<'a> GlyphStore { + /// Initializes the glyph store, but doesn't actually shape anything. + /// + /// Use the `add_*` methods to store glyph data. + pub fn new( + length: usize, + is_whitespace: bool, + is_single_preserved_newline: bool, + is_rtl: bool, + ) -> GlyphStore { + assert!(length > 0); + + GlyphStore { + entry_buffer: vec![GlyphEntry::initial(); length], + detail_store: DetailedGlyphStore::new(), + total_advance: Au(0), + total_word_separators: 0, + has_detailed_glyphs: false, + is_whitespace, + is_single_preserved_newline, + is_rtl, + } + } + + #[inline] + pub fn total_advance(&self) -> Au { + self.total_advance + } + + #[inline] + pub fn len(&self) -> ByteIndex { + ByteIndex(self.entry_buffer.len() as isize) + } + + #[inline] + pub fn is_whitespace(&self) -> bool { + self.is_whitespace + } + + #[inline] + pub fn total_word_separators(&self) -> usize { + self.total_word_separators + } + + pub fn finalize_changes(&mut self) { + self.detail_store.ensure_sorted(); + self.cache_total_advance_and_word_separators() + } + + #[inline(never)] + fn cache_total_advance_and_word_separators(&mut self) { + let mut total_advance = Au(0); + let mut total_word_separators = 0; + for glyph in self.iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), self.len())) { + total_advance += glyph.advance(); + if glyph.char_is_word_separator() { + total_word_separators += 1; + } + } + self.total_advance = total_advance; + self.total_word_separators = total_word_separators; + } + + /// Adds a single glyph. + pub fn add_glyph_for_byte_index(&mut self, i: ByteIndex, character: char, data: &GlyphData) { + let glyph_is_compressible = is_simple_glyph_id(data.id) && + is_simple_advance(data.advance) && + data.offset == Point2D::zero() && + data.cluster_start; // others are stored in detail buffer + + debug_assert!(data.ligature_start); // can't compress ligature continuation glyphs. + debug_assert!(i < self.len()); + + let mut entry = if glyph_is_compressible { + GlyphEntry::simple(data.id, data.advance) + } else { + let glyph = &[DetailedGlyph::new(data.id, data.advance, data.offset)]; + self.has_detailed_glyphs = true; + self.detail_store.add_detailed_glyphs_for_entry(i, glyph); + GlyphEntry::complex(data.cluster_start, data.ligature_start, 1) + }; + + // This list is taken from the non-exhaustive list of word separator characters in + // the CSS Text Module Level 3 Spec: + // See https://drafts.csswg.org/css-text/#word-separator + if matches!( + character, + ' ' | + '\u{00A0}' | // non-breaking space + '\u{1361}' | // Ethiopic word space + '\u{10100}' | // Aegean word separator + '\u{10101}' | // Aegean word separator + '\u{1039F}' | // Ugartic word divider + '\u{1091F}' // Phoenician word separator + ) { + entry.set_char_is_word_separator(); + } + + self.entry_buffer[i.to_usize()] = entry; + } + + pub fn add_glyphs_for_byte_index(&mut self, i: ByteIndex, data_for_glyphs: &[GlyphData]) { + assert!(i < self.len()); + assert!(!data_for_glyphs.is_empty()); + + let glyph_count = data_for_glyphs.len(); + + let first_glyph_data = data_for_glyphs[0]; + let glyphs_vec: Vec<DetailedGlyph> = (0..glyph_count) + .map(|i| { + DetailedGlyph::new( + data_for_glyphs[i].id, + data_for_glyphs[i].advance, + data_for_glyphs[i].offset, + ) + }) + .collect(); + + self.has_detailed_glyphs = true; + self.detail_store + .add_detailed_glyphs_for_entry(i, &glyphs_vec); + + let entry = GlyphEntry::complex( + first_glyph_data.cluster_start, + first_glyph_data.ligature_start, + glyph_count, + ); + + debug!( + "Adding multiple glyphs[idx={:?}, count={}]: {:?}", + i, glyph_count, entry + ); + + self.entry_buffer[i.to_usize()] = entry; + } + + #[inline] + pub fn iter_glyphs_for_byte_range(&'a self, range: &Range<ByteIndex>) -> GlyphIterator<'a> { + if range.begin() >= self.len() { + panic!("iter_glyphs_for_range: range.begin beyond length!"); + } + if range.end() > self.len() { + panic!("iter_glyphs_for_range: range.end beyond length!"); + } + + GlyphIterator { + store: self, + byte_index: if self.is_rtl { + range.end() + } else { + range.begin() - ByteIndex(1) + }, + byte_range: *range, + glyph_range: None, + } + } + + // Scan the glyphs for a given range until we reach a given advance. Returns the index + // and advance of the glyph in the range at the given advance, if reached. Otherwise, returns the + // the number of glyphs and the advance for the given range. + #[inline] + pub fn range_index_of_advance( + &self, + range: &Range<ByteIndex>, + advance: Au, + extra_word_spacing: Au, + ) -> (usize, Au) { + let mut index = 0; + let mut current_advance = Au(0); + for glyph in self.iter_glyphs_for_byte_range(range) { + if glyph.char_is_word_separator() { + current_advance += glyph.advance() + extra_word_spacing + } else { + current_advance += glyph.advance() + } + if current_advance > advance { + break; + } + index += 1; + } + (index, current_advance) + } + + #[inline] + pub fn advance_for_byte_range(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au { + if range.begin() == ByteIndex(0) && range.end() == self.len() { + self.total_advance + extra_word_spacing * (self.total_word_separators as i32) + } else { + self.advance_for_byte_range_simple_glyphs(range, extra_word_spacing) + } + } + + #[inline] + pub fn advance_for_byte_range_simple_glyphs( + &self, + range: &Range<ByteIndex>, + extra_word_spacing: Au, + ) -> Au { + self.iter_glyphs_for_byte_range(range) + .fold(Au(0), |advance, glyph| { + if glyph.char_is_word_separator() { + advance + glyph.advance() + extra_word_spacing + } else { + advance + glyph.advance() + } + }) + } + + pub fn char_is_word_separator(&self, i: ByteIndex) -> bool { + assert!(i < self.len()); + self.entry_buffer[i.to_usize()].char_is_word_separator() + } + + pub fn word_separator_count_in_range(&self, range: &Range<ByteIndex>) -> u32 { + let mut spaces = 0; + for index in range.each_index() { + if self.char_is_word_separator(index) { + spaces += 1 + } + } + spaces + } +} + +impl fmt::Debug for GlyphStore { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + writeln!(formatter, "GlyphStore:")?; + let mut detailed_buffer = self.detail_store.detail_buffer.iter(); + for entry in self.entry_buffer.iter() { + if entry.is_simple() { + writeln!( + formatter, + " simple id={:?} advance={:?}", + entry.id(), + entry.advance() + )?; + continue; + } + if entry.is_initial() { + continue; + } + write!(formatter, " complex...")?; + if detailed_buffer.next().is_none() { + continue; + } + writeln!( + formatter, + " detailed id={:?} advance={:?}", + entry.id(), + entry.advance() + )?; + } + Ok(()) + } +} + +/// An iterator over the glyphs in a byte range in a `GlyphStore`. +pub struct GlyphIterator<'a> { + store: &'a GlyphStore, + byte_index: ByteIndex, + byte_range: Range<ByteIndex>, + glyph_range: Option<EachIndex<ByteIndex>>, +} + +impl<'a> GlyphIterator<'a> { + // Slow path when there is a glyph range. + #[inline(never)] + fn next_glyph_range(&mut self) -> Option<GlyphInfo<'a>> { + match self.glyph_range.as_mut().unwrap().next() { + Some(j) => { + Some(GlyphInfo::Detail( + self.store, + self.byte_index, + j.get() as u16, /* ??? */ + )) + }, + None => { + // No more glyphs for current character. Try to get another. + self.glyph_range = None; + self.next() + }, + } + } + + // Slow path when there is a complex glyph. + #[inline(never)] + fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: ByteIndex) -> Option<GlyphInfo<'a>> { + let glyphs = self + .store + .detail_store + .detailed_glyphs_for_entry(i, entry.glyph_count()); + self.glyph_range = Some(range::each_index( + ByteIndex(0), + ByteIndex(glyphs.len() as isize), + )); + self.next() + } +} + +impl<'a> Iterator for GlyphIterator<'a> { + type Item = GlyphInfo<'a>; + + // I tried to start with something simpler and apply FlatMap, but the + // inability to store free variables in the FlatMap struct was problematic. + // + // This function consists of the fast path and is designed to be inlined into its caller. The + // slow paths, which should not be inlined, are `next_glyph_range()` and + // `next_complex_glyph()`. + #[inline(always)] + fn next(&mut self) -> Option<GlyphInfo<'a>> { + // Would use 'match' here but it borrows contents in a way that interferes with mutation. + if self.glyph_range.is_some() { + return self.next_glyph_range(); + } + + // No glyph range. Look at next byte. + self.byte_index = self.byte_index + + if self.store.is_rtl { + ByteIndex(-1) + } else { + ByteIndex(1) + }; + let i = self.byte_index; + if !self.byte_range.contains(i) { + return None; + } + debug_assert!(i < self.store.len()); + let entry = self.store.entry_buffer[i.to_usize()]; + if entry.is_simple() { + Some(GlyphInfo::Simple(self.store, i)) + } else { + // Fall back to the slow path. + self.next_complex_glyph(&entry, i) + } + } +} + +/// A single series of glyphs within a text run. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct GlyphRun { + /// The glyphs. + pub glyph_store: Arc<GlyphStore>, + /// The byte range of characters in the containing run. + pub range: Range<ByteIndex>, +} + +impl GlyphRun { + pub fn compare(&self, key: &ByteIndex) -> Ordering { + if *key < self.range.begin() { + Ordering::Greater + } else if *key >= self.range.end() { + Ordering::Less + } else { + Ordering::Equal + } + } + + #[inline] + pub fn is_single_preserved_newline(&self) -> bool { + self.glyph_store.is_single_preserved_newline + } +} |