summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/layout/mod.rs120
-rw-r--r--src/library/container.rs26
-rw-r--r--src/library/flow.rs6
-rw-r--r--src/library/grid.rs2
-rw-r--r--src/library/mod.rs7
-rw-r--r--src/library/page.rs21
-rw-r--r--src/library/sized.rs80
-rw-r--r--src/library/table.rs101
8 files changed, 264 insertions, 99 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index cb6f1348..d563dafb 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -17,10 +17,10 @@ use std::rc::Rc;
use crate::eval::{StyleChain, Styled};
use crate::font::FontStore;
-use crate::frame::Frame;
-use crate::geom::{Align, Linear, Point, Sides, Size, Spec};
+use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
+use crate::geom::{Align, Linear, Paint, Point, Sides, Size, Spec};
use crate::image::ImageStore;
-use crate::library::{AlignNode, Move, PadNode, PageNode, SizedNode, TransformNode};
+use crate::library::{AlignNode, Move, PadNode, PageNode, TransformNode};
use crate::Context;
/// The root layout node, a document consisting of top-level page runs.
@@ -153,6 +153,16 @@ impl PackedNode {
}
}
+ /// Fill the frames resulting from a node.
+ pub fn filled(self, fill: Paint) -> Self {
+ FillNode { fill, child: self }.pack()
+ }
+
+ /// Stroke the frames resulting from a node.
+ pub fn stroked(self, stroke: Stroke) -> Self {
+ StrokeNode { stroke, child: self }.pack()
+ }
+
/// Set alignments for this node.
pub fn aligned(self, aligns: Spec<Option<Align>>) -> Self {
if aligns.any(Option::is_some) {
@@ -294,3 +304,107 @@ where
state.finish()
}
}
+
+/// A node that sizes its child.
+#[derive(Debug, Hash)]
+pub struct SizedNode {
+ /// How to size the node horizontally and vertically.
+ pub sizing: Spec<Option<Linear>>,
+ /// The node to be sized.
+ pub child: PackedNode,
+}
+
+impl Layout for SizedNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let is_auto = self.sizing.map_is_none();
+ let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
+
+ // The "pod" is the region into which the child will be layouted.
+ let pod = {
+ // Resolve the sizing to a concrete size.
+ let size = self
+ .sizing
+ .zip(regions.base)
+ .map(|(s, b)| s.map(|v| v.resolve(b)))
+ .unwrap_or(regions.current);
+
+ // Select the appropriate base and expansion for the child depending
+ // on whether it is automatically or linearly sized.
+ let base = is_auto.select(regions.base, size);
+ let expand = regions.expand | !is_auto;
+
+ Regions::one(size, base, expand)
+ };
+
+ let mut frames = self.child.layout(ctx, &pod, styles);
+ let Constrained { item: frame, cts } = &mut frames[0];
+
+ // Ensure frame size matches regions size if expansion is on.
+ let target = regions.expand.select(regions.current, frame.size);
+ Rc::make_mut(frame).resize(target, Align::LEFT_TOP);
+
+ // Set base & exact constraints if the child is automatically sized
+ // since we don't know what the child might have done. Also set base if
+ // our sizing is relative.
+ *cts = Constraints::new(regions.expand);
+ cts.exact = regions.current.filter(regions.expand | is_auto);
+ cts.base = regions.base.filter(is_rel | is_auto);
+
+ frames
+ }
+}
+
+/// Fill the frames resulting from a node.
+#[derive(Debug, Hash)]
+pub struct FillNode {
+ /// How to fill the frames resulting from the `child`.
+ pub fill: Paint,
+ /// The node to fill.
+ pub child: PackedNode,
+}
+
+impl Layout for FillNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions, styles);
+ for Constrained { item: frame, .. } in &mut frames {
+ let shape = Shape::filled(Geometry::Rect(frame.size), self.fill);
+ Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
+ }
+ frames
+ }
+}
+
+/// Stroke the frames resulting from a node.
+#[derive(Debug, Hash)]
+pub struct StrokeNode {
+ /// How to stroke the frames resulting from the `child`.
+ pub stroke: Stroke,
+ /// The node to stroke.
+ pub child: PackedNode,
+}
+
+impl Layout for StrokeNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions, styles);
+ for Constrained { item: frame, .. } in &mut frames {
+ let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke);
+ Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
+ }
+ frames
+ }
+}
diff --git a/src/library/container.rs b/src/library/container.rs
new file mode 100644
index 00000000..ae097a46
--- /dev/null
+++ b/src/library/container.rs
@@ -0,0 +1,26 @@
+//! Inline- and block-level containers.
+
+use super::prelude::*;
+
+/// Size content and place it into a paragraph.
+pub struct BoxNode;
+
+#[class]
+impl BoxNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ let width = args.named("width")?;
+ let height = args.named("height")?;
+ let body: PackedNode = args.find().unwrap_or_default();
+ Ok(Node::inline(body.sized(Spec::new(width, height))))
+ }
+}
+
+/// Place content into a separate flow.
+pub struct BlockNode;
+
+#[class]
+impl BlockNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ Ok(Node::Block(args.find().unwrap_or_default()))
+ }
+}
diff --git a/src/library/flow.rs b/src/library/flow.rs
index cfcd6561..3405b04a 100644
--- a/src/library/flow.rs
+++ b/src/library/flow.rs
@@ -138,6 +138,12 @@ impl<'a> FlowLayouter<'a> {
}
}
+ if self.expand.y {
+ while self.regions.backlog.len() > 0 {
+ self.finish_region();
+ }
+ }
+
self.finish_region();
self.finished
}
diff --git a/src/library/grid.rs b/src/library/grid.rs
index ee9aafe1..59f52427 100644
--- a/src/library/grid.rs
+++ b/src/library/grid.rs
@@ -69,7 +69,7 @@ castable! {
Value::Relative(v) => vec![TrackSizing::Linear(v.into())],
Value::Linear(v) => vec![TrackSizing::Linear(v)],
Value::Fractional(v) => vec![TrackSizing::Fractional(v)],
- Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast()?],
+ Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast::<NonZeroUsize>()?.get()],
Value::Array(values) => values
.into_iter()
.filter_map(|v| v.cast().ok())
diff --git a/src/library/mod.rs b/src/library/mod.rs
index c4ec2988..51d57c3e 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -5,6 +5,7 @@
pub mod align;
pub mod columns;
+pub mod container;
pub mod deco;
pub mod flow;
pub mod grid;
@@ -17,9 +18,9 @@ pub mod page;
pub mod par;
pub mod placed;
pub mod shape;
-pub mod sized;
pub mod spacing;
pub mod stack;
+pub mod table;
pub mod text;
pub mod transform;
pub mod utility;
@@ -27,6 +28,7 @@ pub mod utility;
pub use self::image::*;
pub use align::*;
pub use columns::*;
+pub use container::*;
pub use deco::*;
pub use flow::*;
pub use grid::*;
@@ -38,9 +40,9 @@ pub use page::*;
pub use par::*;
pub use placed::*;
pub use shape::*;
-pub use sized::*;
pub use spacing::*;
pub use stack::*;
+pub use table::*;
pub use text::*;
pub use transform::*;
pub use utility::*;
@@ -96,6 +98,7 @@ pub fn new() -> Scope {
std.def_class::<HeadingNode>("heading");
std.def_class::<ListNode<Unordered>>("list");
std.def_class::<ListNode<Ordered>>("enum");
+ std.def_class::<TableNode>("table");
std.def_class::<ImageNode>("image");
std.def_class::<ShapeNode<Rect>>("rect");
std.def_class::<ShapeNode<Square>>("square");
diff --git a/src/library/page.rs b/src/library/page.rs
index e2c27a36..f3a287dc 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -98,27 +98,22 @@ impl PageNode {
child = ColumnsNode { columns, child: self.0.clone() }.pack();
}
- // Realize margins with padding node.
+ // Realize margins.
child = child.padded(padding);
+ // Realize background fill.
+ if let Some(fill) = styles.get(Self::FILL) {
+ child = child.filled(fill);
+ }
+
// Layout the child.
let expand = size.map(Length::is_finite);
let regions = Regions::repeat(size, size, expand);
- let mut frames: Vec<_> = child
+ child
.layout(ctx, &regions, styles)
.into_iter()
.map(|c| c.item)
- .collect();
-
- // Add background fill if requested.
- if let Some(fill) = styles.get(Self::FILL) {
- for frame in &mut frames {
- let shape = Shape::filled(Geometry::Rect(frame.size), fill);
- Rc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
- }
- }
-
- frames
+ .collect()
}
}
diff --git a/src/library/sized.rs b/src/library/sized.rs
deleted file mode 100644
index 57578717..00000000
--- a/src/library/sized.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-//! Horizontal and vertical sizing of nodes.
-
-use super::prelude::*;
-
-/// Size content and place it into a paragraph.
-pub struct BoxNode;
-
-#[class]
-impl BoxNode {
- fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
- let width = args.named("width")?;
- let height = args.named("height")?;
- let body: PackedNode = args.find().unwrap_or_default();
- Ok(Node::inline(body.sized(Spec::new(width, height))))
- }
-}
-
-/// Place content into a separate flow.
-pub struct BlockNode;
-
-#[class]
-impl BlockNode {
- fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
- Ok(Node::Block(args.find().unwrap_or_default()))
- }
-}
-
-/// A node that sizes its child.
-#[derive(Debug, Hash)]
-pub struct SizedNode {
- /// How to size the node horizontally and vertically.
- pub sizing: Spec<Option<Linear>>,
- /// The node to be sized.
- pub child: PackedNode,
-}
-
-impl Layout for SizedNode {
- fn layout(
- &self,
- ctx: &mut LayoutContext,
- regions: &Regions,
- styles: StyleChain,
- ) -> Vec<Constrained<Rc<Frame>>> {
- let is_auto = self.sizing.map_is_none();
- let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative));
-
- // The "pod" is the region into which the child will be layouted.
- let pod = {
- // Resolve the sizing to a concrete size.
- let size = self
- .sizing
- .zip(regions.base)
- .map(|(s, b)| s.map(|v| v.resolve(b)))
- .unwrap_or(regions.current);
-
- // Select the appropriate base and expansion for the child depending
- // on whether it is automatically or linearly sized.
- let base = is_auto.select(regions.base, size);
- let expand = regions.expand | !is_auto;
-
- Regions::one(size, base, expand)
- };
-
- let mut frames = self.child.layout(ctx, &pod, styles);
- let Constrained { item: frame, cts } = &mut frames[0];
-
- // Ensure frame size matches regions size if expansion is on.
- let target = regions.expand.select(regions.current, frame.size);
- Rc::make_mut(frame).resize(target, Align::LEFT_TOP);
-
- // Set base & exact constraints if the child is automatically sized
- // since we don't know what the child might have done. Also set base if
- // our sizing is relative.
- *cts = Constraints::new(regions.expand);
- cts.exact = regions.current.filter(regions.expand | is_auto);
- cts.base = regions.base.filter(is_rel | is_auto);
-
- frames
- }
-}
diff --git a/src/library/table.rs b/src/library/table.rs
new file mode 100644
index 00000000..c8e0e17b
--- /dev/null
+++ b/src/library/table.rs
@@ -0,0 +1,101 @@
+//! Tabular container.
+
+use super::prelude::*;
+use super::{GridNode, TrackSizing};
+
+/// A table of items.
+#[derive(Debug, Hash)]
+pub struct TableNode {
+ /// Defines sizing for content rows and columns.
+ pub tracks: Spec<Vec<TrackSizing>>,
+ /// Defines sizing of gutter rows and columns between content.
+ pub gutter: Spec<Vec<TrackSizing>>,
+ /// The nodes to be arranged in the table.
+ pub children: Vec<PackedNode>,
+}
+
+#[class]
+impl TableNode {
+ /// The primary cell fill color.
+ pub const PRIMARY: Option<Paint> = None;
+ /// The secondary cell fill color.
+ pub const SECONDARY: Option<Paint> = None;
+ /// How the stroke the cells.
+ pub const STROKE: Option<Paint> = Some(RgbaColor::BLACK.into());
+ /// The stroke's thickness.
+ pub const THICKNESS: Length = Length::pt(1.0);
+ /// How much to pad the cells's content.
+ pub const PADDING: Linear = Length::pt(5.0).into();
+
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ let columns = args.named("columns")?.unwrap_or_default();
+ let rows = args.named("rows")?.unwrap_or_default();
+ let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
+ let column_gutter = args.named("column-gutter")?;
+ let row_gutter = args.named("row-gutter")?;
+ Ok(Node::block(Self {
+ tracks: Spec::new(columns, rows),
+ gutter: Spec::new(
+ column_gutter.unwrap_or_else(|| base_gutter.clone()),
+ row_gutter.unwrap_or(base_gutter),
+ ),
+ children: args.all().collect(),
+ }))
+ }
+
+ fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
+ let fill = args.named("fill")?;
+ styles.set_opt(Self::PRIMARY, args.named("primary")?.or(fill));
+ styles.set_opt(Self::SECONDARY, args.named("secondary")?.or(fill));
+ styles.set_opt(Self::STROKE, args.named("stroke")?);
+ styles.set_opt(Self::THICKNESS, args.named("thickness")?);
+ styles.set_opt(Self::PADDING, args.named("padding")?);
+ Ok(())
+ }
+}
+
+impl Layout for TableNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let primary = styles.get(Self::PRIMARY);
+ let secondary = styles.get(Self::SECONDARY);
+ let thickness = styles.get(Self::THICKNESS);
+ let stroke = styles.get(Self::STROKE).map(|paint| Stroke { paint, thickness });
+ let padding = styles.get(Self::PADDING);
+
+ let cols = self.tracks.x.len();
+ let children = self
+ .children
+ .iter()
+ .cloned()
+ .enumerate()
+ .map(|(i, mut child)| {
+ child = child.padded(Sides::splat(padding));
+
+ if let Some(stroke) = stroke {
+ child = child.stroked(stroke);
+ }
+
+ let x = i % cols;
+ let y = i / cols;
+ if let Some(fill) = [primary, secondary][(x + y) % 2] {
+ child = child.filled(fill);
+ }
+
+ child
+ })
+ .collect();
+
+ let grid = GridNode {
+ tracks: self.tracks.clone(),
+ gutter: self.gutter.clone(),
+ children,
+ };
+
+ grid.layout(ctx, regions, styles)
+ }
+}