// 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.
///|
/// Minimal CFF/CFF2 DICT parsing helpers (only operators needed for outlines).
///|
priv struct CffDictNum {
value : Double
// Dynamic scaling exponent for FontMatrix parsing (range [-9..0]).
scaling : Int
}
///|
priv struct CffDictInfo {
mut charstrings_offset : Int?
mut var_store_offset : Int?
mut fd_array_offset : Int?
mut fd_select_offset : Int?
mut private_size : Int?
mut private_offset : Int?
mut subrs_offset : Int?
mut font_matrix : CffMatrix?
mut font_matrix_upem : Int?
}
///|
fn cff_dict_read_i32_be(view : BytesView, offset : Int) -> Int? {
if offset < 0 || offset + 4 > 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()
let b3 = view.at(offset + 3).to_int()
let u = (b0.reinterpret_as_uint() << 24) |
(b1.reinterpret_as_uint() << 16) |
(b2.reinterpret_as_uint() << 8) |
b3.reinterpret_as_uint()
Some(u.reinterpret_as_int())
}
}
///|
///|
fn cff_dict_parse(data : BytesView) -> CffDictInfo {
let info = CffDictInfo::{
charstrings_offset: None,
var_store_offset: None,
fd_array_offset: None,
fd_select_offset: None,
private_size: None,
private_offset: None,
subrs_offset: None,
font_matrix: None,
font_matrix_upem: None,
}
let stack : Array[CffDictNum] = Array::new()
let mut pos = 0
while pos < data.length() {
let b0 = data.at(pos).to_int()
pos = pos + 1
if b0 == 28 || b0 == 29 || b0 == 30 || b0 == 255 || (b0 >= 32 && b0 <= 254) {
match cff_dict_parse_number_scaled(data, pos, b0) {
None => break
Some((v, scaling, p)) => {
stack.push({ value: v, scaling })
pos = p
}
}
continue
}
let op = if b0 == 12 {
if pos >= data.length() {
break
}
let b1 = data.at(pos).to_int()
pos = pos + 1
1200 + b1
} else {
b0
}
match op {
// CharStrings offset
17 =>
if stack.length() >= 1 {
info.charstrings_offset = cff_dict_number_to_int(
stack.at(stack.length() - 1).value,
)
}
// CFF2 VarStore offset (ItemVariationStore)
24 =>
if stack.length() >= 1 {
info.var_store_offset = cff_dict_number_to_int(
stack.at(stack.length() - 1).value,
)
}
// FDArray offset (Top DICT, escaped op 12 36)
1236 =>
if stack.length() >= 1 {
info.fd_array_offset = cff_dict_number_to_int(
stack.at(stack.length() - 1).value,
)
}
// FDSelect offset (Top DICT, escaped op 12 37)
1237 =>
if stack.length() >= 1 {
info.fd_select_offset = cff_dict_number_to_int(
stack.at(stack.length() - 1).value,
)
}
// Private DICT range: size, offset
18 =>
if stack.length() >= 2 {
info.private_size = cff_dict_number_to_int(stack.at(0).value)
info.private_offset = cff_dict_number_to_int(stack.at(1).value)
}
// Subrs offset (within Private DICT)
19 =>
if stack.length() >= 1 {
info.subrs_offset = cff_dict_number_to_int(
stack.at(stack.length() - 1).value,
)
}
// FontMatrix (escaped op 12 7)
1207 =>
if stack.length() >= 6 {
let mut max_scaling = -2147483648
for i in 0..<6 {
let n = stack.at(i)
if n.value != 0.0 {
max_scaling = max_scaling.max(n.scaling)
}
}
if max_scaling >= -9 && max_scaling <= 0 {
let upem = cff_dict_pow10((-max_scaling).max(0))
let scale = upem.to_double()
let xx = CffFixed::from_f64(stack.at(0).value * scale)
let yx = CffFixed::from_f64(stack.at(1).value * scale)
let xy = CffFixed::from_f64(stack.at(2).value * scale)
let yy = CffFixed::from_f64(stack.at(3).value * scale)
let dx = CffFixed::from_f64(stack.at(4).value * scale)
let dy = CffFixed::from_f64(stack.at(5).value * scale)
info.font_matrix = Some({ xx, yx, xy, yy, dx, dy })
info.font_matrix_upem = Some(upem)
}
}
_ => ()
}
stack.clear()
}
info
}
///|
fn cff_dict_number_to_int(v : Double) -> Int? {
let r = v.round()
if (v - r).abs() <= 0.000001 {
Some(r.to_int())
} else {
None
}
}
///|
fn cff_dict_pow10(exp : Int) -> Int {
let mut v = 1
for _ in 0.. (Double, Int, Int)? {
// Returns (value, scaling, new_pos). BCD real number per CFF spec.
//
// Note: We only need enough of FreeType's dynamic scaling behavior for
// FontMatrix parsing; we approximate it by clamping fractional digits to 6,
// which matches read-fonts/fontations for the fonts in our parity suite.
let mut i = pos
let mut sign = 1.0
let mut int_part = 0.0
let mut frac_part = 0.0
let mut frac_div = 1.0
let mut have_dot = false
let mut frac_len = 0
let mut in_exp = false
let mut exp_sign = 1
let mut exp_val = 0
let mut done = false
while i < data.length() && !done {
let b = data.at(i).to_int()
i = i + 1
let nibs = Array::from_fixed_array([b >> 4, b & 0x0F])
for n in nibs.iter() {
match n {
0..=9 =>
if in_exp {
exp_val = exp_val * 10 + n
} else if have_dot {
frac_part = frac_part * 10.0 + n.to_double()
frac_div = frac_div * 10.0
frac_len = frac_len + 1
} else {
int_part = int_part * 10.0 + n.to_double()
}
10 => have_dot = true
11 => in_exp = true
12 => {
in_exp = true
exp_sign = -1
}
14 => if !in_exp && int_part == 0.0 && frac_part == 0.0 { sign = -1.0 }
15 => {
done = true
break
}
_ => ()
}
}
}
let mut v = sign * (int_part + frac_part / frac_div)
if exp_val != 0 {
let e = exp_sign * exp_val
let mut p = 1.0
let mut j = 0
let abs_e = if e < 0 { -e } else { e }
while j < abs_e {
p = p * 10.0
j = j + 1
}
if e < 0 {
v = v / p
} else {
v = v * p
}
}
let scaling = if frac_len <= 0 { 0 } else { -frac_len.min(6) }
Some((v, scaling, i))
}
///|
fn cff_dict_parse_real(data : BytesView, pos : Int) -> (Double, Int)? {
// Returns (value, new_pos). BCD real number per CFF spec.
let mut i = pos
let mut sign = 1.0
let mut int_part = 0.0
let mut frac_part = 0.0
let mut frac_div = 1.0
let mut have_dot = false
let mut in_exp = false
let mut exp_sign = 1
let mut exp_val = 0
let mut done = false
while i < data.length() && !done {
let b = data.at(i).to_int()
i = i + 1
let nibs = Array::from_fixed_array([b >> 4, b & 0x0F])
for n in nibs.iter() {
match n {
0..=9 =>
if in_exp {
exp_val = exp_val * 10 + n
} else if have_dot {
frac_part = frac_part * 10.0 + n.to_double()
frac_div = frac_div * 10.0
} else {
int_part = int_part * 10.0 + n.to_double()
}
10 => have_dot = true
11 => in_exp = true
12 => {
in_exp = true
exp_sign = -1
}
14 => if !in_exp && int_part == 0.0 && frac_part == 0.0 { sign = -1.0 }
15 => {
done = true
break
}
_ => ()
}
}
}
let mut v = sign * (int_part + frac_part / frac_div)
if exp_val != 0 {
let e = exp_sign * exp_val
let mut p = 1.0
let mut j = 0
let abs_e = if e < 0 { -e } else { e }
while j < abs_e {
p = p * 10.0
j = j + 1
}
if e < 0 {
v = v / p
} else {
v = v * p
}
}
Some((v, i))
}
///|
fn cff_dict_parse_number(
data : BytesView,
pos : Int,
b0 : Int,
) -> (Double, Int)? {
match b0 {
28 =>
match type2_read_i16_be(data, pos) {
None => None
Some(v) => Some((v.to_double(), pos + 2))
}
29 =>
match cff_dict_read_i32_be(data, pos) {
None => None
Some(v) => Some((v.to_double(), pos + 4))
}
30 => cff_dict_parse_real(data, pos)
// 16.16 fixed point (CFF2 dict may use this form).
255 =>
match type2_read_i32_be(data, pos) {
None => None
Some(bits) => Some((bits.to_double() / 65536.0, pos + 4))
}
32..=246 => Some(((b0 - 139).to_double(), pos))
247..=250 =>
if pos >= data.length() {
None
} else {
let b1 = data.at(pos).to_int()
Some((((b0 - 247) * 256 + b1 + 108).to_double(), pos + 1))
}
251..=254 =>
if pos >= data.length() {
None
} else {
let b1 = data.at(pos).to_int()
Some(((-(b0 - 251) * 256 - b1 - 108).to_double(), pos + 1))
}
_ => None
}
}
///|
fn cff_dict_parse_number_scaled(
data : BytesView,
pos : Int,
b0 : Int,
) -> (Double, Int, Int)? {
match b0 {
30 => cff_dict_parse_real_scaled(data, pos)
_ =>
match cff_dict_parse_number(data, pos, b0) {
None => None
Some((v, p)) => Some((v, 0, p))
}
}
}
///|
priv struct CffPrivateDictHintInfo {
hint_params : HintParams
mut subrs_offset : Int?
mut store_index : Int
}
///|
fn CffPrivateDictHintInfo::default() -> CffPrivateDictHintInfo {
{ hint_params: HintParams::default(), subrs_offset: None, store_index: 0 }
}
///|
fn cff_dict_stack_to_fixed(values : Array[Double]) -> Array[CffFixed] {
let out : Array[CffFixed] = Array::new()
for v in values.iter() {
out.push(CffFixed::from_f64(v))
}
out
}
///|
fn cff_dict_stack_apply_delta_prefix_sum(stack : Array[Double]) -> Unit {
if stack.length() <= 1 {
return
}
let mut sum = 0.0
for i in 0.. Bool {
if stack.length() <= 0 {
return false
}
let n = match type2_number_to_int(stack.at(stack.length() - 1)) {
None => return false
Some(v) => v
}
stack.pop() |> ignore
if n < 0 {
return false
}
let region_count = Type2BlendState::region_count(blend_state)
let operand_count = n * (region_count + 1)
if operand_count < 0 || stack.length() < operand_count {
return false
}
let start = stack.length() - operand_count
let values_start = start
let deltas_start = start + n
for region_ix in 0.. new_len {
stack.pop() |> ignore
}
true
}
///|
fn cff_private_dict_hint_info(
private_dict : BytesView,
blend_state : Type2BlendState?,
) -> CffPrivateDictHintInfo {
let info = CffPrivateDictHintInfo::default()
let stack : Array[Double] = Array::new()
let mut pos = 0
while pos < private_dict.length() {
let b0 = private_dict.at(pos).to_int()
pos = pos + 1
if b0 == 28 || b0 == 29 || b0 == 30 || b0 == 255 || (b0 >= 32 && b0 <= 254) {
match cff_dict_parse_number(private_dict, pos, b0) {
None => break
Some((v, p)) => {
stack.push(v)
pos = p
}
}
continue
}
let op = if b0 == 12 {
if pos >= private_dict.length() {
break
}
let b1 = private_dict.at(pos).to_int()
pos = pos + 1
1200 + b1
} else {
b0
}
match op {
6 => {
cff_dict_stack_apply_delta_prefix_sum(stack)
info.hint_params.blues = CffBlues(
cff_dict_stack_to_fixed(stack).op_as_view(),
)
}
7 => {
cff_dict_stack_apply_delta_prefix_sum(stack)
info.hint_params.other_blues = CffBlues(
cff_dict_stack_to_fixed(stack).op_as_view(),
)
}
8 => {
cff_dict_stack_apply_delta_prefix_sum(stack)
info.hint_params.family_blues = CffBlues(
cff_dict_stack_to_fixed(stack).op_as_view(),
)
}
9 => {
cff_dict_stack_apply_delta_prefix_sum(stack)
info.hint_params.family_other_blues = CffBlues(
cff_dict_stack_to_fixed(stack).op_as_view(),
)
}
// BlueScale/Shift/Fuzz/LanguageGroup (escaped opcodes 12 9.. etc).
1209 =>
if stack.length() > 0 {
info.hint_params.blue_scale = CffFixed::from_f64(
stack.at(stack.length() - 1),
)
}
1210 =>
if stack.length() > 0 {
info.hint_params.blue_shift = CffFixed::from_f64(
stack.at(stack.length() - 1),
)
}
1211 =>
if stack.length() > 0 {
info.hint_params.blue_fuzz = CffFixed::from_f64(
stack.at(stack.length() - 1),
)
}
1217 =>
if stack.length() > 0 {
match cff_dict_number_to_int(stack.at(stack.length() - 1)) {
None => ()
Some(v) => info.hint_params.language_group = v
}
}
// Subrs offset (within Private DICT)
19 =>
if stack.length() > 0 {
match cff_dict_number_to_int(stack.at(stack.length() - 1)) {
None => ()
Some(v) => info.subrs_offset = Some(v)
}
}
// VariationStoreIndex (Private DICT)
22 =>
if stack.length() > 0 {
match cff_dict_number_to_int(stack.at(stack.length() - 1)) {
None => ()
Some(v) => {
info.store_index = v
match blend_state {
None => ()
Some(bs) => Type2BlendState::set_store_index(bs, v) |> ignore
}
}
}
}
// Blend (Private DICT): applies blending to the current operand stack
// and leaves the blended values for the next operator.
23 =>
match blend_state {
None => ()
Some(bs) => cff_dict_stack_apply_blend(stack, bs) |> ignore
}
_ => ()
}
// Blend is a stack transformer; other operators consume the stack.
if op != 23 {
stack.clear()
}
}
info
}