// 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.
///|
/// Mapping of characters (codepoints, not graphemes) to nominal glyph ids.
///
/// Ported from `fontations/skrifa/src/charmap.rs` (Apache-2.0 OR MIT).
const TAG_CMAP : UInt = 0x636D6170 // "cmap"
///|
const CMAP_TAG_MAXP : UInt = 0x6D617870 // "maxp"
///|
const CMAP_PLATFORM_UNICODE : Int = 0
///|
const CMAP_PLATFORM_ISO : Int = 2
///|
const CMAP_PLATFORM_WINDOWS : Int = 3
///|
const CMAP_ENCODING_MS_SYMBOL : Int = 0
///|
const CMAP_ENCODING_MS_UNICODE_CS : Int = 1
///|
const CMAP_ENCODING_APPLE_UNICODE_32 : Int = 4
///|
const CMAP_ENCODING_APPLE_VARIANT_SELECTOR : Int = 5
///|
const CMAP_ENCODING_MS_UCS_4 : Int = 10
///|
fn cmap_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 cmap_read_i16_be(view : BytesView, offset : Int) -> Int? {
match cmap_read_u16_be(view, offset) {
None => None
Some(u) => if u >= 0x8000 { Some(u - 0x10000) } else { Some(u) }
}
}
///|
fn cmap_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 cmap_read_u32_be_int(view : BytesView, offset : Int) -> Int? {
match cmap_read_u32_be(view, offset) {
None => None
Some(u) => Some(u.to_uint64().to_int())
}
}
///|
fn cmap_read_u8(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 1 > view.length() {
None
} else {
Some(view.at(offset).to_int())
}
}
///|
fn cmap_read_u24_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 3 > view.length() {
None
} else {
let b0 = view.at(offset).to_int()
let b1 = view.at(offset + 1).to_int()
let b2 = view.at(offset + 2).to_int()
Some((b0 << 16) | (b1 << 8) | b2)
}
}
///|
fn add_delta_u16(value : Int, delta : Int) -> Int {
(value + delta) & 0xFFFF
}
///|
pub(all) enum MapVariant {
UseDefault
Variant(GlyphId)
}
///|
priv struct CmapIterLimits {
max_mappings : Int
}
///|
fn CmapIterLimits::default_for_font(font : FontRef) -> CmapIterLimits {
let num_glyphs = match font.table(CMAP_TAG_MAXP) {
None => 0
Some(maxp) => cmap_read_u16_be(maxp, 4).unwrap_or(0)
}
// A conservative upper bound that still allows iteration over most
// real-world cmaps while preventing pathological tables from producing
// huge allocations.
let cap = if num_glyphs <= 0 { 65536 } else { num_glyphs * 256 }
{ max_mappings: cap }
}
///|
fn CmapIterLimits::default() -> CmapIterLimits {
{ max_mappings: 65536 }
}
///|
priv struct CodepointSubtable {
off : Int
format : Int
is_symbol : Bool
}
///|
fn CodepointSubtable::map(
self : CodepointSubtable,
cmap : BytesView,
codepoint : UInt,
) -> GlyphId? {
match self.map_impl(cmap, codepoint) {
Some(gid) => Some(gid)
None =>
if self.is_symbol && codepoint <= (0x00FF |> Int::reinterpret_as_uint) {
self.map_impl(cmap, codepoint + (0xF000 |> Int::reinterpret_as_uint))
} else {
None
}
}
}
///|
fn CodepointSubtable::map_impl(
self : CodepointSubtable,
cmap : BytesView,
codepoint : UInt,
) -> GlyphId? {
let gid = if self.format == 12 {
cmap_map_format12(cmap, self.off, codepoint)
} else if self.format == 4 {
// format 4 only covers BMP.
if codepoint > (0xFFFF |> Int::reinterpret_as_uint) {
None
} else {
cmap_map_format4(cmap, self.off, codepoint.to_uint64().to_int())
}
} else {
None
}
match gid {
None => None
Some(g) => if g.to_u16() == 0 { None } else { Some(g) }
}
}
///|
pub struct Charmap {
priv cmap : BytesView?
priv codepoint_subtable : CodepointSubtable?
priv variant_off : Int?
priv cmap12_limits : CmapIterLimits
}
///|
pub fn Charmap::default() -> Charmap {
{
cmap: None,
codepoint_subtable: None,
variant_off: None,
cmap12_limits: CmapIterLimits::default(),
}
}
///|
pub fn Charmap::Charmap(font : FontRef) -> Charmap {
let limits = CmapIterLimits::default_for_font(font)
match font.table(TAG_CMAP) {
None =>
{
cmap: None,
codepoint_subtable: None,
variant_off: None,
cmap12_limits: limits,
}
Some(cmap) => {
let (codepoint_subtable, variant_off) = cmap_select_mappings(font, cmap)
{
cmap: Some(cmap),
codepoint_subtable,
variant_off,
cmap12_limits: limits,
}
}
}
}
///|
pub fn Charmap::has_map(self : Charmap) -> Bool {
self.codepoint_subtable is Some(_)
}
///|
pub fn Charmap::is_symbol(self : Charmap) -> Bool {
match self.codepoint_subtable {
None => false
Some(sub) => sub.is_symbol
}
}
///|
pub fn Charmap::has_variant_map(self : Charmap) -> Bool {
self.variant_off is Some(_)
}
///|
pub fn Charmap::map(self : Charmap, codepoint : UInt) -> GlyphId? {
match (self.cmap, self.codepoint_subtable) {
(Some(cmap), Some(sub)) => sub.map(cmap, codepoint)
_ => None
}
}
///|
pub fn Charmap::glyph_id(self : Charmap, codepoint : UInt) -> GlyphId? {
self.map(codepoint)
}
///|
pub fn Charmap::mappings(self : Charmap) -> Array[(UInt, GlyphId)] {
match (self.cmap, self.codepoint_subtable) {
(Some(cmap), Some(sub)) =>
if sub.format == 4 {
cmap_iter_format4(cmap, sub.off)
} else if sub.format == 12 {
cmap_iter_format12(cmap, sub.off, self.cmap12_limits)
} else {
Array::new()
}
_ => Array::new()
}
}
///|
pub fn Charmap::map_variant(
self : Charmap,
codepoint : UInt,
selector : UInt,
) -> MapVariant? {
match (self.cmap, self.variant_off) {
(Some(cmap), Some(off)) => cmap_map_variant(cmap, off, codepoint, selector)
_ => None
}
}
///|
pub fn Charmap::variant_mappings(
self : Charmap,
) -> Array[(UInt, UInt, MapVariant)] {
match (self.cmap, self.variant_off) {
(Some(cmap), Some(off)) => cmap_iter_variants(cmap, off)
_ => Array::new()
}
}
///|
pub fn FontRef::charmap(self : FontRef) -> Charmap {
Charmap(self)
}
///|
pub struct MappingIndex {
priv codepoint_index : Int?
priv codepoint_is_symbol : Bool
priv variant_index : Int?
priv cmap12_limits : CmapIterLimits
}
///|
pub fn MappingIndex::default() -> MappingIndex {
{
codepoint_index: None,
codepoint_is_symbol: false,
variant_index: None,
cmap12_limits: CmapIterLimits::default(),
}
}
///|
pub fn MappingIndex::MappingIndex(font : FontRef) -> MappingIndex {
let limits = CmapIterLimits::default_for_font(font)
match font.table(TAG_CMAP) {
None =>
{
codepoint_index: None,
codepoint_is_symbol: false,
variant_index: None,
cmap12_limits: limits,
}
Some(cmap) => {
let (cp_index, cp_is_symbol, var_index) = cmap_select_mapping_indices(
font, cmap,
)
{
codepoint_index: cp_index,
codepoint_is_symbol: cp_is_symbol,
variant_index: var_index,
cmap12_limits: limits,
}
}
}
}
///|
pub fn MappingIndex::charmap(self : MappingIndex, font : FontRef) -> Charmap {
match font.table(TAG_CMAP) {
None =>
{
cmap: None,
codepoint_subtable: None,
variant_off: None,
cmap12_limits: self.cmap12_limits,
}
Some(cmap) => {
let codepoint_subtable = match self.codepoint_index {
None => None
Some(index) =>
match cmap_encoding_record(cmap, index) {
None => None
Some((_, _, off)) => {
let format = cmap_read_u16_be(cmap, off).unwrap_or(-1)
if format == 4 || format == 12 {
Some(CodepointSubtable::{
off,
format,
is_symbol: self.codepoint_is_symbol,
})
} else {
None
}
}
}
}
let variant_off = match self.variant_index {
None => None
Some(index) =>
match cmap_encoding_record(cmap, index) {
None => None
Some((_, _, off)) =>
if cmap_read_u16_be(cmap, off).unwrap_or(-1) == 14 {
Some(off)
} else {
None
}
}
}
{
cmap: Some(cmap),
codepoint_subtable,
variant_off,
cmap12_limits: self.cmap12_limits,
}
}
}
}
///|
fn cmap_select_mappings(
font : FontRef,
cmap : BytesView,
) -> (CodepointSubtable?, Int?) {
let (cp_index, cp_is_symbol, var_index) = cmap_select_mapping_indices(
font, cmap,
)
let codepoint_subtable = match cp_index {
None => None
Some(index) =>
match cmap_encoding_record(cmap, index) {
None => None
Some((_, _, off)) => {
let format = cmap_read_u16_be(cmap, off).unwrap_or(-1)
if format == 4 || format == 12 {
Some(CodepointSubtable::{ off, format, is_symbol: cp_is_symbol })
} else {
None
}
}
}
}
let variant_off = match var_index {
None => None
Some(index) =>
match cmap_encoding_record(cmap, index) {
None => None
Some((_, _, off)) =>
if cmap_read_u16_be(cmap, off).unwrap_or(-1) == 14 {
Some(off)
} else {
None
}
}
}
(codepoint_subtable, variant_off)
}
///|
fn cmap_select_mapping_indices(
_font : FontRef,
cmap : BytesView,
) -> (Int?, Bool, Int?) {
let num_tables = cmap_read_u16_be(cmap, 2).unwrap_or(0)
let mut best_kind = 0
let mut best_index : Int? = None
let mut best_is_symbol = false
let mut variant_index : Int? = None
// Search encoding records in reverse, prioritizing UCS-4 over UCS-2, and
// preferring symbol over all others (HarfBuzz behavior).
for i in 0.. ()
Some((platform, encoding, off)) => {
let format = cmap_read_u16_be(cmap, off).unwrap_or(-1)
if platform == CMAP_PLATFORM_UNICODE &&
encoding == CMAP_ENCODING_APPLE_VARIANT_SELECTOR {
if variant_index is None && format == 14 {
variant_index = Some(index)
}
continue
}
if format != 4 && format != 12 {
continue
}
let kind = if platform == CMAP_PLATFORM_WINDOWS &&
encoding == CMAP_ENCODING_MS_SYMBOL {
3
} else if (
platform == CMAP_PLATFORM_WINDOWS &&
encoding == CMAP_ENCODING_MS_UCS_4
) ||
(
platform == CMAP_PLATFORM_UNICODE &&
encoding == CMAP_ENCODING_APPLE_UNICODE_32
) {
2
} else if platform == CMAP_PLATFORM_ISO ||
platform == CMAP_PLATFORM_UNICODE ||
(
platform == CMAP_PLATFORM_WINDOWS &&
encoding == CMAP_ENCODING_MS_UNICODE_CS
) {
1
} else {
0
}
if kind > best_kind {
best_kind = kind
best_index = Some(index)
best_is_symbol = kind == 3
}
}
}
}
(best_index, best_is_symbol, variant_index)
}
///|
fn cmap_encoding_record(cmap : BytesView, index : Int) -> (Int, Int, Int)? {
if index < 0 {
return None
}
let num_tables = cmap_read_u16_be(cmap, 2).unwrap_or(-1)
if num_tables < 0 || index >= num_tables {
return None
}
let rec = 4 + index * 8
let platform = cmap_read_u16_be(cmap, rec).unwrap_or(-1)
let encoding = cmap_read_u16_be(cmap, rec + 2).unwrap_or(-1)
let off = cmap_read_u32_be_int(cmap, rec + 4).unwrap_or(-1)
if off < 0 || off >= cmap.length() {
None
} else {
Some((platform, encoding, off))
}
}
///|
fn cmap_map_format12(
cmap : BytesView,
base : Int,
codepoint : UInt,
) -> GlyphId? {
let length = cmap_read_u32_be_int(cmap, base + 4).unwrap_or(-1)
if length < 0 || base + length > cmap.length() {
return None
}
let n_groups = cmap_read_u32_be_int(cmap, base + 12).unwrap_or(-1)
if n_groups <= 0 {
return None
}
let groups_base = base + 16
let mut lo = 0
let mut hi = n_groups - 1
while lo <= hi {
let mid = (lo + hi) / 2
let g = groups_base + mid * 12
let start = cmap_read_u32_be(cmap, g).unwrap_or(0)
let end = cmap_read_u32_be(cmap, g + 4).unwrap_or(0)
if codepoint < start {
hi = mid - 1
} else if codepoint > end {
lo = mid + 1
} else {
let start_gid = cmap_read_u32_be(cmap, g + 8).unwrap_or(0)
let gid = start_gid + (codepoint - start)
let gidi = gid.to_uint64().to_int()
if gidi < 0 || gidi > 0xFFFF {
return None
}
return Some(GlyphId(gidi.to_uint16()))
}
}
None
}
///|
fn cmap_map_format4(cmap : BytesView, base : Int, codepoint : Int) -> GlyphId? {
let length = cmap_read_u16_be(cmap, base + 2).unwrap_or(-1)
if length < 0 || base + length > cmap.length() {
return None
}
let seg_count = cmap_read_u16_be(cmap, base + 6).unwrap_or(-1) / 2
if seg_count <= 0 {
return None
}
let end_codes_base = base + 14
let start_codes_base = end_codes_base + 2 * seg_count + 2
let id_delta_base = start_codes_base + 2 * seg_count
let id_range_off_base = id_delta_base + 2 * seg_count
for i in 0.. end_code {
continue
}
let start_code = cmap_read_u16_be(cmap, start_codes_base + i * 2).unwrap_or(
-1,
)
if start_code < 0 {
return None
}
if codepoint < start_code {
return None
}
let delta = cmap_read_i16_be(cmap, id_delta_base + i * 2).unwrap_or(0)
let range_off = cmap_read_u16_be(cmap, id_range_off_base + i * 2).unwrap_or(
-1,
)
if range_off < 0 {
return None
}
if range_off == 0 {
let gid = add_delta_u16(codepoint, delta)
return Some(GlyphId(gid.to_uint16()))
} else {
let ro_pos = id_range_off_base + i * 2
let glyph_pos = ro_pos + range_off + 2 * (codepoint - start_code)
let raw_gid = cmap_read_u16_be(cmap, glyph_pos).unwrap_or(-1)
if raw_gid < 0 {
return None
}
if raw_gid == 0 {
return Some(GlyphId::default())
} else {
let gid = add_delta_u16(raw_gid, delta)
return Some(GlyphId(gid.to_uint16()))
}
}
}
None
}
///|
fn cmap_iter_format12(
cmap : BytesView,
base : Int,
limits : CmapIterLimits,
) -> Array[(UInt, GlyphId)] {
let out : Array[(UInt, GlyphId)] = Array::new()
let length = cmap_read_u32_be_int(cmap, base + 4).unwrap_or(-1)
if length < 0 || base + length > cmap.length() {
return out
}
let n_groups = cmap_read_u32_be_int(cmap, base + 12).unwrap_or(-1)
if n_groups <= 0 {
return out
}
let groups_base = base + 16
let mut produced = 0
for i in 0.. 0 && gidi <= 0xFFFF {
out.push((cp, GlyphId(gidi.to_uint16())))
produced = produced + 1
if produced >= limits.max_mappings {
return out
}
}
cp = cp + (1 |> Int::reinterpret_as_uint)
}
}
out
}
///|
fn cmap_iter_format4(cmap : BytesView, base : Int) -> Array[(UInt, GlyphId)] {
let out : Array[(UInt, GlyphId)] = Array::new()
// BMP only; enumerate all codepoints and reuse `cmap_map_format4`, keeping
// iterator semantics aligned with point lookups.
for cp in 0..<0x10000 {
match cmap_map_format4(cmap, base, cp) {
None => ()
Some(gid) =>
if gid.to_u16() != 0 {
out.push((cp.reinterpret_as_uint(), gid))
}
}
}
out
}
///|
fn cmap_map_variant(
cmap : BytesView,
base : Int,
codepoint : UInt,
selector : UInt,
) -> MapVariant? {
if cmap_read_u16_be(cmap, base).unwrap_or(-1) != 14 {
return None
}
let length = cmap_read_u32_be_int(cmap, base + 2).unwrap_or(-1)
if length < 0 || base + length > cmap.length() {
return None
}
let n_records = cmap_read_u32_be_int(cmap, base + 6).unwrap_or(-1)
if n_records <= 0 {
return None
}
let records_base = base + 10
let want = selector.to_uint64().to_int()
for i in 0.. ()
Some(gid) => return Some(Variant(gid))
}
}
if default_off != 0 {
if cmap_in_default_uvs(cmap, base + default_off, cp) {
return Some(UseDefault)
}
}
return None
}
None
}
///|
fn cmap_in_default_uvs(cmap : BytesView, base : Int, codepoint : Int) -> Bool {
let n_ranges = cmap_read_u32_be_int(cmap, base).unwrap_or(-1)
if n_ranges <= 0 {
return false
}
let ranges_base = base + 4
for i in 0..= start && codepoint <= end {
return true
}
}
false
}
///|
fn cmap_map_non_default_uvs(
cmap : BytesView,
base : Int,
codepoint : Int,
) -> GlyphId? {
let n_maps = cmap_read_u32_be_int(cmap, base).unwrap_or(-1)
if n_maps <= 0 {
return None
}
let maps_base = base + 4
for i in 0.. 0xFFFF {
return None
}
return Some(GlyphId(gid.to_uint16()))
}
None
}
///|
fn cmap_iter_variants(
cmap : BytesView,
base : Int,
) -> Array[(UInt, UInt, MapVariant)] {
let out : Array[(UInt, UInt, MapVariant)] = Array::new()
if cmap_read_u16_be(cmap, base).unwrap_or(-1) != 14 {
return out
}
let length = cmap_read_u32_be_int(cmap, base + 2).unwrap_or(-1)
if length < 0 || base + length > cmap.length() {
return out
}
let n_records = cmap_read_u32_be_int(cmap, base + 6).unwrap_or(-1)
if n_records <= 0 {
return out
}
let records_base = base + 10
for i in 0.. 0 {
let ranges_base = def_base + 4
for j in 0.. Int::reinterpret_as_uint)
}
}
}
}
if non_default_off != 0 {
let non_base = base + non_default_off
let n_maps = cmap_read_u32_be_int(cmap, non_base).unwrap_or(-1)
if n_maps > 0 {
let maps_base = non_base + 4
for j in 0..= 0 && gid > 0 && gid <= 0xFFFF {
out.push(
(
cp.reinterpret_as_uint(),
sel_u,
Variant(GlyphId(gid.to_uint16())),
),
)
}
}
}
}
}
out
}