use crate::coord::{Bbox, Point, Trans};
use smallvec::SmallVec;
use smol_str::SmolStr;
fn sort_holes(holes: &mut [SmallVec<[Point; 8]>]) {
holes.sort_by(|a, b| {
let ka = a.first().copied().unwrap_or(Point::ZERO);
let kb = b.first().copied().unwrap_or(Point::ZERO);
(ka.x, ka.y, a.len()).cmp(&(kb.x, kb.y, b.len()))
});
}
fn normalize_ring(pts: &mut SmallVec<[Point; 8]>, want_ccw: bool) {
if pts.len() < 3 {
return;
}
let mut start = 0usize;
for i in 1..pts.len() {
let a = pts[i];
let s = pts[start];
if a.y < s.y || (a.y == s.y && a.x < s.x) {
start = i;
}
}
if start != 0 {
pts.rotate_left(start);
}
let mut area2: i128 = 0;
let n = pts.len();
for i in 0..n {
let a = pts[i];
let b = pts[(i + 1) % n];
area2 += (a.x as i128) * (b.y as i128) - (b.x as i128) * (a.y as i128);
}
let is_ccw = area2 > 0;
if is_ccw != want_ccw {
pts[1..].reverse();
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Polygon {
pub hull: SmallVec<[Point; 8]>,
pub holes: Vec<SmallVec<[Point; 8]>>,
}
impl Polygon {
pub fn rect(b: Bbox) -> Self {
let mut p = Self {
hull: b.corners().into_iter().collect(),
holes: Vec::new(),
};
p.normalize();
p
}
pub fn from_hull(hull: impl IntoIterator<Item = Point>) -> Self {
let mut p = Self {
hull: hull.into_iter().collect(),
holes: Vec::new(),
};
p.normalize();
p
}
pub fn from_hull_raw(hull: impl IntoIterator<Item = Point>) -> Self {
Self {
hull: hull.into_iter().collect(),
holes: Vec::new(),
}
}
pub fn normalize(&mut self) {
normalize_ring(&mut self.hull, false);
for hole in &mut self.holes {
normalize_ring(hole, true);
}
sort_holes(&mut self.holes);
}
pub fn add_hole(&mut self, hole: impl IntoIterator<Item = Point>) {
let mut pts: SmallVec<[Point; 8]> = hole.into_iter().collect();
normalize_ring(&mut pts, true);
self.holes.push(pts);
sort_holes(&mut self.holes);
}
pub fn bbox(&self) -> Bbox {
let mut b = Bbox::EMPTY;
for p in &self.hull {
b.expand_to(*p);
}
b
}
pub fn transform(&self, t: Trans) -> Polygon {
Polygon {
hull: self.hull.iter().map(|p| t.apply(*p)).collect(),
holes: self
.holes
.iter()
.map(|h| h.iter().map(|p| t.apply(*p)).collect())
.collect(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum PathCap {
Flat,
Round,
Extended,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Path {
pub points: SmallVec<[Point; 4]>,
pub width: i64,
pub begin_ext: i64,
pub end_ext: i64,
pub cap: PathCap,
}
impl Path {
pub fn new(points: impl IntoIterator<Item = Point>, width: i64) -> Self {
Self {
points: points.into_iter().collect(),
width,
begin_ext: 0,
end_ext: 0,
cap: PathCap::Flat,
}
}
pub fn bbox(&self) -> Bbox {
let mut b = Bbox::EMPTY;
let half = self.width / 2;
for p in &self.points {
b.expand_to(Point::new(p.x - half, p.y - half));
b.expand_to(Point::new(p.x + half, p.y + half));
}
b
}
pub fn transform(&self, t: Trans) -> Path {
Path {
points: self.points.iter().map(|p| t.apply(*p)).collect(),
width: self.width,
begin_ext: self.begin_ext,
end_ext: self.end_ext,
cap: self.cap,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Rect {
pub bbox: Bbox,
}
impl Rect {
pub fn new(b: Bbox) -> Self {
Self { bbox: b }
}
pub fn transform(self, t: Trans) -> Rect {
Rect {
bbox: t.apply_bbox(self.bbox),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum HAlign {
Left,
Center,
Right,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum VAlign {
Bottom,
Middle,
Top,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Text {
pub string: SmolStr,
pub anchor: Point,
pub size: i32,
pub halign: HAlign,
pub valign: VAlign,
}
impl Text {
pub fn new(s: impl Into<SmolStr>, anchor: Point) -> Self {
Self {
string: s.into(),
anchor,
size: 0,
halign: HAlign::Left,
valign: VAlign::Bottom,
}
}
pub fn bbox(&self) -> Bbox {
Bbox::from_point(self.anchor)
}
pub fn transform(&self, t: Trans) -> Text {
Text {
string: self.string.clone(),
anchor: t.apply(self.anchor),
size: self.size,
halign: self.halign,
valign: self.valign,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Shape {
Polygon(Polygon),
Path(Path),
Box(Rect),
Text(Text),
}
impl Shape {
pub fn bbox(&self) -> Bbox {
match self {
Shape::Polygon(p) => p.bbox(),
Shape::Path(p) => p.bbox(),
Shape::Box(r) => r.bbox,
Shape::Text(t) => t.bbox(),
}
}
pub fn transform(&self, t: Trans) -> Shape {
match self {
Shape::Polygon(p) => Shape::Polygon(p.transform(t)),
Shape::Path(p) => Shape::Path(p.transform(t)),
Shape::Box(r) => Shape::Box(r.transform(t)),
Shape::Text(x) => Shape::Text(x.transform(t)),
}
}
pub(crate) fn discriminant(&self) -> u8 {
match self {
Shape::Polygon(_) => 0,
Shape::Path(_) => 1,
Shape::Box(_) => 2,
Shape::Text(_) => 3,
}
}
}
impl From<Polygon> for Shape {
fn from(p: Polygon) -> Self {
Shape::Polygon(p)
}
}
impl From<Path> for Shape {
fn from(p: Path) -> Self {
Shape::Path(p)
}
}
impl From<Rect> for Shape {
fn from(r: Rect) -> Self {
Shape::Box(r)
}
}
impl From<Bbox> for Shape {
fn from(b: Bbox) -> Self {
Shape::Box(Rect::new(b))
}
}
impl From<Text> for Shape {
fn from(t: Text) -> Self {
Shape::Text(t)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn polygon_rect_bbox() {
let b = Bbox::new(Point::new(0, 0), Point::new(10, 5));
let p = Polygon::rect(b);
assert_eq!(p.bbox(), b);
}
#[test]
fn shape_from_bbox() {
let s: Shape = Bbox::new(Point::new(0, 0), Point::new(1, 1)).into();
assert!(matches!(s, Shape::Box(_)));
}
}