use crate::color::Color;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Point {
pub x: f64,
pub y: f64,
}
impl Point {
pub fn new(x: f64, y: f64) -> Point {
Point { x, y }
}
pub fn plus(self, o: Point) -> Point {
Point::new(self.x + o.x, self.y + o.y)
}
pub fn minus(self, o: Point) -> Point {
Point::new(self.x - o.x, self.y - o.y)
}
pub fn scale(self, k: f64) -> Point {
Point::new(self.x * k, self.y * k)
}
pub fn norm(self) -> f64 {
self.x.hypot(self.y)
}
pub fn lerp(self, o: Point, t: f64) -> Point {
self.plus(o.minus(self).scale(t))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Transform {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
pub e: f64,
pub f: f64,
}
impl Default for Transform {
fn default() -> Self {
Transform { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
}
}
impl Transform {
pub fn apply(&self, p: Point) -> Point {
Point::new(self.a * p.x + self.c * p.y + self.e, self.b * p.x + self.d * p.y + self.f)
}
pub fn linear(&self, p: Point) -> Point {
Point::new(self.a * p.x + self.c * p.y, self.b * p.x + self.d * p.y)
}
pub fn compose(&self, o: &Transform) -> Transform {
Transform {
a: self.a * o.a + self.c * o.b,
b: self.b * o.a + self.d * o.b,
c: self.a * o.c + self.c * o.d,
d: self.b * o.c + self.d * o.d,
e: self.a * o.e + self.c * o.f + self.e,
f: self.b * o.e + self.d * o.f + self.f,
}
}
pub fn scaling(sx: f64, sy: f64) -> Transform {
Transform { a: sx, d: sy, ..Default::default() }
}
pub fn translation(dx: f64, dy: f64) -> Transform {
Transform { e: dx, f: dy, ..Default::default() }
}
pub fn rotation(degrees: f64) -> Transform {
let (s, c) = degrees.to_radians().sin_cos();
Transform { a: c, b: s, c: -s, d: c, ..Default::default() }
}
pub fn factor(&self) -> f64 {
(self.a * self.d - self.b * self.c).abs().sqrt()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Segment {
MoveTo(Point),
LineTo(Point),
CurveTo(Point, Point, Point),
Close,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Tip {
Default,
Latex,
Stealth,
Bar,
Circle,
Double,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Style {
pub stroke: Option<Color>,
pub fill: Option<Color>,
pub pattern: Option<(String, Color)>,
pub line_width: f64,
pub dash: Option<Vec<f64>>,
pub stroke_opacity: f64,
pub fill_opacity: f64,
pub rounded_corners: f64,
pub start_tip: Option<Tip>,
pub end_tip: Option<Tip>,
pub tip_scale: f64,
}
impl Default for Style {
fn default() -> Self {
Style {
stroke: None,
fill: None,
pattern: None,
line_width: 0.4 * crate::PT,
dash: None,
stroke_opacity: 1.0,
fill_opacity: 1.0,
rounded_corners: 0.0,
start_tip: None,
end_tip: None,
tip_scale: 1.0,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Path {
pub segments: Vec<Segment>,
pub style: Style,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Shape {
Rectangle,
Circle,
Ellipse,
Diamond,
Coordinate,
}
#[derive(Debug, Clone)]
pub struct Label {
pub lines: Vec<mathra::layout::MathBox>,
pub scale: f64,
pub color: Color,
pub align: char,
pub line_height: f64,
}
impl Label {
pub fn width(&self) -> f64 {
self.lines.iter().map(|c| c.width).fold(0.0f32, f32::max) as f64 * self.scale
}
pub fn height(&self) -> f64 {
self.lines.first().map_or(0.0, |c| c.height as f64 * self.scale)
}
pub fn depth(&self) -> f64 {
let n = self.lines.len();
if n == 0 {
return 0.0;
}
self.lines[n - 1].depth as f64 * self.scale + (n - 1) as f64 * self.line_height
}
}
#[derive(Debug, Clone)]
pub struct Node {
pub center: Point,
pub shape: Shape,
pub half_width: f64,
pub half_height: f64,
pub label: Option<Label>,
pub style: Style,
pub rotation: f64,
}
impl Node {
pub fn border_at_angle(&self, angle: f64) -> Point {
let (s, c) = angle.to_radians().sin_cos();
self.border_toward(Point::new(c, s))
}
pub fn border_toward(&self, d: Point) -> Point {
let n = d.norm();
if n < 1e-12 {
return self.center;
}
let u = d.scale(1.0 / n);
let t = match self.shape {
Shape::Rectangle => {
let tx = if u.x.abs() > 1e-12 { self.half_width / u.x.abs() } else { f64::INFINITY };
let ty = if u.y.abs() > 1e-12 { self.half_height / u.y.abs() } else { f64::INFINITY };
tx.min(ty)
}
Shape::Circle | Shape::Ellipse => {
let (a, b) = (self.half_width.max(1e-12), self.half_height.max(1e-12));
1.0 / ((u.x / a).powi(2) + (u.y / b).powi(2)).sqrt()
}
Shape::Diamond => 1.0 / (u.x.abs() / self.half_width.max(1e-12) + u.y.abs() / self.half_height.max(1e-12)),
Shape::Coordinate => return self.center,
};
self.center.plus(u.scale(t))
}
pub fn anchor(&self, name: &str) -> Option<Point> {
let (w, h) = (self.half_width, self.half_height);
let c = self.center;
let base = self.label.as_ref().map_or(c.y, |r| c.y + (r.height() + r.depth()) / 2.0 - r.height());
let corner = |sx: f64, sy: f64| -> Point {
match self.shape {
Shape::Rectangle | Shape::Coordinate => c.plus(Point::new(sx * w, sy * h)),
_ => self.border_toward(Point::new(sx * w.max(1e-9), sy * h.max(1e-9))),
}
};
Some(match name.trim() {
"center" | "" => c,
"north" => c.plus(Point::new(0.0, h)),
"south" => c.plus(Point::new(0.0, -h)),
"east" => c.plus(Point::new(w, 0.0)),
"west" => c.plus(Point::new(-w, 0.0)),
"north east" => corner(1.0, 1.0),
"north west" => corner(-1.0, 1.0),
"south east" => corner(1.0, -1.0),
"south west" => corner(-1.0, -1.0),
"base" | "mid" => Point::new(c.x, base),
"base west" | "mid west" => Point::new(c.x - w, base),
"base east" | "mid east" => Point::new(c.x + w, base),
"text" => Point::new(c.x - self.label.as_ref().map_or(0.0, |r| r.width()) / 2.0, base),
other => {
let angle: f64 = other.parse().ok()?;
return Some(self.border_at_angle(angle));
}
})
}
}
#[derive(Debug, Clone)]
pub enum Element {
Path(Path),
Node(Node),
Clip(Vec<Segment>),
EndClip,
}
impl Element {
pub fn translate(&mut self, d: Point) {
match self {
Element::Node(n) => n.center = n.center.plus(d),
Element::Path(Path { segments, .. }) | Element::Clip(segments) => {
for s in segments {
match s {
Segment::MoveTo(q) | Segment::LineTo(q) => *q = q.plus(d),
Segment::CurveTo(a, b, c) => {
for q in [a, b, c] {
*q = q.plus(d);
}
}
Segment::Close => {}
}
}
}
Element::EndClip => {}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Scene {
pub elements: Vec<Element>,
pub margin: f64,
}