// 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(all) enum ColorGlyphFormat {
ColrV0
ColrV1
}
///|
pub struct ColorGlyph {
priv collection : ColorGlyphCollection
priv glyph_id : GlyphId
priv format : ColorGlyphFormat
}
///|
pub fn ColorGlyph::format(self : ColorGlyph) -> ColorGlyphFormat {
self.format
}
///|
pub fn ColorGlyph::layers(self : ColorGlyph) -> Array[ColorLayer]? {
match self.format {
ColrV0 => self.collection.layers(self.glyph_id)
ColrV1 => None
}
}
///|
pub fn[P : ColorPainter] ColorGlyph::paint(
self : ColorGlyph,
location : LocationRef,
painter : P,
) -> Result[Unit, PaintError] {
match self.format {
ColrV1 =>
match self.collection.font {
Some(font) => font.paint_colr_v1_at(self.glyph_id, location, painter)
None => Err(ParseError)
}
ColrV0 =>
match self.collection.layers(self.glyph_id) {
None => Err(GlyphNotFound(self.glyph_id))
Some(layers) => {
for layer in layers {
painter.fill_glyph(
layer.glyph_id(),
None,
Solid(layer.palette_index(), 1.0),
)
}
Ok(())
}
}
}
}
///|
pub struct ColorGlyphCollection {
priv font : FontRef?
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::ColorGlyphCollection(
font : FontRef,
) -> ColorGlyphCollection {
let colr = match font.table(COLR_TAG_COLR) {
None => return ColorGlyphCollection::empty(Some(font))
Some(v) => v
}
if colr.length() < 14 {
return ColorGlyphCollection::empty(Some(font))
}
let version = colr_read_u16_be(colr, 0).unwrap_or(-1)
if version != 0 && version != 1 {
return ColorGlyphCollection::empty(Some(font))
}
if version == 1 && colr.length() < 34 {
return ColorGlyphCollection::empty(Some(font))
}
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(Some(font))
}
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(Some(font))
}
if layers_end < layer_off || layers_end > colr.length() {
return ColorGlyphCollection::empty(Some(font))
}
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)
}
}
{
font: Some(font),
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(font : FontRef?) -> ColorGlyphCollection {
{
font,
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
}
///|
fn ColorGlyphCollection::v1_has_glyph(
self : ColorGlyphCollection,
glyph_id : GlyphId,
) -> Bool {
if self.version != 1 {
return false
}
let colr = match self.colr {
None => return false
Some(v) => v
}
let base_glyph_list_off = match self._v1_base_glyph_list_off {
None => return false
Some(v) => v
}
let gid = glyph_id.to_uint64().to_int()
if gid < 0 {
return false
}
let num = colr_read_u32_be_int(colr, base_glyph_list_off).unwrap_or(-1)
if num < 0 {
return false
}
let records_off = base_glyph_list_off + 4
let records_end = records_off + num * 6
if records_end < records_off || records_end > colr.length() {
return false
}
let mut lo = 0
let mut hi = num
while lo < hi {
let mid = (lo + hi) / 2
let pos = records_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 >= num {
return false
}
let pos = records_off + lo * 6
let found = colr_read_u16_be(colr, pos).unwrap_or(-1)
if found != gid {
return false
}
let paint_off = colr_read_u32_be_int(colr, pos + 2).unwrap_or(-1)
if paint_off <= 0 {
return false
}
let abs_off = base_glyph_list_off + paint_off
abs_off >= 0 && abs_off < colr.length()
}
///|
pub fn ColorGlyphCollection::get_with_format(
self : ColorGlyphCollection,
glyph_id : GlyphId,
glyph_format : ColorGlyphFormat,
) -> ColorGlyph? {
match glyph_format {
ColrV0 =>
if self.layers(glyph_id) is Some(_) {
Some({ collection: self, glyph_id, format: glyph_format })
} else {
None
}
ColrV1 =>
if self.v1_has_glyph(glyph_id) {
Some({ collection: self, glyph_id, format: glyph_format })
} else {
None
}
}
}
///|
pub fn ColorGlyphCollection::get(
self : ColorGlyphCollection,
glyph_id : GlyphId,
) -> ColorGlyph? {
match self.get_with_format(glyph_id, ColrV1) {
None => self.get_with_format(glyph_id, ColrV0)
Some(v) => Some(v)
}
}
///|
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(self)
}