// 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.
///|
/// Program state for the TrueType interpreter.
///
/// Ported from `fontations/skrifa/src/outline/glyf/hint/program.rs`
/// (Apache-2.0 OR MIT).
priv struct TtCallFrame {
decoder : TtDecoder
return_pc : Int
kind : TtCallFrameKind
}
///|
priv enum TtCallFrameKind {
ReturnOnly
LoopCall(BytesView, Int)
}
///|
priv struct TtProgramState {
font : BytesView
prep : BytesView
mut glyph : BytesView
mut current : TtProgram
mut decoder : TtDecoder
call_stack : Array[TtCallFrame]
max_call_depth : Int
}
///|
fn TtProgramState::TtProgramState(
font : BytesView,
prep : BytesView,
glyph : BytesView,
current : TtProgram,
max_call_depth : Int,
) -> TtProgramState {
let code = match current {
Font => font
ControlValue => prep
Glyph => glyph
}
{
font,
prep,
glyph,
current,
decoder: TtDecoder(code),
call_stack: Array::new(),
max_call_depth,
}
}
///|
fn TtProgramState::reset(self : TtProgramState, program : TtProgram) -> Unit {
self.current = program
let code = match program {
Font => self.font
ControlValue => self.prep
Glyph => self.glyph
}
self.decoder = TtDecoder(code)
self.call_stack.clear()
}
///|
fn TtProgramState::set_glyph_code(
self : TtProgramState,
glyph : BytesView,
) -> Unit {
self.glyph = glyph
}
///|
fn TtProgramState::push_frame(
self : TtProgramState,
callee : BytesView,
start_pc : Int,
return_pc : Int,
) -> Result[Unit, HintError] {
if self.call_stack.length() >= self.max_call_depth {
return Err(CallStackOverflow)
}
// Save the caller decoder so we can resume there.
self.call_stack.push({ decoder: self.decoder, return_pc, kind: ReturnOnly })
// Switch current decoder to callee.
self.decoder = { code: callee, pc: start_pc }
Ok(())
}
///|
fn TtProgramState::push_loopcall_frame(
self : TtProgramState,
callee : BytesView,
remaining : Int,
start_pc : Int,
return_pc : Int,
) -> Result[Unit, HintError] {
if self.call_stack.length() >= self.max_call_depth {
return Err(CallStackOverflow)
}
self.call_stack.push({
decoder: self.decoder,
return_pc,
kind: LoopCall(callee, remaining),
})
self.decoder = { code: callee, pc: start_pc }
Ok(())
}
///|
fn TtProgramState::pop_frame(self : TtProgramState) -> Result[Unit, HintError] {
if self.call_stack.is_empty() {
return Err(CallStackUnderflow)
}
let frame = self.call_stack.pop().unwrap()
match frame.kind {
ReturnOnly => {
// Restore previous decoder and continue after return_pc.
self.decoder = frame.decoder
self.decoder.pc = frame.return_pc
Ok(())
}
LoopCall(code, remaining) =>
if remaining > 0 {
// Re-run the callee and keep the loopcall frame, decrementing the counter.
self.call_stack.push({
decoder: frame.decoder,
return_pc: frame.return_pc,
kind: LoopCall(code, remaining - 1),
})
self.decoder = { code, pc: 0 }
Ok(())
} else {
self.decoder = frame.decoder
self.decoder.pc = frame.return_pc
Ok(())
}
}
}