// 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 (MVP).
///
/// This supports a single-font CFF FontSet and Type2 charstrings.
///|
const CFF_HEADER_MIN_SIZE : Int = 4
///|
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.sub(start~, end=view.length()))
}
}
///|
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.
let local_ix : Type2Index? = match (dict.private_size, dict.private_offset) {
(Some(size), Some(off)) =>
if size <= 0 || off < 0 || off + size > cff.length() {
None
} else {
let end = off + size
let private = cff.sub(start=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_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)
}