Basic tables

This commit is contained in:
Laurenz 2022-01-17 16:01:01 +01:00
parent 4abdafcd15
commit 0c5243fa80
10 changed files with 273 additions and 99 deletions

View File

@ -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
}
}

26
src/library/container.rs Normal file
View File

@ -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()))
}
}

View File

@ -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
}

View File

@ -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())

View File

@ -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");

View File

@ -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()
}
}

View File

@ -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
}
}

101
src/library/table.rs Normal file
View File

@ -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)
}
}

BIN
tests/ref/layout/table.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,9 @@
#set page(height: 70pt)
#set table(primary: rgb("aaa"), secondary: none)
#table(
columns: (1fr,) * 3,
stroke: rgb("333"),
thickness: 2pt,
[A], [B], [C], [], [], [D \ E \ F \ \ \ G], [H],
)