// Copyright 2026 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.
let flag_on_curve : Int = 0x01
let flag_cubic : Int = 0x80
priv struct DrawPoint {
x : Double
y : Double
}
fn mid_point(a : DrawPoint, b : DrawPoint) -> DrawPoint {
DrawPoint::{ x: (a.x + b.x) * 0.5, y: (a.y + b.y) * 0.5 }
}
fn scale_coord(value : Double, scale : Int, upem : Int) -> Double {
if upem == 0 {
return value
}
value * scale.to_double() / upem.to_double()
}
fn scale_point(p : @sfnt.GlyphPoint, scale_x : Int, scale_y : Int, upem : Int) -> DrawPoint {
DrawPoint::{
x: scale_coord(p.x, scale_x, upem),
y: scale_coord(p.y, scale_y, upem),
}
}
fn[T] draw_contour(
points : Array[@sfnt.GlyphPoint],
start : Int,
end : Int,
session : @draw.DrawSession[T],
scale_x : Int,
scale_y : Int,
upem : Int,
) -> Unit {
if start > end {
return
}
let mut first_oncurve : DrawPoint? = None
let mut first_offcurve : DrawPoint? = None
let mut first_offcurve2 : DrawPoint? = None
let mut last_offcurve : DrawPoint? = None
let mut last_offcurve2 : DrawPoint? = None
for idx in start..=end {
let point = points[idx]
let on_curve = (point.flag & flag_on_curve) != 0
let is_cubic = !on_curve && (point.flag & flag_cubic) != 0
let p = scale_point(point, scale_x, scale_y, upem)
if first_oncurve is None {
if on_curve {
first_oncurve = Some(p)
session.move_to(p.x, p.y)
} else {
if is_cubic && first_offcurve2 is None {
first_offcurve2 = first_offcurve
first_offcurve = Some(p)
} else {
match first_offcurve {
Some(ofc) => {
let mid = mid_point(ofc, p)
first_oncurve = Some(mid)
last_offcurve = Some(p)
session.move_to(mid.x, mid.y)
}
None => first_offcurve = Some(p)
}
}
}
} else {
match last_offcurve {
Some(lof) => {
if on_curve {
match last_offcurve2 {
Some(lof2) => {
session.cubic_to(
lof2.x,
lof2.y,
lof.x,
lof.y,
p.x,
p.y,
)
last_offcurve2 = None
}
None => {
session.quadratic_to(lof.x, lof.y, p.x, p.y)
}
}
last_offcurve = None
} else {
if is_cubic && last_offcurve2 is None {
last_offcurve2 = Some(lof)
last_offcurve = Some(p)
} else {
let mid = mid_point(lof, p)
if is_cubic {
match last_offcurve2 {
Some(lof2) =>
session.cubic_to(
lof2.x,
lof2.y,
lof.x,
lof.y,
mid.x,
mid.y,
)
None => session.quadratic_to(lof.x, lof.y, mid.x, mid.y)
}
last_offcurve2 = None
} else {
session.quadratic_to(lof.x, lof.y, mid.x, mid.y)
}
last_offcurve = Some(p)
}
}
}
None => {
if on_curve {
session.line_to(p.x, p.y)
} else {
last_offcurve = Some(p)
}
}
}
}
}
if first_offcurve is Some(ofc) && last_offcurve is Some(lof) {
let first_for_mid = match first_offcurve2 {
Some(ofc2) => ofc2
None => ofc
}
let mid = mid_point(lof, first_for_mid)
match last_offcurve2 {
Some(lof2) =>
session.cubic_to(
lof2.x,
lof2.y,
lof.x,
lof.y,
mid.x,
mid.y,
)
None => session.quadratic_to(lof.x, lof.y, mid.x, mid.y)
}
last_offcurve = None
last_offcurve2 = None
}
if first_offcurve is Some(ofc) && first_oncurve is Some(onc) {
match first_offcurve2 {
Some(ofc2) =>
session.cubic_to(
ofc2.x,
ofc2.y,
ofc.x,
ofc.y,
onc.x,
onc.y,
)
None => session.quadratic_to(ofc.x, ofc.y, onc.x, onc.y)
}
} else if last_offcurve is Some(lof) && first_oncurve is Some(onc) {
match last_offcurve2 {
Some(lof2) =>
session.cubic_to(
lof2.x,
lof2.y,
lof.x,
lof.y,
onc.x,
onc.y,
)
None => session.quadratic_to(lof.x, lof.y, onc.x, onc.y)
}
} else if first_oncurve is Some(onc) {
session.line_to(onc.x, onc.y)
} else if first_offcurve is Some(ofc) {
session.move_to(ofc.x, ofc.y)
session.quadratic_to(ofc.x, ofc.y, ofc.x, ofc.y)
}
session.close_path()
}
fn[T] draw_glyf_points(
points : Array[@sfnt.GlyphPoint],
session : @draw.DrawSession[T],
scale_x : Int,
scale_y : Int,
upem : Int,
) -> Unit {
let mut start = 0
let count = points.length()
for i in 0.. Result[Bool, FontError] {
let glyf_blob = match self.face.reference_table_optional(tag_glyf) {
Err(err) => return Err(FaceTable(err))
Ok(value) => value
}
let loca_blob = match self.face.reference_table_optional(tag_loca) {
Err(err) => return Err(FaceTable(err))
Ok(value) => value
}
if glyf_blob is None || loca_blob is None {
return Ok(false)
}
let head = match self.ensure_head() {
Err(err) => return Err(err)
Ok(value) => value
}
let coords : Array[Int] = match self.var_coords_norm {
None => []
Some(value) => value
}
let points = match self.glyph_points_with_gvar(glyph, coords) {
Err(err) => return Err(err)
Ok(value) => value
}
match points {
None => Ok(false)
Some(points) => {
let (outline, _) = split_phantoms(points)
if outline.is_empty() {
return Ok(true)
}
let session = @draw.DrawSession::new(funcs, draw_data)
draw_glyf_points(outline, session, self.scale_x, self.scale_y, head.units_per_em)
Ok(true)
}
}
}
///|
/// Convenience wrapper that ignores draw failures.
pub fn[T] Font::draw_glyph(
self : Font,
glyph : UInt,
funcs : @draw.DrawFuncs[T],
draw_data : T,
) -> Result[Unit, FontError] {
match self.draw_glyph_or_fail(glyph, funcs, draw_data) {
Err(err) => Err(err)
Ok(_) => Ok(())
}
}