use crate::parser::{read_i16, read_u16, read_u24, read_u32, read_u8};
use crate::tables::hvar::DeltaSetIndexMap;
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorLayer {
pub layer_glyph_id: u16,
pub palette_index: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PaintRef(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Extend {
Pad,
Repeat,
Reflect,
}
impl Extend {
fn from_wire(v: u8) -> Self {
match v {
1 => Extend::Repeat,
2 => Extend::Reflect,
_ => Extend::Pad,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ColorStop {
pub stop_offset: f32,
pub palette_index: u16,
pub alpha: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ColorLine {
pub extend: Extend,
pub stops: Vec<ColorStop>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Affine2x3 {
pub xx: f32,
pub yx: f32,
pub xy: f32,
pub yy: f32,
pub dx: f32,
pub dy: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)] pub enum CompositeMode {
Clear,
Src,
Dest,
SrcOver,
DestOver,
SrcIn,
DestIn,
SrcOut,
DestOut,
SrcAtop,
DestAtop,
Xor,
Plus,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Multiply,
HslHue,
HslSaturation,
HslColor,
HslLuminosity,
}
impl CompositeMode {
fn from_wire(v: u8) -> Self {
use CompositeMode::*;
match v {
0 => Clear,
1 => Src,
2 => Dest,
3 => SrcOver,
4 => DestOver,
5 => SrcIn,
6 => DestIn,
7 => SrcOut,
8 => DestOut,
9 => SrcAtop,
10 => DestAtop,
11 => Xor,
12 => Plus,
13 => Screen,
14 => Overlay,
15 => Darken,
16 => Lighten,
17 => ColorDodge,
18 => ColorBurn,
19 => HardLight,
20 => SoftLight,
21 => Difference,
22 => Exclusion,
23 => Multiply,
24 => HslHue,
25 => HslSaturation,
26 => HslColor,
27 => HslLuminosity,
_ => Clear,
}
}
pub fn is_bounded(self, source_bounded: bool, backdrop_bounded: bool) -> bool {
use CompositeMode::*;
match self {
Clear => true,
Src | SrcOut => source_bounded,
Dest | DestOut => backdrop_bounded,
SrcIn | DestIn => source_bounded || backdrop_bounded,
_ => source_bounded && backdrop_bounded,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClipBox {
pub x_min: i32,
pub y_min: i32,
pub x_max: i32,
pub y_max: i32,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Paint {
ColrLayers {
layers: Vec<PaintRef>,
},
Solid {
palette_index: u16,
alpha: f32,
},
LinearGradient {
color_line: ColorLine,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
x2: f32,
y2: f32,
},
RadialGradient {
color_line: ColorLine,
x0: f32,
y0: f32,
radius0: f32,
x1: f32,
y1: f32,
radius1: f32,
},
SweepGradient {
color_line: ColorLine,
center_x: f32,
center_y: f32,
start_angle_degrees: f32,
end_angle_degrees: f32,
},
Glyph {
paint: PaintRef,
glyph_id: u16,
},
ColrGlyph {
glyph_id: u16,
},
Transform {
paint: PaintRef,
transform: Affine2x3,
},
Translate {
paint: PaintRef,
dx: f32,
dy: f32,
},
Scale {
paint: PaintRef,
scale_x: f32,
scale_y: f32,
center_x: f32,
center_y: f32,
},
Rotate {
paint: PaintRef,
angle_degrees: f32,
center_x: f32,
center_y: f32,
},
Skew {
paint: PaintRef,
x_skew_degrees: f32,
y_skew_degrees: f32,
center_x: f32,
center_y: f32,
},
Composite {
source: PaintRef,
mode: CompositeMode,
backdrop: PaintRef,
},
}
const MAX_V1_RECORDS: u32 = 1 << 20;
const BOUNDEDNESS_MAX_DEPTH: usize = 64;
const BOUNDEDNESS_BUDGET: u32 = 4096;
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct ColrTable<'a> {
bytes: &'a [u8],
num_base_records: u16,
base_records_offset: u32,
num_layer_records: u16,
layer_records_offset: u32,
base_glyph_paints: Vec<(u16, u32)>,
layer_list: Vec<u32>,
clip_records: Vec<(u16, u16, u32)>,
var_index_map: Option<DeltaSetIndexMap>,
var_index_map_unsupported: bool,
ivs: Option<ItemVariationStore>,
}
impl<'a> ColrTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 14 {
return Err(Error::UnexpectedEof);
}
let version = read_u16(bytes, 0)?;
let num_base_records = read_u16(bytes, 2)?;
let base_records_offset = read_u32(bytes, 4)?;
let layer_records_offset = read_u32(bytes, 8)?;
let num_layer_records = read_u16(bytes, 12)?;
if num_base_records > 0 {
let end = (base_records_offset as u64)
.checked_add(num_base_records as u64 * 6)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
}
if num_layer_records > 0 {
let end = (layer_records_offset as u64)
.checked_add(num_layer_records as u64 * 4)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
}
let mut table = Self {
bytes,
num_base_records,
base_records_offset,
num_layer_records,
layer_records_offset,
base_glyph_paints: Vec::new(),
layer_list: Vec::new(),
clip_records: Vec::new(),
var_index_map: None,
var_index_map_unsupported: false,
ivs: None,
};
if version >= 1 && bytes.len() >= 34 {
table.parse_v1_extras()?;
}
Ok(table)
}
fn parse_v1_extras(&mut self) -> Result<(), Error> {
let bytes = self.bytes;
let base_glyph_list_off = read_u32(bytes, 14)? as usize;
let layer_list_off = read_u32(bytes, 18)? as usize;
let clip_list_off = read_u32(bytes, 22)? as usize;
let var_index_map_off = read_u32(bytes, 26)? as usize;
let ivs_off = read_u32(bytes, 30)? as usize;
if base_glyph_list_off != 0 {
if base_glyph_list_off + 4 > bytes.len() {
return Err(Error::BadOffset);
}
let count = read_u32(bytes, base_glyph_list_off)?;
if count > MAX_V1_RECORDS {
return Err(Error::BadStructure("COLR BaseGlyphList count exceeds cap"));
}
let end = (base_glyph_list_off as u64)
.checked_add(4 + count as u64 * 6)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
self.base_glyph_paints.reserve(count as usize);
for i in 0..count as usize {
let off = base_glyph_list_off + 4 + i * 6;
let gid = read_u16(bytes, off)?;
let paint_off = read_u32(bytes, off + 2)?;
let abs = (base_glyph_list_off as u64)
.checked_add(paint_off as u64)
.ok_or(Error::BadOffset)?;
if paint_off == 0 || abs >= bytes.len() as u64 {
return Err(Error::BadOffset);
}
self.base_glyph_paints.push((gid, abs as u32));
}
}
if layer_list_off != 0 {
if layer_list_off + 4 > bytes.len() {
return Err(Error::BadOffset);
}
let count = read_u32(bytes, layer_list_off)?;
if count > MAX_V1_RECORDS {
return Err(Error::BadStructure("COLR LayerList count exceeds cap"));
}
let end = (layer_list_off as u64)
.checked_add(4 + count as u64 * 4)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
self.layer_list.reserve(count as usize);
for i in 0..count as usize {
let off = layer_list_off + 4 + i * 4;
let paint_off = read_u32(bytes, off)?;
let abs = (layer_list_off as u64)
.checked_add(paint_off as u64)
.ok_or(Error::BadOffset)?;
if paint_off == 0 || abs >= bytes.len() as u64 {
return Err(Error::BadOffset);
}
self.layer_list.push(abs as u32);
}
}
if clip_list_off != 0 {
if clip_list_off + 5 > bytes.len() {
return Err(Error::BadOffset);
}
let format = read_u8(bytes, clip_list_off)?;
if format == 1 {
let count = read_u32(bytes, clip_list_off + 1)?;
if count > MAX_V1_RECORDS {
return Err(Error::BadStructure("COLR ClipList count exceeds cap"));
}
let end = (clip_list_off as u64)
.checked_add(5 + count as u64 * 7)
.ok_or(Error::BadOffset)?;
if end > bytes.len() as u64 {
return Err(Error::BadOffset);
}
self.clip_records.reserve(count as usize);
for i in 0..count as usize {
let off = clip_list_off + 5 + i * 7;
let start = read_u16(bytes, off)?;
let end_gid = read_u16(bytes, off + 2)?;
let box_off = read_u24(bytes, off + 4)?;
let abs = (clip_list_off as u64)
.checked_add(box_off as u64)
.ok_or(Error::BadOffset)?;
if box_off == 0 || abs >= bytes.len() as u64 {
return Err(Error::BadOffset);
}
self.clip_records.push((start, end_gid, abs as u32));
}
}
}
if var_index_map_off != 0 {
if var_index_map_off >= bytes.len() {
return Err(Error::BadOffset);
}
match DeltaSetIndexMap::parse(&bytes[var_index_map_off..]) {
Ok(map) => self.var_index_map = Some(map),
Err(_) => self.var_index_map_unsupported = true,
}
}
if ivs_off != 0 {
if ivs_off >= bytes.len() {
return Err(Error::BadOffset);
}
self.ivs = Some(ItemVariationStore::parse(&bytes[ivs_off..])?);
}
Ok(())
}
fn find_base_record(&self, glyph_id: u16) -> Option<(u16, u16)> {
let base = self.base_records_offset as usize;
let mut lo = 0i32;
let mut hi = self.num_base_records as i32 - 1;
while lo <= hi {
let mid = ((lo + hi) >> 1) as usize;
let off = base + mid * 6;
let gid = read_u16(self.bytes, off).ok()?;
match gid.cmp(&glyph_id) {
std::cmp::Ordering::Less => lo = mid as i32 + 1,
std::cmp::Ordering::Greater => hi = mid as i32 - 1,
std::cmp::Ordering::Equal => {
let first = read_u16(self.bytes, off + 2).ok()?;
let count = read_u16(self.bytes, off + 4).ok()?;
return Some((first, count));
}
}
}
None
}
pub fn layers(&self, glyph_id: u16) -> Vec<ColorLayer> {
let (first, count) = match self.find_base_record(glyph_id) {
Some(p) => p,
None => return Vec::new(),
};
let mut out = Vec::with_capacity(count as usize);
let layer_base = self.layer_records_offset as usize;
for i in 0..count {
let idx = first as usize + i as usize;
if idx >= self.num_layer_records as usize {
break;
}
let off = layer_base + idx * 4;
let layer_glyph_id = match read_u16(self.bytes, off) {
Ok(v) => v,
Err(_) => break,
};
let palette_index = match read_u16(self.bytes, off + 2) {
Ok(v) => v,
Err(_) => break,
};
out.push(ColorLayer {
layer_glyph_id,
palette_index,
});
}
out
}
pub fn num_base_records(&self) -> u16 {
self.num_base_records
}
pub fn has_paint_graph(&self) -> bool {
!self.base_glyph_paints.is_empty()
}
pub fn num_base_glyph_paint_records(&self) -> u32 {
self.base_glyph_paints.len() as u32
}
pub fn base_glyph_paint(&self, glyph_id: u16) -> Option<PaintRef> {
self.base_glyph_paints
.binary_search_by_key(&glyph_id, |&(g, _)| g)
.ok()
.map(|i| PaintRef(self.base_glyph_paints[i].1))
}
pub fn base_glyph_paint_records(&self) -> impl Iterator<Item = (u16, PaintRef)> + '_ {
self.base_glyph_paints
.iter()
.map(|&(g, off)| (g, PaintRef(off)))
}
pub fn layer_list_len(&self) -> u32 {
self.layer_list.len() as u32
}
pub fn var_index_map_unsupported(&self) -> bool {
self.var_index_map_unsupported
}
pub fn has_variations(&self) -> bool {
self.ivs.is_some()
}
pub fn clip_box(&self, glyph_id: u16, coords: &[f32]) -> Option<ClipBox> {
let idx = self
.clip_records
.partition_point(|&(start, _, _)| start <= glyph_id)
.checked_sub(1)?;
let (start, end, abs) = self.clip_records[idx];
if glyph_id < start || glyph_id > end {
return None;
}
let off = abs as usize;
let format = read_u8(self.bytes, off).ok()?;
let x_min = read_i16(self.bytes, off + 1).ok()?;
let y_min = read_i16(self.bytes, off + 3).ok()?;
let x_max = read_i16(self.bytes, off + 5).ok()?;
let y_max = read_i16(self.bytes, off + 7).ok()?;
match format {
1 => Some(ClipBox {
x_min: x_min as i32,
y_min: y_min as i32,
x_max: x_max as i32,
y_max: y_max as i32,
}),
2 => {
let base = read_u32(self.bytes, off + 9).ok()?;
Some(ClipBox {
x_min: (x_min as f32 + self.var_delta(base, 0, coords)).floor() as i32,
y_min: (y_min as f32 + self.var_delta(base, 1, coords)).floor() as i32,
x_max: (x_max as f32 + self.var_delta(base, 2, coords)).ceil() as i32,
y_max: (y_max as f32 + self.var_delta(base, 3, coords)).ceil() as i32,
})
}
_ => None,
}
}
fn var_delta(&self, var_index_base: u32, field: u32, coords: &[f32]) -> f32 {
if var_index_base == 0xFFFF_FFFF {
return 0.0;
}
let Some(ivs) = self.ivs.as_ref() else {
return 0.0;
};
if self.var_index_map_unsupported {
return 0.0;
}
let Some(index) = var_index_base.checked_add(field) else {
return 0.0;
};
let (outer, inner) = match self.var_index_map.as_ref() {
Some(map) => {
let entries = map.entries();
if entries.is_empty() {
return 0.0;
}
let e = entries[(index as usize).min(entries.len() - 1)];
if e == (0xFFFF, 0xFFFF) {
return 0.0;
}
e
}
None => ((index >> 16) as u16, (index & 0xFFFF) as u16),
};
ivs.delta(outer, inner, coords).unwrap_or(0.0)
}
pub fn color_glyph_is_bounded(&self, glyph_id: u16) -> Option<bool> {
let root = self.base_glyph_paint(glyph_id)?;
self.paint_is_bounded(root)
}
pub fn paint_is_bounded(&self, paint: PaintRef) -> Option<bool> {
let mut path = Vec::new();
let mut budget = BOUNDEDNESS_BUDGET;
self.bounded_inner(paint, &mut path, &mut budget)
}
fn bounded_inner(
&self,
paint: PaintRef,
path: &mut Vec<u32>,
budget: &mut u32,
) -> Option<bool> {
if *budget == 0 || path.len() >= BOUNDEDNESS_MAX_DEPTH {
return None;
}
*budget -= 1;
if path.contains(&paint.0) {
return None;
}
path.push(paint.0);
let result = match self.paint(paint, &[])? {
Paint::ColrLayers { layers } => {
let mut all = true;
for layer in layers {
match self.bounded_inner(layer, path, budget) {
Some(b) => all &= b,
None => {
path.pop();
return None;
}
}
}
Some(all)
}
Paint::Solid { .. }
| Paint::LinearGradient { .. }
| Paint::RadialGradient { .. }
| Paint::SweepGradient { .. } => Some(false),
Paint::Glyph { .. } => Some(true),
Paint::ColrGlyph { glyph_id } => {
let root = self.base_glyph_paint(glyph_id);
match root {
Some(root) => self.bounded_inner(root, path, budget),
None => None,
}
}
Paint::Transform { paint, .. }
| Paint::Translate { paint, .. }
| Paint::Scale { paint, .. }
| Paint::Rotate { paint, .. }
| Paint::Skew { paint, .. } => self.bounded_inner(paint, path, budget),
Paint::Composite {
source,
mode,
backdrop,
} => {
let s = self.bounded_inner(source, path, budget);
let b = self.bounded_inner(backdrop, path, budget);
match (s, b) {
(Some(s), Some(b)) => Some(mode.is_bounded(s, b)),
_ => None,
}
}
};
path.pop();
result
}
pub fn paint_format(&self, paint: PaintRef) -> Option<u8> {
read_u8(self.bytes, paint.0 as usize).ok()
}
fn child_paint(&self, base: usize, off: usize) -> Option<PaintRef> {
let rel = read_u24(self.bytes, base + off).ok()?;
if rel == 0 {
return None;
}
let abs = (base as u64).checked_add(rel as u64)?;
if abs >= self.bytes.len() as u64 {
return None;
}
Some(PaintRef(abs as u32))
}
fn color_line(&self, abs: usize, variable: bool, coords: &[f32]) -> Option<ColorLine> {
let extend = Extend::from_wire(read_u8(self.bytes, abs).ok()?);
let num_stops = read_u16(self.bytes, abs + 1).ok()?;
let stride = if variable { 10 } else { 6 };
let mut stops = Vec::with_capacity(num_stops as usize);
for i in 0..num_stops as usize {
let off = abs + 3 + i * stride;
let raw_offset = read_i16(self.bytes, off).ok()?;
let palette_index = read_u16(self.bytes, off + 2).ok()?;
let raw_alpha = read_i16(self.bytes, off + 4).ok()?;
let (d_offset, d_alpha) = if variable {
let base = read_u32(self.bytes, off + 6).ok()?;
(
self.var_delta(base, 0, coords),
self.var_delta(base, 1, coords),
)
} else {
(0.0, 0.0)
};
stops.push(ColorStop {
stop_offset: f2dot14_var(raw_offset, d_offset),
palette_index,
alpha: f2dot14_var(raw_alpha, d_alpha).clamp(0.0, 1.0),
});
}
stops.sort_by(|a, b| {
a.stop_offset
.partial_cmp(&b.stop_offset)
.unwrap_or(std::cmp::Ordering::Equal)
});
Some(ColorLine { extend, stops })
}
fn paint_color_line(
&self,
base: usize,
off: usize,
variable: bool,
coords: &[f32],
) -> Option<ColorLine> {
let rel = read_u24(self.bytes, base + off).ok()?;
if rel == 0 {
return None;
}
let abs = (base as u64).checked_add(rel as u64)?;
if abs >= self.bytes.len() as u64 {
return None;
}
self.color_line(abs as usize, variable, coords)
}
pub fn paint(&self, paint: PaintRef, coords: &[f32]) -> Option<Paint> {
let b = self.bytes;
let p = paint.0 as usize;
let format = read_u8(b, p).ok()?;
match format {
1 => {
let num_layers = read_u8(b, p + 1).ok()? as usize;
let first = read_u32(b, p + 2).ok()? as usize;
let mut layers = Vec::with_capacity(num_layers);
for i in 0..num_layers {
let Some(&abs) = self.layer_list.get(first + i) else {
break;
};
layers.push(PaintRef(abs));
}
Some(Paint::ColrLayers { layers })
}
2 | 3 => {
let palette_index = read_u16(b, p + 1).ok()?;
let raw_alpha = read_i16(b, p + 3).ok()?;
let d_alpha = if format == 3 {
let base = read_u32(b, p + 5).ok()?;
self.var_delta(base, 0, coords)
} else {
0.0
};
Some(Paint::Solid {
palette_index,
alpha: f2dot14_var(raw_alpha, d_alpha).clamp(0.0, 1.0),
})
}
4 | 5 => {
let variable = format == 5;
let color_line = self.paint_color_line(p, 1, variable, coords)?;
let mut v = [0.0f32; 6];
let vb = if variable {
read_u32(b, p + 16).ok()?
} else {
0xFFFF_FFFF
};
for (i, slot) in v.iter_mut().enumerate() {
let raw = read_i16(b, p + 4 + i * 2).ok()?;
let d = if variable {
self.var_delta(vb, i as u32, coords)
} else {
0.0
};
*slot = raw as f32 + d;
}
Some(Paint::LinearGradient {
color_line,
x0: v[0],
y0: v[1],
x1: v[2],
y1: v[3],
x2: v[4],
y2: v[5],
})
}
6 | 7 => {
let variable = format == 7;
let color_line = self.paint_color_line(p, 1, variable, coords)?;
let vb = if variable {
read_u32(b, p + 16).ok()?
} else {
0xFFFF_FFFF
};
let mut v = [0.0f32; 6];
for (i, slot) in v.iter_mut().enumerate() {
let raw = if i == 2 || i == 5 {
read_u16(b, p + 4 + i * 2).ok()? as f32
} else {
read_i16(b, p + 4 + i * 2).ok()? as f32
};
let d = if variable {
self.var_delta(vb, i as u32, coords)
} else {
0.0
};
*slot = raw + d;
}
Some(Paint::RadialGradient {
color_line,
x0: v[0],
y0: v[1],
radius0: v[2],
x1: v[3],
y1: v[4],
radius1: v[5],
})
}
8 | 9 => {
let variable = format == 9;
let color_line = self.paint_color_line(p, 1, variable, coords)?;
let vb = if variable {
read_u32(b, p + 12).ok()?
} else {
0xFFFF_FFFF
};
let cx = read_i16(b, p + 4).ok()?;
let cy = read_i16(b, p + 6).ok()?;
let sa = read_i16(b, p + 8).ok()?;
let ea = read_i16(b, p + 10).ok()?;
let (d0, d1, d2, d3) = if variable {
(
self.var_delta(vb, 0, coords),
self.var_delta(vb, 1, coords),
self.var_delta(vb, 2, coords),
self.var_delta(vb, 3, coords),
)
} else {
(0.0, 0.0, 0.0, 0.0)
};
Some(Paint::SweepGradient {
color_line,
center_x: cx as f32 + d0,
center_y: cy as f32 + d1,
start_angle_degrees: (f2dot14_var(sa, d2) + 1.0) * 180.0,
end_angle_degrees: (f2dot14_var(ea, d3) + 1.0) * 180.0,
})
}
10 => {
let child = self.child_paint(p, 1)?;
let glyph_id = read_u16(b, p + 4).ok()?;
Some(Paint::Glyph {
paint: child,
glyph_id,
})
}
11 => {
let glyph_id = read_u16(b, p + 1).ok()?;
Some(Paint::ColrGlyph { glyph_id })
}
12 | 13 => {
let child = self.child_paint(p, 1)?;
let t_rel = read_u24(b, p + 4).ok()?;
if t_rel == 0 {
return None;
}
let t = (p as u64).checked_add(t_rel as u64)?;
if t >= b.len() as u64 {
return None;
}
let t = t as usize;
let vb = if format == 13 {
read_u32(b, t + 24).ok()?
} else {
0xFFFF_FFFF
};
let mut v = [0.0f32; 6];
for (i, slot) in v.iter_mut().enumerate() {
let raw = crate::parser::read_i32(b, t + i * 4).ok()?;
let d = if format == 13 {
self.var_delta(vb, i as u32, coords)
} else {
0.0
};
*slot = (raw as f32 + d) / 65536.0;
}
Some(Paint::Transform {
paint: child,
transform: Affine2x3 {
xx: v[0],
yx: v[1],
xy: v[2],
yy: v[3],
dx: v[4],
dy: v[5],
},
})
}
14 | 15 => {
let child = self.child_paint(p, 1)?;
let dx = read_i16(b, p + 4).ok()?;
let dy = read_i16(b, p + 6).ok()?;
let (d0, d1) = if format == 15 {
let vb = read_u32(b, p + 8).ok()?;
(self.var_delta(vb, 0, coords), self.var_delta(vb, 1, coords))
} else {
(0.0, 0.0)
};
Some(Paint::Translate {
paint: child,
dx: dx as f32 + d0,
dy: dy as f32 + d1,
})
}
16..=23 => {
let child = self.child_paint(p, 1)?;
let uniform = format >= 20;
let around_center = matches!(format, 18 | 19 | 22 | 23);
let variable = format % 2 == 1;
let n_scales = if uniform { 1 } else { 2 };
let mut off = p + 4;
let mut raw = [0i16; 4];
let n_fields = n_scales + if around_center { 2 } else { 0 };
for slot in raw.iter_mut().take(n_fields) {
*slot = read_i16(b, off).ok()?;
off += 2;
}
let vb = if variable {
read_u32(b, off).ok()?
} else {
0xFFFF_FFFF
};
let d = |i: u32| -> f32 {
if variable {
self.var_delta(vb, i, coords)
} else {
0.0
}
};
let scale_x = f2dot14_var(raw[0], d(0));
let scale_y = if uniform {
scale_x
} else {
f2dot14_var(raw[1], d(1))
};
let (center_x, center_y) = if around_center {
let ci = n_scales as u32;
(
raw[n_scales] as f32 + d(ci),
raw[n_scales + 1] as f32 + d(ci + 1),
)
} else {
(0.0, 0.0)
};
Some(Paint::Scale {
paint: child,
scale_x,
scale_y,
center_x,
center_y,
})
}
24..=27 => {
let child = self.child_paint(p, 1)?;
let around_center = format >= 26;
let variable = format % 2 == 1;
let angle = read_i16(b, p + 4).ok()?;
let (cx, cy) = if around_center {
(read_i16(b, p + 6).ok()?, read_i16(b, p + 8).ok()?)
} else {
(0, 0)
};
let vb_off = if around_center { p + 10 } else { p + 6 };
let vb = if variable {
read_u32(b, vb_off).ok()?
} else {
0xFFFF_FFFF
};
let d = |i: u32| -> f32 {
if variable {
self.var_delta(vb, i, coords)
} else {
0.0
}
};
Some(Paint::Rotate {
paint: child,
angle_degrees: f2dot14_var(angle, d(0)) * 180.0,
center_x: if around_center { cx as f32 + d(1) } else { 0.0 },
center_y: if around_center { cy as f32 + d(2) } else { 0.0 },
})
}
28..=31 => {
let child = self.child_paint(p, 1)?;
let around_center = format >= 30;
let variable = format % 2 == 1;
let xa = read_i16(b, p + 4).ok()?;
let ya = read_i16(b, p + 6).ok()?;
let (cx, cy) = if around_center {
(read_i16(b, p + 8).ok()?, read_i16(b, p + 10).ok()?)
} else {
(0, 0)
};
let vb_off = if around_center { p + 12 } else { p + 8 };
let vb = if variable {
read_u32(b, vb_off).ok()?
} else {
0xFFFF_FFFF
};
let d = |i: u32| -> f32 {
if variable {
self.var_delta(vb, i, coords)
} else {
0.0
}
};
Some(Paint::Skew {
paint: child,
x_skew_degrees: f2dot14_var(xa, d(0)) * 180.0,
y_skew_degrees: f2dot14_var(ya, d(1)) * 180.0,
center_x: if around_center { cx as f32 + d(2) } else { 0.0 },
center_y: if around_center { cy as f32 + d(3) } else { 0.0 },
})
}
32 => {
let source = self.child_paint(p, 1)?;
let mode = CompositeMode::from_wire(read_u8(b, p + 4).ok()?);
let backdrop = self.child_paint(p, 5)?;
Some(Paint::Composite {
source,
mode,
backdrop,
})
}
_ => None,
}
}
}
#[inline]
fn f2dot14_var(raw: i16, delta: f32) -> f32 {
(raw as f32 + delta) / 16384.0
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_colr_one_base_three_layers() -> Vec<u8> {
let mut bytes = vec![0u8; 32];
bytes[0..2].copy_from_slice(&0u16.to_be_bytes());
bytes[2..4].copy_from_slice(&1u16.to_be_bytes());
bytes[4..8].copy_from_slice(&14u32.to_be_bytes());
bytes[8..12].copy_from_slice(&20u32.to_be_bytes());
bytes[12..14].copy_from_slice(&3u16.to_be_bytes());
bytes[14..16].copy_from_slice(&65u16.to_be_bytes());
bytes[16..18].copy_from_slice(&0u16.to_be_bytes());
bytes[18..20].copy_from_slice(&3u16.to_be_bytes());
bytes[20..22].copy_from_slice(&100u16.to_be_bytes());
bytes[22..24].copy_from_slice(&2u16.to_be_bytes());
bytes[24..26].copy_from_slice(&101u16.to_be_bytes());
bytes[26..28].copy_from_slice(&5u16.to_be_bytes());
bytes[28..30].copy_from_slice(&102u16.to_be_bytes());
bytes[30..32].copy_from_slice(&0xFFFFu16.to_be_bytes());
bytes
}
#[test]
fn parses_v0_header() {
let bytes = synth_colr_one_base_three_layers();
let colr = ColrTable::parse(&bytes).expect("parse");
assert_eq!(colr.num_base_records(), 1);
assert!(!colr.has_paint_graph());
assert!(!colr.has_variations());
}
#[test]
fn layers_for_known_base_glyph() {
let bytes = synth_colr_one_base_three_layers();
let colr = ColrTable::parse(&bytes).expect("parse");
let layers = colr.layers(65);
assert_eq!(
layers,
vec![
ColorLayer {
layer_glyph_id: 100,
palette_index: 2
},
ColorLayer {
layer_glyph_id: 101,
palette_index: 5
},
ColorLayer {
layer_glyph_id: 102,
palette_index: 0xFFFF
},
]
);
}
#[test]
fn layers_for_non_base_glyph_is_empty() {
let bytes = synth_colr_one_base_three_layers();
let colr = ColrTable::parse(&bytes).expect("parse");
assert!(colr.layers(0).is_empty());
assert!(colr.layers(64).is_empty());
assert!(colr.layers(66).is_empty());
assert!(colr.layers(0xFFFF).is_empty());
}
#[test]
fn rejects_truncated_header() {
assert!(matches!(
ColrTable::parse(&[0u8; 10]),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_array_past_end() {
let mut bytes = vec![0u8; 14];
bytes[2..4].copy_from_slice(&1u16.to_be_bytes());
bytes[4..8].copy_from_slice(&14u32.to_be_bytes());
assert!(matches!(ColrTable::parse(&bytes), Err(Error::BadOffset)));
}
#[test]
fn binary_search_three_records() {
let mut bytes = vec![0u8; 14 + 18 + 12];
bytes[0..2].copy_from_slice(&0u16.to_be_bytes());
bytes[2..4].copy_from_slice(&3u16.to_be_bytes());
bytes[4..8].copy_from_slice(&14u32.to_be_bytes());
bytes[8..12].copy_from_slice(&32u32.to_be_bytes());
bytes[12..14].copy_from_slice(&3u16.to_be_bytes());
let recs: [(u16, u16, u16); 3] = [(10, 0, 1), (50, 1, 1), (200, 2, 1)];
for (i, (g, first, count)) in recs.iter().enumerate() {
let off = 14 + i * 6;
bytes[off..off + 2].copy_from_slice(&g.to_be_bytes());
bytes[off + 2..off + 4].copy_from_slice(&first.to_be_bytes());
bytes[off + 4..off + 6].copy_from_slice(&count.to_be_bytes());
}
for i in 0..3 {
let off = 32 + i * 4;
bytes[off..off + 2].copy_from_slice(&(1000 + i as u16).to_be_bytes());
bytes[off + 2..off + 4].copy_from_slice(&(i as u16).to_be_bytes());
}
let colr = ColrTable::parse(&bytes).expect("parse");
for (gid, _first, _count) in &recs {
let layers = colr.layers(*gid);
assert_eq!(layers.len(), 1, "gid {gid}");
}
assert!(colr.layers(0).is_empty());
assert!(colr.layers(11).is_empty());
assert!(colr.layers(199).is_empty());
assert!(colr.layers(201).is_empty());
}
}