///|
/// Translate all points of an outline by (dx, dy).
/// Port of FT_Outline_Translate from ftoutln.c.
pub fn outline_translate(
outline : @types.Outline,
dx : Int64,
dy : Int64,
) -> Unit {
for i in 0.. Unit {
for i in 0.. Unit {
let n_contours = outline.n_contours()
if n_contours == 0 {
return
}
for c = 0, first = 0; c < n_contours; {
let last = outline.contours()[c].reinterpret_as_int()
// Reverse points and tags between first and last
for i = first, j = last; i < j; {
// Swap points
let tmp_pt = outline.points()[i]
outline.points()[i] = outline.points()[j]
outline.points()[j] = tmp_pt
// Swap tags
let tmp_tag = outline.tags()[i]
outline.tags()[i] = outline.tags()[j]
outline.tags()[j] = tmp_tag
continue i + 1, j - 1
}
continue c + 1, last + 1
}
// Flip fill direction flag
outline.set_flags(outline.flags() ^ @types.OUTLINE_REVERSE_FILL)
}
///|
/// Callback interface for outline decomposition.
/// MoonBit version of FT_Outline_Funcs.
struct OutlineFuncs {
move_to : (@types.Vector) -> Unit raise @error.FTError
line_to : (@types.Vector) -> Unit raise @error.FTError
conic_to : (@types.Vector, @types.Vector) -> Unit raise @error.FTError
cubic_to : (@types.Vector, @types.Vector, @types.Vector) -> Unit raise @error.FTError
shift : Int
delta : Int64
}
///|
pub fn OutlineFuncs::new(
move_to~ : (@types.Vector) -> Unit raise @error.FTError,
line_to~ : (@types.Vector) -> Unit raise @error.FTError,
conic_to~ : (@types.Vector, @types.Vector) -> Unit raise @error.FTError,
cubic_to~ : (@types.Vector, @types.Vector, @types.Vector) -> Unit raise @error.FTError,
shift? : Int = 0,
delta? : Int64 = 0L,
) -> OutlineFuncs {
{ move_to, line_to, conic_to, cubic_to, shift, delta }
}
///|
/// Validate outline backing storage and contour endpoints before walking it.
/// This intentionally accepts trailing unused points in reusable outline buffers
/// and repeated contour endpoints that encode empty contours; the critical
/// requirement for safe decomposition is that contour endpoints never decrease
/// and always stay in bounds for the declared point count.
pub fn outline_check(outline : @types.Outline) -> Unit raise @error.FTError {
let n_points = outline.n_points()
let n_contours = outline.n_contours()
if n_points == 0 && n_contours == 0 {
return
}
if n_points <= 0 || n_contours <= 0 {
raise @error.FTError::InvalidOutline
}
if outline.points().length() < n_points ||
outline.tags().length() < n_points ||
outline.contours().length() < n_contours {
raise @error.FTError::InvalidOutline
}
let mut end0 = -1
for i in 0..= n_points {
raise @error.FTError::InvalidOutline
}
end0 = end
}
}
///|
/// Walk an outline's contours, emitting move_to/line_to/conic_to/cubic_to callbacks.
/// Port of FT_Outline_Decompose from ftoutln.c.
pub fn outline_decompose(
outline : @types.Outline,
funcs : OutlineFuncs,
) -> Unit raise @error.FTError {
outline_check(outline)
let n_contours = outline.n_contours()
if n_contours == 0 {
return
}
let shift = funcs.shift
let delta = funcs.delta
fn apply(v : @types.Vector) -> @types.Vector {
@types.Vector::new((v.x() << shift) - delta, (v.y() << shift) - delta)
}
for c = 0, first = 0; c < n_contours; {
let last = outline.contours()[c].reinterpret_as_int()
if last < first {
continue c + 1, first
}
// Find first on-curve point
let mut start = first
let mut start_ct = @types.CurveTag::from_byte(outline.tags()[start])
// If first point is off-curve conic, check last point
if start_ct == Conic {
let last_ct = @types.CurveTag::from_byte(outline.tags()[last])
if last_ct == On {
start = last
start_ct = On
} else {
// Both off-curve: start at virtual on-curve midpoint
let p0 = outline.points()[first]
let p1 = outline.points()[last]
let mid = @types.Vector::new(
(p0.x() + p1.x()) / 2L,
(p0.y() + p1.y()) / 2L,
)
(funcs.move_to)(apply(mid))
// Process from first point
let mut i = first
while i <= last {
let pt = apply(outline.points()[i])
match @types.CurveTag::from_byte(outline.tags()[i]) {
On => (funcs.line_to)(pt)
Conic => {
// Look ahead for next on-curve or another conic
let next_i = if i < last { i + 1 } else { first }
let next_pt = apply(outline.points()[next_i])
match @types.CurveTag::from_byte(outline.tags()[next_i]) {
On => {
(funcs.conic_to)(pt, next_pt)
i += 1 // skip the on-curve point we just consumed
}
Conic | Cubic => {
// Two consecutive off-curve: insert virtual on-curve midpoint
let virt = @types.Vector::new(
(pt.x() + next_pt.x()) / 2L,
(pt.y() + next_pt.y()) / 2L,
)
(funcs.conic_to)(pt, virt)
}
}
}
Cubic =>
if i + 1 <= last {
let ctrl2 = apply(outline.points()[i + 1])
let next_i = if i + 2 <= last { i + 2 } else { first }
let to = apply(outline.points()[next_i])
(funcs.cubic_to)(pt, ctrl2, to)
i += 2
}
}
i += 1
}
// Close contour
(funcs.line_to)(apply(mid))
continue c + 1, last + 1
}
}
// Normal case: start point is on-curve
let start_pt = apply(outline.points()[start])
(funcs.move_to)(start_pt)
let mut i = if start == last { first } else { start + 1 }
let mut done = false
while !done {
let pt = apply(outline.points()[i])
match @types.CurveTag::from_byte(outline.tags()[i]) {
On => (funcs.line_to)(pt)
Conic => {
let next_i = if i < last { i + 1 } else { first }
let next_pt = apply(outline.points()[next_i])
match @types.CurveTag::from_byte(outline.tags()[next_i]) {
On => {
(funcs.conic_to)(pt, next_pt)
i = next_i
}
Conic | Cubic => {
let virt = @types.Vector::new(
(pt.x() + next_pt.x()) / 2L,
(pt.y() + next_pt.y()) / 2L,
)
(funcs.conic_to)(pt, virt)
}
}
}
Cubic => {
// Cubic: expect two off-curve points + one on-curve
let next_i = if i < last { i + 1 } else { first }
let ctrl2 = apply(outline.points()[next_i])
let to_i = if next_i < last { next_i + 1 } else { first }
let to = apply(outline.points()[to_i])
(funcs.cubic_to)(pt, ctrl2, to)
i = to_i
}
}
if i == start || (start == last && i == last) {
done = true
} else {
i = if i < last { i + 1 } else { first }
}
}
continue c + 1, last + 1
}
}
///|
/// Compute the orientation of an outline.
/// Returns 1 for clockwise, -1 for counter-clockwise, 0 for degenerate.
/// Port of FT_Outline_Get_Orientation from ftoutln.c.
pub fn outline_get_orientation(outline : @types.Outline) -> Int {
let n_points = outline.n_points()
if n_points < 3 {
return 0
}
// Use the shoelace formula (signed area)
let mut area = 0L
for i = 0, prev = outline.points()[n_points - 1]; i < n_points; {
let curr = outline.points()[i]
area += (prev.x() - curr.x()) * (prev.y() + curr.y())
continue i + 1, curr
}
if area > 0L {
1 // clockwise (TrueType convention)
} else if area < 0L {
-1 // counter-clockwise (Type 1 convention)
} else {
0
}
}