// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Color glyph layers (OpenType `COLR` v0) MVP.
///
/// Ported from `fontations/skrifa/src/color/*` (Apache-2.0 OR MIT).
const COLR_TAG_COLR : UInt = 0x434F4C52 // "COLR"
///|
fn colr_read_u16_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 2 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
Some((b0 << 8) | b1)
}
}
///|
fn colr_read_u32_be(view : BytesView, offset : Int) -> UInt? {
if offset < 0 || offset + 4 > view.length() {
None
} else {
let b0 = view.at(offset).to_uint()
let b1 = view.at(offset + 1).to_uint()
let b2 = view.at(offset + 2).to_uint()
let b3 = view.at(offset + 3).to_uint()
Some((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
}
}
///|
fn colr_read_u32_be_int(view : BytesView, offset : Int) -> Int? {
match colr_read_u32_be(view, offset) {
None => None
Some(u) => Some(u.to_uint64().to_int())
}
}
///|
pub struct ColorLayer {
glyph_id : GlyphId
palette_index : Int
}
///|
pub fn ColorLayer::glyph_id(self : ColorLayer) -> GlyphId {
self.glyph_id
}
///|
pub fn ColorLayer::palette_index(self : ColorLayer) -> Int {
self.palette_index
}
///|
pub struct ColorGlyphCollection {
priv colr : BytesView?
priv version : Int
priv base_off : Int
priv layer_off : Int
priv num_base : Int
priv num_layers : Int
priv _v1_base_glyph_list_off : Int?
priv _v1_layer_list_off : Int?
priv _v1_clip_list_off : Int?
priv _v1_var_index_map_off : Int?
priv _v1_item_variation_store_off : Int?
}
///|
pub fn ColorGlyphCollection::new(font : FontRef) -> ColorGlyphCollection {
let colr = match font.table(COLR_TAG_COLR) {
None => return ColorGlyphCollection::empty()
Some(v) => v
}
if colr.length() < 14 {
return ColorGlyphCollection::empty()
}
let version = colr_read_u16_be(colr, 0).unwrap_or(-1)
if version != 0 && version != 1 {
return ColorGlyphCollection::empty()
}
if version == 1 && colr.length() < 34 {
return ColorGlyphCollection::empty()
}
let num_base = colr_read_u16_be(colr, 2).unwrap_or(-1)
let base_off = colr_read_u32_be_int(colr, 4).unwrap_or(-1)
let layer_off = colr_read_u32_be_int(colr, 8).unwrap_or(-1)
let num_layers = colr_read_u16_be(colr, 12).unwrap_or(-1)
if num_base < 0 || base_off < 0 || layer_off < 0 || num_layers < 0 {
return ColorGlyphCollection::empty()
}
let base_end = base_off + num_base * 6
let layers_end = layer_off + num_layers * 4
if base_end < base_off || base_end > colr.length() {
return ColorGlyphCollection::empty()
}
if layers_end < layer_off || layers_end > colr.length() {
return ColorGlyphCollection::empty()
}
let mut _v1_base_glyph_list_off : Int? = None
let mut _v1_layer_list_off : Int? = None
let mut _v1_clip_list_off : Int? = None
let mut _v1_var_index_map_off : Int? = None
let mut _v1_item_variation_store_off : Int? = None
if version == 1 {
let bg = colr_read_u32_be_int(colr, 14).unwrap_or(-1)
let ll = colr_read_u32_be_int(colr, 18).unwrap_or(-1)
let cl = colr_read_u32_be_int(colr, 22).unwrap_or(-1)
let vim = colr_read_u32_be_int(colr, 26).unwrap_or(-1)
let ivs = colr_read_u32_be_int(colr, 30).unwrap_or(-1)
// In COLR, Offset32 uses 0 as "NULL".
if bg > 0 && bg < colr.length() {
_v1_base_glyph_list_off = Some(bg)
}
if ll > 0 && ll < colr.length() {
_v1_layer_list_off = Some(ll)
}
if cl > 0 && cl < colr.length() {
_v1_clip_list_off = Some(cl)
}
if vim > 0 && vim < colr.length() {
_v1_var_index_map_off = Some(vim)
}
if ivs > 0 && ivs < colr.length() {
_v1_item_variation_store_off = Some(ivs)
}
}
ColorGlyphCollection::{
colr: Some(colr),
version,
base_off,
layer_off,
num_base,
num_layers,
_v1_base_glyph_list_off,
_v1_layer_list_off,
_v1_clip_list_off,
_v1_var_index_map_off,
_v1_item_variation_store_off,
}
}
///|
fn ColorGlyphCollection::empty() -> ColorGlyphCollection {
ColorGlyphCollection::{
colr: None,
version: 0,
base_off: 0,
layer_off: 0,
num_base: 0,
num_layers: 0,
_v1_base_glyph_list_off: None,
_v1_layer_list_off: None,
_v1_clip_list_off: None,
_v1_var_index_map_off: None,
_v1_item_variation_store_off: None,
}
}
///|
pub fn ColorGlyphCollection::version(self : ColorGlyphCollection) -> Int {
self.version
}
///|
pub fn ColorGlyphCollection::has_colr_v1(self : ColorGlyphCollection) -> Bool {
self.colr is Some(_) && self.version == 1
}
///|
pub fn ColorGlyphCollection::layers(
self : ColorGlyphCollection,
glyph_id : GlyphId,
) -> Array[ColorLayer]? {
let colr = match self.colr {
None => return None
Some(v) => v
}
let gid = glyph_id.to_uint64().to_int()
if gid < 0 {
return None
}
// Base glyph records are sorted by glyphId; binary search.
let mut lo = 0
let mut hi = self.num_base
while lo < hi {
let mid = (lo + hi) / 2
let pos = self.base_off + mid * 6
let mgid = colr_read_u16_be(colr, pos).unwrap_or(-1)
if mgid < gid {
lo = mid + 1
} else {
hi = mid
}
}
if lo < 0 || lo >= self.num_base {
return None
}
let pos = self.base_off + lo * 6
let found = colr_read_u16_be(colr, pos).unwrap_or(-1)
if found != gid {
return None
}
let first_layer = colr_read_u16_be(colr, pos + 2).unwrap_or(-1)
let num = colr_read_u16_be(colr, pos + 4).unwrap_or(-1)
if first_layer < 0 || num < 0 {
return None
}
if first_layer + num > self.num_layers {
return None
}
let out : Array[ColorLayer] = Array::new(capacity=num)
for i in 0.. ColorGlyphCollection {
ColorGlyphCollection::new(self)
}