// 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.
///|
/// Context for reusing dynamic memory allocations.
///
/// Ported from upstream `zeno/src/scratch.rs` (Apache-2.0 OR MIT).
///
/// NOTE: This is currently a minimal subset: it only stores the heap raster
/// storage used by the rasterizer.
struct Scratch {
segments : Array[Segment]
mut render : HeapStorage
}
///|
pub fn Scratch::Scratch() -> Scratch {
{ segments: [], render: HeapStorage() }
}
///|
pub fn Scratch::default() -> Scratch {
Scratch()
}
///|
fn Scratch::render_storage(self : Scratch) -> HeapStorage {
self.render
}
///|
fn Scratch::set_render_storage(self : Scratch, storage : HeapStorage) -> Unit {
self.render = storage
}
///|
/// Applies the style and transform to the path and emits the result to the sink.
///
/// This is a thin wrapper that exists to mirror upstream `Scratch::apply`.
pub fn Scratch::apply(
self : Scratch,
data : &PathData,
style : Style,
transform : Transform?,
sink : &PathBuilder,
) -> Fill {
fn map_commands(cmds : Iter[Command], t : Transform) -> Iter[Command] {
let buf : Array[Command] = []
let it = cmds
while it.next() is Some(cmd) {
buf.push(cmd.transform(t))
}
let mut i = 0
Iter::new(fn() {
if i < buf.length() {
let v = buf[i]
i = i + 1
Some(v)
} else {
None
}
})
}
match style {
Fill(fill) => {
if transform is Some(t) {
let ts = TransformSink::{ sink, transform: t }
data.copy_to(ts)
} else {
data.copy_to(sink)
}
fill
}
Stroke(stroke) => {
if transform is Some(t) {
if stroke.scale {
let ts = TransformSink::{ sink, transform: t }
stroke_with_storage(data.commands(), stroke, ts, self.segments)
} else {
stroke_with_storage(
map_commands(data.commands(), t),
stroke,
sink,
self.segments,
)
}
} else {
stroke_with_storage(data.commands(), stroke, sink, self.segments)
}
NonZero
}
}
}
///|
/// Computes the bounding box of the path.
///
/// Mirrors upstream `Scratch::bounds` (fill-only accurate until we port stroke).
pub fn Scratch::bounds(
self : Scratch,
data : &PathData,
style : Style,
transform : Transform?,
) -> Bounds {
let b = ScratchBoundsBuilder()
self.apply(data, style, transform, b) |> ignore
b.build()
}
// Internal bounds sink for `Scratch::bounds` (not exported; mirrors upstream behavior).
///|
priv struct ScratchBoundsBuilder {
mut count : Int
mut start : Point
mut current : Point
mut min : Point
mut max : Point
}
///|
fn ScratchBoundsBuilder::ScratchBoundsBuilder() -> ScratchBoundsBuilder {
{
count: 0,
start: Vector::zero(),
current: Vector::zero(),
min: Vector(1.0e308, 1.0e308),
max: Vector(-1.0e308, -1.0e308),
}
}
///|
fn ScratchBoundsBuilder::add(self : ScratchBoundsBuilder, p : Point) -> Unit {
let x = p.x()
let y = p.y()
if x < self.min.x() {
self.min = Vector(x, self.min.y())
}
if x > self.max.x() {
self.max = Vector(x, self.max.y())
}
if y < self.min.y() {
self.min = Vector(self.min.x(), y)
}
if y > self.max.y() {
self.max = Vector(self.max.x(), y)
}
self.count = self.count + 1
}
///|
fn ScratchBoundsBuilder::build(self : ScratchBoundsBuilder) -> Bounds {
if self.count != 0 {
{ min: self.min, max: self.max }
} else {
Bounds::default()
}
}
///|
impl PathBuilder for ScratchBoundsBuilder with current_point(self) {
self.current
}
///|
impl PathBuilder for ScratchBoundsBuilder with move_to(self, p) {
self.add(p)
self.start = p
self.current = p
}
///|
impl PathBuilder for ScratchBoundsBuilder with line_to(self, p) {
self.add(p)
self.current = p
}
///|
impl PathBuilder for ScratchBoundsBuilder with quad_to(self, c, p) {
self.add(c)
self.add(p)
self.current = p
}
///|
impl PathBuilder for ScratchBoundsBuilder with curve_to(self, c1, c2, p) {
self.add(c1)
self.add(c2)
self.add(p)
self.current = p
}
///|
impl PathBuilder for ScratchBoundsBuilder with close(self) {
self.current = self.start
}