// 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/CFF2 font matrix helpers.
///
/// Ported from `fontations/skrifa/src/outline/cff/mod.rs` and
/// `fontations/read-fonts/src/tables/postscript/dict.rs`.
///|
priv struct CffMatrix {
xx : CffFixed
yx : CffFixed
xy : CffFixed
yy : CffFixed
dx : CffFixed
dy : CffFixed
}
///|
fn CffMatrix::is_identity(self : CffMatrix) -> Bool {
self.xx.bits == CffFixed::one().bits &&
self.yx.bits == CffFixed::zero().bits &&
self.xy.bits == CffFixed::zero().bits &&
self.yy.bits == CffFixed::one().bits &&
self.dx.bits == CffFixed::zero().bits &&
self.dy.bits == CffFixed::zero().bits
}
///|
fn CffMatrix::transform(
self : CffMatrix,
x : CffFixed,
y : CffFixed,
) -> (CffFixed, CffFixed) {
(
self.xx.mul(x).add(self.xy.mul(y)).add(self.dx),
self.yx.mul(x).add(self.yy.mul(y)).add(self.dy),
)
}
///|
fn cff_matrix_mul_scaled(
a : CffMatrix,
b : CffMatrix,
scaling : Int,
) -> CffMatrix {
// Ported from `matrix_mul_scaled` in fontations.
let val = CffFixed::from_i32(scaling)
let xx = a.xx.mul_div(b.xx, val).add(a.xy.mul_div(b.yx, val))
let yx = a.yx.mul_div(b.xx, val).add(a.yy.mul_div(b.yx, val))
let xy = a.xx.mul_div(b.xy, val).add(a.xy.mul_div(b.yy, val))
let yy = a.yx.mul_div(b.xy, val).add(a.yy.mul_div(b.yy, val))
// Note: this transforms the translation component of `b` by the upper 2x2 of
// `a` (matches FreeType concatenation semantics).
let dx = b.dx.mul_div(a.xx, val).add(b.dy.mul_div(a.xy, val))
let dy = b.dx.mul_div(a.yx, val).add(b.dy.mul_div(a.yy, val))
{ xx, yx, xy, yy, dx, dy }
}
///|
fn cff_normalize_font_matrix(
matrix0 : CffMatrix,
scaled_upem0 : Int,
) -> (CffMatrix, Int) {
// Ported from `normalize_font_matrix` in read-fonts.
let factor = if matrix0.yy.bits != CffFixed::zero().bits {
matrix0.yy.abs()
} else {
// Use yx if yy is zero.
matrix0.yx.abs()
}
let (m1, scaled_upem) = if factor.bits != CffFixed::one().bits {
let scaled_upem = CffFixed::from_bits(scaled_upem0).div(factor).bits
(
CffMatrix::{
xx: matrix0.xx.div(factor),
yx: matrix0.yx.div(factor),
xy: matrix0.xy.div(factor),
yy: matrix0.yy.div(factor),
dx: matrix0.dx.div(factor),
dy: matrix0.dy.div(factor),
},
scaled_upem,
)
} else {
(matrix0, scaled_upem0)
}
// FreeType shifts off the fractional parts of the translation.
let m2 = CffMatrix::{
xx: m1.xx,
yx: m1.yx,
xy: m1.xy,
yy: m1.yy,
dx: CffFixed::from_bits(m1.dx.bits >> 16),
dy: CffFixed::from_bits(m1.dy.bits >> 16),
}
(m2, scaled_upem)
}
///|
fn cff_matrix_transform_hinted(
matrix : CffMatrix,
x : CffFixed,
y : CffFixed,
) -> (CffFixed, CffFixed) {
// Ported from `HintedTransformingSink`: apply the transform to 26.6 values,
// but our hinted coordinates are 16.16.
let x_26_6 = CffFixed::from_bits(x.bits >> 10)
let y_26_6 = CffFixed::from_bits(y.bits >> 10)
let (tx, ty) = CffMatrix::transform(matrix, x_26_6, y_26_6)
(CffFixed::from_bits(tx.bits << 10), CffFixed::from_bits(ty.bits << 10))
}