use crate::diagnostic::Diagnostic;
use crate::geom::{Bbox, Point};
#[derive(Debug, Clone, PartialEq)]
pub struct Drawing {
pub shapes: Vec<Shape>,
pub shape_layers: Vec<i32>,
pub shape_classes: Vec<Option<String>>,
pub shape_spans: Vec<Option<crate::diagnostic::Span>>,
pub bbox: Bbox,
pub prelude_thick: f64,
pub canvas_margin: CanvasMargin,
pub canvas: Option<Bbox>,
pub anims: Vec<Anim>,
pub interactions: Vec<Interaction>,
pub anim_scroll: bool,
pub diagnostics: Vec<String>,
pub warnings: Vec<Diagnostic>,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct CanvasMargin {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
impl CanvasMargin {
pub fn horizontal(self) -> f64 {
self.left + self.right
}
pub fn vertical(self) -> f64 {
self.top + self.bottom
}
pub fn scale_by(&mut self, factor: f64) {
self.top *= factor;
self.right *= factor;
self.bottom *= factor;
self.left *= factor;
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Anim {
pub shape: usize,
pub effect: String,
pub start: f64,
pub duration: f64,
pub repeat: i64,
pub yoyo: bool,
pub ease: Option<String>,
pub path: Option<usize>,
pub color: Option<String>,
pub out: bool,
pub from: Option<String>,
pub morph: Option<usize>,
pub type_word: bool,
pub scramble_chars: Option<String>,
pub wiggles: Option<i64>,
pub draw_from: Option<f64>,
pub draw_to: Option<f64>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Interaction {
pub shape: usize,
pub inertia: bool,
pub bounds: Option<usize>,
pub axis: Option<&'static str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Dash {
#[default]
Solid,
Dashed(f64),
Dotted(Option<f64>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Fill {
Gray(f64),
Color(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Hatch {
pub cross: bool,
pub angle: f64,
pub sep: f64,
pub width: f64,
pub color: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Gradient {
pub from: String,
pub to: String,
pub angle: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Style {
pub stroke: Option<String>,
pub fill: Option<Fill>,
pub hatch: Option<Hatch>,
pub gradient: Option<Gradient>,
pub fill_opacity: Option<f64>,
pub fill_open: bool,
pub dash: Dash,
pub thick: Option<f64>,
pub invis: bool,
pub invis_bounds: bool,
pub arrow_ht: f64,
pub arrow_wid: f64,
pub arrow_filled: bool,
}
impl Default for Style {
fn default() -> Self {
Style {
stroke: None,
fill: None,
hatch: None,
gradient: None,
fill_opacity: None,
fill_open: false,
dash: Dash::Solid,
thick: None,
invis: false,
invis_bounds: false,
arrow_ht: 0.1,
arrow_wid: 0.05,
arrow_filled: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Arrowheads {
#[default]
None,
Start,
End,
Both,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextLine {
pub s: String,
pub math: Option<crate::math::MathSpan>,
pub halign: i8,
pub valign: i8,
pub text_offset: f64,
pub bold: bool,
pub italic: bool,
pub family: Option<String>,
pub size_pt: Option<f64>,
pub rotate: Option<f64>,
pub aligned: bool,
}
pub(crate) const FONT_PT_CLASSIC: f64 = 11.0;
pub(crate) const DP_TEXT_RATIO: f64 = 0.66;
pub(crate) const TEXT_EM_IN: f64 = FONT_PT_CLASSIC / 72.0;
pub(crate) const TEXT_CHAR_W_RATIO: f64 = 0.6;
pub(crate) const TEXT_LINE_H_RATIO: f64 = 1.2;
impl TextLine {
pub(crate) fn width_factor(&self) -> f64 {
let size = self.size_pt.map_or(1.0, |pt| pt / FONT_PT_CLASSIC);
let weight = if self.bold { 1.05 } else { 1.0 };
size * weight
}
pub(crate) fn height_factor(&self) -> f64 {
self.size_pt.map_or(1.0, |pt| pt / FONT_PT_CLASSIC)
}
pub(crate) fn ink_width_in(&self) -> f64 {
match &self.math {
Some(m) => m.width,
None => {
self.s.chars().count() as f64 * TEXT_CHAR_W_RATIO * TEXT_EM_IN * self.width_factor()
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Shape {
Box {
c: Point,
w: f64,
h: f64,
rad: f64,
style: Style,
text: Vec<TextLine>,
},
Circle {
c: Point,
r: f64,
style: Style,
text: Vec<TextLine>,
},
Ellipse {
c: Point,
w: f64,
h: f64,
style: Style,
text: Vec<TextLine>,
},
Path {
pts: Vec<Point>,
closed: bool,
arrows: Arrowheads,
style: Style,
text: Vec<TextLine>,
},
Spline {
pts: Vec<Point>,
tension: Option<f64>,
arrows: Arrowheads,
style: Style,
text: Vec<TextLine>,
},
Arc {
c: Point,
r: f64,
a0: f64,
a1: f64,
cw: bool,
arrows: Arrowheads,
style: Style,
text: Vec<TextLine>,
},
Brace {
a: Point,
b: Point,
cubics: Vec<[Point; 4]>,
label_at: Point,
style: Style,
text: Vec<TextLine>,
},
Text {
at: Point,
text: Vec<TextLine>,
bbox: Bbox,
w: f64,
h: f64,
standalone: bool,
},
}
impl Shape {
pub fn is_visible(&self) -> bool {
match self {
Shape::Box { style, .. }
| Shape::Circle { style, .. }
| Shape::Ellipse { style, .. }
| Shape::Path { style, .. }
| Shape::Spline { style, .. }
| Shape::Arc { style, .. }
| Shape::Brace { style, .. } => !style.invis,
Shape::Text { text, .. } => !text.is_empty(),
}
}
}