diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 1d82966c4..74d7d1470 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -45,10 +45,15 @@ impl RgbaColor { /// White color. pub const WHITE: Self = Self { r: 255, g: 255, b: 255, a: 255 }; - /// Constructs a new RGBA color. + /// Construct a new RGBA color. pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { Self { r, g, b, a } } + + /// Construct a new, opaque gray color. + pub fn gray(luma: u8) -> Self { + Self::new(luma, luma, luma, 255) + } } impl FromStr for RgbaColor { diff --git a/src/library/container.rs b/src/library/container.rs deleted file mode 100644 index 2bcbbd192..000000000 --- a/src/library/container.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::prelude::*; -use super::{ShapeKind, ShapeNode}; - -/// `box`: Place content in a rectangular box. -pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult { - let width = args.named("width")?; - let height = args.named("height")?; - let body: Template = args.find().unwrap_or_default(); - Ok(Value::Template(Template::from_inline(move |style| { - ShapeNode { - shape: ShapeKind::Rect, - width, - height, - fill: None, - child: Some(body.to_flow(style).pack()), - } - }))) -} - -/// `block`: Place content in a block. -pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult { - let body: Template = args.expect("body")?; - Ok(Value::Template(Template::from_block(move |style| { - body.to_flow(style) - }))) -} diff --git a/src/library/mod.rs b/src/library/mod.rs index f11e05f39..fdede8bd1 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -4,7 +4,6 @@ //! definitions. mod align; -mod container; mod deco; mod document; mod flow; @@ -14,6 +13,7 @@ mod pad; mod page; mod par; mod shape; +mod sized; mod spacing; mod stack; mod text; @@ -35,7 +35,6 @@ mod prelude { pub use self::image::*; pub use align::*; -pub use container::*; pub use deco::*; pub use document::*; pub use flow::*; @@ -44,6 +43,7 @@ pub use pad::*; pub use page::*; pub use par::*; pub use shape::*; +pub use sized::*; pub use spacing::*; pub use stack::*; pub use text::*; diff --git a/src/library/par.rs b/src/library/par.rs index c8befc2d0..d0f310160 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -77,7 +77,7 @@ impl Layout for ParNode { // Prepare paragraph layout by building a representation on which we can // do line breaking without layouting each and every line from scratch. - let layouter = ParLayouter::new(self, ctx, regions.clone(), bidi); + let layouter = ParLayouter::new(self, ctx, regions, bidi); // Find suitable linebreaks. layouter.layout(ctx, regions.clone()) @@ -182,12 +182,9 @@ impl<'a> ParLayouter<'a> { fn new( par: &'a ParNode, ctx: &mut LayoutContext, - mut regions: Regions, + regions: &Regions, bidi: BidiInfo<'a>, ) -> Self { - // Disable expansion for children. - regions.expand = Spec::splat(false); - let mut items = vec![]; let mut ranges = vec![]; let mut starts = vec![]; @@ -219,7 +216,10 @@ impl<'a> ParLayouter<'a> { } } ParChild::Node(ref node, align) => { - let frame = node.layout(ctx, ®ions).remove(0); + let size = Size::new(regions.current.w, regions.base.h); + let expand = Spec::splat(false); + let pod = Regions::one(size, regions.base, expand); + let frame = node.layout(ctx, &pod).remove(0); items.push(ParItem::Frame(Rc::take(frame.item), align)); ranges.push(range); } diff --git a/src/library/shape.rs b/src/library/shape.rs index c64dedb32..112987ad1 100644 --- a/src/library/shape.rs +++ b/src/library/shape.rs @@ -1,7 +1,7 @@ use std::f64::consts::SQRT_2; use super::prelude::*; -use super::PadNode; +use super::{PadNode, SizedNode}; use crate::util::RcExt; /// `rect`: A rectangle with optional content. @@ -55,7 +55,7 @@ pub fn circle(_: &mut EvalContext, args: &mut Args) -> TypResult { } fn shape_impl( - shape: ShapeKind, + kind: ShapeKind, mut width: Option, mut height: Option, fill: Option, @@ -65,20 +65,30 @@ fn shape_impl( if body.is_none() { let v = Length::pt(30.0).into(); height.get_or_insert(v); - width.get_or_insert(match shape { + width.get_or_insert(match kind { ShapeKind::Square | ShapeKind::Circle => v, ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * v, }); } - Value::Template(Template::from_inline(move |style| ShapeNode { - shape, - width, - height, - fill: Some(Paint::Color( - fill.unwrap_or(Color::Rgba(RgbaColor::new(175, 175, 175, 255))), - )), - child: body.as_ref().map(|template| template.to_flow(style).pack()), + // Set default fill if there's no fill. + let fill = fill.unwrap_or(Color::Rgba(RgbaColor::gray(175))); + + Value::Template(Template::from_inline(move |style| { + let shape = Layout::pack(ShapeNode { + kind, + fill: Some(Paint::Color(fill)), + child: body.as_ref().map(|body| body.to_flow(style).pack()), + }); + + if width.is_some() || height.is_some() { + Layout::pack(SizedNode { + sizing: Spec::new(width, height), + child: shape, + }) + } else { + shape + } })) } @@ -86,11 +96,7 @@ fn shape_impl( #[derive(Debug, Hash)] pub struct ShapeNode { /// Which shape to place the child into. - pub shape: ShapeKind, - /// The width, if any. - pub width: Option, - /// The height, if any. - pub height: Option, + pub kind: ShapeKind, /// How to fill the shape, if at all. pub fill: Option, /// The child node to place into the shape, if any. @@ -116,33 +122,12 @@ impl Layout for ShapeNode { ctx: &mut LayoutContext, regions: &Regions, ) -> Vec>> { - // Resolve width and height relative to the region's base. - let width = self.width.map(|w| w.resolve(regions.base.w)); - let height = self.height.map(|h| h.resolve(regions.base.h)); - - // Generate constraints. - let mut cts = Constraints::new(regions.expand); - cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height)); - - // Set tight exact and base constraints if the child is - // automatically sized since we don't know what the child might do. - if self.width.is_none() { - cts.exact.x = Some(regions.current.w); - cts.base.x = Some(regions.base.w); - } - - // Same here. - if self.height.is_none() { - cts.exact.y = Some(regions.current.h); - cts.base.y = Some(regions.base.h); - } - // Layout. let mut frame = if let Some(child) = &self.child { let mut node: &dyn Layout = child; let padded; - if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) { + if matches!(self.kind, ShapeKind::Circle | ShapeKind::Ellipse) { // Padding with this ratio ensures that a rectangular child fits // perfectly into a circle / an ellipse. padded = PadNode { @@ -152,29 +137,14 @@ impl Layout for ShapeNode { node = &padded; } - // The "pod" is the region into which the child will be layouted. - let mut pod = { - let size = Size::new( - width.unwrap_or(regions.current.w), - height.unwrap_or(regions.base.h), - ); - - let base = Size::new( - if width.is_some() { size.w } else { regions.base.w }, - if height.is_some() { size.h } else { regions.base.h }, - ); - - let expand = Spec::new(width.is_some(), height.is_some()); - Regions::one(size, base, expand) - }; - // Now, layout the child. - let mut frames = node.layout(ctx, &pod); + let mut frames = node.layout(ctx, regions); - if matches!(self.shape, ShapeKind::Square | ShapeKind::Circle) { + if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) { // Relayout with full expansion into square region to make sure // the result is really a square or circle. let size = frames[0].item.size; + let mut pod = regions.clone(); pod.current.w = size.w.max(size.h).min(pod.current.w); pod.current.h = pod.current.w; pod.expand = Spec::splat(true); @@ -185,14 +155,12 @@ impl Layout for ShapeNode { assert_eq!(frames.len(), 1); Rc::take(frames.into_iter().next().unwrap().item) } else { - // Resolve shape size. - let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default()); - Frame::new(size, size.h) + Frame::new(regions.current, regions.current.h) }; // Add background shape if desired. if let Some(fill) = self.fill { - let (pos, geometry) = match self.shape { + let (pos, geometry) = match self.kind { ShapeKind::Square | ShapeKind::Rect => { (Point::zero(), Geometry::Rect(frame.size)) } @@ -204,6 +172,11 @@ impl Layout for ShapeNode { frame.prepend(pos, Element::Geometry(geometry, fill)); } + // Generate tight constraints for now. + let mut cts = Constraints::new(regions.expand); + cts.exact = regions.current.to_spec().map(Some); + cts.base = regions.base.to_spec().map(Some); + vec![frame.constrain(cts)] } } diff --git a/src/library/sized.rs b/src/library/sized.rs new file mode 100644 index 000000000..631f9e6f7 --- /dev/null +++ b/src/library/sized.rs @@ -0,0 +1,100 @@ +use super::prelude::*; + +/// `box`: Size content and place it into a paragraph. +pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult { + let width = args.named("width")?; + let height = args.named("height")?; + let body: Template = args.find().unwrap_or_default(); + Ok(Value::Template(Template::from_inline(move |style| { + let flow = body.to_flow(style).pack(); + if width.is_some() || height.is_some() { + Layout::pack(SizedNode { + sizing: Spec::new(width, height), + child: flow, + }) + } else { + flow + } + }))) +} + +/// `block`: Size content and place it into the flow. +pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult { + let width = args.named("width")?; + let height = args.named("height")?; + let body: Template = args.find().unwrap_or_default(); + Ok(Value::Template(Template::from_block(move |style| { + let flow = body.to_flow(style).pack(); + if width.is_some() || height.is_some() { + Layout::pack(SizedNode { + sizing: Spec::new(width, height), + child: flow, + }) + } else { + flow + } + }))) +} + +/// A node that sizes its child. +#[derive(Debug, Hash)] +pub struct SizedNode { + /// The node to-be-sized. + pub child: PackedNode, + /// How to size the node horizontally and vertically. + pub sizing: Spec>, +} + +impl Layout for SizedNode { + fn layout( + &self, + ctx: &mut LayoutContext, + regions: &Regions, + ) -> Vec>> { + // Resolve width and height relative to the region's base. + let width = self.sizing.x.map(|w| w.resolve(regions.base.w)); + let height = self.sizing.y.map(|h| h.resolve(regions.base.h)); + + // Generate constraints. + let mut cts = Constraints::new(regions.expand); + cts.set_base_if_linear(regions.base, self.sizing); + + // Set tight exact and base constraints if the child is + // automatically sized since we don't know what the child might do. + if self.sizing.x.is_none() { + cts.exact.x = Some(regions.current.w); + cts.base.x = Some(regions.base.w); + } + + // Same here. + if self.sizing.y.is_none() { + cts.exact.y = Some(regions.current.h); + cts.base.y = Some(regions.base.h); + } + + // The "pod" is the region into which the child will be layouted. + let pod = { + let size = Size::new( + width.unwrap_or(regions.current.w), + height.unwrap_or(regions.current.h), + ); + + let base = Size::new( + if width.is_some() { size.w } else { regions.base.w }, + if height.is_some() { size.h } else { regions.base.h }, + ); + + let expand = Spec::new( + width.is_some() || regions.expand.x, + height.is_some() || regions.expand.y, + ); + + // TODO: Allow multiple regions if only width is set. + Regions::one(size, base, expand) + }; + + let mut frames = self.child.layout(ctx, &pod); + frames[0].cts = cts; + frames + } +} diff --git a/src/library/stack.rs b/src/library/stack.rs index 46825939f..dec5ec2dd 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -35,12 +35,12 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult { children.push(StackChild::Spacing(*v)); delayed = None; } - Child::Any(template) => { + Child::Any(child) => { if let Some(v) = delayed { children.push(StackChild::Spacing(v)); } - let node = template.to_flow(style).pack(); + let node = child.to_flow(style).pack(); children.push(StackChild::Node(node)); delayed = spacing; }