// 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.
///|
/// Autohint-specific metrics.
///
/// Ported incrementally from `fontations/skrifa/src/outline/autohint/metrics/*`
/// (Apache-2.0 OR MIT).
///|
const AUTOHINT_MAX_WIDTHS : Int = 16
///|
const AUTOHINT_MAX_BLUES : Int = 8
///|
const AUTOHINT_SCALE_HORIZONTAL_SNAP : Int = 1 << 0
///|
const AUTOHINT_SCALE_VERTICAL_SNAP : Int = 1 << 1
///|
const AUTOHINT_SCALE_STEM_ADJUST : Int = 1 << 2
///|
const AUTOHINT_SCALE_MONO : Int = 1 << 3
///|
const AUTOHINT_SCALE_NO_HORIZONTAL : Int = 1 << 4
///|
const AUTOHINT_SCALE_NO_VERTICAL : Int = 1 << 5
///|
const AUTOHINT_SCALE_NO_ADVANCE : Int = 1 << 6
///|
priv struct AutoHintScale {
x_scale : Int
mut y_scale : Int
x_delta : Int
y_delta : Int
size : Double
units_per_em : Int
flags : Int
}
///|
priv struct AutoHintWidthMetrics {
edge_distance_threshold : Int
standard_width : Int
is_extra_light : Bool
}
///|
fn AutoHintWidthMetrics::default() -> AutoHintWidthMetrics {
{ edge_distance_threshold: 0, standard_width: 0, is_extra_light: false }
}
///|
priv struct AutoHintScaledWidth {
scaled : Int
mut fitted : Int
}
///|
fn AutoHintScaledWidth::default() -> AutoHintScaledWidth {
{ scaled: 0, fitted: 0 }
}
///|
priv struct AutoHintUnscaledBlue {
mut position : Int
mut overshoot : Int
ascender : Int
descender : Int
zones : Int
}
///|
priv struct AutoHintScaledBlue {
position : AutoHintScaledWidth
overshoot : AutoHintScaledWidth
zones : Int
mut is_active : Bool
}
///|
priv struct AutoHintUnscaledAxisMetrics {
dim : Int
widths : Array[Int]
mut width_metrics : AutoHintWidthMetrics
mut blues : Array[AutoHintUnscaledBlue]
}
///|
fn AutoHintUnscaledAxisMetrics::default(
dim : Int,
) -> AutoHintUnscaledAxisMetrics {
{
dim,
widths: Array::new(),
width_metrics: AutoHintWidthMetrics::default(),
blues: Array::new(),
}
}
///|
fn AutoHintUnscaledAxisMetrics::max_width(
self : AutoHintUnscaledAxisMetrics,
) -> Int? {
if self.widths.is_empty() {
None
} else {
Some(self.widths.at(self.widths.length() - 1))
}
}
///|
priv struct AutoHintScaledAxisMetrics {
dim : Int
mut scale : Int
mut delta : Int
widths : Array[AutoHintScaledWidth]
mut width_metrics : AutoHintWidthMetrics
blues : Array[AutoHintScaledBlue]
}
///|
fn AutoHintScaledAxisMetrics::default(dim : Int) -> AutoHintScaledAxisMetrics {
{
dim,
scale: 0,
delta: 0,
widths: Array::new(),
width_metrics: AutoHintWidthMetrics::default(),
blues: Array::new(),
}
}
///|
priv struct AutoHintUnscaledStyleMetrics {
group : AutoHintScriptGroup
hint_top_to_bottom : Bool
axes : Array[AutoHintUnscaledAxisMetrics]
}
///|
priv struct AutoHintScaledStyleMetrics {
scale : AutoHintScale
axes : Array[AutoHintScaledAxisMetrics]
}
///|
const AUTOHINT_BLUE_NONE : Int = 0
///|
const AUTOHINT_BLUE_TOP : Int = 1 << 1
///|
const AUTOHINT_BLUE_SUB_TOP : Int = 1 << 2
///|
const AUTOHINT_BLUE_NEUTRAL : Int = 1 << 3
///|
const AUTOHINT_BLUE_ADJUSTMENT : Int = 1 << 4
///|
const AUTOHINT_BLUE_X_HEIGHT : Int = 1 << 5
///|
const AUTOHINT_BLUE_LONG : Int = 1 << 6
///|
const AUTOHINT_BLUE_HORIZONTAL : Int = 1 << 2
///|
const AUTOHINT_BLUE_RIGHT : Int = AUTOHINT_BLUE_TOP
///|
fn autohint_blue_contains(zones : Int, other : Int) -> Bool {
(zones & other) == other
}
///|
fn autohint_blue_is_top_like(zones : Int) -> Bool {
(zones & (AUTOHINT_BLUE_TOP | AUTOHINT_BLUE_SUB_TOP)) != 0
}
///|
fn autohint_blue_is_top(zones : Int) -> Bool {
autohint_blue_contains(zones, AUTOHINT_BLUE_TOP)
}
///|
fn autohint_blue_is_sub_top(zones : Int) -> Bool {
autohint_blue_contains(zones, AUTOHINT_BLUE_SUB_TOP)
}
///|
fn autohint_blue_is_neutral(zones : Int) -> Bool {
autohint_blue_contains(zones, AUTOHINT_BLUE_NEUTRAL)
}
///|
fn autohint_blue_is_x_height(zones : Int) -> Bool {
autohint_blue_contains(zones, AUTOHINT_BLUE_X_HEIGHT)
}
///|
fn autohint_blue_is_horizontal(zones : Int) -> Bool {
autohint_blue_contains(zones, AUTOHINT_BLUE_HORIZONTAL)
}
///|
fn autohint_blue_is_right(zones : Int) -> Bool {
autohint_blue_contains(zones, AUTOHINT_BLUE_RIGHT)
}
///|
fn autohint_blue_retain_top_like_or_neutral(zones : Int) -> Int {
zones & (AUTOHINT_BLUE_TOP | AUTOHINT_BLUE_SUB_TOP | AUTOHINT_BLUE_NEUTRAL)
}
///|
fn autohint_pix_round(a : Int) -> Int {
(a + 32) & -64
}
///|
fn autohint_pix_floor(a : Int) -> Int {
a & -64
}
///|
fn autohint_fixed_mul_div(a : Int, b : Int, c : Int) -> Int {
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 q : Int64 = if cc == 0 {
(0x7FFFFFFF).to_int64()
} else {
(aa * bb + (cc >> 1)) / cc
}
let out = q.to_int()
if sign < 0 {
-out
} else {
out
}
}
///|
fn AutoHintScale::AutoHintScale(
size : Double,
units_per_em : Int,
font_style : @moon_skrifa.Style,
target : Target,
group : AutoHintScriptGroup,
) -> AutoHintScale {
let scale_bits = if units_per_em > 0 {
let scaled_h = (size * 64.0).to_int()
(scaled_h << 16) / units_per_em
} else {
0
}
let is_italic = !(font_style is Normal)
let is_mono = target is Mono
let is_light = target.is_light() || target.preserve_linear_metrics()
let mut flags = 0
// Snap vertical stems for monochrome and horizontal LCD rendering.
if is_mono || target.is_lcd() {
flags = flags | AUTOHINT_SCALE_HORIZONTAL_SNAP
}
// Snap horizontal stems for monochrome and vertical LCD rendering.
if is_mono || target.is_vertical_lcd() {
flags = flags | AUTOHINT_SCALE_VERTICAL_SNAP
}
// Adjust stems to full pixels unless in LCD or light modes.
if !(target.is_lcd() || is_light) {
flags = flags | AUTOHINT_SCALE_STEM_ADJUST
}
if is_mono {
flags = flags | AUTOHINT_SCALE_MONO
}
if group is Default {
// Disable horizontal hinting completely for LCD, light hinting and italic fonts.
if target.is_lcd() || is_light || is_italic {
flags = flags | AUTOHINT_SCALE_NO_HORIZONTAL
}
} else {
// CJK doesn't hint advances.
flags = flags | AUTOHINT_SCALE_NO_ADVANCE
}
if !(group is Default) {
flags = flags | AUTOHINT_SCALE_NO_ADVANCE
}
{
x_scale: scale_bits,
y_scale: scale_bits,
x_delta: 0,
y_delta: 0,
size,
units_per_em,
flags,
}
}
///|
fn autohint_style_script_group(style_ix : Int) -> AutoHintScriptGroup {
autohint_script_group(autohint_style_script_ix(style_ix))
}
///|
fn autohint_style_hint_top_to_bottom(style_ix : Int) -> Bool {
autohint_script_hint_top_to_bottom(autohint_style_script_ix(style_ix))
}
///|
fn autohint_style_std_chars(style_ix : Int) -> String {
autohint_script_std_chars(autohint_style_script_ix(style_ix))
}
///|
fn autohint_style_blues(style_ix : Int) -> Array[(String, Int)] {
autohint_script_blues(autohint_style_script_ix(style_ix))
}
///|
fn autohint_char_to_codepoint(c : Char) -> Int? {
// MoonBit represents `Char` as a Unicode scalar value.
// Most implementations provide `to_int()`; keep this in a tiny helper
// so it's easy to adjust if the stdlib changes.
Some(c.to_int())
}
///|
fn autohint_load_outline_for_metrics(
outlines : OutlineGlyphCollection,
gid : @moon_skrifa.GlyphId,
) -> AutoHintOutline? {
let font = outlines.font
let outline = AutoHintOutline::from_path(Array::new())
match outline.fill_glyf(font, gid) {
Ok(_) => if outline.is_empty() { None } else { Some(outline) }
Err(_) => {
let glyph = match outlines.get(gid) {
None => return None
Some(g) => g
}
let settings = DrawSettings::unhinted(
@moon_skrifa.Size::unscaled(),
@moon_skrifa.LocationRef::default(),
).with_path_style(FreeType)
let path = match glyph.path(settings) {
Err(_) => return None
Ok(p) => p
}
let o = AutoHintOutline::from_path(path)
if o.is_empty() {
None
} else {
Some(o)
}
}
}
}
///|
fn autohint_pick_standard_glyph(
outlines : OutlineGlyphCollection,
std_chars : String,
) -> @moon_skrifa.GlyphId? {
let font = outlines.font
for ch in std_chars {
if ch == ' ' {
continue
}
if ch == '|' {
continue
}
match autohint_char_to_codepoint(ch) {
None => continue
Some(cp_i) => {
let cp = cp_i |> Int::reinterpret_as_uint
match font.charmap().map(cp) {
None => continue
Some(gid) => {
if gid.to_uint64() == 0 {
continue
}
match autohint_load_outline_for_metrics(outlines, gid) {
None => continue
Some(_) => return Some(gid)
}
}
}
}
}
}
None
}
///|
fn autohint_compute_unscaled_widths(
outlines : OutlineGlyphCollection,
units_per_em : Int,
std_chars : String,
) -> Array[AutoHintUnscaledAxisMetrics] {
let axes : Array[AutoHintUnscaledAxisMetrics] = Array::new()
axes.push(AutoHintUnscaledAxisMetrics::default(AUTOHINT_DIM_HORIZONTAL))
axes.push(AutoHintUnscaledAxisMetrics::default(AUTOHINT_DIM_VERTICAL))
let gid = match autohint_pick_standard_glyph(outlines, std_chars) {
None => return axes
Some(g) => g
}
let outline = match autohint_load_outline_for_metrics(outlines, gid) {
None => return axes
Some(o) => o
}
if outline.points.is_empty() {
return axes
}
let axis = AutoHintAxis::default()
for dim in 0..<2 {
axis.reset(dim, outline.orientation)
let ok = autohint_compute_segments(outline, axis, Default)
if !ok {
continue
}
autohint_link_segments(outline, axis, 0, Default, None)
let widths = axes.at(dim).widths
let segments = axis.segments
for seg_ix in 0.. ()
Some(link_ix) => {
let link = segments.at(link_ix)
if link_ix > seg_ix && link.link_ix == Some(seg_ix) {
let dist = (seg.pos - link.pos).abs()
if widths.length() < AUTOHINT_MAX_WIDTHS {
widths.push(dist)
}
}
}
}
}
if widths.is_empty() {
widths.push(0)
}
autohint_sort_and_quantize_widths(widths, units_per_em / 100)
}
for dim in 0..<2 {
let axis_metrics = axes.at(dim)
let widths = axis_metrics.widths
let mut stdw = if widths.is_empty() {
autohint_derived_constant(units_per_em, 50)
} else {
widths.at(0)
}
if stdw <= 0 {
stdw = autohint_derived_constant(units_per_em, 50).max(1)
}
axis_metrics.width_metrics = {
edge_distance_threshold: (stdw / 5).max(1),
standard_width: stdw,
is_extra_light: false,
}
}
axes
}
///|
fn autohint_sort_ints(values : Array[Int]) -> Unit {
if values.length() <= 1 {
return
}
for i in 1.. 0 && values[j - 1] > key {
values[j] = values[j - 1]
j = j - 1
}
values[j] = key
}
}
///|
fn autohint_compute_default_blues(
outlines : OutlineGlyphCollection,
blues_data : Array[(String, Int)],
units_per_em : Int,
) -> Array[AutoHintUnscaledBlue] {
let blues : Array[AutoHintUnscaledBlue] = Array::new()
for entry in blues_data.iter() {
let (blue_str, blue_zones) = entry
if blues.length() >= AUTOHINT_MAX_BLUES {
continue
}
let values : Array[Int] = Array::new()
let mut ascender = 0
let mut descender = 0
let is_top_like = autohint_blue_is_top_like(blue_zones)
let is_long = autohint_blue_contains(blue_zones, AUTOHINT_BLUE_LONG)
for ch in blue_str {
if ch == ' ' {
continue
}
if ch == '|' {
// Default group doesn't use sentinel, but tolerate it.
continue
}
let cp_i = autohint_char_to_codepoint(ch).unwrap_or(-1)
if cp_i < 0 {
continue
}
let cp = cp_i |> Int::reinterpret_as_uint
let gid = match outlines.font.charmap().map(cp) {
None => continue
Some(g) => g
}
if gid.to_uint64() == 0 {
continue
}
let outline = match autohint_load_outline_for_metrics(outlines, gid) {
None => continue
Some(o) => o
}
if outline.points.length() <= 2 {
continue
}
let mut best_y : Int? = None
// First pass: compute extrema + bounds.
let mut extremum : Int? = None
for p in outline.points.iter() {
let y = p.fy
if extremum is None {
extremum = Some(y)
} else if is_top_like {
if y > extremum.unwrap_or(y) {
extremum = Some(y)
}
} else if y < extremum.unwrap_or(y) {
extremum = Some(y)
}
if y > ascender {
ascender = y
}
if y < descender {
descender = y
}
}
if is_long && extremum is Some(extremum_y) && is_top_like {
// Heuristic for Hebrew LONG zones: prefer the highest Y that spans
// a sufficiently long horizontal segment, ignoring narrow bumps.
let length_threshold = units_per_em / 25
let flat_threshold = units_per_em / 14
let candidates : Array[Int] = Array::new()
for p in outline.points.iter() {
let y = p.fy
if extremum_y - y >= 0 && extremum_y - y <= flat_threshold {
candidates.push(y)
}
}
if !candidates.is_empty() {
autohint_sort_ints(candidates)
// Walk unique Y levels from highest to lowest.
let mut last_y : Int? = None
let mut i = candidates.length() - 1
while true {
let y0 = candidates.at(i)
if last_y is Some(v) && v == y0 {
()
} else {
last_y = Some(y0)
let mut min_x : Int? = None
let mut max_x : Int? = None
for p in outline.points.iter() {
if p.fy != y0 {
continue
}
let x = p.fx
min_x = match min_x {
None => Some(x)
Some(v) => Some(v.min(x))
}
max_x = match max_x {
None => Some(x)
Some(v) => Some(v.max(x))
}
}
match (min_x, max_x) {
(Some(lo), Some(hi)) =>
if hi - lo >= length_threshold {
best_y = Some(y0)
break
}
_ => ()
}
}
if i == 0 {
break
}
i = i - 1
}
}
}
if best_y is None {
best_y = extremum
}
match best_y {
None => ()
Some(v) => values.push(v)
}
}
if values.is_empty() {
continue
}
autohint_sort_ints(values)
let val = values.at(values.length() / 2)
let mut zones = autohint_blue_retain_top_like_or_neutral(blue_zones)
if autohint_blue_is_x_height(blue_zones) {
zones = zones | AUTOHINT_BLUE_ADJUSTMENT
}
blues.push({ position: val, overshoot: val, ascender, descender, zones })
}
if blues.is_empty() {
return blues
}
// Sort from bottom to top, then adjust to avoid overlaps.
let sorted_ix : Array[Int] = Array::new()
for i in 0.. 0 {
let first = blues.at(sorted_ix[j - 1])
let second = blues.at(key)
let a = if autohint_blue_is_top_like(first.zones) {
first.position
} else {
first.overshoot
}
let b = if autohint_blue_is_top_like(second.zones) {
second.position
} else {
second.overshoot
}
if b >= a {
break
}
sorted_ix[j] = sorted_ix[j - 1]
j = j - 1
}
sorted_ix[j] = key
}
for i in 0..<(sorted_ix.length() - 1) {
let i1 = sorted_ix[i]
let i2 = sorted_ix[i + 1]
let b1 = blues.at(i1)
let b2 = blues.at(i2)
let a = if autohint_blue_is_top_like(b1.zones) {
b1.overshoot
} else {
b1.position
}
let b = if autohint_blue_is_top_like(b2.zones) {
b2.overshoot
} else {
b2.position
}
if a > b {
if autohint_blue_is_top_like(b1.zones) {
blues.at(i1).overshoot = b
} else {
blues.at(i1).position = b
}
}
}
blues
}
///|
fn autohint_compute_cjk_blues(
outlines : OutlineGlyphCollection,
blues_data : Array[(String, Int)],
) -> Array[AutoHintUnscaledBlue] {
// This returns only vertical blues (horizontal are disabled).
let blues : Array[AutoHintUnscaledBlue] = Array::new()
for entry in blues_data.iter() {
let (blue_str, blue_zones) = entry
if blues.length() >= AUTOHINT_MAX_BLUES {
continue
}
if autohint_blue_is_horizontal(blue_zones) {
continue
}
autohint_blue_is_right(blue_zones) |> ignore
let is_top = autohint_blue_is_top(blue_zones)
let fills : Array[Int] = Array::new()
let flats : Array[Int] = Array::new()
let mut is_fill = true
for ch in blue_str {
if ch == ' ' {
continue
}
if ch == '|' {
is_fill = false
continue
}
let cp_i = autohint_char_to_codepoint(ch).unwrap_or(-1)
if cp_i < 0 {
continue
}
let cp = cp_i |> Int::reinterpret_as_uint
let gid = match outlines.font.charmap().map(cp) {
None => continue
Some(g) => g
}
if gid.to_uint64() == 0 {
continue
}
let outline = match autohint_load_outline_for_metrics(outlines, gid) {
None => continue
Some(o) => o
}
if outline.points.length() <= 2 {
continue
}
let mut best : Int? = None
for p in outline.points.iter() {
let v = p.fy
if best is None {
best = Some(v)
} else if is_top {
if v > best.unwrap_or(v) {
best = Some(v)
}
} else if v < best.unwrap_or(v) {
best = Some(v)
}
}
match best {
None => ()
Some(v) => if is_fill { fills.push(v) } else { flats.push(v) }
}
}
if fills.is_empty() && flats.is_empty() {
continue
}
// Sort and take medians.
autohint_sort_ints(fills)
autohint_sort_ints(flats)
let blue_ref = if fills.is_empty() {
flats.at(flats.length() / 2)
} else {
fills.at(fills.length() / 2)
}
let blue_shoot = if flats.is_empty() {
blue_ref
} else {
flats.at(flats.length() / 2)
}
blues.push({
position: blue_ref,
overshoot: blue_shoot,
ascender: blue_ref,
descender: blue_ref,
zones: autohint_blue_retain_top_like_or_neutral(blue_zones),
})
}
blues
}
///|
fn autohint_compute_unscaled_blues(
outlines : OutlineGlyphCollection,
group : AutoHintScriptGroup,
style_ix : Int,
units_per_em : Int,
) -> Array[Array[AutoHintUnscaledBlue]] {
let out : Array[Array[AutoHintUnscaledBlue]] = Array::new()
match group {
Default => {
out.push(Array::new())
out.push(
autohint_compute_default_blues(
outlines,
autohint_style_blues(style_ix),
units_per_em,
),
)
}
Cjk => {
// CJK blues are computed in both directions upstream, but horizontal
// zones are disabled. Keep structure identical: [h, v].
out.push(Array::new())
out.push(
autohint_compute_cjk_blues(outlines, autohint_style_blues(style_ix)),
)
}
}
out
}
///|
fn autohint_compute_unscaled_style_metrics(
outlines : OutlineGlyphCollection,
style_ix : Int,
units_per_em : Int,
) -> AutoHintUnscaledStyleMetrics {
let group = autohint_style_script_group(style_ix)
let hint_top_to_bottom = autohint_style_hint_top_to_bottom(style_ix)
let axes = autohint_compute_unscaled_widths(
outlines,
units_per_em,
autohint_style_std_chars(style_ix),
)
let blues = autohint_compute_unscaled_blues(
outlines, group, style_ix, units_per_em,
)
// Attach blues per axis.
for dim in 0..<2 {
axes.at(dim).blues = blues.at(dim)
}
{ group, hint_top_to_bottom, axes }
}
///|
fn autohint_scale_default_axis_metrics(
axis : AutoHintUnscaledAxisMetrics,
scale : AutoHintScale,
) -> (AutoHintScale, AutoHintScaledAxisMetrics) {
let dim = axis.dim
let scale = scale
let scaled = AutoHintScaledAxisMetrics::default(dim)
if dim == AUTOHINT_DIM_HORIZONTAL {
scaled.scale = scale.x_scale
scaled.delta = scale.x_delta
} else {
scaled.scale = scale.y_scale
scaled.delta = scale.y_delta
}
// Y-scale correction to optimize alignment.
if dim == AUTOHINT_DIM_VERTICAL {
for blue in axis.blues.iter() {
if autohint_blue_contains(blue.zones, AUTOHINT_BLUE_ADJUSTMENT) {
let scaled_shoot = autohint_fixed_mul(scaled.scale, blue.overshoot)
let fitted = (scaled_shoot + 40) & -64
if scaled_shoot != fitted {
let new_scale = autohint_fixed_mul_div(
scaled.scale,
fitted,
scaled_shoot,
)
let mut max_height = scale.units_per_em
for b in axis.blues.iter() {
max_height = max_height.max(b.ascender).max(-b.descender)
}
let mut dist = autohint_fixed_mul(
max_height,
new_scale - scaled.scale,
).abs()
dist = dist & -128
if dist == 0 {
scaled.scale = new_scale
scale.y_scale = new_scale
}
}
break
}
}
}
// Scale widths.
scaled.width_metrics = axis.width_metrics
for w in axis.widths.iter() {
let s = autohint_fixed_mul(scaled.scale, w)
scaled.widths.push({ scaled: s, fitted: s })
}
scaled.width_metrics = {
edge_distance_threshold: axis.width_metrics.edge_distance_threshold,
standard_width: axis.width_metrics.standard_width,
is_extra_light: autohint_fixed_mul(
axis.width_metrics.standard_width,
scaled.scale,
) <
32 + 8,
}
if dim == AUTOHINT_DIM_VERTICAL {
// Scale blues and activate narrow zones.
for blue in axis.blues.iter() {
let pos = autohint_fixed_mul(scaled.scale, blue.position) + scaled.delta
let shoot = autohint_fixed_mul(scaled.scale, blue.overshoot) +
scaled.delta
let out_blue = AutoHintScaledBlue::{
position: { scaled: pos, fitted: pos },
overshoot: { scaled: shoot, fitted: shoot },
zones: blue.zones,
is_active: false,
}
let out_blue = out_blue
let dist = autohint_fixed_mul(
blue.position - blue.overshoot,
scaled.scale,
)
if dist >= -48 && dist <= 48 {
let mut delta = dist.abs()
if delta < 32 {
delta = 0
} else if delta < 48 {
delta = 32
} else {
delta = 64
}
if dist < 0 {
delta = -delta
}
out_blue.position.fitted = autohint_pix_round(out_blue.position.scaled)
out_blue.overshoot.fitted = out_blue.position.fitted - delta
out_blue.is_active = true
}
scaled.blues.push(out_blue)
}
// Disable sub-top blue zones that overlap with other active zones.
for i in 0..= b.position.fitted {
scaled.blues.at(i).is_active = false
break
}
}
}
}
(scale, scaled)
}
///|
fn autohint_scale_cjk_axis_metrics(
axis : AutoHintUnscaledAxisMetrics,
scale : AutoHintScale,
) -> (AutoHintScale, AutoHintScaledAxisMetrics) {
let dim = axis.dim
let scale = scale
let scaled = AutoHintScaledAxisMetrics::default(dim)
if dim == AUTOHINT_DIM_HORIZONTAL {
scaled.scale = scale.x_scale
scaled.delta = scale.x_delta
} else {
scaled.scale = scale.y_scale
scaled.delta = scale.y_delta
}
// Scale blues (CJK uses different overshoot rounding).
for blue in axis.blues.iter() {
let pos = autohint_fixed_mul(blue.position, scaled.scale) + scaled.delta
let shoot = autohint_fixed_mul(blue.overshoot, scaled.scale) + scaled.delta
let out_blue = AutoHintScaledBlue::{
position: { scaled: pos, fitted: pos },
overshoot: { scaled: shoot, fitted: shoot },
zones: blue.zones,
is_active: false,
}
let out_blue = out_blue
let dist = autohint_fixed_mul(blue.position - blue.overshoot, scaled.scale)
if dist >= -48 && dist <= 48 {
out_blue.position.fitted = autohint_pix_round(out_blue.position.scaled)
let delta1 = autohint_fixed_div(out_blue.position.fitted, scaled.scale) -
blue.overshoot
let mut delta2 = autohint_fixed_mul(delta1.abs(), scaled.scale)
if delta2 < 32 {
delta2 = 0
} else {
delta2 = autohint_pix_round(delta2)
}
if delta1 < 0 {
delta2 = -delta2
}
out_blue.overshoot.fitted = out_blue.position.fitted - delta2
out_blue.is_active = true
}
scaled.blues.push(out_blue)
}
// Match FreeType behavior: do not compute scaled width values.
for _ in 0.. AutoHintScaledStyleMetrics {
let mut scale = scale
let axes : Array[AutoHintScaledAxisMetrics] = Array::new()
for dim in 0..<2 {
let axis = unscaled.axes.at(dim)
let (s, scaled_axis) = match unscaled.group {
Default => autohint_scale_default_axis_metrics(axis, scale)
Cjk => autohint_scale_cjk_axis_metrics(axis, scale)
}
scale = s
axes.push(scaled_axis)
}
{ scale, axes }
}
///|
fn autohint_sort_and_quantize_widths(
widths : Array[Int],
threshold : Int,
) -> Unit {
if widths.length() <= 1 {
return
}
// Insertion sort (MAX_WIDTHS is small).
for i in 1.. 0 && widths[j - 1] > key {
widths[j] = widths[j - 1]
j = j - 1
}
widths[j] = key
}
let mut cur_ix = 0
let mut cur_val = widths[cur_ix]
let last_ix = widths.length() - 1
let mut ix = 1
// Compute and use mean values for clusters not larger than `threshold`.
while ix < widths.length() {
if widths[ix] - cur_val > threshold || ix == last_ix {
let mut sum = 0
// Fix loop for end of array?
if widths[ix] - cur_val <= threshold && ix == last_ix {
ix = ix + 1
}
for j in cur_ix.. String {
let mut s = "["
for i in 0.. ignore
AUTOHINT_BLUE_LONG |> ignore
AUTOHINT_MAX_WIDTHS |> ignore
autohint_pix_floor(0) |> ignore
let scale = AutoHintScale(16.0, 2048, Normal, Mono, Default)
// Basic smoke checks (also marks fields as used for warnings).
inspect((scale.x_scale != 0).to_string(), content="true")
inspect(
((scale.flags & AUTOHINT_SCALE_MONO) != 0).to_string(),
content="true",
)
inspect(scale.y_scale.to_string(), content=scale.y_scale.to_string())
inspect(scale.x_delta.to_string(), content=scale.x_delta.to_string())
inspect(scale.y_delta.to_string(), content=scale.y_delta.to_string())
inspect(scale.size.to_string(), content=scale.size.to_string())
inspect(
scale.units_per_em.to_string(),
content=scale.units_per_em.to_string(),
)
}