zng_ext_font/
emoji_util.rs

1/*
2Loaded data is !Send+!Sync so we probably don't need to cache it.
3*/
4
5use std::{fmt, mem::size_of};
6
7use byteorder::{BigEndian, ReadBytesExt};
8use icu_properties::sets;
9use zng_color::{ColorScheme, Rgba, rgba};
10use zng_var::impl_from_and_into_var;
11use zng_view_api::font::GlyphIndex;
12
13pub(super) fn maybe_emoji(c: char) -> bool {
14    sets::emoji().contains(c)
15}
16
17pub(super) fn definitely_emoji(c: char) -> bool {
18    sets::emoji_presentation().contains(c) || is_modifier(c)
19}
20
21pub(super) fn is_modifier(c: char) -> bool {
22    sets::emoji_modifier().contains(c)
23}
24
25pub(super) fn is_component(c: char) -> bool {
26    sets::emoji_component().contains(c)
27}
28
29/*
30https://learn.microsoft.com/en-us/typography/opentype/spec/otff
31All OpenType fonts use Motorola-style byte ordering (Big Endian)
32
33Offset32 = uint32
34 */
35
36// OpenType is Big Endian, table IDs are their ASCII name (4 chars) as an `u32`.
37
38/// Color Palette Table
39const CPAL: u32 = u32::from_be_bytes(*b"CPAL");
40
41/// Color Table.
42const COLR: u32 = u32::from_be_bytes(*b"COLR");
43
44/// CPAL table.
45///
46/// The palettes for a font are available in [`FontFace::color_palettes`].
47///
48/// [`FontFace::color_palettes`]: crate::FontFace::color_palettes
49#[derive(Clone, Debug)]
50pub struct ColorPalettes {
51    num_palettes: u16,
52    num_palette_entries: u16,
53    colors: Box<[Rgba]>,
54    types: Box<[ColorPaletteType]>,
55}
56impl Default for ColorPalettes {
57    /// Empty.
58    fn default() -> Self {
59        Self::empty()
60    }
61}
62impl ColorPalettes {
63    /// No palettes.
64    pub fn empty() -> Self {
65        Self {
66            num_palettes: 0,
67            num_palette_entries: 0,
68            colors: Box::new([]),
69            types: Box::new([]),
70        }
71    }
72
73    /// Load the table, if present in the font.
74    pub fn load(font: &ttf_parser::RawFace) -> std::io::Result<Self> {
75        // ttf_parser::Face does not expose CPAL
76
77        let table = match font.table(ttf_parser::Tag(CPAL)) {
78            Some(t) => t,
79            None => return Ok(Self::empty()),
80        };
81
82        /*
83        https://learn.microsoft.com/en-us/typography/opentype/spec/cpal
84        CPAL version 0
85
86        The CPAL header version 0 is organized as follows:
87        Type 	 Name 	                        Description
88        uint16 	 version 	                    Table version number (=0).
89        uint16 	 numPaletteEntries 	            Number of palette entries in each palette.
90        uint16 	 numPalettes 	                Number of palettes in the table.
91        uint16 	 numColorRecords 	            Total number of color records, combined for all palettes.
92        Offset32 colorRecordsArrayOffset 	    Offset from the beginning of CPAL table to the first ColorRecord.
93        uint16 	 colorRecordIndices[numPalettes] Index of each palette’s first color record in the combined color record array.
94         */
95
96        let mut cursor = std::io::Cursor::new(&table);
97
98        let version = cursor.read_u16::<BigEndian>()?;
99        let num_palette_entries = cursor.read_u16::<BigEndian>()?;
100        let num_palettes = cursor.read_u16::<BigEndian>()?;
101        let _num_color_records = cursor.read_u16::<BigEndian>()?;
102        let color_records_array_offset = cursor.read_u32::<BigEndian>()? as u64;
103
104        let color_record_indices = cursor.position();
105        let mut colors = Vec::with_capacity(num_palettes as usize * num_palette_entries as usize);
106        for i in 0..num_palettes {
107            cursor.set_position(color_record_indices + i as u64 * size_of::<u16>() as u64);
108
109            let color_record_index = cursor.read_u16::<BigEndian>()? as u64;
110            let color_record_offset = color_records_array_offset + color_record_index * size_of::<(u8, u8, u8, u8)>() as u64;
111
112            cursor.set_position(color_record_offset);
113            for _ in 0..num_palette_entries {
114                let b = cursor.read_u8()?;
115                let g = cursor.read_u8()?;
116                let r = cursor.read_u8()?;
117                let a = cursor.read_u8()?;
118
119                colors.push(rgba(r, g, b, a));
120            }
121        }
122
123        let mut palette_types = vec![];
124
125        if version >= 1 {
126            cursor.set_position(color_record_indices + num_palettes as u64 * size_of::<u16>() as u64);
127
128            /*
129            CPAL version 1
130
131            {version..colorRecordIndices[numPalettes]}
132
133            Offset32 paletteTypesArrayOffset 	   Offset from the beginning of CPAL table to the Palette Types Array. Set to 0 if no array is provided.
134            Offset32 paletteLabelsArrayOffset 	   Offset from the beginning of CPAL table to the Palette Labels Array. Set to 0 if no array is provided.
135            Offset32 paletteEntryLabelsArrayOffset Offset from the beginning of CPAL table to the Palette Entry Labels Array. Set to 0 if no array is provided.
136            */
137            let palette_types_array_offset = cursor.read_u32::<BigEndian>()? as u64;
138            let _palette_labels_array_offset = cursor.read_u32::<BigEndian>()? as u64;
139            let _palette_entry_labels_array_offset = cursor.read_u32::<BigEndian>()? as u64;
140
141            if palette_types_array_offset > 0 {
142                palette_types.reserve(num_palettes as usize);
143
144                cursor.set_position(palette_types_array_offset);
145                for _ in 0..num_palettes {
146                    let flags = cursor.read_u32::<BigEndian>()?;
147                    let flags = ColorPaletteType::from_bits(flags).unwrap_or_else(ColorPaletteType::empty);
148                    palette_types.push(flags);
149                }
150            }
151        }
152
153        Ok(Self {
154            num_palettes,
155            num_palette_entries,
156            colors: colors.into_boxed_slice(),
157            types: palette_types.into_boxed_slice(),
158        })
159    }
160
161    /// Number of palettes.
162    pub fn len(&self) -> usize {
163        self.num_palettes as usize
164    }
165
166    /// If the font does not have any color palette.
167    pub fn is_empty(&self) -> bool {
168        self.num_palettes == 0
169    }
170
171    /// Gets the requested palette or the first if it is not found.
172    pub fn palette(&self, p: impl Into<FontColorPalette>) -> Option<ColorPalette> {
173        let i = self.palette_i(p.into());
174        self.palette_get(i.unwrap_or(0))
175    }
176
177    /// Gets the requested palette.
178    pub fn palette_exact(&self, p: impl Into<FontColorPalette>) -> Option<ColorPalette> {
179        let i = self.palette_i(p.into())?;
180        self.palette_get(i)
181    }
182
183    fn palette_i(&self, p: FontColorPalette) -> Option<usize> {
184        match p {
185            FontColorPalette::Light => self
186                .types
187                .iter()
188                .position(|p| p.contains(ColorPaletteType::USABLE_WITH_LIGHT_BACKGROUND)),
189            FontColorPalette::Dark => self
190                .types
191                .iter()
192                .position(|p| p.contains(ColorPaletteType::USABLE_WITH_DARK_BACKGROUND)),
193            FontColorPalette::Index(i) => {
194                if i < self.num_palette_entries {
195                    Some(i as _)
196                } else {
197                    None
198                }
199            }
200        }
201    }
202
203    fn palette_get(&self, i: usize) -> Option<ColorPalette> {
204        let len = self.num_palette_entries as usize;
205        let s = len * i;
206        let e = s + len;
207
208        self.colors.get(s..e).map(|c| ColorPalette {
209            flags: self.types.get(i).copied().unwrap_or_else(ColorPaletteType::empty),
210            colors: c,
211        })
212    }
213
214    /// Iterate over color palettes.
215    pub fn iter(&self) -> impl ExactSizeIterator<Item = ColorPalette> {
216        self.colors
217            .chunks_exact(self.num_palette_entries as _)
218            .enumerate()
219            .map(|(i, c)| ColorPalette {
220                flags: self.types.get(i).copied().unwrap_or_else(ColorPaletteType::empty),
221                colors: c,
222            })
223    }
224}
225
226bitflags! {
227    /// Represents a color palette v1 flag.
228    ///
229    /// See [`ColorPalettes`] for more details.
230    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231    pub struct ColorPaletteType: u32 {
232        /// Palette is appropriate to use when displaying the font on a light background such as white.
233        const USABLE_WITH_LIGHT_BACKGROUND = 0x0001;
234        /// Palette is appropriate to use when displaying the font on a dark background such as black.
235        const USABLE_WITH_DARK_BACKGROUND = 0x0002;
236    }
237}
238
239/// Represents a color palette entry.
240///
241/// See [`ColorPalettes`] for more details.
242pub struct ColorPalette<'a> {
243    /// Palette v1 flags.
244    pub flags: ColorPaletteType,
245    /// Palette colors.
246    pub colors: &'a [Rgba],
247}
248
249/// COLR table.
250///
251/// The color glyphs for a font are available in [`FontFace::color_glyphs`].
252///
253/// [`FontFace::color_glyphs`]: crate::FontFace::color_glyphs
254#[derive(Clone, Debug)]
255pub struct ColorGlyphs {
256    base_glyph_records: Vec<BaseGlyphRecord>,
257    layer_records: Vec<LayerRecord>,
258}
259impl ColorGlyphs {
260    /// No color glyphs.
261    pub fn empty() -> Self {
262        Self {
263            base_glyph_records: vec![],
264            layer_records: vec![],
265        }
266    }
267
268    /// Load the table, if present in the font.
269    pub fn load(font: &ttf_parser::RawFace) -> std::io::Result<Self> {
270        let table = match font.table(ttf_parser::Tag(COLR)) {
271            Some(t) => t,
272            None => return Ok(Self::empty()),
273        };
274
275        /*
276        https://learn.microsoft.com/en-us/typography/opentype/spec/colr#colr-formats
277        COLR version 0
278
279        Type 	 Name 	                Description
280        uint16 	 version 	            Table version number—set to 0.
281        uint16   numBaseGlyphRecords 	Number of BaseGlyph records.
282        Offset32 baseGlyphRecordsOffset	Offset to baseGlyphRecords array.
283        Offset32 layerRecordsOffset 	Offset to layerRecords array.
284        uint16 	 numLayerRecords 	    Number of Layer records.
285        */
286
287        let mut cursor = std::io::Cursor::new(&table);
288
289        let _version = cursor.read_u16::<BigEndian>()?;
290        let num_base_glyph_records = cursor.read_u16::<BigEndian>()?;
291        let base_glyph_records_offset = cursor.read_u32::<BigEndian>()? as u64;
292        let layer_records_offset = cursor.read_u32::<BigEndian>()? as u64;
293        let num_layer_records = cursor.read_u16::<BigEndian>()?;
294
295        let mut base_glyph_records = Vec::with_capacity(num_base_glyph_records as _);
296
297        cursor.set_position(base_glyph_records_offset);
298        for _ in 0..num_base_glyph_records {
299            /*
300            https://learn.microsoft.com/en-us/typography/opentype/spec/colr#baseglyph-and-layer-records
301
302            BaseGlyph record:
303
304            Type   Name            Description
305            uint16 glyphID         Glyph ID of the base glyph.
306            uint16 firstLayerIndex Index (base 0) into the layerRecords array.
307            uint16 numLayers       Number of color layers associated with this glyph.
308            */
309
310            base_glyph_records.push(BaseGlyphRecord {
311                glyph_id: cursor.read_u16::<BigEndian>()?,
312                first_layer_index: cursor.read_u16::<BigEndian>()?,
313                num_layers: cursor.read_u16::<BigEndian>()?,
314            });
315        }
316
317        let mut layer_records = Vec::with_capacity(num_layer_records as _);
318        cursor.set_position(layer_records_offset);
319        for _ in 0..num_layer_records {
320            /*
321            Layer record:
322
323            Type   Name 	    Description
324            uint16 glyphID      Glyph ID of the glyph used for a given layer.
325            uint16 paletteIndex Index (base 0) for a palette entry in the CPAL table.
326            */
327
328            layer_records.push(LayerRecord {
329                glyph_id: cursor.read_u16::<BigEndian>()?,
330                palette_index: cursor.read_u16::<BigEndian>()?,
331            });
332        }
333
334        Ok(Self {
335            base_glyph_records,
336            layer_records,
337        })
338    }
339
340    /// Iterate over color glyphs that replace the `base_glyph` to render in color.
341    ///
342    /// The `base_glyph` is the glyph selected by the font during shaping.
343    ///
344    /// Returns an iterator that *renders* an Emoji by overlaying colored glyphs, from the back (first item)
345    /// to the front (last item). Paired with each glyph is an index in the font's [`ColorPalette::colors`] or
346    /// `None` if the base text color must be used.
347    ///
348    /// Yields the `base_glyph` with no palette color if the font does not provide colored replacements for it.
349    pub fn glyph(&self, base_glyph: GlyphIndex) -> Option<ColorGlyph> {
350        match self.base_glyph_records.binary_search_by_key(&(base_glyph as u16), |e| e.glyph_id) {
351            Ok(i) => {
352                let rec = &self.base_glyph_records[i];
353
354                let s = rec.first_layer_index as usize;
355                let e = s + rec.num_layers as usize;
356                Some(ColorGlyph {
357                    layers: &self.layer_records[s..e],
358                })
359            }
360            Err(_) => None,
361        }
362    }
363
364    /// If the font does not have any colored glyphs.
365    pub fn is_empty(&self) -> bool {
366        self.base_glyph_records.is_empty()
367    }
368
369    /// Number of base glyphs that have colored replacements.
370    pub fn len(&self) -> usize {
371        self.base_glyph_records.len()
372    }
373}
374
375/// Represents all layer glyphs selected by the font to replace a colored glyph.
376///
377/// Get using [`ColorGlyphs::glyph`].
378pub struct ColorGlyph<'a> {
379    layers: &'a [LayerRecord],
380}
381impl<'a> ColorGlyph<'a> {
382    /// Iterate over the layer glyphs and palette color from back to front.
383    pub fn iter(&self) -> impl ExactSizeIterator<Item = (GlyphIndex, Option<usize>)> + 'a {
384        self.layers.iter().map(|l| (l.glyph_id(), l.palette_index()))
385    }
386
387    /// Number of layer glyphs that replace the colored glyph.
388    pub fn layers_len(&self) -> usize {
389        self.layers.len()
390    }
391}
392
393#[derive(Debug, Clone, Copy)]
394struct BaseGlyphRecord {
395    glyph_id: u16,
396    first_layer_index: u16,
397    num_layers: u16,
398}
399
400#[derive(Debug, Clone, Copy)]
401struct LayerRecord {
402    glyph_id: u16,
403    palette_index: u16,
404}
405impl LayerRecord {
406    fn glyph_id(&self) -> GlyphIndex {
407        self.glyph_id as _
408    }
409
410    fn palette_index(&self) -> Option<usize> {
411        if self.palette_index == 0xFFFF {
412            None
413        } else {
414            Some(self.palette_index as _)
415        }
416    }
417}
418
419/// Color palette selector for colored fonts.
420#[derive(Clone, Copy, PartialEq, Eq)]
421pub enum FontColorPalette {
422    /// Select first font palette tagged [`ColorPaletteType::USABLE_WITH_LIGHT_BACKGROUND`], or 0 if the
423    /// font does not tag any palette or no match is found.
424    Light,
425    /// Select first font palette tagged [`ColorPaletteType::USABLE_WITH_DARK_BACKGROUND`], or 0 if the
426    /// font does not tag any palette or no match is found.
427    Dark,
428    /// Select one of the font provided palette by index.
429    ///
430    /// The palette list of a font is available in [`FontFace::color_palettes`]. If the index
431    /// is not found uses the first font palette.
432    ///
433    /// [`FontFace::color_palettes`]: crate::FontFace::color_palettes
434    Index(u16),
435}
436impl fmt::Debug for FontColorPalette {
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        if f.alternate() {
439            write!(f, "FontColorPalette::")?;
440        }
441        match self {
442            Self::Light => write!(f, "Light"),
443            Self::Dark => write!(f, "Dark"),
444            Self::Index(arg0) => f.debug_tuple("Index").field(arg0).finish(),
445        }
446    }
447}
448impl_from_and_into_var! {
449    fn from(index: u16) -> FontColorPalette {
450        FontColorPalette::Index(index)
451    }
452
453    fn from(color_scheme: ColorScheme) -> FontColorPalette {
454        match color_scheme {
455            ColorScheme::Light => FontColorPalette::Light,
456            ColorScheme::Dark => FontColorPalette::Dark,
457        }
458    }
459}