// 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.
///|
/// CFF (OpenType `CFF ` table) outline extraction.
///
/// This supports a single-font CFF FontSet and Type2 charstrings.
///|
const CFF_HEADER_MIN_SIZE : Int = 4
///|
fn cff_read_u16_be(view : BytesView, offset : Int) -> Int? {
type2_read_u16_be(view, offset)
}
///|
fn cff_fdselect_index(
data : BytesView,
offset : Int,
glyph_index : Int,
) -> Int? {
// Same as CFF2 FDSelect formats 0 and 3.
if offset < 0 || offset >= data.length() {
return None
}
let select = data[offset:data.length()]
if select.length() < 1 {
return None
}
let format = select.at(0).to_int()
match format {
0 =>
if glyph_index < 0 || 1 + glyph_index >= select.length() {
None
} else {
Some(select.at(1 + glyph_index).to_int())
}
3 =>
if select.length() < 3 {
None
} else {
let n_ranges = cff_read_u16_be(select, 1).unwrap_or(0)
if n_ranges <= 0 {
return None
}
let ranges_base = 3
let rec_size = 3
let sentinel_off = ranges_base + n_ranges * rec_size
if sentinel_off + 2 > select.length() {
return None
}
let sentinel = cff_read_u16_be(select, sentinel_off).unwrap_or(0)
if glyph_index < 0 || glyph_index >= sentinel {
return None
}
let mut r = 0
while r < n_ranges {
let rec = ranges_base + r * rec_size
if rec + 3 > select.length() {
break
}
let first = cff_read_u16_be(select, rec).unwrap_or(0)
let fd = select.at(rec + 2).to_int()
let next_first = if r + 1 < n_ranges {
cff_read_u16_be(select, rec + rec_size).unwrap_or(sentinel)
} else {
sentinel
}
if glyph_index >= first && glyph_index < next_first {
return Some(fd)
}
r = r + 1
}
None
}
_ => None
}
}
///|
fn cff_index1_total_size(ix : Type2Index) -> Int? {
if ix.count == 0 {
Some(2)
} else {
match type2_index_read_offset(ix, ix.count) {
None => None
Some(end_off) => Some(ix.objects_base + end_off)
}
}
}
///|
fn cff_slice(view : BytesView, start : Int) -> BytesView? {
if start < 0 || start > view.length() {
None
} else {
Some(view[start:view.length()])
}
}
///|
fn cff_scale_for_size(size : @moon_skrifa.Size, upem : Int) -> CffFixed? {
match size.ppem() {
None => None
Some(ppem) =>
if upem <= 0 {
None
} else {
// Match fontations: intermediate 26.6 scale in 16.16.
let ppem_26_6 = (ppem * 64.0).round().to_int()
Some(CffFixed::from_bits(ppem_26_6 * 65536 / upem))
}
}
}
///|
fn cff_scale_for_hinting(scale : CffFixed) -> CffFixed {
// Ported from `scale_for_hinting`.
CffFixed::from_bits((scale.bits + 32) / 64)
}
///|
fn cff_outline_path(
font : @moon_skrifa.FontRef,
gid : @moon_skrifa.GlyphId,
) -> Array[PathElement]? {
let cff = match font.table(TAG_CFF) {
None => return None
Some(v) => v
}
if cff.length() < CFF_HEADER_MIN_SIZE {
return None
}
let hdr_size = cff.at(2).to_int()
if hdr_size < CFF_HEADER_MIN_SIZE || hdr_size > cff.length() {
return None
}
// Name INDEX
let name_ix = match cff_slice(cff, hdr_size) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let mut pos = hdr_size
let name_size = match cff_index1_total_size(name_ix) {
None => return None
Some(v) => v
}
pos = pos + name_size
// Top DICT INDEX
let top_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let top_size = match cff_index1_total_size(top_ix) {
None => return None
Some(v) => v
}
let top_dict = match type2_index_get(top_ix, 0) {
None => return None
Some(v) => v
}
pos = pos + top_size
// String INDEX
let str_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let str_size = match cff_index1_total_size(str_ix) {
None => return None
Some(v) => v
}
pos = pos + str_size
// Global Subrs INDEX
let global_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
// Parse Top DICT for CharStrings and Private DICT.
let dict = cff_dict_parse(top_dict)
let charstrings_off = match dict.charstrings_offset {
None => return None
Some(v) => v
}
let charstrings_ix = match cff_slice(cff, charstrings_off) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let glyph_index = gid.to_uint64().to_int()
if glyph_index < 0 || glyph_index >= charstrings_ix.count {
return None
}
let charstring = match type2_index_get(charstrings_ix, glyph_index) {
None => return None
Some(v) => v
}
if charstring.length() == 0 {
return Some(Array::new())
}
// Optional local Subrs.
//
// For CID-keyed CFF, Private DICT/Subrs may come from FDArray+FDSelect
// instead of Top DICT.
let mut private_size : Int? = dict.private_size
let mut private_off : Int? = dict.private_offset
if dict.fd_array_offset is Some(fd_array_off) {
let fd_select_ix = match dict.fd_select_offset {
None => 0
Some(sel_off) =>
cff_fdselect_index(cff, sel_off, glyph_index).unwrap_or(0)
}
let fd_ix = match cff_slice(cff, fd_array_off) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let fd_data = match type2_index_get(fd_ix, fd_select_ix) {
None => return None
Some(v) => v
}
let fd_dict = cff_dict_parse(fd_data)
private_size = fd_dict.private_size
private_off = fd_dict.private_offset
}
let local_ix : Type2Index? = match (private_size, private_off) {
(Some(size), Some(off)) =>
if size <= 0 || off < 0 || off + size > cff.length() {
None
} else {
let end = off + size
let private = cff[off:end]
let priv_dict = cff_dict_parse(private)
match priv_dict.subrs_offset {
None => None
Some(subrs_off) => {
let subrs_abs = off + subrs_off
match cff_slice(cff, subrs_abs) {
None => None
Some(s) => type2_index1_read(s)
}
}
}
}
_ => None
}
type2_path(charstring, Some(global_ix), local_ix, None)
}
///|
fn cff_hint_scale_for_hinting(
size : @moon_skrifa.Size,
upem : Int,
) -> CffFixed? {
match cff_scale_for_size(size, upem) {
None => None
Some(scale) => Some(cff_scale_for_hinting(scale))
}
}
///|
fn cff_outline_path_hinted(
font : @moon_skrifa.FontRef,
gid : @moon_skrifa.GlyphId,
size : @moon_skrifa.Size,
) -> Array[PathElement]? {
let cff = match font.table(TAG_CFF) {
None => return None
Some(v) => v
}
if cff.length() < CFF_HEADER_MIN_SIZE {
return None
}
let hdr_size = cff.at(2).to_int()
if hdr_size < CFF_HEADER_MIN_SIZE || hdr_size > cff.length() {
return None
}
// Name INDEX
let name_ix = match cff_slice(cff, hdr_size) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let mut pos = hdr_size
let name_size = match cff_index1_total_size(name_ix) {
None => return None
Some(v) => v
}
pos = pos + name_size
// Top DICT INDEX
let top_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let top_size = match cff_index1_total_size(top_ix) {
None => return None
Some(v) => v
}
let top_dict = match type2_index_get(top_ix, 0) {
None => return None
Some(v) => v
}
pos = pos + top_size
// String INDEX
let str_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let str_size = match cff_index1_total_size(str_ix) {
None => return None
Some(v) => v
}
pos = pos + str_size
// Global Subrs INDEX
let global_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let dict = cff_dict_parse(top_dict)
let charstrings_off = match dict.charstrings_offset {
None => return None
Some(v) => v
}
let charstrings_ix = match cff_slice(cff, charstrings_off) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let glyph_index = gid.to_uint64().to_int()
if glyph_index < 0 || glyph_index >= charstrings_ix.count {
return None
}
let charstring = match type2_index_get(charstrings_ix, glyph_index) {
None => return None
Some(v) => v
}
if charstring.length() == 0 {
return Some(Array::new())
}
// Resolve private dict + optional font dict matrix for CID-keyed CFF.
let mut private_size : Int? = dict.private_size
let mut private_off : Int? = dict.private_offset
let mut sub_matrix : CffMatrix? = None
let mut sub_matrix_upem : Int? = None
if dict.fd_array_offset is Some(fd_array_off) {
let fd_select_ix = match dict.fd_select_offset {
None => 0
Some(sel_off) =>
cff_fdselect_index(cff, sel_off, glyph_index).unwrap_or(0)
}
let fd_ix = match cff_slice(cff, fd_array_off) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let fd_data = match type2_index_get(fd_ix, fd_select_ix) {
None => return None
Some(v) => v
}
let fd_dict = cff_dict_parse(fd_data)
private_size = fd_dict.private_size
private_off = fd_dict.private_offset
sub_matrix = fd_dict.font_matrix
sub_matrix_upem = fd_dict.font_matrix_upem
}
// Compute font matrix + adjusted UPEM per FreeType (via fontations).
let upem = outline_units_per_em(font).to_int()
let mut matrix : CffMatrix? = None
let mut matrix_upem = upem
let top_norm : (CffMatrix, Int)? = match
(dict.font_matrix, dict.font_matrix_upem) {
(Some(m), Some(u)) => Some(cff_normalize_font_matrix(m, u))
_ => None
}
match (top_norm, sub_matrix, sub_matrix_upem) {
(Some((top_m, top_u)), Some(sub_m), Some(sub_u)) => {
let scaling = if top_u > 1 && sub_u > 1 { top_u.min(sub_u) } else { 1 }
let combined = cff_matrix_mul_scaled(top_m, sub_m, scaling)
let upem_bits = CffFixed::from_bits(sub_u).mul_div(
CffFixed::from_bits(top_u),
CffFixed::from_bits(scaling),
).bits
let (norm_m, norm_u) = cff_normalize_font_matrix(combined, upem_bits)
if !CffMatrix::is_identity(norm_m) {
matrix = Some(norm_m)
}
matrix_upem = norm_u
}
(Some((top_m, top_u)), _, _) => {
if !CffMatrix::is_identity(top_m) {
matrix = Some(top_m)
}
matrix_upem = top_u
}
(None, Some(sub_m), Some(sub_u)) => {
let (norm_m, norm_u) = cff_normalize_font_matrix(sub_m, sub_u)
if !CffMatrix::is_identity(norm_m) {
matrix = Some(norm_m)
}
matrix_upem = norm_u
}
_ => ()
}
// Compute hint scale, adjusting for matrix-derived UPEM if needed.
let scale = match cff_scale_for_size(size, upem) {
None => return None
Some(v) => v
}
let scale = if matrix_upem != upem && upem > 0 && matrix_upem > 0 {
scale.mul_div(CffFixed::from_bits(upem), CffFixed::from_bits(matrix_upem))
} else {
scale
}
let hint_scale = cff_scale_for_hinting(scale)
// Private DICT + local Subrs (optional). Note: Private DICT may be present
// with size==0 (valid; means all defaults).
let mut priv_info = CffPrivateDictHintInfo::default()
match (private_size, private_off) {
(Some(size), Some(off)) => {
if size < 0 || off < 0 || off + size > cff.length() {
return None
}
let private_dict = cff[off:off + size]
priv_info = cff_private_dict_hint_info(private_dict, None)
}
_ => ()
}
let local_ix : Type2Index? = match (private_off, priv_info.subrs_offset) {
(Some(off), Some(subrs_off)) => {
let subrs_abs = off + subrs_off
match cff_slice(cff, subrs_abs) {
None => None
Some(s) => type2_index1_read(s)
}
}
_ => None
}
let hint_state = HintState(priv_info.hint_params, hint_scale)
type2_path_hinted(
charstring,
Some(global_ix),
local_ix,
None,
hint_state,
matrix,
)
}
///|
fn cff_glyph_count(font : @moon_skrifa.FontRef) -> Int? {
let cff = match font.table(TAG_CFF) {
None => return None
Some(v) => v
}
if cff.length() < CFF_HEADER_MIN_SIZE {
return None
}
let hdr_size = cff.at(2).to_int()
if hdr_size < CFF_HEADER_MIN_SIZE || hdr_size > cff.length() {
return None
}
// Name INDEX
let name_ix = match cff_slice(cff, hdr_size) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let name_size = match cff_index1_total_size(name_ix) {
None => return None
Some(v) => v
}
let mut pos = hdr_size + name_size
// Top DICT INDEX
let top_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let top_size = match cff_index1_total_size(top_ix) {
None => return None
Some(v) => v
}
let top_dict = match type2_index_get(top_ix, 0) {
None => return None
Some(v) => v
}
pos = pos + top_size
// String INDEX
let str_ix = match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
let str_size = match cff_index1_total_size(str_ix) {
None => return None
Some(v) => v
}
pos = pos + str_size
// Global Subrs INDEX (skip over it, but validate)
match cff_slice(cff, pos) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(_) => ()
}
}
let dict = cff_dict_parse(top_dict)
let charstrings_off = match dict.charstrings_offset {
None => return None
Some(v) => v
}
let charstrings_ix = match cff_slice(cff, charstrings_off) {
None => return None
Some(s) =>
match type2_index1_read(s) {
None => return None
Some(ix) => ix
}
}
Some(charstrings_ix.count)
}