// 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.
///|
/// Persistent state for TrueType embedded hinting.
///
/// Ported from `fontations/skrifa/src/outline/glyf/hint/instance.rs`
/// (Apache-2.0 OR MIT).
const TAG_MAXP_TT : UInt = 0x6D617870 // "maxp"
///|
const TAG_FPGM_TT : UInt = 0x6670676D // "fpgm"
///|
const TAG_PREP_TT : UInt = 0x70726570 // "prep"
///|
const TAG_CVT_TT : UInt = 0x63767420 // "cvt "
///|
const TAG_FVAR_TT : UInt = 0x66766172 // "fvar"
///|
let tt_empty_bytes : Bytes = Bytes::from_array(Array::new().op_as_view())
///|
fn tt_empty_bytes_view() -> BytesView {
tt_empty_bytes.op_as_view()
}
///|
fn tt_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 tt_read_i16_be(view : BytesView, offset : Int) -> Int? {
match tt_read_u16_be(view, offset) {
None => None
Some(u) => Some(if u >= 0x8000 { u - 0x10000 } else { u })
}
}
///|
priv struct TtHintInstance {
mut functions : TtDefinitionSet
mut instructions : TtDefinitionSet
mut cvt : Array[Int]
mut storage : Array[Int]
mut retained : TtRetainedGraphicsState
mut twilight_points : Array[TtPoint]
mut twilight_original : Array[TtPoint]
mut twilight_flags : Array[Int]
mut max_stack : Int
max_call_depth : Int
mut max_function_defs : Int
mut max_instruction_defs : Int
}
///|
fn TtHintInstance::default() -> TtHintInstance {
{
functions: TtDefinitionSet(0),
instructions: TtDefinitionSet(0),
cvt: Array::new(),
storage: Array::new(),
retained: TtRetainedGraphicsState::default(),
twilight_points: Array::new(),
twilight_original: Array::new(),
twilight_flags: Array::new(),
max_stack: 0,
max_call_depth: 32,
max_function_defs: 0,
max_instruction_defs: 0,
}
}
///|
fn TtHintInstance::is_enabled(self : TtHintInstance) -> Bool {
(self.retained.instruct_control & 1) == 0
}
///|
fn TtHintInstance::backward_compatibility(self : TtHintInstance) -> Bool {
if self.retained.target.preserve_linear_metrics() {
true
} else if self.retained.target.is_smooth() {
(self.retained.instruct_control & 0x4) == 0
} else {
false
}
}
///|
fn tt_parse_maxp_limits(maxp : BytesView) -> (Int, Int, Int, Int, Int) {
// Returns (max_function_defs, max_instruction_defs, max_twilight_points, max_stack, max_storage).
let max_function_defs = tt_read_u16_be(maxp, 20).unwrap_or(0)
let max_instruction_defs = tt_read_u16_be(maxp, 22).unwrap_or(0)
let max_twilight_points = tt_read_u16_be(maxp, 16).unwrap_or(0) + 4
let max_stack = tt_read_u16_be(maxp, 24).unwrap_or(0) + 32
let max_storage = tt_read_u16_be(maxp, 18).unwrap_or(0)
(
max_function_defs, max_instruction_defs, max_twilight_points, max_stack, max_storage,
)
}
///|
fn tt_load_cvt(font : @moon_skrifa.FontRef) -> Array[Int] {
match font.table(TAG_CVT_TT) {
None => Array::new()
Some(cvt) => {
let n = cvt.length() / 2
let out : Array[Int] = Array::new()
for i in 0.. Array[Int] {
// CVT values are already in 26.6; scale by (scale_bits >> 6) (16.16).
let s = scale_bits >> 6
let out : Array[Int] = Array::new()
for v in cvt.iter() {
let vv = v.to_int64() * s.to_int64()
let adj = 0x8000 - (if vv < 0 { 1 } else { 0 })
out.push(((vv + adj.to_int64()) >> 16).to_int())
}
out
}
///|
fn tt_fvar_axis_count(font : @moon_skrifa.FontRef) -> Int {
match font.table(TAG_FVAR_TT) {
None => 0
Some(fvar) => tt_read_u16_be(fvar, 8).unwrap_or(0)
}
}
///|
fn tt_make_twilight_zone(point_count : Int) -> TtZone {
let pts : Array[TtPoint] = Array::make(point_count, { x: 0, y: 0 })
let orig : Array[TtPoint] = Array::make(point_count, { x: 0, y: 0 })
let flags : Array[Int] = Array::make(point_count, 0)
let contours : Array[Int] = Array::from_fixed_array([point_count])
{ unscaled: Array::new(), original: orig, points: pts, flags, contours }
}
///|
fn tt_make_empty_glyph_zone() -> TtZone {
{
unscaled: Array::new(),
original: Array::new(),
points: Array::new(),
flags: Array::new(),
contours: Array::new(),
}
}
///|
fn TtHintInstance::reconfigure(
self : TtHintInstance,
font : @moon_skrifa.FontRef,
scale_bits : Int,
rounded_ppem : Int,
target : Target,
coords : ArrayView[@moon_skrifa.NormalizedCoord],
) -> Result[Unit, HintError] {
let maxp = match font.table(TAG_MAXP_TT) {
None => return Err(Unsupported)
Some(v) => v
}
let (
max_function_defs,
max_instruction_defs,
max_twilight_points,
max_stack,
max_storage,
) = tt_parse_maxp_limits(maxp)
self.functions = TtDefinitionSet(max_function_defs)
self.instructions = TtDefinitionSet(max_instruction_defs)
self.max_stack = max_stack
self.max_function_defs = max_function_defs
self.max_instruction_defs = max_instruction_defs
self.storage = Array::make(max_storage, 0)
// Load and scale CVT.
let axis_count = tt_fvar_axis_count(font)
let base_cvt = tt_load_cvt(font)
let cvt = match
cvar_deltas_fixed16_16(font, axis_count, coords, base_cvt.length()) {
None => base_cvt
Some(deltas_bits) => {
let out : Array[Int] = Array::new()
for i in 0.. return Err(e)
Ok(_) => ()
}
match engine.run_program(ControlValue, false) {
Err(e) => return Err(e)
Ok(_) => ()
}
// Persist results.
self.functions = engine.functions
self.instructions = engine.instructions
self.cvt = engine.cvt
self.storage = engine.storage
self.retained = engine.graphics.retained
// Capture twilight state after prep.
self.twilight_points = engine.graphics.twilight.points
self.twilight_original = engine.graphics.twilight.original
self.twilight_flags = engine.graphics.twilight.flags
Ok(())
}
///|
fn TtHintInstance::hint_glyph(
self : TtHintInstance,
fpgm : BytesView,
prep : BytesView,
glyph_code : BytesView,
glyph_zone : TtZone,
is_composite : Bool,
is_pedantic : Bool,
axis_count : Int,
coords : ArrayView[@moon_skrifa.NormalizedCoord],
) -> Result[TtZone, HintError] {
// Clone mutable state per glyph (matches upstream's copy-on-write behavior).
let cvt = self.cvt.copy()
let storage = self.storage.copy()
let retained = self.retained
let point_count = if glyph_zone.points.is_empty() {
None
} else {
Some(glyph_zone.points.length())
}
let engine = TtEngine(
fpgm,
prep,
glyph_code,
retained,
cvt,
storage,
point_count,
axis_count,
coords,
self.max_stack,
self.max_call_depth,
self.max_function_defs,
self.max_instruction_defs,
)
// Copy definitions from the instance.
engine.functions = self.functions
engine.instructions = self.instructions
// Reset twilight zone to the instance state for each glyph run.
let tz = TtZone::{
unscaled: Array::new(),
original: self.twilight_original.copy(),
points: self.twilight_points.copy(),
flags: self.twilight_flags.copy(),
contours: Array::from_fixed_array([self.twilight_points.length()]),
}
engine.graphics.twilight = tz
engine.graphics.glyph = glyph_zone
engine.graphics.is_composite = is_composite
engine.program.set_glyph_code(glyph_code)
match engine.run_program(Glyph, is_pedantic) {
Err(e) =>
if is_pedantic {
return Err(e)
} else {
return Ok(engine.graphics.glyph)
}
Ok(_) => ()
}
Ok(engine.graphics.glyph)
}
///|
test "hint: scaled cvar cvt" {
let font = cvar_test_font()
let coords : Array[@moon_skrifa.NormalizedCoord] = Array::from_fixed_array([
8192, -8192,
])
let inst = TtHintInstance::default()
// From fontations-reference: ppem * 64 / upem (upem=1000).
let scale_bits = 67109
let ppem = 16
inst
.reconfigure(font, scale_bits, ppem, Target::default(), coords.op_as_view())
.unwrap()
|> ignore
debug_inspect(
inst.cvt,
content=(
#|[
#| 778,
#| 10,
#| 731,
#| 0,
#| 731,
#| 10,
#| 549,
#| 10,
#| 0,
#| 0,
#| 0,
#| -10,
#| 0,
#| -10,
#| -256,
#| -10,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 95,
#| 137,
#| 99,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 60,
#| 0,
#| 81,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 51,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#| 0,
#|]
),
)
}