// 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.
///|
/// Bounds, origin and placement.
///
/// Ported from upstream `zeno/src/geometry.rs` (Apache-2.0 OR MIT).
pub(all) enum Origin {
TopLeft
BottomLeft
}
///|
pub fn Origin::default() -> Origin {
TopLeft
}
///|
pub struct Bounds {
mut min : Point
mut max : Point
}
///|
pub fn Bounds::Bounds(min : Point, max : Point) -> Bounds {
{ min, max }
}
///|
pub fn Bounds::default() -> Bounds {
{ min: Vector::zero(), max: Vector::zero() }
}
///|
pub fn Bounds::from_points(points : Array[Point]) -> Bounds {
let b = Bounds::{
min: Vector(1.0e308, 1.0e308),
max: Vector(-1.0e308, -1.0e308),
}
for p in points {
b.add_point(p)
}
if b.min.x() >= b.max.x() || b.min.y() >= b.max.y() {
Bounds::default()
} else {
b
}
}
///|
pub fn Bounds::add_point(self : Bounds, p : Point) -> Unit {
let x = p.x()
let y = p.y()
self.min = Vector(
if self.min.x() < x {
self.min.x()
} else {
x
},
if self.min.y() < y {
self.min.y()
} else {
y
},
)
self.max = Vector(
if self.max.x() > x {
self.max.x()
} else {
x
},
if self.max.y() > y {
self.max.y()
} else {
y
},
)
}
///|
pub fn Bounds::width(self : Bounds) -> Double {
self.max.x() - self.min.x()
}
///|
pub fn Bounds::height(self : Bounds) -> Double {
self.max.y() - self.min.y()
}
///|
pub fn Bounds::is_empty(self : Bounds) -> Bool {
self.min.x() >= self.max.x() || self.min.y() >= self.max.y()
}
///|
pub fn Bounds::contains(self : Bounds, p : Point) -> Bool {
p.x() > self.min.x() &&
p.x() < self.max.x() &&
p.y() > self.min.y() &&
p.y() < self.max.y()
}
///|
pub(all) struct Placement {
left : Int
top : Int
width : UInt
height : UInt
}
///|
pub fn Placement::compute(
origin : Origin,
offset : Vector,
bounds : Bounds,
) -> (Vector, Placement) {
let b = bounds
// Copy semantics: ensure we don't mutate the original bounds reference.
// We emulate zeno by snapping to pixel boundaries after applying offset.
b.min = (b.min + offset).floor()
b.max = (b.max + offset).ceil()
let off = Vector(-b.min.x(), -b.min.y())
let width = b.width().to_int().reinterpret_as_uint()
let height = b.height().to_int().reinterpret_as_uint()
let left = (-off.x()).to_int()
let top = match origin {
BottomLeft =>
((-off.y()).floor() + height.reinterpret_as_int().to_double()).to_int()
TopLeft => (-off.y()).to_int()
}
(off, { left, top, width, height })
}
///|
pub impl Add for Vector with add(self, other) {
Vector(self.x() + other.x(), self.y() + other.y())
}