// 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.
///|
/// Graphics state + projection helpers for the TrueType interpreter.
///
/// Ported from `fontations/skrifa/src/outline/glyf/hint/{graphics.rs,projection.rs}`
/// (Apache-2.0 OR MIT).
///|
priv enum TtCoordAxis {
Both
X
Y
}
///|
/// 2.14 fixed point vector used for projection/freedom vectors.
priv struct TtVec14 {
x : Int
y : Int
}
///|
fn TtVec14::default() -> TtVec14 {
// (1, 0) in 2.14 (x-axis), matches fontations/FreeType defaults.
{ x: 0x4000, y: 0 }
}
///|
/// Persistent graphics state (retained across programs as in FreeType).
priv struct TtRetainedGraphicsState {
mut auto_flip : Bool
mut control_value_cutin : Int
mut delta_base : Int
mut delta_shift : Int
mut instruct_control : Int
mut min_distance : Int
mut scan_control : Bool
mut scan_type : Int
mut single_width_cutin : Int
mut single_width : Int
target : Target
scale : Int
ppem : Int
is_rotated : Bool
is_stretched : Bool
}
///|
fn TtRetainedGraphicsState::default() -> TtRetainedGraphicsState {
{
auto_flip: true,
// 17/16 pixels in 26.6: (17 * 64 / 16) = 68
control_value_cutin: 68,
delta_base: 9,
delta_shift: 3,
instruct_control: 0,
// 1 pixel in 26.6
min_distance: 64,
scan_control: false,
scan_type: 0,
single_width_cutin: 0,
single_width: 0,
target: Target::default(),
scale: 0,
ppem: 0,
is_rotated: false,
is_stretched: false,
}
}
///|
fn TtRetainedGraphicsState::TtRetainedGraphicsState(
scale : Int,
ppem : Int,
target : Target,
) -> TtRetainedGraphicsState {
TtRetainedGraphicsState::new_with_flags(scale, ppem, target, false, false)
}
///|
fn TtRetainedGraphicsState::new_with_flags(
scale : Int,
ppem : Int,
target : Target,
is_rotated : Bool,
is_stretched : Bool,
) -> TtRetainedGraphicsState {
let d = TtRetainedGraphicsState::default()
{
auto_flip: d.auto_flip,
control_value_cutin: d.control_value_cutin,
delta_base: d.delta_base,
delta_shift: d.delta_shift,
instruct_control: d.instruct_control,
min_distance: d.min_distance,
scan_control: d.scan_control,
scan_type: d.scan_type,
single_width_cutin: d.single_width_cutin,
single_width: d.single_width,
target,
scale,
ppem,
is_rotated,
is_stretched,
}
}
///|
priv struct TtGraphicsState {
mut retained : TtRetainedGraphicsState
mut proj_vector : TtVec14
mut proj_axis : TtCoordAxis
mut dual_proj_vector : TtVec14
mut dual_proj_axis : TtCoordAxis
mut freedom_vector : TtVec14
mut freedom_axis : TtCoordAxis
mut fdotp : Int
mut round_state : TtRoundState
mut rp0 : Int
mut rp1 : Int
mut rp2 : Int
mut loop_counter : Int
mut zp0 : TtZonePointer
mut zp1 : TtZonePointer
mut zp2 : TtZonePointer
mut twilight : TtZone
mut glyph : TtZone
mut is_composite : Bool
mut backward_compatibility : Bool
mut is_pedantic : Bool
mut did_iup_x : Bool
mut did_iup_y : Bool
}
///|
priv struct TtPointDisplacement {
zone : TtZonePointer
point_ix : Int
dx : Int
dy : Int
}
///|
fn TtGraphicsState::default() -> TtGraphicsState {
// Default vectors: proj=(0,1), freedom=(0,1)
let v = TtVec14::default()
let s = TtGraphicsState::{
retained: TtRetainedGraphicsState::default(),
proj_vector: v,
proj_axis: Y,
dual_proj_vector: v,
dual_proj_axis: Y,
freedom_vector: v,
freedom_axis: Y,
fdotp: 0x4000,
round_state: TtRoundState::default(),
rp0: 0,
rp1: 0,
rp2: 0,
loop_counter: 1,
zp0: TtZonePointer::default(),
zp1: TtZonePointer::default(),
zp2: TtZonePointer::default(),
twilight: TtZone::default(),
glyph: TtZone::default(),
is_composite: false,
backward_compatibility: true,
is_pedantic: false,
did_iup_x: false,
did_iup_y: false,
}
s.update_projection_state()
s
}
///|
/// Resets transient graphics state and keeps retained fields.
fn TtGraphicsState::reset(self : TtGraphicsState) -> Unit {
let v = TtVec14::default()
let is_composite = self.is_composite
self.proj_vector = v
self.dual_proj_vector = v
self.freedom_vector = v
self.round_state = TtRoundState::default()
self.rp0 = 0
self.rp1 = 0
self.rp2 = 0
self.loop_counter = 1
self.zp0 = TtZonePointer::default()
self.zp1 = TtZonePointer::default()
self.zp2 = TtZonePointer::default()
self.is_composite = is_composite
self.backward_compatibility = true
self.did_iup_x = false
self.did_iup_y = false
self.update_projection_state()
}
///|
/// Resets retained graphics state to default values.
fn TtGraphicsState::reset_retained(self : TtGraphicsState) -> Unit {
let scale = self.retained.scale
let ppem = self.retained.ppem
let target = self.retained.target
self.retained = TtRetainedGraphicsState(scale, ppem, target)
}
///|
fn TtGraphicsState::zp0_zone(self : TtGraphicsState) -> TtZone {
match self.zp0 {
Twilight => self.twilight
Glyph => self.glyph
}
}
///|
fn TtGraphicsState::zp1_zone(self : TtGraphicsState) -> TtZone {
match self.zp1 {
Twilight => self.twilight
Glyph => self.glyph
}
}
///|
fn TtGraphicsState::zp2_zone(self : TtGraphicsState) -> TtZone {
match self.zp2 {
Twilight => self.twilight
Glyph => self.glyph
}
}
///|
fn tt_dot14(ax : Int, ay : Int, bx : Int, by : Int) -> Int {
let mut v1 = ax.to_int64() * bx.to_int64()
let v2 = ay.to_int64() * by.to_int64()
v1 = v1 + v2
v1 = v1 + 0x2000 + (v1 >> 63)
(v1 >> 14).to_int()
}
///|
/// Updates cached state derived from projection vectors.
fn TtGraphicsState::update_projection_state(self : TtGraphicsState) -> Unit {
// 1.0 in 2.14 fixed point.
let one = 0x4000
if self.freedom_vector.x == one {
self.fdotp = self.proj_vector.x
} else if self.freedom_vector.y == one {
self.fdotp = self.proj_vector.y
} else {
let px = self.proj_vector.x
let py = self.proj_vector.y
let fx = self.freedom_vector.x
let fy = self.freedom_vector.y
self.fdotp = (px * fx + py * fy) >> 14
}
self.proj_axis = Both
if self.proj_vector.x == one {
self.proj_axis = X
} else if self.proj_vector.y == one {
self.proj_axis = Y
}
self.dual_proj_axis = Both
if self.dual_proj_vector.x == one {
self.dual_proj_axis = X
} else if self.dual_proj_vector.y == one {
self.dual_proj_axis = Y
}
self.freedom_axis = Both
if self.fdotp == one {
if self.freedom_vector.x == one {
self.freedom_axis = X
} else if self.freedom_vector.y == one {
self.freedom_axis = Y
}
}
// Avoid tiny fdotp that can lead to overflows.
if self.fdotp.abs() < 0x400 {
self.fdotp = one
}
}
///|
/// Projects (p1 - p2) along current projection vector, returning 26.6.
fn TtGraphicsState::project(
self : TtGraphicsState,
p1 : TtPoint,
p2 : TtPoint,
) -> Int {
match self.proj_axis {
X => p1.x - p2.x
Y => p1.y - p2.y
Both => {
let dx = p1.x - p2.x
let dy = p1.y - p2.y
tt_dot14(dx, dy, self.proj_vector.x, self.proj_vector.y)
}
}
}
///|
/// Dual-projects (p1 - p2) along dual projection vector, returning 26.6.
fn TtGraphicsState::dual_project(
self : TtGraphicsState,
p1 : TtPoint,
p2 : TtPoint,
) -> Int {
match self.dual_proj_axis {
X => p1.x - p2.x
Y => p1.y - p2.y
Both => {
let dx = p1.x - p2.x
let dy = p1.y - p2.y
tt_dot14(dx, dy, self.dual_proj_vector.x, self.dual_proj_vector.y)
}
}
}
///|
/// Dual-projects (p1 - p2) for unscaled points (font units), returning i32.
fn TtGraphicsState::dual_project_unscaled(
self : TtGraphicsState,
p1 : TtPoint,
p2 : TtPoint,
) -> Int {
match self.dual_proj_axis {
X => p1.x - p2.x
Y => p1.y - p2.y
Both => {
let dx = p1.x - p2.x
let dy = p1.y - p2.y
tt_dot14(dx, dy, self.dual_proj_vector.x, self.dual_proj_vector.y)
}
}
}
///|
fn TtGraphicsState::round(self : TtGraphicsState, distance_bits : Int) -> Int {
self.round_state.round(distance_bits)
}
///|
/// Returns the factor for scaling unscaled points to pixels (26.6).
fn TtGraphicsState::unscaled_to_pixels(self : TtGraphicsState) -> Int {
if self.is_composite {
0x10000
} else {
self.retained.scale
}
}
///|
fn tt_mul_div_round(a : Int, b : Int, c : Int) -> Int {
// a * b / c with rounding, matching FreeType-ish behavior.
if c == 0 {
return 0x7FFFFFFF
}
let mut sign = 1
let mut aa = a.to_int64()
let mut bb = b.to_int64()
let mut cc = c.to_int64()
if aa < 0 {
aa = -aa
sign = -sign
}
if bb < 0 {
bb = -bb
sign = -sign
}
if cc < 0 {
cc = -cc
sign = -sign
}
let res = (aa * bb + (cc >> 1)) / cc
let out = res.to_int()
if sign < 0 {
-out
} else {
out
}
}
///|
/// Computes adjustment made to a point along the current freedom vector.
fn TtGraphicsState::point_displacement(
self : TtGraphicsState,
opcode : Int,
) -> Result[TtPointDisplacement, HintError] {
let (zone_ptr, point_ix) = if (opcode & 1) != 0 {
(self.zp0, self.rp1)
} else {
(self.zp1, self.rp2)
}
let zone = match zone_ptr {
Twilight => self.twilight
Glyph => self.glyph
}
let point = match zone.point(point_ix) {
Err(e) => return Err(e)
Ok(v) => v
}
let original = match zone.original_point(point_ix) {
Err(e) => return Err(e)
Ok(v) => v
}
let dist = self.project(point, original)
let fv = self.freedom_vector
let dx = tt_mul_div_round(dist, fv.x, self.fdotp)
let dy = tt_mul_div_round(dist, fv.y, self.fdotp)
Ok({ zone: zone_ptr, point_ix, dx, dy })
}
///|
/// Moves a point in zp2 by (dx, dy) in 26.6 units.
fn TtGraphicsState::move_zp2_point(
self : TtGraphicsState,
index : Int,
dx : Int,
dy : Int,
touch : Bool,
) -> Result[Unit, HintError] {
// Backward compatibility behavior matches fontations/FreeType:
// - Never adjust x in back-compat mode.
// - Never adjust y in back-compat mode after IUP has been applied in both
// directions (avoids late y tweaks after interpolation).
let back_compat = self.backward_compatibility
let back_compat_and_did_iup = back_compat && self.did_iup_x && self.did_iup_y
let fv = self.freedom_vector
let zone = self.zp2_zone()
if fv.x != 0 {
if !back_compat {
let p = match zone.point(index) {
Err(e) => return Err(e)
Ok(v) => v
}
match zone.set_point(index, { x: p.x + dx, y: p.y }) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
if touch {
match zone.touch(index, X) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
}
if fv.y != 0 {
if !back_compat_and_did_iup {
let p = match zone.point(index) {
Err(e) => return Err(e)
Ok(v) => v
}
match zone.set_point(index, { x: p.x, y: p.y + dy }) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
if touch {
match zone.touch(index, Y) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
}
Ok(())
}
///|
/// Moves a point in the given zone by a distance along the freedom vector.
fn TtGraphicsState::move_point(
self : TtGraphicsState,
zone_ptr : TtZonePointer,
index : Int,
distance_bits : Int,
) -> Result[Unit, HintError] {
let back_compat = self.backward_compatibility
let back_compat_and_did_iup = back_compat && self.did_iup_x && self.did_iup_y
let fv = self.freedom_vector
let fdotp = self.fdotp
let zone = match zone_ptr {
Twilight => self.twilight
Glyph => self.glyph
}
match self.freedom_axis {
X => {
if !back_compat {
let p = match zone.point(index) {
Err(e) => return Err(e)
Ok(v) => v
}
match zone.set_point(index, { x: p.x + distance_bits, y: p.y }) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
match zone.touch(index, X) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
Y => {
if !back_compat_and_did_iup {
let p = match zone.point(index) {
Err(e) => return Err(e)
Ok(v) => v
}
match zone.set_point(index, { x: p.x, y: p.y + distance_bits }) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
match zone.touch(index, Y) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
Both => {
if fv.x != 0 {
if !back_compat {
let dx = tt_mul_div_round(distance_bits, fv.x, fdotp)
let p = match zone.point(index) {
Err(e) => return Err(e)
Ok(v) => v
}
match zone.set_point(index, { x: p.x + dx, y: p.y }) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
match zone.touch(index, X) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
if fv.y != 0 {
if !back_compat_and_did_iup {
let dy = tt_mul_div_round(distance_bits, fv.y, fdotp)
let p = match zone.point(index) {
Err(e) => return Err(e)
Ok(v) => v
}
match zone.set_point(index, { x: p.x, y: p.y + dy }) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
match zone.touch(index, Y) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
}
}
Ok(())
}