use crate::ast::TextAttr;
use crate::errors::PikruError;
use crate::types::{BoxIn, EvalValue, Length as Inches, OffsetIn, Point, PtIn, UnitVec};
use super::defaults;
use super::shapes::Shape;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Value {
Len(Inches),
Scalar(f64),
Color(u32), }
impl Value {
#[allow(dead_code)]
pub fn as_len(self) -> Result<Inches, PikruError> {
match self {
Value::Len(l) => Ok(l),
Value::Scalar(_) => Err(PikruError::Generic(
"Expected length value, got scalar".to_string(),
)),
Value::Color(_) => Err(PikruError::Generic(
"Expected length value, got color".to_string(),
)),
}
}
#[allow(dead_code)]
pub fn as_scalar(self) -> Result<f64, PikruError> {
match self {
Value::Scalar(s) => Ok(s),
Value::Len(_) => Err(PikruError::Generic(
"Expected scalar value, got length".to_string(),
)),
Value::Color(_) => Err(PikruError::Generic(
"Expected scalar value, got color".to_string(),
)),
}
}
}
impl From<EvalValue> for Value {
fn from(ev: EvalValue) -> Self {
match ev {
EvalValue::Length(l) => Value::Len(l),
EvalValue::Scalar(s) => Value::Scalar(s),
EvalValue::Color(c) => Value::Color(c),
}
}
}
impl From<Value> for EvalValue {
fn from(v: Value) -> Self {
match v {
Value::Len(l) => EvalValue::Length(l),
Value::Scalar(s) => EvalValue::Scalar(s),
Value::Color(c) => EvalValue::Color(c),
}
}
}
pub type PointIn = PtIn;
pub type BoundingBox = BoxIn;
pub fn pin(x: f64, y: f64) -> PointIn {
Point::new(Inches(x), Inches(y))
}
#[derive(Debug, Clone)]
pub struct PositionedText {
pub value: String,
pub above: bool,
pub below: bool,
pub center: bool,
pub ljust: bool,
pub rjust: bool,
pub bold: bool,
pub italic: bool,
pub mono: bool,
pub big: bool,
pub small: bool,
pub xtra: bool, pub aligned: bool, }
impl PositionedText {
pub fn new(value: String) -> Self {
Self {
value,
above: false,
below: false,
center: false,
ljust: false,
rjust: false,
bold: false,
italic: false,
mono: false,
big: false,
small: false,
xtra: false,
aligned: false,
}
}
pub fn from_textposition(value: String, pos: Option<&crate::ast::TextPosition>) -> Self {
let mut pt = Self::new(value);
if let Some(pos) = pos {
for attr in &pos.attrs {
match attr {
TextAttr::Above => pt.above = true,
TextAttr::Below => pt.below = true,
TextAttr::Center => pt.center = true,
TextAttr::LJust => pt.ljust = true,
TextAttr::RJust => pt.rjust = true,
TextAttr::Bold => pt.bold = true,
TextAttr::Italic => pt.italic = true,
TextAttr::Mono => pt.mono = true,
TextAttr::Big => {
if pt.big {
pt.xtra = true;
} else {
pt.big = true;
}
}
TextAttr::Small => {
if pt.small {
pt.xtra = true;
} else {
pt.small = true;
}
}
TextAttr::Aligned => pt.aligned = true,
}
}
}
pt
}
pub fn font_scale(&self) -> f64 {
let mut scale = 1.0;
if self.big {
scale *= 1.25;
}
if self.small {
scale *= 0.8;
}
if self.xtra {
scale *= scale; }
scale
}
pub fn width_inches(&self, charwid: f64) -> f64 {
let length_hundredths = if self.mono {
super::monospace_text_length(&self.value)
} else {
super::proportional_text_length(&self.value)
};
let mut width = length_hundredths as f64 * charwid * self.font_scale() * 0.01;
if self.bold && !self.mono {
width *= 1.1;
}
width
}
pub fn height(&self, charht: f64) -> f64 {
self.font_scale() * charht
}
}
#[derive(Debug, Clone)]
pub struct RenderedObject {
pub name: Option<String>,
pub name_is_explicit: bool,
pub text_name: Option<String>,
pub shape: super::shapes::ShapeEnum,
pub start_attachment: Option<EndpointObject>,
pub end_attachment: Option<EndpointObject>,
pub layer: i32,
pub direction: crate::ast::Direction,
pub class_name: crate::ast::ClassName,
}
impl RenderedObject {
pub fn translate(&mut self, offset: OffsetIn) {
self.shape.translate(offset);
}
pub fn edge_point(&self, dir: UnitVec) -> PointIn {
use super::shapes::EdgeDirection;
let edge_dir = if dir == UnitVec::NORTH {
EdgeDirection::North
} else if dir == UnitVec::SOUTH {
EdgeDirection::South
} else if dir == UnitVec::EAST {
EdgeDirection::East
} else if dir == UnitVec::WEST {
EdgeDirection::West
} else if dir == UnitVec::NORTH_EAST {
EdgeDirection::NorthEast
} else if dir == UnitVec::NORTH_WEST {
EdgeDirection::NorthWest
} else if dir == UnitVec::SOUTH_EAST {
EdgeDirection::SouthEast
} else if dir == UnitVec::SOUTH_WEST {
EdgeDirection::SouthWest
} else {
EdgeDirection::Center
};
self.shape.edge_point(edge_dir)
}
pub fn center(&self) -> PointIn {
self.shape.center()
}
pub fn width(&self) -> Inches {
self.shape.width()
}
pub fn height(&self) -> Inches {
self.shape.height()
}
pub fn start(&self) -> PointIn {
self.shape.start()
}
pub fn end(&self) -> PointIn {
self.shape.end()
}
pub fn style(&self) -> &ObjectStyle {
self.shape.style()
}
pub fn text(&self) -> &[PositionedText] {
self.shape.text()
}
pub fn waypoints(&self) -> Option<&[PointIn]> {
self.shape.waypoints()
}
pub fn class(&self) -> ClassName {
self.class_name
}
pub fn children(&self) -> Option<&[RenderedObject]> {
if let super::shapes::ShapeEnum::Sublist(ref s) = self.shape {
Some(&s.children)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct EndpointObject {
pub class: ClassName,
pub center: PointIn,
pub width: Inches,
pub height: Inches,
pub corner_radius: Inches,
pub is_dotted_name: bool,
}
impl EndpointObject {
pub fn from_rendered(obj: &RenderedObject) -> Self {
Self {
class: obj.shape.class(),
center: obj.shape.center(),
width: obj.shape.width(),
height: obj.shape.height(),
corner_radius: obj.shape.style().corner_radius,
is_dotted_name: false,
}
}
pub fn from_rendered_dotted(obj: &RenderedObject) -> Self {
Self {
class: obj.shape.class(),
center: obj.shape.center(),
width: obj.shape.width(),
height: obj.shape.height(),
corner_radius: obj.shape.style().corner_radius,
is_dotted_name: true,
}
}
}
pub use crate::ast::ClassName;
impl ClassName {
pub fn is_round(self) -> bool {
matches!(self, Self::Circle | Self::Ellipse | Self::Oval)
}
pub fn diagonal_factor(self) -> f64 {
if self.is_round() {
std::f64::consts::FRAC_1_SQRT_2
} else {
1.0
}
}
}
#[derive(Debug, Clone)]
pub struct ObjectStyle {
pub stroke: String,
pub fill: String,
pub stroke_width: Inches,
pub dashed: Option<Inches>,
pub dotted: Option<Inches>,
pub arrow_start: bool,
pub arrow_end: bool,
pub invisible: bool,
pub corner_radius: Inches,
pub chop: bool,
pub fit: bool,
pub close_path: bool,
pub clockwise: bool,
}
impl Default for ObjectStyle {
fn default() -> Self {
Self {
stroke: "black".to_string(),
fill: "none".to_string(),
stroke_width: defaults::STROKE_WIDTH,
dashed: None,
dotted: None,
arrow_start: false,
arrow_end: false,
invisible: false,
corner_radius: Inches::ZERO,
chop: false,
fit: false,
close_path: false,
clockwise: false,
}
}
}