use std::{fmt, mem::size_of};
use byteorder::{BigEndian, ByteOrder as _, ReadBytesExt};
use icu_properties::props::{self, BinaryProperty};
use zng_color::{ColorScheme, Rgba};
use zng_var::impl_from_and_into_var;
use zng_view_api::font::GlyphIndex;
pub(super) fn maybe_emoji(c: char) -> bool {
props::Emoji::for_char(c)
}
pub(super) fn definitely_emoji(c: char) -> bool {
props::EmojiPresentation::for_char(c) || is_modifier(c)
}
pub(super) fn is_modifier(c: char) -> bool {
props::EmojiModifier::for_char(c)
}
pub(super) fn is_component(c: char) -> bool {
props::EmojiComponent::for_char(c)
}
const CPAL: skrifa::Tag = skrifa::Tag::new(b"CPAL");
const COLR: skrifa::Tag = skrifa::Tag::new(b"COLR");
#[derive(Clone, Copy)]
pub struct ColorPalettes<'a> {
table: read_fonts::FontData<'a>,
num_palettes: u16,
num_palette_entries: u16,
color_records_array_offset: u32,
color_record_indices_offset: u32,
palette_types_array_offset: u32,
}
impl ColorPalettes<'static> {
pub fn empty() -> Self {
Self {
table: read_fonts::FontData::new(&[]),
num_palettes: 0,
num_palette_entries: 0,
color_records_array_offset: 0,
color_record_indices_offset: 0,
palette_types_array_offset: 0,
}
}
pub(crate) fn new<'a>(font: read_fonts::FontRef<'a>) -> ColorPalettes<'a> {
match Self::new_impl(font) {
Ok(g) => g,
Err(e) => {
tracing::error!("error parsing color palettes, {e}");
Self::empty()
}
}
}
fn new_impl<'a>(font: read_fonts::FontRef<'a>) -> std::io::Result<ColorPalettes<'a>> {
let table = match font.table_data(CPAL) {
Some(t) => t,
None => return Ok(Self::empty()),
};
let mut cursor = std::io::Cursor::new(&table);
let version = cursor.read_u16::<BigEndian>()?;
let num_palette_entries = cursor.read_u16::<BigEndian>()?;
let num_palettes = cursor.read_u16::<BigEndian>()?;
let _num_color_records = cursor.read_u16::<BigEndian>()?;
let color_records_array_offset = cursor.read_u32::<BigEndian>()?;
let mut palette_types_array_offset = 0;
let color_record_indices = cursor.position();
if version >= 1 {
cursor.set_position(color_record_indices + num_palettes as u64 * size_of::<u16>() as u64);
palette_types_array_offset = cursor.read_u32::<BigEndian>()?;
let _palette_labels_array_offset = cursor.read_u32::<BigEndian>()? as u64;
let _palette_entry_labels_array_offset = cursor.read_u32::<BigEndian>()? as u64;
}
Ok(ColorPalettes {
table,
num_palettes,
num_palette_entries,
color_record_indices_offset: color_record_indices as u32,
color_records_array_offset,
palette_types_array_offset,
})
}
}
impl<'a> ColorPalettes<'a> {
pub fn len(&self) -> u16 {
self.num_palettes
}
pub fn is_empty(&self) -> bool {
self.num_palettes == 0
}
pub fn palette(&self, p: impl Into<FontColorPalette>) -> Option<ColorPalette<'a>> {
let i = self.palette_i(p.into());
self.palette_get(i.unwrap_or(0))
}
pub fn palette_exact(&self, p: impl Into<FontColorPalette>) -> Option<ColorPalette<'a>> {
let i = self.palette_i(p.into())?;
self.palette_get(i)
}
fn palette_types_iter(&self) -> impl Iterator<Item = ColorPaletteType> + 'a {
let mut cursor = std::io::Cursor::new(&self.table.as_bytes()[self.palette_types_array_offset as usize..]);
let mut i = if self.palette_types_array_offset > 0 {
self.num_palettes
} else {
0
};
std::iter::from_fn(move || {
if i > 0 {
i -= 1;
let flags = cursor.read_u32::<BigEndian>().ok()?;
Some(ColorPaletteType::from_bits_retain(flags))
} else {
None
}
})
}
fn palette_i(&self, p: FontColorPalette) -> Option<u16> {
match p {
FontColorPalette::Light => self
.palette_types_iter()
.position(|p| p.contains(ColorPaletteType::USABLE_WITH_LIGHT_BACKGROUND))
.map(|i| i as u16),
FontColorPalette::Dark => self
.palette_types_iter()
.position(|p| p.contains(ColorPaletteType::USABLE_WITH_DARK_BACKGROUND))
.map(|i| i as u16),
FontColorPalette::Index(i) => {
if i < self.num_palettes {
Some(i as _)
} else {
None
}
}
}
}
fn index_palette_type(&self, i: u16) -> ColorPaletteType {
if self.palette_types_array_offset == 0 || i >= self.num_palettes {
return ColorPaletteType::empty();
}
let t = &self.table.as_ref()[self.palette_types_array_offset as usize + i as usize * 4..];
let flags = BigEndian::read_u32(t);
ColorPaletteType::from_bits_retain(flags)
}
fn palette_get(&self, i: u16) -> Option<ColorPalette<'a>> {
if i < self.num_palettes {
let byte_i =
BigEndian::read_u16(&self.table.as_bytes()[self.color_record_indices_offset as usize + i as usize * 2..]) as usize * 4;
let start = self.color_records_array_offset as usize + byte_i;
let palette_len = self.num_palette_entries as usize * 4;
Some(ColorPalette {
table: &self.table.as_bytes()[start..start + palette_len],
flags: self.index_palette_type(i),
})
} else {
None
}
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = ColorPalette<'_>> {
(0..self.num_palettes).map(|i| self.palette_get(i).unwrap())
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColorPaletteType: u32 {
const USABLE_WITH_LIGHT_BACKGROUND = 0x0001;
const USABLE_WITH_DARK_BACKGROUND = 0x0002;
}
}
#[non_exhaustive]
pub struct ColorPalette<'a> {
table: &'a [u8],
flags: ColorPaletteType,
}
impl<'a> ColorPalette<'a> {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u16 {
(self.table.len() / 4) as u16
}
pub fn index(&self, i: u16) -> Rgba {
let i = i as usize * 4;
let b = self.table[i];
let g = self.table[i + 1];
let r = self.table[i + 2];
let a = self.table[i + 3];
Rgba::new(r, g, b, a)
}
pub fn get(&self, i: u16) -> Option<Rgba> {
if i < self.len() { Some(self.index(i)) } else { None }
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = Rgba> + '_ {
(0..self.len()).map(|i| self.index(i))
}
pub fn flags(&self) -> ColorPaletteType {
self.flags
}
}
#[derive(Clone, Copy)]
pub struct ColorGlyphs<'a> {
table: read_fonts::FontData<'a>,
num_base_glyph_records: u16,
base_glyph_records_offset: u32,
layer_records_offset: u32,
}
impl ColorGlyphs<'static> {
pub fn empty() -> Self {
Self {
table: read_fonts::FontData::new(&[]),
num_base_glyph_records: 0,
base_glyph_records_offset: 0,
layer_records_offset: 0,
}
}
pub(crate) fn new<'a>(font: read_fonts::FontRef<'a>) -> ColorGlyphs<'a> {
match Self::new_impl(font) {
Ok(g) => g,
Err(e) => {
tracing::error!("error parsing color glyphs, {e}");
Self::empty()
}
}
}
fn new_impl<'a>(font: read_fonts::FontRef<'a>) -> std::io::Result<ColorGlyphs<'a>> {
let table = match font.table_data(COLR) {
Some(t) => t,
None => return Ok(Self::empty()),
};
let mut cursor = std::io::Cursor::new(table);
let _version = cursor.read_u16::<BigEndian>()?;
let num_base_glyph_records = cursor.read_u16::<BigEndian>()?;
let base_glyph_records_offset = cursor.read_u32::<BigEndian>()?;
let layer_records_offset = cursor.read_u32::<BigEndian>()?;
Ok(ColorGlyphs {
table,
num_base_glyph_records,
base_glyph_records_offset,
layer_records_offset,
})
}
}
impl<'a> ColorGlyphs<'a> {
pub fn is_empty(&self) -> bool {
self.num_base_glyph_records == 0
}
pub fn len(&self) -> u16 {
self.num_base_glyph_records
}
pub fn glyph(&self, base_glyph: GlyphIndex) -> Option<ColorGlyph<'a>> {
if self.is_empty() {
return None;
}
let (first_layer_index, num_layers) = self.find_base_glyph(base_glyph)?;
let record_size = 4;
let table = &self.table.as_bytes()[self.layer_records_offset as usize + (first_layer_index as usize * record_size)..];
Some(ColorGlyph { table, num_layers })
}
fn find_base_glyph(&self, base_glyph: GlyphIndex) -> Option<(u16, u16)> {
let base_glyph: u16 = base_glyph.try_into().ok()?;
let record_size = 6;
let base = self.base_glyph_records_offset as usize;
let mut left = 0;
let mut right = self.num_base_glyph_records as isize - 1;
while left <= right {
let mid = (left + right) / 2;
let pos = base + mid as usize * record_size;
if pos + record_size > self.table.len() {
return None;
}
let glyph_id = BigEndian::read_u16(&self.table.as_ref()[pos..pos + 2]);
match glyph_id.cmp(&base_glyph) {
std::cmp::Ordering::Equal => {
let first_layer_index = BigEndian::read_u16(&self.table.as_ref()[pos + 2..pos + 4]);
let num_layers = BigEndian::read_u16(&self.table.as_ref()[pos + 4..pos + 6]);
return if num_layers > 0 {
Some((first_layer_index, num_layers))
} else {
None
};
}
std::cmp::Ordering::Less => left = mid + 1,
std::cmp::Ordering::Greater => right = mid - 1,
}
}
None
}
}
#[derive(Clone, Copy)]
pub struct ColorGlyph<'a> {
table: &'a [u8],
num_layers: u16,
}
impl<'a> ColorGlyph<'a> {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u16 {
self.num_layers
}
pub fn index(&self, layer: u16) -> (GlyphIndex, Option<u16>) {
let t = &self.table[layer as usize * 4..];
let glyph_id = BigEndian::read_u16(t);
let pallet_index = BigEndian::read_u16(&t[2..]);
if pallet_index == 0xFFFF {
(glyph_id as _, None)
} else {
(glyph_id as _, Some(pallet_index))
}
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (GlyphIndex, Option<u16>)> + '_ {
(0..self.num_layers).map(move |i| self.index(i))
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FontColorPalette {
Light,
Dark,
Index(u16),
}
impl fmt::Debug for FontColorPalette {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "FontColorPalette::")?;
}
match self {
Self::Light => write!(f, "Light"),
Self::Dark => write!(f, "Dark"),
Self::Index(arg0) => f.debug_tuple("Index").field(arg0).finish(),
}
}
}
impl_from_and_into_var! {
fn from(index: u16) -> FontColorPalette {
FontColorPalette::Index(index)
}
fn from(color_scheme: ColorScheme) -> FontColorPalette {
match color_scheme {
ColorScheme::Light => FontColorPalette::Light,
ColorScheme::Dark => FontColorPalette::Dark,
_ => FontColorPalette::Light,
}
}
}