diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-20 20:19:30 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-20 20:19:30 +0100 |
| commit | 898728f260923a91444eb23b522d0abf01a4299b (patch) | |
| tree | 64fdd3f78e16e6428e765a8e2d99c3cd910bd9df /src/library | |
| parent | 6cb9fe9064a037224b6560b69b441b72e787fa94 (diff) | |
Square, circle and ellipse 🔵
Diffstat (limited to 'src/library')
| -rw-r--r-- | src/library/font.rs | 2 | ||||
| -rw-r--r-- | src/library/image.rs | 10 | ||||
| -rw-r--r-- | src/library/mod.rs | 3 | ||||
| -rw-r--r-- | src/library/shapes.rs | 155 | ||||
| -rw-r--r-- | src/library/spacing.rs | 7 |
5 files changed, 146 insertions, 31 deletions
diff --git a/src/library/font.rs b/src/library/font.rs index 0993f7f0..ed2c0ef3 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -16,7 +16,7 @@ use super::*; /// - Font Stretch: `stretch`, of type `relative`, between 0.5 and 2.0. /// - Top edge of the font: `top-edge`, of type `vertical-font-metric`. /// - Bottom edge of the font: `bottom-edge`, of type `vertical-font-metric`. -/// - Fill color the glyphs: `color`, of type `color`. +/// - Color the glyphs: `color`, of type `color`. /// - Serif family definition: `serif`, of type `font-familiy-list`. /// - Sans-serif family definition: `sans-serif`, of type `font-familiy-list`. /// - Monospace family definition: `monospace`, of type `font-familiy-list`. diff --git a/src/library/image.rs b/src/library/image.rs index 8cb09463..10217e31 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -25,7 +25,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let loaded = ctx.env.resources.load(&path.v, ImageResource::parse); if let Some((res, img)) = loaded { let dimensions = img.buf.dimensions(); - ctx.push(NodeImage { + ctx.push(ImageNode { res, dimensions, width, @@ -41,7 +41,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// An image node. #[derive(Debug, Clone, PartialEq)] -struct NodeImage { +struct ImageNode { /// How to align this image node in its parent. aligns: LayoutAligns, /// The resource id of the image file. @@ -54,7 +54,7 @@ struct NodeImage { height: Option<Linear>, } -impl Layout for NodeImage { +impl Layout for ImageNode { fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Fragment { let Areas { current, full, .. } = areas; @@ -90,8 +90,8 @@ impl Layout for NodeImage { } } -impl From<NodeImage> for AnyNode { - fn from(image: NodeImage) -> Self { +impl From<ImageNode> for AnyNode { + fn from(image: ImageNode) -> Self { Self::new(image) } } diff --git a/src/library/mod.rs b/src/library/mod.rs index d0920cf1..b09f94a0 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -51,6 +51,8 @@ pub fn new() -> Scope { } func!("align", align); + func!("circle", circle); + func!("ellipse", ellipse); func!("font", font); func!("h", h); func!("image", image); @@ -58,6 +60,7 @@ pub fn new() -> Scope { func!("page", page); func!("pagebreak", pagebreak); func!("paragraph", par); + func!("square", square); func!("rect", rect); func!("repr", repr); func!("rgb", rgb); diff --git a/src/library/shapes.rs b/src/library/shapes.rs index 211a4f2e..a5faf73e 100644 --- a/src/library/shapes.rs +++ b/src/library/shapes.rs @@ -1,49 +1,162 @@ +use std::f64::consts::SQRT_2; + use super::*; -use crate::layout::{BackgroundNode, Fill, FixedNode}; +use crate::color::Color; +use crate::layout::{BackgroundNode, BackgroundShape, Fill, FixedNode, PadNode}; -/// `rect`: Create a rectangular box. +/// `rect`: Create a rectangle. /// /// # Positional parameters /// - Body: optional, of type `template`. /// /// # Named parameters -/// - Width of the box: `width`, of type `linear` relative to parent width. -/// - Height of the box: `height`, of type `linear` relative to parent height. -/// - Main layouting direction: `main-dir`, of type `direction`. -/// - Cross layouting direction: `cross-dir`, of type `direction`. -/// - Fill color of the box: `fill`, of type `color`. +/// - Width: `width`, of type `linear` relative to parent width. +/// - Height: `height`, of type `linear` relative to parent height. +/// - Fill color: `fill`, of type `color`. /// /// # Return value /// A template that places the body into a rectangle. -/// -/// # Relevant types and constants -/// - Type `direction` -/// - `ltr` (left to right) -/// - `rtl` (right to left) -/// - `ttb` (top to bottom) -/// - `btt` (bottom to top) pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { let width = args.get(ctx, "width"); let height = args.get(ctx, "height"); - let main = args.get(ctx, "main-dir"); - let cross = args.get(ctx, "cross-dir"); let fill = args.get(ctx, "fill"); let body = args.find::<TemplateValue>(ctx).unwrap_or_default(); + rect_impl("rect", width, height, None, fill, body) +} - Value::template("box", move |ctx| { +/// `square`: Create a square. +/// +/// # Positional parameters +/// - Body: optional, of type `template`. +/// +/// # Named parameters +/// - Side length: `length`, of type `length`. +/// - Width: `width`, of type `linear` relative to parent width. +/// - Height: `height`, of type `linear` relative to parent height. +/// - Fill color: `fill`, of type `color`. +/// +/// Note that you can specify only one of `length`, `width` and `height`. The +/// width and height parameters exist so that you can size the square relative +/// to its parent's size, which isn't possible by setting the side length. +/// +/// # Return value +/// A template that places the body into a square. +pub fn square(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { + let length = args.get::<Length>(ctx, "length").map(Linear::from); + let width = length.or_else(|| args.get(ctx, "width")); + let height = if width.is_none() { args.get(ctx, "height") } else { None }; + let fill = args.get(ctx, "fill"); + let body = args.find::<TemplateValue>(ctx).unwrap_or_default(); + rect_impl("square", width, height, Some(1.0), fill, body) +} + +fn rect_impl( + name: &str, + width: Option<Linear>, + height: Option<Linear>, + aspect: Option<f64>, + fill: Option<Color>, + body: TemplateValue, +) -> Value { + Value::template(name, move |ctx| { let snapshot = ctx.state.clone(); + let child = ctx.exec(&body).into(); + let node = FixedNode { width, height, aspect, child }; + + if let Some(color) = fill { + ctx.push(BackgroundNode { + shape: BackgroundShape::Rect, + fill: Fill::Color(color), + child: node.into(), + }); + } else { + ctx.push(node); + } + + ctx.state = snapshot; + }) +} - ctx.set_dirs(Gen::new(main, cross)); +/// `ellipse`: Create an ellipse. +/// +/// # Positional parameters +/// - Body: optional, of type `template`. +/// +/// # Named parameters +/// - Width: `width`, of type `linear` relative to parent width. +/// - Height: `height`, of type `linear` relative to parent height. +/// - Fill color: `fill`, of type `color`. +/// +/// # Return value +/// A template that places the body into an ellipse. +pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { + let width = args.get(ctx, "width"); + let height = args.get(ctx, "height"); + let fill = args.get(ctx, "fill"); + let body = args.find::<TemplateValue>(ctx).unwrap_or_default(); + ellipse_impl("ellipse", width, height, None, fill, body) +} +/// `circle`: Create a circle. +/// +/// # Positional parameters +/// - Body: optional, of type `template`. +/// +/// # Named parameters +/// - Radius: `radius`, of type `length`. +/// - Width: `width`, of type `linear` relative to parent width. +/// - Height: `height`, of type `linear` relative to parent height. +/// - Fill color: `fill`, of type `color`. +/// +/// Note that you can specify only one of `radius`, `width` and `height`. The +/// width and height parameters exist so that you can size the circle relative +/// to its parent's size, which isn't possible by setting the radius. +/// +/// # Return value +/// A template that places the body into a circle. +pub fn circle(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { + let radius = args.get::<Length>(ctx, "radius").map(|r| 2.0 * Linear::from(r)); + let width = radius.or_else(|| args.get(ctx, "width")); + let height = if width.is_none() { args.get(ctx, "height") } else { None }; + let fill = args.get(ctx, "fill"); + let body = args.find::<TemplateValue>(ctx).unwrap_or_default(); + ellipse_impl("circle", width, height, Some(1.0), fill, body) +} + +fn ellipse_impl( + name: &str, + width: Option<Linear>, + height: Option<Linear>, + aspect: Option<f64>, + fill: Option<Color>, + body: TemplateValue, +) -> Value { + Value::template(name, move |ctx| { + // This padding ratio ensures that the rectangular padded area fits + // perfectly into the ellipse. + const PAD: f64 = 0.5 - SQRT_2 / 4.0; + + let snapshot = ctx.state.clone(); let child = ctx.exec(&body).into(); - let fixed = FixedNode { width, height, child }; + let node = FixedNode { + width, + height, + aspect, + child: PadNode { + padding: Sides::uniform(Relative::new(PAD).into()), + child, + } + .into(), + }; + if let Some(color) = fill { ctx.push(BackgroundNode { + shape: BackgroundShape::Ellipse, fill: Fill::Color(color), - child: fixed.into(), + child: node.into(), }); } else { - ctx.push(fixed); + ctx.push(node); } ctx.state = snapshot; diff --git a/src/library/spacing.rs b/src/library/spacing.rs index fee802fa..c96b8be4 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -9,7 +9,7 @@ use crate::layout::SpacingNode; /// # Return value /// A template that adds horizontal spacing. pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - spacing(ctx, args, SpecAxis::Horizontal) + spacing_impl(ctx, args, SpecAxis::Horizontal) } /// `v`: Add vertical spacing. @@ -20,11 +20,10 @@ pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// # Return value /// A template that adds vertical spacing. pub fn v(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - spacing(ctx, args, SpecAxis::Vertical) + spacing_impl(ctx, args, SpecAxis::Vertical) } -/// Apply spacing along a specific axis. -fn spacing(ctx: &mut EvalContext, args: &mut FuncArgs, axis: SpecAxis) -> Value { +fn spacing_impl(ctx: &mut EvalContext, args: &mut FuncArgs, axis: SpecAxis) -> Value { let spacing: Option<Linear> = args.require(ctx, "spacing"); Value::template("spacing", move |ctx| { if let Some(linear) = spacing { |
