// 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.
///|
/// Outline point types.
pub enum OutlinePointKind {
MoveTo
LineTo
QuadraticTo
CubicTo
} derive(Eq, Show, ToJson)
///|
/// Outline point with type.
pub struct OutlinePoint {
mut x : Double
mut y : Double
kind : OutlinePointKind
} derive(Eq, Show, ToJson)
///|
pub fn OutlinePoint::new(x : Double, y : Double, kind : OutlinePointKind) -> OutlinePoint {
OutlinePoint::{ x, y, kind }
}
priv struct OutlineVector {
mut x : Double
mut y : Double
}
fn OutlineVector::normalize_len(self : OutlineVector) -> Double {
let len = (self.x * self.x + self.y * self.y).sqrt()
if len != 0.0 {
self.x = self.x / len
self.y = self.y / len
}
len
}
///|
/// Outline path container (points + contour end indices).
pub struct Outline {
points : Array[OutlinePoint]
contours : Array[Int]
} derive(Show, ToJson)
///|
pub fn Outline::new() -> Outline {
Outline::{ points: [], contours: [] }
}
///|
pub fn Outline::reset(self : Outline) -> Unit {
self.points.clear()
self.contours.clear()
}
///|
pub fn[T] Outline::replay(self : Outline, pen : DrawFuncs[T], pen_data : T) -> Unit {
let session = DrawSession::new(pen, pen_data)
let mut first = 0
for contour in self.contours {
let mut i = first
while i < contour {
let p1 = self.points[i]
i = i + 1
match p1.kind {
OutlinePointKind::MoveTo => {
session.move_to(p1.x, p1.y)
}
OutlinePointKind::LineTo => {
session.line_to(p1.x, p1.y)
}
OutlinePointKind::QuadraticTo => {
let p2 = self.points[i]
i = i + 1
session.quadratic_to(p1.x, p1.y, p2.x, p2.y)
}
OutlinePointKind::CubicTo => {
let p2 = self.points[i]
let p3 = self.points[i + 1]
i = i + 2
session.cubic_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y)
}
}
}
session.close_path()
first = contour
}
}
///|
pub fn Outline::control_area(self : Outline) -> Double {
let mut area = 0.0
let mut first = 0
for contour in self.contours {
for i in first.. Unit {
for point in self.points {
point.x = point.x + dx
point.y = point.y + dy
}
}
///|
pub fn Outline::slant(self : Outline, slant_xy : Double) -> Unit {
for point in self.points {
point.x = point.x + slant_xy * point.y
}
}
///|
pub fn Outline::embolden(
self : Outline,
x_strength : Double,
y_strength : Double,
x_shift : Double,
y_shift : Double,
) -> Unit {
if x_strength == 0.0 && y_strength == 0.0 {
return
}
if self.points.is_empty() {
return
}
let x_strength = x_strength / 2.0
let y_strength = y_strength / 2.0
let orientation_negative = self.control_area() < 0.0
let mut first = 0
for contour_index in 0.. -15.0 / 16.0 {
d = d + 1.0
shift.x = input_vec.y + output_vec.y
shift.y = input_vec.x + output_vec.x
if orientation_negative {
shift.x = -shift.x
} else {
shift.y = -shift.y
}
let mut q = output_vec.x * input_vec.y - output_vec.y * input_vec.x
if orientation_negative {
q = -q
}
let l = if l_in < l_out { l_in } else { l_out }
if x_strength * q <= l * d {
shift.x = shift.x * x_strength / d
} else {
shift.x = shift.x * l / q
}
if y_strength * q <= l * d {
shift.y = shift.y * y_strength / d
} else {
shift.y = shift.y * l / q
}
} else {
shift.x = 0.0
shift.y = 0.0
}
while i != j {
self.points[i].x = self.points[i].x + x_shift + shift.x
self.points[i].y = self.points[i].y + y_shift + shift.y
i = if i < last { i + 1 } else { first }
}
} else {
i = j
}
input_vec = output_vec
l_in = l_out
j = if j < last { j + 1 } else { first }
}
first = last + 1
}
}
fn outline_recording_pen_move_to(data : Outline, _st : DrawState, to_x : Double, to_y : Double) -> Unit {
data.points.push(OutlinePoint::new(to_x, to_y, OutlinePointKind::MoveTo))
}
fn outline_recording_pen_line_to(data : Outline, _st : DrawState, to_x : Double, to_y : Double) -> Unit {
data.points.push(OutlinePoint::new(to_x, to_y, OutlinePointKind::LineTo))
}
fn outline_recording_pen_quadratic_to(
data : Outline,
_st : DrawState,
control_x : Double,
control_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
data.points.push(OutlinePoint::new(control_x, control_y, OutlinePointKind::QuadraticTo))
data.points.push(OutlinePoint::new(to_x, to_y, OutlinePointKind::QuadraticTo))
}
fn outline_recording_pen_cubic_to(
data : Outline,
_st : DrawState,
control1_x : Double,
control1_y : Double,
control2_x : Double,
control2_y : Double,
to_x : Double,
to_y : Double,
) -> Unit {
data.points.push(OutlinePoint::new(control1_x, control1_y, OutlinePointKind::CubicTo))
data.points.push(OutlinePoint::new(control2_x, control2_y, OutlinePointKind::CubicTo))
data.points.push(OutlinePoint::new(to_x, to_y, OutlinePointKind::CubicTo))
}
fn outline_recording_pen_close_path(data : Outline, _st : DrawState) -> Unit {
data.contours.push(data.points.length())
}
///|
pub fn outline_recording_pen_get_funcs() -> DrawFuncs[Outline] {
let funcs : DrawFuncs[Outline] = DrawFuncs::new()
funcs.set_move_to(Some(outline_recording_pen_move_to))
funcs.set_line_to(Some(outline_recording_pen_line_to))
funcs.set_quadratic_to(Some(outline_recording_pen_quadratic_to))
funcs.set_cubic_to(Some(outline_recording_pen_cubic_to))
funcs.set_close_path(Some(outline_recording_pen_close_path))
funcs.make_immutable()
funcs
}