From 4d617bcd67f9e42218da190dc9a0bf2f10d4b78d Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Wed, 16 Mar 2022 12:36:50 +0100 Subject: [PATCH] `LineNode` --- src/geom/angle.rs | 10 ++++++ src/geom/point.rs | 6 ++++ src/library/graphics/line.rs | 61 +++++++++++++++++++++++++++++++++++ src/library/graphics/mod.rs | 2 ++ src/library/mod.rs | 12 +++++++ tests/ref/graphics/line.png | Bin 0 -> 3467 bytes tests/typ/graphics/line.typ | 47 +++++++++++++++++++++++++++ 7 files changed, 138 insertions(+) create mode 100644 src/library/graphics/line.rs create mode 100644 tests/ref/graphics/line.png create mode 100644 tests/typ/graphics/line.typ diff --git a/src/geom/angle.rs b/src/geom/angle.rs index ef3276e82..4e08a518a 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -40,6 +40,16 @@ impl Angle { (self.0).0 } + /// Get the sine of this angle. + pub fn sin(self) -> f64 { + self.to_rad().sin() + } + + /// Get the cosine of this angle. + pub fn cos(self) -> f64 { + self.to_rad().cos() + } + /// Create an angle from a value in a unit. pub fn with_unit(val: f64, unit: AngularUnit) -> Self { Self(Scalar(val * unit.raw_scale())) diff --git a/src/geom/point.rs b/src/geom/point.rs index 6d77507b4..7ab0d3753 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -49,6 +49,12 @@ impl Point { } } +impl From> for Point { + fn from(spec: Spec) -> Self { + Self::new(spec.x, spec.y) + } +} + impl Get for Point { type Component = Length; diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs new file mode 100644 index 000000000..141abb08b --- /dev/null +++ b/src/library/graphics/line.rs @@ -0,0 +1,61 @@ +use crate::library::prelude::*; + +/// Display a line without affecting the layout. +#[derive(Debug, Hash)] +pub struct LineNode(Spec, Spec); + +#[node] +impl LineNode { + /// How the stroke the line. + pub const STROKE: Smart = Smart::Auto; + /// The line's thickness. + pub const THICKNESS: Length = Length::pt(1.0); + + fn construct(_: &mut Context, args: &mut Args) -> TypResult { + let origin = args.named::>("origin")?.unwrap_or_default(); + let to = match args.named::>("to")? { + Some(to) => to.zip(origin).map(|(to, from)| to - from), + None => { + let length = + args.named::("length")?.unwrap_or(Length::cm(1.0).into()); + let angle = args.named::("angle")?.unwrap_or_default(); + + let x = angle.cos() * length; + let y = angle.sin() * length; + + Spec::new(x, y) + } + }; + + Ok(Content::inline(Self(origin, to))) + } +} + +impl Layout for LineNode { + fn layout( + &self, + _: &mut Context, + regions: &Regions, + styles: StyleChain, + ) -> TypResult>> { + let target = regions.expand.select(regions.first, Size::zero()); + let mut frame = Frame::new(target); + + let thickness = styles.get(Self::THICKNESS); + let stroke = Some(Stroke { + paint: styles.get(Self::STROKE).unwrap_or(Color::BLACK.into()), + thickness, + }); + + let resolved_origin = + self.0.zip(regions.base).map(|(l, b)| Linear::resolve(l, b)); + let resolved_to = self.1.zip(regions.base).map(|(l, b)| Linear::resolve(l, b)); + + let geometry = Geometry::Line(resolved_to.into()); + + let shape = Shape { geometry, fill: None, stroke }; + frame.prepend(resolved_origin.into(), Element::Shape(shape)); + + Ok(vec![Arc::new(frame)]) + } +} diff --git a/src/library/graphics/mod.rs b/src/library/graphics/mod.rs index 353f09ca8..e9a6188f3 100644 --- a/src/library/graphics/mod.rs +++ b/src/library/graphics/mod.rs @@ -2,10 +2,12 @@ mod hide; mod image; +mod line; mod shape; mod transform; pub use self::image::*; pub use hide::*; +pub use line::*; pub use shape::*; pub use transform::*; diff --git a/src/library/mod.rs b/src/library/mod.rs index 8f00e5fe0..88f003c2d 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -53,6 +53,7 @@ pub fn new() -> Scope { // Graphics. std.def_node::("image"); + std.def_node::("line"); std.def_node::("rect"); std.def_node::("square"); std.def_node::("ellipse"); @@ -170,3 +171,14 @@ castable! { Expected: "content", Value::Content(content) => content.pack(), } + +castable! { + Spec, + Expected: "two-dimensional length array", + Value::Array(array) => { + let e = "point array must contain exactly two entries"; + let a = array.get(0).map_err(|_| e)?.clone().cast::()?; + let b = array.get(1).map_err(|_| e)?.clone().cast::()?; + Spec::new(a, b) + }, +} diff --git a/tests/ref/graphics/line.png b/tests/ref/graphics/line.png new file mode 100644 index 0000000000000000000000000000000000000000..49f05f0d790e7e426535a2ede1db4b2ad990bfb3 GIT binary patch literal 3467 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 sVgyqJ|HAF3+3#}{_*lRGREF#Bz&