// 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.
///|
/// Apply edge hints to an autohint outline (point alignment passes).
///
/// Ported from `fontations/skrifa/src/outline/autohint/hint/outline.rs`
/// (Apache-2.0 OR MIT).
///|
fn autohint_store_point(point : AutoHintPoint, dim : Int, u : Int) -> Unit {
if dim == AUTOHINT_DIM_HORIZONTAL {
point.x = u
point.flags.set_marker(TouchedX)
} else {
point.y = u
point.flags.set_marker(TouchedY)
}
}
///|
/// Align all points of an edge to the same coordinate value.
fn autohint_align_edge_points(
outline : AutoHintOutline,
axis : AutoHintAxis,
group : AutoHintScriptGroup,
scale_flags : Int,
) -> Unit {
// Snapping is configurable for CJK.
let snap = group is Default ||
(
axis.dim == AUTOHINT_DIM_HORIZONTAL &&
(scale_flags & AUTOHINT_SCALE_HORIZONTAL_SNAP) != 0
) ||
(
axis.dim == AUTOHINT_DIM_VERTICAL &&
(scale_flags & AUTOHINT_SCALE_VERTICAL_SNAP) != 0
)
for segment in axis.segments.iter() {
match segment.edge_ix {
None => ()
Some(edge_ix) => {
let edge = axis.edges.at(edge_ix)
let delta = edge.pos - edge.opos
let touch_marker = if axis.dim == AUTOHINT_DIM_HORIZONTAL {
AutoHintPointMarker::TouchedX
} else {
TouchedY
}
let mut point_ix = segment.first_ix
let last_ix = segment.last_ix
while true {
let point = outline.points.at(point_ix)
if axis.dim == AUTOHINT_DIM_HORIZONTAL {
if snap {
point.x = edge.pos
} else {
point.x = point.x + delta
}
point.flags.set_marker(touch_marker)
} else {
if snap {
point.y = edge.pos
} else {
point.y = point.y + delta
}
point.flags.set_marker(touch_marker)
}
if point_ix == last_ix {
break
}
point_ix = point.next()
}
}
}
}
}
///|
/// Align the strong points; equivalent to the TrueType `IP` instruction.
fn autohint_align_strong_points(
outline : AutoHintOutline,
axis : AutoHintAxis,
) -> Unit {
if axis.edges.is_empty() {
return
}
let dim = axis.dim
let touch_mask = (if dim == AUTOHINT_DIM_HORIZONTAL {
AutoHintPointMarker::TouchedX
} else {
TouchedY
}).bit()
let weak_mask = AutoHintPointMarker::WeakInterpolation.bit()
for i in 0..= 0 {
autohint_store_point(point, dim, first_edge.pos - (first_edge.opos - ou))
continue
}
// Is the point after the last edge?
let last_edge = axis.edges.at(axis.edges.length() - 1)
let delta1 = u - last_edge.fpos
if delta1 >= 0 {
autohint_store_point(point, dim, last_edge.pos + (ou - last_edge.opos))
continue
}
// Find enclosing edges; for a small number of edges, use linear search.
let mut min_ix = 0
if axis.edges.length() <= 8 {
let mut found = false
for j in 0..= u {
if edge.fpos == u {
autohint_store_point(point, dim, edge.pos)
found = true
}
min_ix = j
break
}
}
if found {
continue
}
} else {
let mut lo = 0
let mut hi = axis.edges.length()
while lo < hi {
let mid = (lo + hi) >> 1
let edge = axis.edges.at(mid)
let fpos = edge.fpos
if u < fpos {
hi = mid
} else if u > fpos {
lo = mid + 1
} else {
autohint_store_point(point, dim, edge.pos)
lo = -1
break
}
}
if lo == -1 {
continue
}
min_ix = lo
}
// Point is not on an edge.
if min_ix > 0 {
let before_ix = min_ix - 1
let edge_before = axis.edges.at(before_ix)
let before_pos = edge_before.pos
let before_fpos = edge_before.fpos
let scale = if edge_before.scale == 0 {
let edge_after = axis.edges.at(min_ix)
let scale = autohint_fixed_div(
edge_after.pos - edge_before.pos,
edge_after.fpos - before_fpos,
)
axis.edges.at(before_ix).scale = scale
scale
} else {
edge_before.scale
}
autohint_store_point(
point,
dim,
before_pos + autohint_fixed_mul(u - before_fpos, scale),
)
}
}
}
///|
fn autohint_iup_shift(
outline : AutoHintOutline,
p1_ix : Int,
p2_ix : Int,
ref_ix : Int,
) -> Unit {
let ref_point = outline.points.at(ref_ix)
let delta = ref_point.u - ref_point.v
if delta == 0 {
return
}
for ix in p1_ix.. Unit {
if p1_ix > p2_ix {
return
}
let mut ref1 = outline.points.at(ref1_ix)
let mut ref2 = outline.points.at(ref2_ix)
if ref1.v > ref2.v {
let tmp = ref1
ref1 = ref2
ref2 = tmp
}
let (u1, v1) = (ref1.u, ref1.v)
let (u2, v2) = (ref2.u, ref2.v)
let d1 = u1 - v1
let d2 = u2 - v2
if u1 == u2 || v1 == v2 {
for ix in p1_ix..<(p2_ix + 1) {
let p = outline.points.at(ix)
p.u = if p.v <= v1 { p.v + d1 } else if p.v >= v2 { p.v + d2 } else { u1 }
}
} else {
let scale = autohint_fixed_div(u2 - u1, v2 - v1)
for ix in p1_ix..<(p2_ix + 1) {
let p = outline.points.at(ix)
p.u = if p.v <= v1 {
p.v + d1
} else if p.v >= v2 {
p.v + d2
} else {
u1 + autohint_fixed_mul(p.v - v1, scale)
}
}
}
}
///|
/// Align the weak points; equivalent to the TrueType `IUP` instruction.
fn autohint_align_weak_points(outline : AutoHintOutline, dim : Int) -> Unit {
let touch_mask = (if dim == AUTOHINT_DIM_HORIZONTAL {
for i in 0..= outline.points.length() {
continue
}
let mut first_touched : Int? = None
for ix in first..<(last + 1) {
if outline.points.at(ix).flags.has_marker_mask(touch_mask) {
first_touched = Some(ix)
break
}
}
match first_touched {
None => continue
Some(first_touched_ix) => {
let mut point_ix = first_touched_ix
let mut last_touched_ix = first_touched_ix
while true {
// Skip any touched neighbors.
while point_ix < last &&
outline.points.at(point_ix + 1).flags.has_marker_mask(
touch_mask,
) {
point_ix = point_ix + 1
}
last_touched_ix = point_ix
// Find next touched point.
point_ix = point_ix + 1
while true {
if point_ix > last {
break
}
if outline.points.at(point_ix).flags.has_marker_mask(touch_mask) {
break
}
point_ix = point_ix + 1
}
if point_ix > last {
break
}
autohint_iup_interpolate(
outline,
last_touched_ix + 1,
point_ix - 1,
last_touched_ix,
point_ix,
)
}
if last_touched_ix == first_touched_ix {
// Only one point was touched.
autohint_iup_shift(outline, first, last, first_touched_ix)
} else {
// Interpolate the remainder.
if last_touched_ix < last {
autohint_iup_interpolate(
outline,
last_touched_ix + 1,
last,
last_touched_ix,
first_touched_ix,
)
}
if first_touched_ix > first {
autohint_iup_interpolate(
outline,
first,
first_touched_ix - 1,
last_touched_ix,
first_touched_ix,
)
}
}
}
}
}
// Save interpolated values.
if dim == AUTOHINT_DIM_HORIZONTAL {
for i in 0..