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: skrifa::Tag = skrifa::Tag::new(b"CPAL");
40
41const COLR: skrifa::Tag = skrifa::Tag::new(b"COLR");
43
44#[derive(Clone, Copy)]
50pub struct ColorPalettes<'a> {
51 table: read_fonts::FontData<'a>,
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: read_fonts::FontData::new(&[]),
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(crate) fn new<'a>(font: read_fonts::FontRef<'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: read_fonts::FontRef<'a>) -> std::io::Result<ColorPalettes<'a>> {
85 let table = match font.table_data(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.as_bytes()[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.as_ref()[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 =
213 BigEndian::read_u16(&self.table.as_bytes()[self.color_record_indices_offset as usize + i as usize * 2..]) as usize * 4;
214
215 let start = self.color_records_array_offset as usize + byte_i;
216 let palette_len = self.num_palette_entries as usize * 4;
217 Some(ColorPalette {
218 table: &self.table.as_bytes()[start..start + palette_len],
219 flags: self.index_palette_type(i),
220 })
221 } else {
222 None
223 }
224 }
225
226 pub fn iter(&self) -> impl ExactSizeIterator<Item = ColorPalette<'_>> {
228 (0..self.num_palettes).map(|i| self.palette_get(i).unwrap())
229 }
230}
231
232bitflags! {
233 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
237 pub struct ColorPaletteType: u32 {
238 const USABLE_WITH_LIGHT_BACKGROUND = 0x0001;
240 const USABLE_WITH_DARK_BACKGROUND = 0x0002;
242 }
243}
244
245#[non_exhaustive]
249pub struct ColorPalette<'a> {
250 table: &'a [u8],
251 flags: ColorPaletteType,
252}
253impl<'a> ColorPalette<'a> {
254 #[allow(clippy::len_without_is_empty)]
258 pub fn len(&self) -> u16 {
259 (self.table.len() / 4) as u16
260 }
261
262 pub fn index(&self, i: u16) -> Rgba {
264 let i = i as usize * 4;
265 let b = self.table[i];
266 let g = self.table[i + 1];
267 let r = self.table[i + 2];
268 let a = self.table[i + 3];
269 Rgba::new(r, g, b, a)
270 }
271
272 pub fn get(&self, i: u16) -> Option<Rgba> {
274 if i < self.len() { Some(self.index(i)) } else { None }
275 }
276
277 pub fn iter(&self) -> impl ExactSizeIterator<Item = Rgba> + '_ {
279 (0..self.len()).map(|i| self.index(i))
280 }
281
282 pub fn flags(&self) -> ColorPaletteType {
284 self.flags
285 }
286}
287
288#[derive(Clone, Copy)]
294pub struct ColorGlyphs<'a> {
295 table: read_fonts::FontData<'a>,
296 num_base_glyph_records: u16,
297 base_glyph_records_offset: u32,
298 layer_records_offset: u32,
299}
300impl ColorGlyphs<'static> {
301 pub fn empty() -> Self {
303 Self {
304 table: read_fonts::FontData::new(&[]),
305 num_base_glyph_records: 0,
306 base_glyph_records_offset: 0,
307 layer_records_offset: 0,
308 }
309 }
310
311 pub(crate) fn new<'a>(font: read_fonts::FontRef<'a>) -> ColorGlyphs<'a> {
315 match Self::new_impl(font) {
316 Ok(g) => g,
317 Err(e) => {
318 tracing::error!("error parsing color glyphs, {e}");
319 Self::empty()
320 }
321 }
322 }
323 fn new_impl<'a>(font: read_fonts::FontRef<'a>) -> std::io::Result<ColorGlyphs<'a>> {
324 let table = match font.table_data(COLR) {
325 Some(t) => t,
326 None => return Ok(Self::empty()),
327 };
328
329 let mut cursor = std::io::Cursor::new(table);
342
343 let _version = cursor.read_u16::<BigEndian>()?;
344 let num_base_glyph_records = cursor.read_u16::<BigEndian>()?;
345 let base_glyph_records_offset = cursor.read_u32::<BigEndian>()?;
346 let layer_records_offset = cursor.read_u32::<BigEndian>()?;
347
348 Ok(ColorGlyphs {
349 table,
350 num_base_glyph_records,
351 base_glyph_records_offset,
352 layer_records_offset,
353 })
354 }
355}
356impl<'a> ColorGlyphs<'a> {
357 pub fn is_empty(&self) -> bool {
359 self.num_base_glyph_records == 0
360 }
361
362 pub fn len(&self) -> u16 {
364 self.num_base_glyph_records
365 }
366
367 pub fn glyph(&self, base_glyph: GlyphIndex) -> Option<ColorGlyph<'a>> {
376 if self.is_empty() {
377 return None;
378 }
379
380 let (first_layer_index, num_layers) = self.find_base_glyph(base_glyph)?;
381
382 let record_size = 4;
383 let table = &self.table.as_bytes()[self.layer_records_offset as usize + (first_layer_index as usize * record_size)..];
384 Some(ColorGlyph { table, num_layers })
385 }
386
387 fn find_base_glyph(&self, base_glyph: GlyphIndex) -> Option<(u16, u16)> {
389 let base_glyph: u16 = base_glyph.try_into().ok()?;
401
402 let record_size = 6;
403 let base = self.base_glyph_records_offset as usize;
404
405 let mut left = 0;
406 let mut right = self.num_base_glyph_records as isize - 1;
407
408 while left <= right {
409 let mid = (left + right) / 2;
410 let pos = base + mid as usize * record_size;
411
412 if pos + record_size > self.table.len() {
414 return None;
415 }
416
417 let glyph_id = BigEndian::read_u16(&self.table.as_ref()[pos..pos + 2]);
418 match glyph_id.cmp(&base_glyph) {
419 std::cmp::Ordering::Equal => {
420 let first_layer_index = BigEndian::read_u16(&self.table.as_ref()[pos + 2..pos + 4]);
421 let num_layers = BigEndian::read_u16(&self.table.as_ref()[pos + 4..pos + 6]);
422 return if num_layers > 0 {
423 Some((first_layer_index, num_layers))
424 } else {
425 None
426 };
427 }
428 std::cmp::Ordering::Less => left = mid + 1,
429 std::cmp::Ordering::Greater => right = mid - 1,
430 }
431 }
432
433 None
434 }
435}
436
437#[derive(Clone, Copy)]
441pub struct ColorGlyph<'a> {
442 table: &'a [u8],
443 num_layers: u16,
444}
445impl<'a> ColorGlyph<'a> {
446 #[allow(clippy::len_without_is_empty)]
450 pub fn len(&self) -> u16 {
451 self.num_layers
452 }
453
454 pub fn index(&self, layer: u16) -> (GlyphIndex, Option<u16>) {
460 let t = &self.table[layer as usize * 4..];
468 let glyph_id = BigEndian::read_u16(t);
469 let pallet_index = BigEndian::read_u16(&t[2..]);
470 if pallet_index == 0xFFFF {
471 (glyph_id as _, None)
472 } else {
473 (glyph_id as _, Some(pallet_index))
474 }
475 }
476
477 pub fn iter(&self) -> impl ExactSizeIterator<Item = (GlyphIndex, Option<u16>)> + '_ {
479 (0..self.num_layers).map(move |i| self.index(i))
480 }
481}
482
483#[derive(Clone, Copy, PartialEq, Eq)]
485pub enum FontColorPalette {
486 Light,
489 Dark,
492 Index(u16),
499}
500impl fmt::Debug for FontColorPalette {
501 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 if f.alternate() {
503 write!(f, "FontColorPalette::")?;
504 }
505 match self {
506 Self::Light => write!(f, "Light"),
507 Self::Dark => write!(f, "Dark"),
508 Self::Index(arg0) => f.debug_tuple("Index").field(arg0).finish(),
509 }
510 }
511}
512impl_from_and_into_var! {
513 fn from(index: u16) -> FontColorPalette {
514 FontColorPalette::Index(index)
515 }
516
517 fn from(color_scheme: ColorScheme) -> FontColorPalette {
518 match color_scheme {
519 ColorScheme::Light => FontColorPalette::Light,
520 ColorScheme::Dark => FontColorPalette::Dark,
521 _ => FontColorPalette::Light,
522 }
523 }
524}