// 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.
///|
/// Types for collecting the output when drawing a glyph outline.
///
/// Ported from `fontations/skrifa/src/outline/pen.rs` (Apache-2.0 OR MIT).
///|
/// Style for converting glyf point streams to path commands.
///
/// Some glyph point streams are ambiguous when the first point is off-curve;
/// major implementations differ. This lets callers choose which behavior to
/// match.
pub(all) enum PathStyle {
FreeType
HarfBuzz
}
///|
pub fn PathStyle::default() -> PathStyle {
FreeType
}
///|
/// Interface for accepting a sequence of path commands.
pub(open) trait OutlinePen {
fn move_to(Self, Double, Double) -> Unit
fn line_to(Self, Double, Double) -> Unit
fn quad_to(Self, Double, Double, Double, Double) -> Unit
fn curve_to(Self, Double, Double, Double, Double, Double, Double) -> Unit
fn close(Self) -> Unit
}
///|
pub impl OutlinePen for Array[PathElement] with fn move_to(out, x, y) {
out.push(MoveTo(x, y))
}
///|
pub impl OutlinePen for Array[PathElement] with fn line_to(out, x, y) {
out.push(LineTo(x, y))
}
///|
pub impl OutlinePen for Array[PathElement] with fn quad_to(out, cx0, cy0, x, y) {
out.push(QuadTo(cx0, cy0, x, y))
}
///|
pub impl OutlinePen for Array[PathElement] with fn curve_to(
out,
cx0,
cy0,
cx1,
cy1,
x,
y,
) {
out.push(CurveTo(cx0, cy0, cx1, cy1, x, y))
}
///|
pub impl OutlinePen for Array[PathElement] with fn close(out) {
out.push(Close)
}
///|
/// Pen that generates SVG style path data.
pub struct SvgPen {
priv mut s : String
priv precision : Int?
}
///|
pub fn SvgPen::SvgPen() -> SvgPen {
{ s: "", precision: None }
}
///|
/// Creates a new SVG pen that formats floating point values with the given
/// precision (the number of digits printed after the decimal point).
pub fn SvgPen::with_precision(precision : Int) -> SvgPen {
{ s: "", precision: Some(precision) }
}
///|
pub fn SvgPen::clear(self : SvgPen) -> Unit {
self.s = ""
}
///|
pub fn SvgPen::to_string(self : SvgPen) -> String {
self.s
}
///|
fn SvgPen::maybe_space(self : SvgPen) -> Unit {
if !self.s.is_empty() {
self.s = self.s + " "
}
}
///|
fn svg_pen_pow10_int(p : Int) -> Int {
let mut v = 1
for _ in 0.. Int {
let i = x.to_int()
let frac = x - i.to_double()
let diff = frac - 0.5
if diff.abs() <= 0.000000000001 {
if i % 2 == 0 {
i
} else {
i + 1
}
} else if frac < 0.5 {
i
} else {
i + 1
}
}
///|
/// Formats a floating point value with a fixed number of digits after the
/// decimal point, rounding to nearest.
fn svg_pen_format_fixed(x : Double, precision : Int) -> String {
let sign = if x < 0.0 { "-" } else { "" }
if precision <= 0 {
return sign + svg_pen_round_ties_even_pos(x.abs()).to_string()
}
let pow10 = svg_pen_pow10_int(precision)
let scaled = svg_pen_round_ties_even_pos(x.abs() * pow10.to_double())
let int_part = scaled / pow10
let frac_part = scaled % pow10
let mut frac = frac_part.to_string()
// Pad leading zeros.
while frac.length() < precision {
frac = "0" + frac
}
sign + int_part.to_string() + "." + frac
}
///|
fn SvgPen::fmt_num(self : SvgPen, x : Double) -> String {
match self.precision {
None => x.to_string()
Some(p) => svg_pen_format_fixed(x, p)
}
}
///|
pub impl OutlinePen for SvgPen with fn move_to(pen, x, y) {
pen.maybe_space()
pen.s = pen.s + ("M" + pen.fmt_num(x) + "," + pen.fmt_num(y))
}
///|
pub impl OutlinePen for SvgPen with fn line_to(pen, x, y) {
pen.maybe_space()
pen.s = pen.s + ("L" + pen.fmt_num(x) + "," + pen.fmt_num(y))
}
///|
pub impl OutlinePen for SvgPen with fn quad_to(pen, cx0, cy0, x, y) {
pen.maybe_space()
pen.s = pen.s +
(
"Q" +
pen.fmt_num(cx0) +
"," +
pen.fmt_num(cy0) +
" " +
pen.fmt_num(x) +
"," +
pen.fmt_num(y)
)
}
///|
pub impl OutlinePen for SvgPen with fn curve_to(pen, cx0, cy0, cx1, cy1, x, y) {
pen.maybe_space()
pen.s = pen.s +
(
"C" +
pen.fmt_num(cx0) +
"," +
pen.fmt_num(cy0) +
" " +
pen.fmt_num(cx1) +
"," +
pen.fmt_num(cy1) +
" " +
pen.fmt_num(x) +
"," +
pen.fmt_num(y)
)
}
///|
pub impl OutlinePen for SvgPen with fn close(pen) {
pen.maybe_space()
pen.s = pen.s + "Z"
}
///|
/// Pen that drops all drawing output.
pub struct NullPen {
priv _unit : Unit
}
///|
pub fn NullPen::NullPen() -> NullPen {
{ _unit: () }
}
///|
pub impl OutlinePen for NullPen with fn move_to(_, _, _) {
}
///|
pub impl OutlinePen for NullPen with fn line_to(_, _, _) {
}
///|
pub impl OutlinePen for NullPen with fn quad_to(_, _, _, _, _) {
}
///|
pub impl OutlinePen for NullPen with fn curve_to(_, _, _, _, _, _, _) {
}
///|
pub impl OutlinePen for NullPen with fn close(_) {
}