// 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.
///|
/// Runtime autohinting support.
///
/// Upstream `fontations/skrifa` includes a runtime autohinter used when a font
/// does not provide embedded instructions. In this port, `GlyphStyles` is the
/// (font-invariant) precomputed style map that can be reused across hinting
/// instances.
///|
pub struct GlyphStyles {
priv map : GlyphStyleMap
}
///|
priv struct GlyphStyleMap {
styles : Array[GlyphStyle]
metrics_map : Array[Byte]
mut metrics_count : Int
}
///|
priv struct GlyphStyle {
bits : Int
}
///|
const AUTOHINT_STYLE_INDEX_MASK : Int = 0xFF
///|
const AUTOHINT_STYLE_INDEX_KEEP_MASK : Int = -1 - AUTOHINT_STYLE_INDEX_MASK
///|
const AUTOHINT_STYLE_UNASSIGNED : Int = AUTOHINT_STYLE_INDEX_MASK
///|
const AUTOHINT_STYLE_NON_BASE : Int = 0x100
///|
const AUTOHINT_STYLE_DIGIT : Int = 0x200
///|
const AUTOHINT_STYLE_LATN : Int = 60
///|
const AUTOHINT_STYLE_HANI : Int = 89
///|
const AUTOHINT_MAX_STYLES : Int = 90
///|
fn GlyphStyle::default() -> GlyphStyle {
{ bits: AUTOHINT_STYLE_UNASSIGNED }
}
///|
fn GlyphStyle::is_unassigned(self : GlyphStyle) -> Bool {
(self.bits & AUTOHINT_STYLE_INDEX_MASK) == AUTOHINT_STYLE_UNASSIGNED
}
///|
fn GlyphStyle::is_digit(self : GlyphStyle) -> Bool {
(self.bits & AUTOHINT_STYLE_DIGIT) != 0
}
///|
fn GlyphStyle::is_non_base(self : GlyphStyle) -> Bool {
(self.bits & AUTOHINT_STYLE_NON_BASE) != 0
}
///|
fn GlyphStyle::style_index(self : GlyphStyle) -> Int? {
let ix = self.bits & AUTOHINT_STYLE_INDEX_MASK
if ix == AUTOHINT_STYLE_UNASSIGNED {
None
} else {
Some(ix)
}
}
///|
fn GlyphStyle::with_style_index(style_index : Int) -> GlyphStyle {
{ bits: style_index & AUTOHINT_STYLE_INDEX_MASK }
}
///|
fn GlyphStyle::with_style_index_non_base(style_index : Int) -> GlyphStyle {
{ bits: (style_index & AUTOHINT_STYLE_INDEX_MASK) | AUTOHINT_STYLE_NON_BASE }
}
///|
fn GlyphStyle::maybe_assign(
self : GlyphStyle,
other : GlyphStyle,
) -> GlyphStyle {
let other_ix = other.bits & AUTOHINT_STYLE_INDEX_MASK
let self_ix = self.bits & AUTOHINT_STYLE_INDEX_MASK
if other_ix <= self_ix {
{
bits: (self.bits & AUTOHINT_STYLE_INDEX_KEEP_MASK) |
other_ix |
(other.bits & AUTOHINT_STYLE_INDEX_KEEP_MASK),
}
} else {
self
}
}
///|
fn GlyphStyle::set_digit(self : GlyphStyle) -> GlyphStyle {
{ bits: self.bits | AUTOHINT_STYLE_DIGIT }
}
///|
fn GlyphStyleMap::default() -> GlyphStyleMap {
let styles : Array[GlyphStyle] = Array::new()
let metrics_map : Array[Byte] = Array::new()
for _ in 0.. Unit {
if style_ix < 0 || style_ix >= self.metrics_map.length() {
return
}
if self.metrics_map.at(style_ix).to_int() != 0xFF {
return
}
self.metrics_map.set(style_ix, self.metrics_count.to_byte())
self.metrics_count = self.metrics_count + 1
}
///|
fn autohint_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)
}
}
///|
const AUTOHINT_TAG_MAXP : UInt = 0x6D617870 // "maxp"
///|
fn autohint_num_glyphs(font : @moon_skrifa.FontRef) -> Int {
match font.table(AUTOHINT_TAG_MAXP) {
None => 0
Some(maxp) => autohint_read_u16_be(maxp, 4).unwrap_or(0)
}
}
///|
fn autohint_style_for_codepoint(
ranges : ArrayView[AutoHintStyleRange],
cp : UInt,
) -> (Int, Int, GlyphStyle)? {
let ch = cp.to_uint64().to_int()
match autohint_style_range_for_codepoint(ch, ranges) {
None => None
Some((start, end, bits)) => {
let style_ix = bits & AUTOHINT_STYLE_INDEX_MASK
let style = if (bits & AUTOHINT_STYLE_NON_BASE) != 0 {
GlyphStyle::with_style_index_non_base(style_ix)
} else {
GlyphStyle::with_style_index(style_ix)
}
Some((start, end, style))
}
}
}
///|
fn GlyphStyleMap::GlyphStyleMap(
glyph_count : Int,
font : @moon_skrifa.FontRef,
) -> GlyphStyleMap {
let map = GlyphStyleMap::default()
let ranges = autohint_style_ranges()
for _ in 0..= map.styles.length() {
continue
}
let mut style = map.styles.at(gi)
match last_range {
Some((start, end, s)) =>
if cp.to_uint64().to_int() >= start && cp.to_uint64().to_int() <= end {
style = style.maybe_assign(s)
map.styles.set(gi, style)
continue
}
None => ()
}
match autohint_style_for_codepoint(ranges, cp) {
None => ()
Some((start, end, s)) => {
style = style.maybe_assign(s)
map.styles.set(gi, style)
// Keep track of the style range we just used for cheap locality.
last_range = Some((start, end, s))
match s.style_index() {
None => ()
Some(ix) => map.use_style(ix)
}
}
}
}
// Step 4: assign a default to all remaining glyphs (Hani, matches upstream).
let mut need_hani = false
for i in 0.. Int::reinterpret_as_uint) {
None => ()
Some(gid) => {
let gi = gid.to_uint64().to_int()
if gi >= 0 && gi < map.styles.length() {
map.styles.set(gi, map.styles.at(gi).set_digit())
}
}
}
}
map
}
///|
/// Precomputes the glyph style map for a font.
///
/// This is invariant per font and can be reused when creating multiple
/// `HintingInstance`s with `Engine::Auto(...)`.
pub fn GlyphStyles::GlyphStyles(
outlines : OutlineGlyphCollection,
) -> GlyphStyles {
let font = outlines.font
let glyph_count = autohint_num_glyphs(font)
let map = if glyph_count <= 0 {
GlyphStyleMap::default()
} else {
GlyphStyleMap(glyph_count, font)
}
{ map, }
}
///|
pub fn GlyphStyles::style_index(
self : GlyphStyles,
gid : @moon_skrifa.GlyphId,
) -> Int? {
let gi = gid.to_uint64().to_int()
if gi < 0 || gi >= self.map.styles.length() {
None
} else {
self.map.styles.at(gi).style_index()
}
}
///|
pub fn GlyphStyles::is_digit(
self : GlyphStyles,
gid : @moon_skrifa.GlyphId,
) -> Bool {
let gi = gid.to_uint64().to_int()
if gi < 0 || gi >= self.map.styles.length() {
false
} else {
self.map.styles.at(gi).is_digit()
}
}
///|
fn GlyphStyles::_is_non_base(
self : GlyphStyles,
gid : @moon_skrifa.GlyphId,
) -> Bool {
let gi = gid.to_uint64().to_int()
if gi < 0 || gi >= self.map.styles.length() {
false
} else {
self.map.styles.at(gi).is_non_base()
}
}