1use std::{fmt, mem::size_of};
6
7use byteorder::{BigEndian, ByteOrder as _, ReadBytesExt};
8use icu_properties::props::{self, BinaryProperty};
9use zng_color::{ColorScheme, 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 props::Emoji::for_char(c)
15}
16
17pub(super) fn definitely_emoji(c: char) -> bool {
18 props::EmojiPresentation::for_char(c) || is_modifier(c)
19}
20
21pub(super) fn is_modifier(c: char) -> bool {
22 props::EmojiModifier::for_char(c)
23}
24
25pub(super) fn is_component(c: char) -> bool {
26 props::EmojiComponent::for_char(c)
27}
28
29const CPAL: u32 = u32::from_be_bytes(*b"CPAL");
40
41const COLR: u32 = u32::from_be_bytes(*b"COLR");
43
44#[derive(Clone, Copy)]
50pub struct ColorPalettes<'a> {
51 table: &'a [u8],
52 num_palettes: u16,
53 num_palette_entries: u16,
54 color_records_array_offset: u32,
55 color_record_indices_offset: u32,
56 palette_types_array_offset: u32,
58}
59impl ColorPalettes<'static> {
60 pub fn empty() -> Self {
62 Self {
63 table: &[],
64 num_palettes: 0,
65 num_palette_entries: 0,
66 color_records_array_offset: 0,
67 color_record_indices_offset: 0,
68 palette_types_array_offset: 0,
69 }
70 }
71
72 pub fn new<'a>(font: ttf_parser::RawFace<'a>) -> ColorPalettes<'a> {
76 match Self::new_impl(font) {
77 Ok(g) => g,
78 Err(e) => {
79 tracing::error!("error parsing color palettes, {e}");
80 Self::empty()
81 }
82 }
83 }
84 fn new_impl<'a>(font: ttf_parser::RawFace<'a>) -> std::io::Result<ColorPalettes<'a>> {
85 let table = match font.table(ttf_parser::Tag(CPAL)) {
86 Some(t) => t,
87 None => return Ok(Self::empty()),
88 };
89 let mut cursor = std::io::Cursor::new(&table);
104
105 let version = cursor.read_u16::<BigEndian>()?;
106 let num_palette_entries = cursor.read_u16::<BigEndian>()?;
107 let num_palettes = cursor.read_u16::<BigEndian>()?;
108 let _num_color_records = cursor.read_u16::<BigEndian>()?;
109 let color_records_array_offset = cursor.read_u32::<BigEndian>()?;
110
111 let mut palette_types_array_offset = 0;
112 let color_record_indices = cursor.position();
113 if version >= 1 {
114 cursor.set_position(color_record_indices + num_palettes as u64 * size_of::<u16>() as u64);
115
116 palette_types_array_offset = cursor.read_u32::<BigEndian>()?;
126 let _palette_labels_array_offset = cursor.read_u32::<BigEndian>()? as u64;
127 let _palette_entry_labels_array_offset = cursor.read_u32::<BigEndian>()? as u64;
128 }
129
130 Ok(ColorPalettes {
131 table,
132 num_palettes,
133 num_palette_entries,
134 color_record_indices_offset: color_record_indices as u32,
135 color_records_array_offset,
136 palette_types_array_offset,
137 })
138 }
139}
140impl<'a> ColorPalettes<'a> {
141 pub fn len(&self) -> u16 {
143 self.num_palettes
144 }
145
146 pub fn is_empty(&self) -> bool {
148 self.num_palettes == 0
149 }
150
151 pub fn palette(&self, p: impl Into<FontColorPalette>) -> Option<ColorPalette<'a>> {
153 let i = self.palette_i(p.into());
154 self.palette_get(i.unwrap_or(0))
155 }
156
157 pub fn palette_exact(&self, p: impl Into<FontColorPalette>) -> Option<ColorPalette<'a>> {
159 let i = self.palette_i(p.into())?;
160 self.palette_get(i)
161 }
162
163 fn palette_types_iter(&self) -> impl Iterator<Item = ColorPaletteType> + 'a {
164 let mut cursor = std::io::Cursor::new(&self.table[self.palette_types_array_offset as usize..]);
165 let mut i = if self.palette_types_array_offset > 0 {
166 self.num_palettes
167 } else {
168 0
169 };
170 std::iter::from_fn(move || {
171 if i > 0 {
172 i -= 1;
173 let flags = cursor.read_u32::<BigEndian>().ok()?;
174 Some(ColorPaletteType::from_bits_retain(flags))
175 } else {
176 None
177 }
178 })
179 }
180
181 fn palette_i(&self, p: FontColorPalette) -> Option<u16> {
182 match p {
183 FontColorPalette::Light => self
184 .palette_types_iter()
185 .position(|p| p.contains(ColorPaletteType::USABLE_WITH_LIGHT_BACKGROUND))
186 .map(|i| i as u16),
187 FontColorPalette::Dark => self
188 .palette_types_iter()
189 .position(|p| p.contains(ColorPaletteType::USABLE_WITH_DARK_BACKGROUND))
190 .map(|i| i as u16),
191 FontColorPalette::Index(i) => {
192 if i < self.num_palettes {
193 Some(i as _)
194 } else {
195 None
196 }
197 }
198 }
199 }
200
201 fn index_palette_type(&self, i: u16) -> ColorPaletteType {
202 if self.palette_types_array_offset == 0 || i >= self.num_palettes {
203 return ColorPaletteType::empty();
204 }
205 let t = &self.table[self.palette_types_array_offset as usize + i as usize * 4..];
206 let flags = BigEndian::read_u32(t);
207 ColorPaletteType::from_bits_retain(flags)
208 }
209
210 fn palette_get(&self, i: u16) -> Option<ColorPalette<'a>> {
211 if i < self.num_palettes {
212 let byte_i = BigEndian::read_u16(&self.table[self.color_record_indices_offset as usize + i as usize * 2..]) as usize * 4;
213
214 let start = self.color_records_array_offset as usize + byte_i;
215 let palette_len = self.num_palette_entries as usize * 4;
216 Some(ColorPalette {
217 table: &self.table[start..start + palette_len],
218 flags: self.index_palette_type(i),
219 })
220 } else {
221 None
222 }
223 }
224
225 pub fn iter(&self) -> impl ExactSizeIterator<Item = ColorPalette<'_>> {
227 (0..self.num_palettes).map(|i| self.palette_get(i).unwrap())
228 }
229}
230
231bitflags! {
232 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
236 pub struct ColorPaletteType: u32 {
237 const USABLE_WITH_LIGHT_BACKGROUND = 0x0001;
239 const USABLE_WITH_DARK_BACKGROUND = 0x0002;
241 }
242}
243
244#[non_exhaustive]
248pub struct ColorPalette<'a> {
249 table: &'a [u8],
250 flags: ColorPaletteType,
251}
252impl<'a> ColorPalette<'a> {
253 #[allow(clippy::len_without_is_empty)]
257 pub fn len(&self) -> u16 {
258 (self.table.len() / 4) as u16
259 }
260
261 pub fn index(&self, i: u16) -> Rgba {
263 let i = i as usize * 4;
264 let b = self.table[i];
265 let g = self.table[i + 1];
266 let r = self.table[i + 2];
267 let a = self.table[i + 3];
268 Rgba::new(r, g, b, a)
269 }
270
271 pub fn get(&self, i: u16) -> Option<Rgba> {
273 if i < self.len() { Some(self.index(i)) } else { None }
274 }
275
276 pub fn iter(&self) -> impl ExactSizeIterator<Item = Rgba> + '_ {
278 (0..self.len()).map(|i| self.index(i))
279 }
280
281 pub fn flags(&self) -> ColorPaletteType {
283 self.flags
284 }
285}
286
287#[derive(Clone, Copy)]
293pub struct ColorGlyphs<'a> {
294 table: &'a [u8],
295 num_base_glyph_records: u16,
296 base_glyph_records_offset: u32,
297 layer_records_offset: u32,
298}
299impl ColorGlyphs<'static> {
300 pub fn empty() -> Self {
302 Self {
303 table: &[],
304 num_base_glyph_records: 0,
305 base_glyph_records_offset: 0,
306 layer_records_offset: 0,
307 }
308 }
309
310 pub fn new<'a>(font: ttf_parser::RawFace<'a>) -> ColorGlyphs<'a> {
314 match Self::new_impl(font) {
315 Ok(g) => g,
316 Err(e) => {
317 tracing::error!("error parsing color glyphs, {e}");
318 Self::empty()
319 }
320 }
321 }
322 fn new_impl<'a>(font: ttf_parser::RawFace<'a>) -> std::io::Result<ColorGlyphs<'a>> {
323 let table = match font.table(ttf_parser::Tag(COLR)) {
324 Some(t) => t,
325 None => return Ok(Self::empty()),
326 };
327
328 let mut cursor = std::io::Cursor::new(table);
341
342 let _version = cursor.read_u16::<BigEndian>()?;
343 let num_base_glyph_records = cursor.read_u16::<BigEndian>()?;
344 let base_glyph_records_offset = cursor.read_u32::<BigEndian>()?;
345 let layer_records_offset = cursor.read_u32::<BigEndian>()?;
346
347 Ok(ColorGlyphs {
348 table,
349 num_base_glyph_records,
350 base_glyph_records_offset,
351 layer_records_offset,
352 })
353 }
354}
355impl<'a> ColorGlyphs<'a> {
356 pub fn is_empty(&self) -> bool {
358 self.num_base_glyph_records == 0
359 }
360
361 pub fn len(&self) -> u16 {
363 self.num_base_glyph_records
364 }
365
366 pub fn glyph(&self, base_glyph: GlyphIndex) -> Option<ColorGlyph<'a>> {
375 if self.is_empty() {
376 return None;
377 }
378
379 let (first_layer_index, num_layers) = self.find_base_glyph(base_glyph)?;
380
381 let record_size = 4;
382 let table = &self.table[self.layer_records_offset as usize + (first_layer_index as usize * record_size)..];
383 Some(ColorGlyph { table, num_layers })
384 }
385
386 fn find_base_glyph(&self, base_glyph: GlyphIndex) -> Option<(u16, u16)> {
388 let base_glyph: u16 = base_glyph.try_into().ok()?;
400
401 let record_size = 6;
402 let base = self.base_glyph_records_offset as usize;
403
404 let mut left = 0;
405 let mut right = self.num_base_glyph_records as isize - 1;
406
407 while left <= right {
408 let mid = (left + right) / 2;
409 let pos = base + mid as usize * record_size;
410
411 if pos + record_size > self.table.len() {
413 return None;
414 }
415
416 let glyph_id = BigEndian::read_u16(&self.table[pos..pos + 2]);
417 match glyph_id.cmp(&base_glyph) {
418 std::cmp::Ordering::Equal => {
419 let first_layer_index = BigEndian::read_u16(&self.table[pos + 2..pos + 4]);
420 let num_layers = BigEndian::read_u16(&self.table[pos + 4..pos + 6]);
421 return if num_layers > 0 {
422 Some((first_layer_index, num_layers))
423 } else {
424 None
425 };
426 }
427 std::cmp::Ordering::Less => left = mid + 1,
428 std::cmp::Ordering::Greater => right = mid - 1,
429 }
430 }
431
432 None
433 }
434}
435
436#[derive(Clone, Copy)]
440pub struct ColorGlyph<'a> {
441 table: &'a [u8],
442 num_layers: u16,
443}
444impl<'a> ColorGlyph<'a> {
445 #[allow(clippy::len_without_is_empty)]
449 pub fn len(&self) -> u16 {
450 self.num_layers
451 }
452
453 pub fn index(&self, layer: u16) -> (GlyphIndex, Option<u16>) {
459 let t = &self.table[layer as usize * 4..];
467 let glyph_id = BigEndian::read_u16(t);
468 let pallet_index = BigEndian::read_u16(&t[2..]);
469 if pallet_index == 0xFFFF {
470 (glyph_id as _, None)
471 } else {
472 (glyph_id as _, Some(pallet_index))
473 }
474 }
475
476 pub fn iter(&self) -> impl ExactSizeIterator<Item = (GlyphIndex, Option<u16>)> + '_ {
478 (0..self.num_layers).map(move |i| self.index(i))
479 }
480}
481
482#[derive(Clone, Copy, PartialEq, Eq)]
484pub enum FontColorPalette {
485 Light,
488 Dark,
491 Index(u16),
498}
499impl fmt::Debug for FontColorPalette {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 if f.alternate() {
502 write!(f, "FontColorPalette::")?;
503 }
504 match self {
505 Self::Light => write!(f, "Light"),
506 Self::Dark => write!(f, "Dark"),
507 Self::Index(arg0) => f.debug_tuple("Index").field(arg0).finish(),
508 }
509 }
510}
511impl_from_and_into_var! {
512 fn from(index: u16) -> FontColorPalette {
513 FontColorPalette::Index(index)
514 }
515
516 fn from(color_scheme: ColorScheme) -> FontColorPalette {
517 match color_scheme {
518 ColorScheme::Light => FontColorPalette::Light,
519 ColorScheme::Dark => FontColorPalette::Dark,
520 _ => FontColorPalette::Light,
521 }
522 }
523}