summaryrefslogtreecommitdiff
path: root/src/library/layout
diff options
context:
space:
mode:
Diffstat (limited to 'src/library/layout')
-rw-r--r--src/library/layout/align.rs62
-rw-r--r--src/library/layout/columns.rs117
-rw-r--r--src/library/layout/container.rs80
-rw-r--r--src/library/layout/flow.rs267
-rw-r--r--src/library/layout/grid.rs593
-rw-r--r--src/library/layout/mod.rs814
-rw-r--r--src/library/layout/pad.rs83
-rw-r--r--src/library/layout/page.rs421
-rw-r--r--src/library/layout/place.rs56
-rw-r--r--src/library/layout/spacing.rs100
-rw-r--r--src/library/layout/stack.rs321
-rw-r--r--src/library/layout/transform.rs116
12 files changed, 0 insertions, 3030 deletions
diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs
deleted file mode 100644
index 2ee565cc..00000000
--- a/src/library/layout/align.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use crate::library::prelude::*;
-use crate::library::text::{HorizontalAlign, ParNode};
-
-/// Align content along the layouting axes.
-#[derive(Debug, Hash)]
-pub struct AlignNode {
- /// How to align the content horizontally and vertically.
- pub aligns: Axes<Option<RawAlign>>,
- /// The content to be aligned.
- pub child: Content,
-}
-
-#[node(LayoutBlock)]
-impl AlignNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
- let body: Content = args.expect("body")?;
-
- if let Axes { x: Some(x), y: None } = aligns {
- if !body.has::<dyn LayoutBlock>() {
- return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
- }
- }
-
- Ok(body.aligned(aligns))
- }
-}
-
-impl LayoutBlock for AlignNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // The child only needs to expand along an axis if there's no alignment.
- let mut pod = regions.clone();
- pod.expand &= self.aligns.as_ref().map(Option::is_none);
-
- // Align paragraphs inside the child.
- let mut passed = StyleMap::new();
- if let Some(align) = self.aligns.x {
- passed.set(ParNode::ALIGN, HorizontalAlign(align));
- }
-
- // Layout the child.
- let mut frames = self.child.layout_block(world, &pod, passed.chain(&styles))?;
- for (region, frame) in regions.iter().zip(&mut frames) {
- // Align in the target size. The target size depends on whether we
- // should expand.
- let target = regions.expand.select(region, frame.size());
- let aligns = self
- .aligns
- .map(|align| align.resolve(styles))
- .unwrap_or(Axes::new(Align::Left, Align::Top));
-
- frame.resize(target, aligns);
- }
-
- Ok(frames)
- }
-}
diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs
deleted file mode 100644
index df259eab..00000000
--- a/src/library/layout/columns.rs
+++ /dev/null
@@ -1,117 +0,0 @@
-use crate::library::prelude::*;
-use crate::library::text::TextNode;
-
-/// Separate a region into multiple equally sized columns.
-#[derive(Debug, Hash)]
-pub struct ColumnsNode {
- /// How many columns there should be.
- pub columns: NonZeroUsize,
- /// The child to be layouted into the columns. Most likely, this should be a
- /// flow or stack node.
- pub child: Content,
-}
-
-#[node(LayoutBlock)]
-impl ColumnsNode {
- /// The size of the gutter space between each column.
- #[property(resolve)]
- pub const GUTTER: Rel<Length> = Ratio::new(0.04).into();
-
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self {
- columns: args.expect("column count")?,
- child: args.expect("body")?,
- }
- .pack())
- }
-}
-
-impl LayoutBlock for ColumnsNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // Separating the infinite space into infinite columns does not make
- // much sense.
- if !regions.first.x.is_finite() {
- return self.child.layout_block(world, regions, styles);
- }
-
- // Determine the width of the gutter and each column.
- let columns = self.columns.get();
- let gutter = styles.get(Self::GUTTER).relative_to(regions.base.x);
- let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64;
-
- // Create the pod regions.
- let pod = Regions {
- first: Size::new(width, regions.first.y),
- base: Size::new(width, regions.base.y),
- backlog: std::iter::once(&regions.first.y)
- .chain(regions.backlog.as_slice())
- .flat_map(|&height| std::iter::repeat(height).take(columns))
- .skip(1)
- .collect(),
- last: regions.last,
- expand: Axes::new(true, regions.expand.y),
- };
-
- // Layout the children.
- let mut frames = self.child.layout_block(world, &pod, styles)?.into_iter();
- let mut finished = vec![];
-
- let dir = styles.get(TextNode::DIR);
- let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
-
- // Stitch together the columns for each region.
- for region in regions.iter().take(total_regions) {
- // The height should be the parent height if we should expand.
- // Otherwise its the maximum column height for the frame. In that
- // case, the frame is first created with zero height and then
- // resized.
- let height = if regions.expand.y { region.y } else { Abs::zero() };
- let mut output = Frame::new(Size::new(regions.first.x, height));
- let mut cursor = Abs::zero();
-
- for _ in 0 .. columns {
- let frame = match frames.next() {
- Some(frame) => frame,
- None => break,
- };
-
- if !regions.expand.y {
- output.size_mut().y.set_max(frame.height());
- }
-
- let width = frame.width();
- let x = if dir.is_positive() {
- cursor
- } else {
- regions.first.x - cursor - width
- };
-
- output.push_frame(Point::with_x(x), frame);
- cursor += width + gutter;
- }
-
- finished.push(output);
- }
-
- Ok(finished)
- }
-}
-
-/// A column break.
-#[derive(Debug, Clone, Hash)]
-pub struct ColbreakNode {
- pub weak: bool,
-}
-
-#[node]
-impl ColbreakNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let weak = args.named("weak")?.unwrap_or(false);
- Ok(Self { weak }.pack())
- }
-}
diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs
deleted file mode 100644
index 023809d0..00000000
--- a/src/library/layout/container.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-use crate::library::prelude::*;
-
-/// An inline-level container that sizes content.
-#[derive(Debug, Clone, Hash)]
-pub struct BoxNode {
- /// How to size the content horizontally and vertically.
- pub sizing: Axes<Option<Rel<Length>>>,
- /// The content to be sized.
- pub child: Content,
-}
-
-#[node(LayoutInline)]
-impl BoxNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let width = args.named("width")?;
- let height = args.named("height")?;
- let body = args.eat::<Content>()?.unwrap_or_default();
- Ok(body.boxed(Axes::new(width, height)))
- }
-}
-
-impl LayoutInline for BoxNode {
- fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // 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
- .resolve(styles)
- .zip(regions.base)
- .map(|(s, b)| s.map(|v| v.relative_to(b)))
- .unwrap_or(regions.first);
-
- // Select the appropriate base and expansion for the child depending
- // on whether it is automatically or relatively sized.
- let is_auto = self.sizing.as_ref().map(Option::is_none);
- let base = is_auto.select(regions.base, size);
- let expand = regions.expand | !is_auto;
-
- Regions::one(size, base, expand)
- };
-
- // Layout the child.
- let mut frames = self.child.layout_inline(world, &pod, styles)?;
-
- // Ensure frame size matches regions size if expansion is on.
- let frame = &mut frames[0];
- let target = regions.expand.select(regions.first, frame.size());
- frame.resize(target, Align::LEFT_TOP);
-
- Ok(frames)
- }
-}
-
-/// A block-level container that places content into a separate flow.
-#[derive(Debug, Clone, Hash)]
-pub struct BlockNode(pub Content);
-
-#[node(LayoutBlock)]
-impl BlockNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self(args.eat()?.unwrap_or_default()).pack())
- }
-}
-
-impl LayoutBlock for BlockNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- self.0.layout_block(world, regions, styles)
- }
-}
diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs
deleted file mode 100644
index f4d18699..00000000
--- a/src/library/layout/flow.rs
+++ /dev/null
@@ -1,267 +0,0 @@
-use std::cmp::Ordering;
-
-use super::{AlignNode, PlaceNode, Spacing};
-use crate::library::prelude::*;
-use crate::library::text::ParNode;
-
-/// Arrange spacing, paragraphs and block-level nodes into a flow.
-///
-/// This node is reponsible for layouting both the top-level content flow and
-/// the contents of boxes.
-#[derive(Hash)]
-pub struct FlowNode(pub StyleVec<FlowChild>);
-
-/// A child of a flow node.
-#[derive(Hash, PartialEq)]
-pub enum FlowChild {
- /// Vertical spacing between other children.
- Spacing(Spacing),
- /// Arbitrary block-level content.
- Block(Content),
- /// A column / region break.
- Colbreak,
-}
-
-#[node(LayoutBlock)]
-impl FlowNode {}
-
-impl LayoutBlock for FlowNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let mut layouter = FlowLayouter::new(regions);
-
- for (child, map) in self.0.iter() {
- let styles = map.chain(&styles);
- match child {
- FlowChild::Spacing(kind) => {
- layouter.layout_spacing(*kind, styles);
- }
- FlowChild::Block(block) => {
- layouter.layout_block(world, block, styles)?;
- }
- FlowChild::Colbreak => {
- layouter.finish_region();
- }
- }
- }
-
- Ok(layouter.finish())
- }
-}
-
-impl Debug for FlowNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("Flow ")?;
- self.0.fmt(f)
- }
-}
-
-impl Debug for FlowChild {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Spacing(kind) => write!(f, "{:?}", kind),
- Self::Block(block) => block.fmt(f),
- Self::Colbreak => f.pad("Colbreak"),
- }
- }
-}
-
-impl PartialOrd for FlowChild {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- match (self, other) {
- (Self::Spacing(a), Self::Spacing(b)) => a.partial_cmp(b),
- _ => None,
- }
- }
-}
-
-/// Performs flow layout.
-pub struct FlowLayouter {
- /// The regions to layout children into.
- regions: Regions,
- /// Whether the flow should expand to fill the region.
- expand: Axes<bool>,
- /// The full size of `regions.size` that was available before we started
- /// subtracting.
- full: Size,
- /// The size used by the frames for the current region.
- used: Size,
- /// The sum of fractions in the current region.
- fr: Fr,
- /// Spacing and layouted blocks.
- items: Vec<FlowItem>,
- /// Finished frames for previous regions.
- finished: Vec<Frame>,
-}
-
-/// A prepared item in a flow layout.
-enum FlowItem {
- /// Absolute spacing between other items.
- Absolute(Abs),
- /// Fractional spacing between other items.
- Fractional(Fr),
- /// A frame for a layouted block and how to align it.
- Frame(Frame, Axes<Align>),
- /// An absolutely placed frame.
- Placed(Frame),
-}
-
-impl FlowLayouter {
- /// Create a new flow layouter.
- pub fn new(regions: &Regions) -> Self {
- let expand = regions.expand;
- let full = regions.first;
-
- // Disable vertical expansion for children.
- let mut regions = regions.clone();
- regions.expand.y = false;
-
- Self {
- regions,
- expand,
- full,
- used: Size::zero(),
- fr: Fr::zero(),
- items: vec![],
- finished: vec![],
- }
- }
-
- /// Layout spacing.
- pub fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
- match spacing {
- Spacing::Relative(v) => {
- // Resolve the spacing and limit it to the remaining space.
- let resolved = v.resolve(styles).relative_to(self.full.y);
- let limited = resolved.min(self.regions.first.y);
- self.regions.first.y -= limited;
- self.used.y += limited;
- self.items.push(FlowItem::Absolute(resolved));
- }
- Spacing::Fractional(v) => {
- self.items.push(FlowItem::Fractional(v));
- self.fr += v;
- }
- }
- }
-
- /// Layout a block.
- pub fn layout_block(
- &mut self,
- world: Tracked<dyn World>,
- block: &Content,
- styles: StyleChain,
- ) -> SourceResult<()> {
- // Don't even try layouting into a full region.
- if self.regions.is_full() {
- self.finish_region();
- }
-
- // Placed nodes that are out of flow produce placed items which aren't
- // aligned later.
- if let Some(placed) = block.downcast::<PlaceNode>() {
- if placed.out_of_flow() {
- let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
- self.items.push(FlowItem::Placed(frame));
- return Ok(());
- }
- }
-
- // How to align the block.
- let aligns = Axes::new(
- // For non-expanding paragraphs it is crucial that we align the
- // whole paragraph as it is itself aligned.
- styles.get(ParNode::ALIGN),
- // Vertical align node alignment is respected by the flow.
- block
- .downcast::<AlignNode>()
- .and_then(|aligned| aligned.aligns.y)
- .map(|align| align.resolve(styles))
- .unwrap_or(Align::Top),
- );
-
- let frames = block.layout_block(world, &self.regions, styles)?;
- let len = frames.len();
- for (i, mut frame) in frames.into_iter().enumerate() {
- // Set the generic block role.
- frame.apply_role(Role::GenericBlock);
-
- // Grow our size, shrink the region and save the frame for later.
- let size = frame.size();
- self.used.y += size.y;
- self.used.x.set_max(size.x);
- self.regions.first.y -= size.y;
- self.items.push(FlowItem::Frame(frame, aligns));
-
- if i + 1 < len {
- self.finish_region();
- }
- }
-
- Ok(())
- }
-
- /// Finish the frame for one region.
- pub fn finish_region(&mut self) {
- // Determine the size of the flow in this region dependening on whether
- // the region expands.
- let mut size = self.expand.select(self.full, self.used);
-
- // Account for fractional spacing in the size calculation.
- let remaining = self.full.y - self.used.y;
- if self.fr.get() > 0.0 && self.full.y.is_finite() {
- self.used.y = self.full.y;
- size.y = self.full.y;
- }
-
- let mut output = Frame::new(size);
- let mut offset = Abs::zero();
- let mut ruler = Align::Top;
-
- // Place all frames.
- for item in self.items.drain(..) {
- match item {
- FlowItem::Absolute(v) => {
- offset += v;
- }
- FlowItem::Fractional(v) => {
- offset += v.share(self.fr, remaining);
- }
- FlowItem::Frame(frame, aligns) => {
- ruler = ruler.max(aligns.y);
- let x = aligns.x.position(size.x - frame.width());
- let y = offset + ruler.position(size.y - self.used.y);
- let pos = Point::new(x, y);
- offset += frame.height();
- output.push_frame(pos, frame);
- }
- FlowItem::Placed(frame) => {
- output.push_frame(Point::zero(), frame);
- }
- }
- }
-
- // Advance to the next region.
- self.regions.next();
- self.full = self.regions.first;
- self.used = Size::zero();
- self.fr = Fr::zero();
- self.finished.push(output);
- }
-
- /// Finish layouting and return the resulting frames.
- pub fn finish(mut self) -> Vec<Frame> {
- if self.expand.y {
- while self.regions.backlog.len() > 0 {
- self.finish_region();
- }
- }
-
- self.finish_region();
- self.finished
- }
-}
diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs
deleted file mode 100644
index 1bb67691..00000000
--- a/src/library/layout/grid.rs
+++ /dev/null
@@ -1,593 +0,0 @@
-use crate::library::prelude::*;
-
-/// Arrange content in a grid.
-#[derive(Debug, Hash)]
-pub struct GridNode {
- /// Defines sizing for content rows and columns.
- pub tracks: Axes<Vec<TrackSizing>>,
- /// Defines sizing of gutter rows and columns between content.
- pub gutter: Axes<Vec<TrackSizing>>,
- /// The content to be arranged in a grid.
- pub cells: Vec<Content>,
-}
-
-#[node(LayoutBlock)]
-impl GridNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- 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(Self {
- tracks: Axes::new(columns, rows),
- gutter: Axes::new(
- column_gutter.unwrap_or_else(|| base_gutter.clone()),
- row_gutter.unwrap_or(base_gutter),
- ),
- cells: args.all()?,
- }
- .pack())
- }
-}
-
-impl LayoutBlock for GridNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // Prepare grid layout by unifying content and gutter tracks.
- let layouter = GridLayouter::new(
- world,
- self.tracks.as_deref(),
- self.gutter.as_deref(),
- &self.cells,
- regions,
- styles,
- );
-
- // Measure the columns and layout the grid row-by-row.
- layouter.layout()
- }
-}
-
-/// Defines how to size a grid cell along an axis.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum TrackSizing {
- /// A track that fits its cell's contents.
- Auto,
- /// A track size specified in absolute terms and relative to the parent's
- /// size.
- Relative(Rel<Length>),
- /// A track size specified as a fraction of the remaining free space in the
- /// parent.
- Fractional(Fr),
-}
-
-castable! {
- Vec<TrackSizing>,
- Expected: "integer, auto, relative length, fraction, or array of the latter three",
- Value::Auto => vec![TrackSizing::Auto],
- Value::Length(v) => vec![TrackSizing::Relative(v.into())],
- Value::Ratio(v) => vec![TrackSizing::Relative(v.into())],
- Value::Relative(v) => vec![TrackSizing::Relative(v)],
- Value::Fraction(v) => vec![TrackSizing::Fractional(v)],
- Value::Int(v) => vec![TrackSizing::Auto; Value::Int(v).cast::<NonZeroUsize>()?.get()],
- Value::Array(values) => values
- .into_iter()
- .filter_map(|v| v.cast().ok())
- .collect(),
-}
-
-castable! {
- TrackSizing,
- Expected: "auto, relative length, or fraction",
- Value::Auto => Self::Auto,
- Value::Length(v) => Self::Relative(v.into()),
- Value::Ratio(v) => Self::Relative(v.into()),
- Value::Relative(v) => Self::Relative(v),
- Value::Fraction(v) => Self::Fractional(v),
-}
-
-/// Performs grid layout.
-pub struct GridLayouter<'a> {
- /// The core context.
- world: Tracked<'a, dyn World>,
- /// The grid cells.
- cells: &'a [Content],
- /// The column tracks including gutter tracks.
- cols: Vec<TrackSizing>,
- /// The row tracks including gutter tracks.
- rows: Vec<TrackSizing>,
- /// The regions to layout children into.
- regions: Regions,
- /// The inherited styles.
- styles: StyleChain<'a>,
- /// Resolved column sizes.
- rcols: Vec<Abs>,
- /// Rows in the current region.
- lrows: Vec<Row>,
- /// The full height of the current region.
- full: Abs,
- /// The used-up size of the current region. The horizontal size is
- /// determined once after columns are resolved and not touched again.
- used: Size,
- /// The sum of fractions in the current region.
- fr: Fr,
- /// Frames for finished regions.
- finished: Vec<Frame>,
-}
-
-/// Produced by initial row layout, auto and relative rows are already finished,
-/// fractional rows not yet.
-enum Row {
- /// Finished row frame of auto or relative row.
- Frame(Frame),
- /// Fractional row with y index.
- Fr(Fr, usize),
-}
-
-impl<'a> GridLayouter<'a> {
- /// Create a new grid layouter.
- ///
- /// This prepares grid layout by unifying content and gutter tracks.
- pub fn new(
- world: Tracked<'a, dyn World>,
- tracks: Axes<&[TrackSizing]>,
- gutter: Axes<&[TrackSizing]>,
- cells: &'a [Content],
- regions: &Regions,
- styles: StyleChain<'a>,
- ) -> Self {
- let mut cols = vec![];
- let mut rows = vec![];
-
- // Number of content columns: Always at least one.
- let c = tracks.x.len().max(1);
-
- // Number of content rows: At least as many as given, but also at least
- // as many as needed to place each item.
- let r = {
- let len = cells.len();
- let given = tracks.y.len();
- let needed = len / c + (len % c).clamp(0, 1);
- given.max(needed)
- };
-
- let auto = TrackSizing::Auto;
- let zero = TrackSizing::Relative(Rel::zero());
- let get_or = |tracks: &[_], idx, default| {
- tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
- };
-
- // Collect content and gutter columns.
- for x in 0 .. c {
- cols.push(get_or(tracks.x, x, auto));
- cols.push(get_or(gutter.x, x, zero));
- }
-
- // Collect content and gutter rows.
- for y in 0 .. r {
- rows.push(get_or(tracks.y, y, auto));
- rows.push(get_or(gutter.y, y, zero));
- }
-
- // Remove superfluous gutter tracks.
- cols.pop();
- rows.pop();
-
- let full = regions.first.y;
- let rcols = vec![Abs::zero(); cols.len()];
- let lrows = vec![];
-
- // We use the regions for auto row measurement. Since at that moment,
- // columns are already sized, we can enable horizontal expansion.
- let mut regions = regions.clone();
- regions.expand = Axes::new(true, false);
-
- Self {
- world,
- cells,
- cols,
- rows,
- regions,
- styles,
- rcols,
- lrows,
- full,
- used: Size::zero(),
- fr: Fr::zero(),
- finished: vec![],
- }
- }
-
- /// Determines the columns sizes and then layouts the grid row-by-row.
- pub fn layout(mut self) -> SourceResult<Vec<Frame>> {
- self.measure_columns()?;
-
- for y in 0 .. self.rows.len() {
- // Skip to next region if current one is full, but only for content
- // rows, not for gutter rows.
- if y % 2 == 0 && self.regions.is_full() {
- self.finish_region()?;
- }
-
- match self.rows[y] {
- TrackSizing::Auto => self.layout_auto_row(y)?,
- TrackSizing::Relative(v) => self.layout_relative_row(v, y)?,
- TrackSizing::Fractional(v) => {
- self.lrows.push(Row::Fr(v, y));
- self.fr += v;
- }
- }
- }
-
- self.finish_region()?;
- Ok(self.finished)
- }
-
- /// Determine all column sizes.
- fn measure_columns(&mut self) -> SourceResult<()> {
- // Sum of sizes of resolved relative tracks.
- let mut rel = Abs::zero();
-
- // Sum of fractions of all fractional tracks.
- let mut fr = Fr::zero();
-
- // Resolve the size of all relative columns and compute the sum of all
- // fractional tracks.
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- match col {
- TrackSizing::Auto => {}
- TrackSizing::Relative(v) => {
- let resolved =
- v.resolve(self.styles).relative_to(self.regions.base.x);
- *rcol = resolved;
- rel += resolved;
- }
- TrackSizing::Fractional(v) => fr += v,
- }
- }
-
- // Size that is not used by fixed-size columns.
- let available = self.regions.first.x - rel;
- if available >= Abs::zero() {
- // Determine size of auto columns.
- let (auto, count) = self.measure_auto_columns(available)?;
-
- // If there is remaining space, distribute it to fractional columns,
- // otherwise shrink auto columns.
- let remaining = available - auto;
- if remaining >= Abs::zero() {
- if !fr.is_zero() {
- self.grow_fractional_columns(remaining, fr);
- }
- } else {
- self.shrink_auto_columns(available, count);
- }
- }
-
- // Sum up the resolved column sizes once here.
- self.used.x = self.rcols.iter().sum();
-
- Ok(())
- }
-
- /// Measure the size that is available to auto columns.
- fn measure_auto_columns(&mut self, available: Abs) -> SourceResult<(Abs, usize)> {
- let mut auto = Abs::zero();
- let mut count = 0;
-
- // Determine size of auto columns by laying out all cells in those
- // columns, measuring them and finding the largest one.
- for (x, &col) in self.cols.iter().enumerate() {
- if col != TrackSizing::Auto {
- continue;
- }
-
- let mut resolved = Abs::zero();
- for y in 0 .. self.rows.len() {
- if let Some(cell) = self.cell(x, y) {
- let size = Size::new(available, self.regions.base.y);
- let mut pod =
- Regions::one(size, self.regions.base, Axes::splat(false));
-
- // For relative rows, we can already resolve the correct
- // base, for auto it's already correct and for fr we could
- // only guess anyway.
- if let TrackSizing::Relative(v) = self.rows[y] {
- pod.base.y =
- v.resolve(self.styles).relative_to(self.regions.base.y);
- }
-
- let frame =
- cell.layout_block(self.world, &pod, self.styles)?.remove(0);
- resolved.set_max(frame.width());
- }
- }
-
- self.rcols[x] = resolved;
- auto += resolved;
- count += 1;
- }
-
- Ok((auto, count))
- }
-
- /// Distribute remaining space to fractional columns.
- fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) {
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if let TrackSizing::Fractional(v) = col {
- *rcol = v.share(fr, remaining);
- }
- }
- }
-
- /// Redistribute space to auto columns so that each gets a fair share.
- fn shrink_auto_columns(&mut self, available: Abs, count: usize) {
- // The fair share each auto column may have.
- let fair = available / count as f64;
-
- // The number of overlarge auto columns and the space that will be
- // equally redistributed to them.
- let mut overlarge: usize = 0;
- let mut redistribute = available;
-
- // Find out the number of and space used by overlarge auto columns.
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if col == TrackSizing::Auto {
- if *rcol > fair {
- overlarge += 1;
- } else {
- redistribute -= *rcol;
- }
- }
- }
-
- // Redistribute the space equally.
- let share = redistribute / overlarge as f64;
- for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
- if col == TrackSizing::Auto && *rcol > fair {
- *rcol = share;
- }
- }
- }
-
- /// Layout a row with automatic height. Such a row may break across multiple
- /// regions.
- fn layout_auto_row(&mut self, y: usize) -> SourceResult<()> {
- let mut resolved: Vec<Abs> = vec![];
-
- // Determine the size for each region of the row.
- for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(cell) = self.cell(x, y) {
- let mut pod = self.regions.clone();
- pod.first.x = rcol;
- pod.base.x = rcol;
-
- // All widths should be `rcol` except the base for auto columns.
- if self.cols[x] == TrackSizing::Auto {
- pod.base.x = self.regions.base.x;
- }
-
- let mut sizes = cell
- .layout_block(self.world, &pod, self.styles)?
- .into_iter()
- .map(|frame| frame.height());
-
- // For each region, we want to know the maximum height any
- // column requires.
- for (target, size) in resolved.iter_mut().zip(&mut sizes) {
- target.set_max(size);
- }
-
- // New heights are maximal by virtue of being new. Note that
- // this extend only uses the rest of the sizes iterator.
- resolved.extend(sizes);
- }
- }
-
- // Nothing to layout.
- if resolved.is_empty() {
- return Ok(());
- }
-
- // Layout into a single region.
- if let &[first] = resolved.as_slice() {
- let frame = self.layout_single_row(first, y)?;
- self.push_row(frame);
- return Ok(());
- }
-
- // Expand all but the last region if the space is not
- // eaten up by any fr rows.
- if self.fr.is_zero() {
- let len = resolved.len();
- for (region, target) in self.regions.iter().zip(&mut resolved[.. len - 1]) {
- target.set_max(region.y);
- }
- }
-
- // Layout into multiple regions.
- let frames = self.layout_multi_row(&resolved, y)?;
- let len = frames.len();
- for (i, frame) in frames.into_iter().enumerate() {
- self.push_row(frame);
- if i + 1 < len {
- self.finish_region()?;
- }
- }
-
- Ok(())
- }
-
- /// Layout a row with relative height. Such a row cannot break across
- /// multiple regions, but it may force a region break.
- fn layout_relative_row(&mut self, v: Rel<Length>, y: usize) -> SourceResult<()> {
- let resolved = v.resolve(self.styles).relative_to(self.regions.base.y);
- let frame = self.layout_single_row(resolved, y)?;
-
- // Skip to fitting region.
- let height = frame.height();
- while !self.regions.first.y.fits(height) && !self.regions.in_last() {
- self.finish_region()?;
-
- // Don't skip multiple regions for gutter and don't push a row.
- if y % 2 == 1 {
- return Ok(());
- }
- }
-
- self.push_row(frame);
-
- Ok(())
- }
-
- /// Layout a row with fixed height and return its frame.
- fn layout_single_row(&mut self, height: Abs, y: usize) -> SourceResult<Frame> {
- let mut output = Frame::new(Size::new(self.used.x, height));
-
- let mut pos = Point::zero();
-
- for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(cell) = self.cell(x, y) {
- let size = Size::new(rcol, height);
-
- // Set the base to the region's base for auto rows and to the
- // size for relative and fractional rows.
- let base = Axes::new(self.cols[x], self.rows[y])
- .map(|s| s == TrackSizing::Auto)
- .select(self.regions.base, size);
-
- let pod = Regions::one(size, base, Axes::splat(true));
- let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0);
- match frame.role() {
- Some(Role::ListLabel | Role::ListItemBody) => {
- output.apply_role(Role::ListItem)
- }
- Some(Role::TableCell) => output.apply_role(Role::TableRow),
- _ => {}
- }
-
- output.push_frame(pos, frame);
- }
-
- pos.x += rcol;
- }
-
- Ok(output)
- }
-
- /// Layout a row spanning multiple regions.
- fn layout_multi_row(
- &mut self,
- heights: &[Abs],
- y: usize,
- ) -> SourceResult<Vec<Frame>> {
- // Prepare frames.
- let mut outputs: Vec<_> = heights
- .iter()
- .map(|&h| Frame::new(Size::new(self.used.x, h)))
- .collect();
-
- // Prepare regions.
- let size = Size::new(self.used.x, heights[0]);
- let mut pod = Regions::one(size, self.regions.base, Axes::splat(true));
- pod.backlog = heights[1 ..].to_vec();
-
- // Layout the row.
- let mut pos = Point::zero();
- for (x, &rcol) in self.rcols.iter().enumerate() {
- if let Some(cell) = self.cell(x, y) {
- pod.first.x = rcol;
- pod.base.x = rcol;
-
- // All widths should be `rcol` except the base for auto columns.
- if self.cols[x] == TrackSizing::Auto {
- pod.base.x = self.regions.base.x;
- }
-
- // Push the layouted frames into the individual output frames.
- let frames = cell.layout_block(self.world, &pod, self.styles)?;
- for (output, frame) in outputs.iter_mut().zip(frames) {
- match frame.role() {
- Some(Role::ListLabel | Role::ListItemBody) => {
- output.apply_role(Role::ListItem)
- }
- Some(Role::TableCell) => output.apply_role(Role::TableRow),
- _ => {}
- }
- output.push_frame(pos, frame);
- }
- }
-
- pos.x += rcol;
- }
-
- Ok(outputs)
- }
-
- /// Push a row frame into the current region.
- fn push_row(&mut self, frame: Frame) {
- self.regions.first.y -= frame.height();
- self.used.y += frame.height();
- self.lrows.push(Row::Frame(frame));
- }
-
- /// Finish rows for one region.
- fn finish_region(&mut self) -> SourceResult<()> {
- // Determine the size of the grid in this region, expanding fully if
- // there are fr rows.
- let mut size = self.used;
- if self.fr.get() > 0.0 && self.full.is_finite() {
- size.y = self.full;
- }
-
- // The frame for the region.
- let mut output = Frame::new(size);
- let mut pos = Point::zero();
-
- // Place finished rows and layout fractional rows.
- for row in std::mem::take(&mut self.lrows) {
- let frame = match row {
- Row::Frame(frame) => frame,
- Row::Fr(v, y) => {
- let remaining = self.full - self.used.y;
- let height = v.share(self.fr, remaining);
- self.layout_single_row(height, y)?
- }
- };
-
- let height = frame.height();
- output.push_frame(pos, frame);
- pos.y += height;
- }
-
- self.finished.push(output);
- self.regions.next();
- self.full = self.regions.first.y;
- self.used.y = Abs::zero();
- self.fr = Fr::zero();
-
- Ok(())
- }
-
- /// Get the content of the cell in column `x` and row `y`.
- ///
- /// Returns `None` if it's a gutter cell.
- #[track_caller]
- fn cell(&self, x: usize, y: usize) -> Option<&'a Content> {
- assert!(x < self.cols.len());
- assert!(y < self.rows.len());
-
- // Even columns and rows are children, odd ones are gutter.
- if x % 2 == 0 && y % 2 == 0 {
- let c = 1 + self.cols.len() / 2;
- self.cells.get((y / 2) * c + x / 2)
- } else {
- None
- }
- }
-}
diff --git a/src/library/layout/mod.rs b/src/library/layout/mod.rs
deleted file mode 100644
index 000cb212..00000000
--- a/src/library/layout/mod.rs
+++ /dev/null
@@ -1,814 +0,0 @@
-//! Composable layouts.
-
-mod align;
-mod columns;
-mod container;
-mod flow;
-mod grid;
-mod pad;
-mod page;
-mod place;
-mod spacing;
-mod stack;
-mod transform;
-
-pub use align::*;
-pub use columns::*;
-pub use container::*;
-pub use flow::*;
-pub use grid::*;
-pub use pad::*;
-pub use page::*;
-pub use place::*;
-pub use spacing::*;
-pub use stack::*;
-pub use transform::*;
-
-use std::mem;
-
-use comemo::Tracked;
-use typed_arena::Arena;
-
-use crate::diag::SourceResult;
-use crate::frame::Frame;
-use crate::geom::*;
-use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
-use crate::library::text::{
- LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
-};
-use crate::model::{
- capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain,
- StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
-};
-use crate::World;
-
-/// The root-level layout.
-#[capability]
-pub trait Layout: 'static + Sync + Send {
- /// Layout into one frame per page.
- fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>>;
-}
-
-impl Layout for Content {
- #[comemo::memoize]
- fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>> {
- let styles = StyleChain::with_root(&world.config().styles);
- let scratch = Scratch::default();
-
- let mut builder = Builder::new(world, &scratch, true);
- builder.accept(self, styles)?;
-
- let (doc, shared) = builder.into_doc(styles)?;
- doc.layout(world, shared)
- }
-}
-
-/// Block-level layout.
-#[capability]
-pub trait LayoutBlock: 'static + Sync + Send {
- /// Layout into one frame per region.
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>>;
-}
-
-impl LayoutBlock for Content {
- #[comemo::memoize]
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- if let Some(node) = self.to::<dyn LayoutBlock>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_block(world, regions, styles);
- }
-
- let scratch = Scratch::default();
- let mut builder = Builder::new(world, &scratch, false);
- builder.accept(self, styles)?;
- let (flow, shared) = builder.into_flow(styles)?;
- flow.layout_block(world, regions, shared)
- }
-}
-
-/// Inline-level layout.
-#[capability]
-pub trait LayoutInline: 'static + Sync + Send {
- /// Layout into a single frame.
- fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>>;
-}
-
-impl LayoutInline for Content {
- #[comemo::memoize]
- fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- if let Some(node) = self.to::<dyn LayoutInline>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_inline(world, regions, styles);
- }
-
- if let Some(node) = self.to::<dyn LayoutBlock>() {
- let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
- let styles = barrier.chain(&styles);
- return node.layout_block(world, regions, styles);
- }
-
- let scratch = Scratch::default();
- let mut builder = Builder::new(world, &scratch, false);
- builder.accept(self, styles)?;
- let (flow, shared) = builder.into_flow(styles)?;
- flow.layout_block(world, regions, shared)
- }
-}
-
-/// A sequence of regions to layout into.
-#[derive(Debug, Clone, Hash)]
-pub struct Regions {
- /// The (remaining) size of the first region.
- pub first: Size,
- /// The base size for relative sizing.
- pub base: Size,
- /// The height of followup regions. The width is the same for all regions.
- pub backlog: Vec<Abs>,
- /// The height of the final region that is repeated once the backlog is
- /// drained. The width is the same for all regions.
- pub last: Option<Abs>,
- /// Whether nodes should expand to fill the regions instead of shrinking to
- /// fit the content.
- pub expand: Axes<bool>,
-}
-
-impl Regions {
- /// Create a new region sequence with exactly one region.
- pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
- Self {
- first: size,
- base,
- backlog: vec![],
- last: None,
- expand,
- }
- }
-
- /// Create a new sequence of same-size regions that repeats indefinitely.
- pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
- Self {
- first: size,
- base,
- backlog: vec![],
- last: Some(size.y),
- expand,
- }
- }
-
- /// Create new regions where all sizes are mapped with `f`.
- ///
- /// Note that since all regions must have the same width, the width returned
- /// by `f` is ignored for the backlog and the final region.
- pub fn map<F>(&self, mut f: F) -> Self
- where
- F: FnMut(Size) -> Size,
- {
- let x = self.first.x;
- Self {
- first: f(self.first),
- base: f(self.base),
- backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(),
- last: self.last.map(|y| f(Size::new(x, y)).y),
- expand: self.expand,
- }
- }
-
- /// Whether the first region is full and a region break is called for.
- pub fn is_full(&self) -> bool {
- Abs::zero().fits(self.first.y) && !self.in_last()
- }
-
- /// Whether the first region is the last usable region.
- ///
- /// If this is true, calling `next()` will have no effect.
- pub fn in_last(&self) -> bool {
- self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
- }
-
- /// Advance to the next region if there is any.
- pub fn next(&mut self) {
- if let Some(height) = (!self.backlog.is_empty())
- .then(|| self.backlog.remove(0))
- .or(self.last)
- {
- self.first.y = height;
- self.base.y = height;
- }
- }
-
- /// An iterator that returns the sizes of the first and all following
- /// regions, equivalently to what would be produced by calling
- /// [`next()`](Self::next) repeatedly until all regions are exhausted.
- /// This iterater may be infinite.
- pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
- let first = std::iter::once(self.first);
- let backlog = self.backlog.iter();
- let last = self.last.iter().cycle();
- first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
- }
-}
-
-/// Builds a document or a flow node from content.
-struct Builder<'a> {
- /// The core context.
- world: Tracked<'a, dyn World>,
- /// Scratch arenas for building.
- scratch: &'a Scratch<'a>,
- /// The current document building state.
- doc: Option<DocBuilder<'a>>,
- /// The current flow building state.
- flow: FlowBuilder<'a>,
- /// The current paragraph building state.
- par: ParBuilder<'a>,
- /// The current list building state.
- list: ListBuilder<'a>,
-}
-
-/// Temporary storage arenas for building.
-#[derive(Default)]
-struct Scratch<'a> {
- /// An arena where intermediate style chains are stored.
- styles: Arena<StyleChain<'a>>,
- /// An arena where intermediate content resulting from show rules is stored.
- templates: Arena<Content>,
-}
-
-impl<'a> Builder<'a> {
- pub fn new(
- world: Tracked<'a, dyn World>,
- scratch: &'a Scratch<'a>,
- top: bool,
- ) -> Self {
- Self {
- world,
- scratch,
- doc: top.then(|| DocBuilder::default()),
- flow: FlowBuilder::default(),
- par: ParBuilder::default(),
- list: ListBuilder::default(),
- }
- }
-
- pub fn into_doc(
- mut self,
- styles: StyleChain<'a>,
- ) -> SourceResult<(DocNode, StyleChain<'a>)> {
- self.interrupt(Interruption::Page, styles, true)?;
- let (pages, shared) = self.doc.unwrap().pages.finish();
- Ok((DocNode(pages), shared))
- }
-
- pub fn into_flow(
- mut self,
- styles: StyleChain<'a>,
- ) -> SourceResult<(FlowNode, StyleChain<'a>)> {
- self.interrupt(Interruption::Par, styles, false)?;
- let (children, shared) = self.flow.0.finish();
- Ok((FlowNode(children), shared))
- }
-
- pub fn accept(
- &mut self,
- content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- if let Some(text) = content.downcast::<TextNode>() {
- if let Some(realized) = styles.apply(self.world, Target::Text(&text.0))? {
- let stored = self.scratch.templates.alloc(realized);
- return self.accept(stored, styles);
- }
- } else if let Some(styled) = content.downcast::<StyledNode>() {
- return self.styled(styled, styles);
- } else if let Some(seq) = content.downcast::<SequenceNode>() {
- return self.sequence(seq, styles);
- } else if content.has::<dyn Show>() {
- if self.show(&content, styles)? {
- return Ok(());
- }
- }
-
- if self.list.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt(Interruption::List, styles, false)?;
-
- if content.is::<ListItem>() {
- self.list.accept(content, styles);
- return Ok(());
- }
-
- if self.par.accept(content, styles) {
- return Ok(());
- }
-
- self.interrupt(Interruption::Par, styles, false)?;
-
- if self.flow.accept(content, styles) {
- return Ok(());
- }
-
- let keep = content
- .downcast::<PagebreakNode>()
- .map_or(false, |pagebreak| !pagebreak.weak);
- self.interrupt(Interruption::Page, styles, keep)?;
-
- if let Some(doc) = &mut self.doc {
- doc.accept(content, styles);
- }
-
- // We might want to issue a warning or error for content that wasn't
- // handled (e.g. a pagebreak in a flow building process). However, we
- // don't have the spans here at the moment.
- Ok(())
- }
-
- fn show(
- &mut self,
- content: &'a Content,
- styles: StyleChain<'a>,
- ) -> SourceResult<bool> {
- if let Some(mut realized) = styles.apply(self.world, Target::Node(content))? {
- let mut map = StyleMap::new();
- let barrier = Barrier::new(content.id());
- map.push(StyleEntry::Barrier(barrier));
- map.push(StyleEntry::Barrier(barrier));
- realized = realized.styled_with_map(map);
- let stored = self.scratch.templates.alloc(realized);
- self.accept(stored, styles)?;
- Ok(true)
- } else {
- Ok(false)
- }
- }
-
- fn styled(
- &mut self,
- styled: &'a StyledNode,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- let stored = self.scratch.styles.alloc(styles);
- let styles = styled.map.chain(stored);
- let intr = styled.map.interruption();
-
- if let Some(intr) = intr {
- self.interrupt(intr, styles, false)?;
- }
-
- self.accept(&styled.sub, styles)?;
-
- if let Some(intr) = intr {
- self.interrupt(intr, styles, true)?;
- }
-
- Ok(())
- }
-
- fn interrupt(
- &mut self,
- intr: Interruption,
- styles: StyleChain<'a>,
- keep: bool,
- ) -> SourceResult<()> {
- if intr >= Interruption::List && !self.list.is_empty() {
- mem::take(&mut self.list).finish(self)?;
- }
-
- if intr >= Interruption::Par {
- if !self.par.is_empty() {
- mem::take(&mut self.par).finish(self);
- }
- }
-
- if intr >= Interruption::Page {
- if let Some(doc) = &mut self.doc {
- if !self.flow.is_empty() || (doc.keep_next && keep) {
- mem::take(&mut self.flow).finish(doc, styles);
- }
- doc.keep_next = !keep;
- }
- }
-
- Ok(())
- }
-
- fn sequence(
- &mut self,
- seq: &'a SequenceNode,
- styles: StyleChain<'a>,
- ) -> SourceResult<()> {
- for content in &seq.0 {
- self.accept(content, styles)?;
- }
- Ok(())
- }
-}
-
-/// Accepts pagebreaks and pages.
-struct DocBuilder<'a> {
- /// The page runs built so far.
- pages: StyleVecBuilder<'a, PageNode>,
- /// Whether to keep a following page even if it is empty.
- keep_next: bool,
-}
-
-impl<'a> DocBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
- if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
- self.keep_next = !pagebreak.weak;
- }
-
- if let Some(page) = content.downcast::<PageNode>() {
- self.pages.push(page.clone(), styles);
- self.keep_next = false;
- }
- }
-}
-
-impl Default for DocBuilder<'_> {
- fn default() -> Self {
- Self {
- pages: StyleVecBuilder::new(),
- keep_next: true,
- }
- }
-}
-
-/// Accepts flow content.
-#[derive(Default)]
-struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
-
-impl<'a> FlowBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- // Weak flow elements:
- // Weakness | Element
- // 0 | weak colbreak
- // 1 | weak fractional spacing
- // 2 | weak spacing
- // 3 | generated weak spacing
- // 4 | generated weak fractional spacing
- // 5 | par spacing
-
- if let Some(_) = content.downcast::<ParbreakNode>() {
- /* Nothing to do */
- } else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
- if colbreak.weak {
- self.0.weak(FlowChild::Colbreak, styles, 0);
- } else {
- self.0.destructive(FlowChild::Colbreak, styles);
- }
- } else if let Some(vertical) = content.downcast::<VNode>() {
- let child = FlowChild::Spacing(vertical.amount);
- let frac = vertical.amount.is_fractional();
- if vertical.weak {
- let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- } else if content.has::<dyn LayoutBlock>() {
- let child = FlowChild::Block(content.clone());
- if content.is::<PlaceNode>() {
- self.0.ignorant(child, styles);
- } else {
- self.0.supportive(child, styles);
- }
- } else {
- return false;
- }
-
- true
- }
-
- fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
- let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
- styles.get(ParNode::LEADING).into()
- } else {
- styles.get(ParNode::SPACING).into()
- };
-
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- self.0.supportive(FlowChild::Block(par.pack()), styles);
- self.0.weak(FlowChild::Spacing(amount), styles, 5);
- }
-
- fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
- let (flow, shared) = self.0.finish();
- let styles = if flow.is_empty() { styles } else { shared };
- let node = PageNode(FlowNode(flow).pack());
- doc.pages.push(node, styles);
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-/// Accepts paragraph content.
-#[derive(Default)]
-struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
-
-impl<'a> ParBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- // Weak par elements:
- // Weakness | Element
- // 0 | weak fractional spacing
- // 1 | weak spacing
- // 2 | space
-
- if content.is::<SpaceNode>() {
- self.0.weak(ParChild::Text(' '.into()), styles, 2);
- } else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
- let c = if linebreak.justify { '\u{2028}' } else { '\n' };
- self.0.destructive(ParChild::Text(c.into()), styles);
- } else if let Some(horizontal) = content.downcast::<HNode>() {
- let child = ParChild::Spacing(horizontal.amount);
- let frac = horizontal.amount.is_fractional();
- if horizontal.weak {
- let weakness = u8::from(!frac);
- self.0.weak(child, styles, weakness);
- } else if frac {
- self.0.destructive(child, styles);
- } else {
- self.0.ignorant(child, styles);
- }
- } else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
- self.0.supportive(ParChild::Quote { double: quote.double }, styles);
- } else if let Some(text) = content.downcast::<TextNode>() {
- self.0.supportive(ParChild::Text(text.0.clone()), styles);
- } else if content.has::<dyn LayoutInline>() {
- self.0.supportive(ParChild::Inline(content.clone()), styles);
- } else {
- return false;
- }
-
- true
- }
-
- fn finish(self, parent: &mut Builder<'a>) {
- let (mut children, shared) = self.0.finish();
- if children.is_empty() {
- return;
- }
-
- // Paragraph indent should only apply if the paragraph starts with
- // text and follows directly after another paragraph.
- let indent = shared.get(ParNode::INDENT);
- if !indent.is_zero()
- && children
- .items()
- .find_map(|child| match child {
- ParChild::Spacing(_) => None,
- ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
- ParChild::Inline(_) => Some(false),
- })
- .unwrap_or_default()
- && parent
- .flow
- .0
- .items()
- .rev()
- .find_map(|child| match child {
- FlowChild::Spacing(_) => None,
- FlowChild::Block(content) => Some(content.is::<ParNode>()),
- FlowChild::Colbreak => Some(false),
- })
- .unwrap_or_default()
- {
- children.push_front(ParChild::Spacing(indent.into()));
- }
-
- parent.flow.par(ParNode(children), shared, !indent.is_zero());
- }
-
- fn is_empty(&self) -> bool {
- self.0.is_empty()
- }
-}
-
-/// Accepts list / enum items, spaces, paragraph breaks.
-struct ListBuilder<'a> {
- /// The list items collected so far.
- items: StyleVecBuilder<'a, ListItem>,
- /// Whether the list contains no paragraph breaks.
- tight: bool,
- /// Whether the list can be attached.
- attachable: bool,
- /// Trailing content for which it is unclear whether it is part of the list.
- staged: Vec<(&'a Content, StyleChain<'a>)>,
-}
-
-impl<'a> ListBuilder<'a> {
- fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
- if self.items.is_empty() {
- if content.is::<ParbreakNode>() {
- self.attachable = false;
- } else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
- self.attachable = true;
- }
- }
-
- if let Some(item) = content.downcast::<ListItem>() {
- if self
- .items
- .items()
- .next()
- .map_or(true, |first| item.kind() == first.kind())
- {
- self.items.push(item.clone(), styles);
- self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
- } else {
- return false;
- }
- } else if !self.items.is_empty()
- && (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
- {
- self.staged.push((content, styles));
- } else {
- return false;
- }
-
- true
- }
-
- fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
- let (items, shared) = self.items.finish();
- let kind = match items.items().next() {
- Some(item) => item.kind(),
- None => return Ok(()),
- };
-
- let tight = self.tight;
- let attached = tight && self.attachable;
- let content = match kind {
- LIST => ListNode::<LIST> { tight, attached, items }.pack(),
- ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
- DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
- };
-
- let stored = parent.scratch.templates.alloc(content);
- parent.accept(stored, shared)?;
-
- for (content, styles) in self.staged {
- parent.accept(content, styles)?;
- }
-
- parent.list.attachable = true;
-
- Ok(())
- }
-
- fn is_empty(&self) -> bool {
- self.items.is_empty()
- }
-}
-
-impl Default for ListBuilder<'_> {
- fn default() -> Self {
- Self {
- items: StyleVecBuilder::default(),
- tight: true,
- attachable: true,
- staged: vec![],
- }
- }
-}
-
-/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
-struct CollapsingBuilder<'a, T> {
- /// The internal builder.
- builder: StyleVecBuilder<'a, T>,
- /// Staged weak and ignorant items that we can't yet commit to the builder.
- /// The option is `Some(_)` for weak items and `None` for ignorant items.
- staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
- /// What the last non-ignorant item was.
- last: Last,
-}
-
-/// What the last non-ignorant item was.
-#[derive(Debug, Copy, Clone, Eq, PartialEq)]
-enum Last {
- Weak,
- Destructive,
- Supportive,
-}
-
-impl<'a, T> CollapsingBuilder<'a, T> {
- /// Create a new style-vec builder.
- pub fn new() -> Self {
- Self {
- builder: StyleVecBuilder::new(),
- staged: vec![],
- last: Last::Destructive,
- }
- }
-
- /// Whether the builder is empty.
- pub fn is_empty(&self) -> bool {
- self.builder.is_empty() && self.staged.is_empty()
- }
-
- /// Can only exist when there is at least one supportive item to its left
- /// and to its right, with no destructive items in between. There may be
- /// ignorant items in between in both directions.
- ///
- /// Between weak items, there may be at least one per layer and among the
- /// candidates the strongest one (smallest `weakness`) wins. When tied,
- /// the one that compares larger through `PartialOrd` wins.
- pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
- where
- T: PartialOrd,
- {
- if self.last == Last::Destructive {
- return;
- }
-
- if self.last == Last::Weak {
- if let Some(i) =
- self.staged.iter().position(|(prev_item, _, prev_weakness)| {
- prev_weakness.map_or(false, |prev_weakness| {
- weakness < prev_weakness
- || (weakness == prev_weakness && item > *prev_item)
- })
- })
- {
- self.staged.remove(i);
- } else {
- return;
- }
- }
-
- self.staged.push((item, styles, Some(weakness)));
- self.last = Last::Weak;
- }
-
- /// Forces nearby weak items to collapse.
- pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
- self.flush(false);
- self.builder.push(item, styles);
- self.last = Last::Destructive;
- }
-
- /// Allows nearby weak items to exist.
- pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
- self.flush(true);
- self.builder.push(item, styles);
- self.last = Last::Supportive;
- }
-
- /// Has no influence on other items.
- pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
- self.staged.push((item, styles, None));
- }
-
- /// Iterate over the contained items.
- pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
- self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
- }
-
- /// Return the finish style vec and the common prefix chain.
- pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
- self.flush(false);
- self.builder.finish()
- }
-
- /// Push the staged items, filtering out weak items if `supportive` is
- /// false.
- fn flush(&mut self, supportive: bool) {
- for (item, styles, meta) in self.staged.drain(..) {
- if supportive || meta.is_none() {
- self.builder.push(item, styles);
- }
- }
- }
-}
-
-impl<'a, T> Default for CollapsingBuilder<'a, T> {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs
deleted file mode 100644
index 920660d6..00000000
--- a/src/library/layout/pad.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use crate::library::prelude::*;
-
-/// Pad content at the sides.
-#[derive(Debug, Hash)]
-pub struct PadNode {
- /// The amount of padding.
- pub padding: Sides<Rel<Length>>,
- /// The content whose sides to pad.
- pub child: Content,
-}
-
-#[node(LayoutBlock)]
-impl PadNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let all = args.named("rest")?.or(args.find()?);
- let x = args.named("x")?;
- let y = args.named("y")?;
- let left = args.named("left")?.or(x).or(all).unwrap_or_default();
- let top = args.named("top")?.or(y).or(all).unwrap_or_default();
- let right = args.named("right")?.or(x).or(all).unwrap_or_default();
- let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
- let body = args.expect::<Content>("body")?;
- let padding = Sides::new(left, top, right, bottom);
- Ok(body.padded(padding))
- }
-}
-
-impl LayoutBlock for PadNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // Layout child into padded regions.
- let padding = self.padding.resolve(styles);
- let pod = regions.map(|size| shrink(size, padding));
- let mut frames = self.child.layout_block(world, &pod, styles)?;
-
- for frame in &mut frames {
- // Apply the padding inversely such that the grown size padded
- // yields the frame's size.
- let padded = grow(frame.size(), padding);
- let padding = padding.relative_to(padded);
- let offset = Point::new(padding.left, padding.top);
-
- // Grow the frame and translate everything in the frame inwards.
- frame.set_size(padded);
- frame.translate(offset);
- }
-
- Ok(frames)
- }
-}
-
-/// Shrink a size by padding relative to the size itself.
-fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
- size - padding.relative_to(size).sum_by_axis()
-}
-
-/// Grow a size by padding relative to the grown size.
-/// This is the inverse operation to `shrink()`.
-///
-/// For the horizontal axis the derivation looks as follows.
-/// (Vertical axis is analogous.)
-///
-/// Let w be the grown target width,
-/// s be the given width,
-/// l be the left padding,
-/// r be the right padding,
-/// p = l + r.
-///
-/// We want that: w - l.resolve(w) - r.resolve(w) = s
-///
-/// Thus: w - l.resolve(w) - r.resolve(w) = s
-/// <=> w - p.resolve(w) = s
-/// <=> w - p.rel * w - p.abs = s
-/// <=> (1 - p.rel) * w = s + p.abs
-/// <=> w = (s + p.abs) / (1 - p.rel)
-fn grow(size: Size, padding: Sides<Rel<Abs>>) -> Size {
- size.zip(padding.sum_by_axis())
- .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get()))
-}
diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs
deleted file mode 100644
index 8d081749..00000000
--- a/src/library/layout/page.rs
+++ /dev/null
@@ -1,421 +0,0 @@
-use std::str::FromStr;
-
-use super::ColumnsNode;
-use crate::library::prelude::*;
-
-/// Layouts its child onto one or multiple pages.
-#[derive(PartialEq, Clone, Hash)]
-pub struct PageNode(pub Content);
-
-#[node]
-impl PageNode {
- /// The unflipped width of the page.
- #[property(resolve)]
- pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into());
- /// The unflipped height of the page.
- #[property(resolve)]
- pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height().into());
- /// Whether the page is flipped into landscape orientation.
- pub const FLIPPED: bool = false;
-
- /// The page's margins.
- #[property(fold)]
- pub const MARGINS: Sides<Option<Smart<Rel<Length>>>> = Sides::splat(Smart::Auto);
-
- /// How many columns the page has.
- pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
- /// The page's background color.
- pub const FILL: Option<Paint> = None;
-
- /// The page's header.
- #[property(referenced)]
- pub const HEADER: Marginal = Marginal::None;
- /// The page's footer.
- #[property(referenced)]
- pub const FOOTER: Marginal = Marginal::None;
- /// Content in the page's background.
- #[property(referenced)]
- pub const BACKGROUND: Marginal = Marginal::None;
- /// Content in the page's foreground.
- #[property(referenced)]
- pub const FOREGROUND: Marginal = Marginal::None;
-
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self(args.expect("body")?).pack())
- }
-
- fn set(...) {
- if let Some(paper) = args.named_or_find::<Paper>("paper")? {
- styles.set(Self::WIDTH, Smart::Custom(paper.width().into()));
- styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
- }
- }
-}
-
-impl PageNode {
- /// Layout the page run into a sequence of frames, one per page.
- pub fn layout(
- &self,
- world: Tracked<dyn World>,
- mut page: usize,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- // When one of the lengths is infinite the page fits its content along
- // that axis.
- let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf());
- let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf());
- let mut size = Size::new(width, height);
- if styles.get(Self::FLIPPED) {
- std::mem::swap(&mut size.x, &mut size.y);
- }
-
- let mut min = width.min(height);
- if !min.is_finite() {
- min = Paper::A4.width();
- }
-
- // Determine the margins.
- let default = Rel::from(0.1190 * min);
- let padding = styles.get(Self::MARGINS).map(|side| side.unwrap_or(default));
-
- let mut child = self.0.clone();
-
- // Realize columns.
- let columns = styles.get(Self::COLUMNS);
- if columns.get() > 1 {
- child = ColumnsNode { columns, child: self.0.clone() }.pack();
- }
-
- // 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 regions = Regions::repeat(size, size, size.map(Abs::is_finite));
- let mut frames = child.layout_block(world, &regions, styles)?;
-
- let header = styles.get(Self::HEADER);
- let footer = styles.get(Self::FOOTER);
- let foreground = styles.get(Self::FOREGROUND);
- let background = styles.get(Self::BACKGROUND);
-
- // Realize overlays.
- for frame in &mut frames {
- let size = frame.size();
- let pad = padding.resolve(styles).relative_to(size);
- let pw = size.x - pad.left - pad.right;
- let py = size.y - pad.bottom;
- for (role, marginal, pos, area) in [
- (
- Role::Header,
- header,
- Point::with_x(pad.left),
- Size::new(pw, pad.top),
- ),
- (
- Role::Footer,
- footer,
- Point::new(pad.left, py),
- Size::new(pw, pad.bottom),
- ),
- (Role::Foreground, foreground, Point::zero(), size),
- (Role::Background, background, Point::zero(), size),
- ] {
- if let Some(content) = marginal.resolve(world, page)? {
- let pod = Regions::one(area, area, Axes::splat(true));
- let mut sub = content.layout_block(world, &pod, styles)?.remove(0);
- sub.apply_role(role);
-
- if role == Role::Background {
- frame.prepend_frame(pos, sub);
- } else {
- frame.push_frame(pos, sub);
- }
- }
- }
-
- page += 1;
- }
-
- Ok(frames)
- }
-}
-
-impl Debug for PageNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.write_str("Page(")?;
- self.0.fmt(f)?;
- f.write_str(")")
- }
-}
-
-/// A page break.
-#[derive(Debug, Copy, Clone, Hash)]
-pub struct PagebreakNode {
- pub weak: bool,
-}
-
-#[node]
-impl PagebreakNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let weak = args.named("weak")?.unwrap_or(false);
- Ok(Self { weak }.pack())
- }
-}
-
-/// A header, footer, foreground or background definition.
-#[derive(Debug, Clone, PartialEq, Hash)]
-pub enum Marginal {
- /// Nothing,
- None,
- /// Bare content.
- Content(Content),
- /// A closure mapping from a page number to content.
- Func(Func, Span),
-}
-
-impl Marginal {
- /// Resolve the marginal based on the page number.
- pub fn resolve(
- &self,
- world: Tracked<dyn World>,
- page: usize,
- ) -> SourceResult<Option<Content>> {
- Ok(match self {
- Self::None => None,
- Self::Content(content) => Some(content.clone()),
- Self::Func(func, span) => {
- let args = Args::new(*span, [Value::Int(page as i64)]);
- Some(func.call_detached(world, args)?.display(world))
- }
- })
- }
-}
-
-impl Cast<Spanned<Value>> for Marginal {
- fn is(value: &Spanned<Value>) -> bool {
- matches!(&value.v, Value::Content(_) | Value::Func(_))
- }
-
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- match value.v {
- Value::None => Ok(Self::None),
- Value::Str(v) => Ok(Self::Content(TextNode(v.into()).pack())),
- Value::Content(v) => Ok(Self::Content(v)),
- Value::Func(v) => Ok(Self::Func(v, value.span)),
- v => Err(format!(
- "expected none, content or function, found {}",
- v.type_name(),
- )),
- }
- }
-}
-
-/// Specification of a paper.
-#[derive(Debug, Copy, Clone)]
-pub struct Paper {
- /// The width of the paper in millimeters.
- width: f64,
- /// The height of the paper in millimeters.
- height: f64,
-}
-
-impl Paper {
- /// The width of the paper.
- pub fn width(self) -> Abs {
- Abs::mm(self.width)
- }
-
- /// The height of the paper.
- pub fn height(self) -> Abs {
- Abs::mm(self.height)
- }
-}
-
-/// Defines paper constants and a paper parsing implementation.
-macro_rules! papers {
- ($(($var:ident: $width:expr, $height: expr, $($pats:tt)*))*) => {
- /// Predefined papers.
- ///
- /// Each paper is parsable from its name in kebab-case.
- impl Paper {
- $(pub const $var: Self = Self { width: $width, height: $height };)*
- }
-
- impl FromStr for Paper {
- type Err = &'static str;
-
- fn from_str(name: &str) -> Result<Self, Self::Err> {
- match name.to_lowercase().as_str() {
- $($($pats)* => Ok(Self::$var),)*
- _ => Err("invalid paper name"),
- }
- }
- }
- };
-}
-
-castable! {
- Paper,
- Expected: "string",
- Value::Str(string) => Self::from_str(&string)?,
-}
-
-// All paper sizes in mm.
-//
-// Resources:
-// - https://papersizes.io/
-// - https://en.wikipedia.org/wiki/Paper_size
-// - https://www.theedkins.co.uk/jo/units/oldunits/print.htm
-// - https://vintagepaper.co/blogs/news/traditional-paper-sizes
-papers! {
- // ---------------------------------------------------------------------- //
- // ISO 216 A Series
- (A0: 841.0, 1189.0, "a0")
- (A1: 594.0, 841.0, "a1")
- (A2: 420.0, 594.0, "a2")
- (A3: 297.0, 420.0, "a3")
- (A4: 210.0, 297.0, "a4")
- (A5: 148.0, 210.0, "a5")
- (A6: 105.0, 148.0, "a6")
- (A7: 74.0, 105.0, "a7")
- (A8: 52.0, 74.0, "a8")
- (A9: 37.0, 52.0, "a9")
- (A10: 26.0, 37.0, "a10")
- (A11: 18.0, 26.0, "a11")
-
- // ISO 216 B Series
- (ISO_B1: 707.0, 1000.0, "iso-b1")
- (ISO_B2: 500.0, 707.0, "iso-b2")
- (ISO_B3: 353.0, 500.0, "iso-b3")
- (ISO_B4: 250.0, 353.0, "iso-b4")
- (ISO_B5: 176.0, 250.0, "iso-b5")
- (ISO_B6: 125.0, 176.0, "iso-b6")
- (ISO_B7: 88.0, 125.0, "iso-b7")
- (ISO_B8: 62.0, 88.0, "iso-b8")
-
- // ISO 216 C Series
- (ISO_C3: 324.0, 458.0, "iso-c3")
- (ISO_C4: 229.0, 324.0, "iso-c4")
- (ISO_C5: 162.0, 229.0, "iso-c5")
- (ISO_C6: 114.0, 162.0, "iso-c6")
- (ISO_C7: 81.0, 114.0, "iso-c7")
- (ISO_C8: 57.0, 81.0, "iso-c8")
-
- // DIN D Series (extension to ISO)
- (DIN_D3: 272.0, 385.0, "din-d3")
- (DIN_D4: 192.0, 272.0, "din-d4")
- (DIN_D5: 136.0, 192.0, "din-d5")
- (DIN_D6: 96.0, 136.0, "din-d6")
- (DIN_D7: 68.0, 96.0, "din-d7")
- (DIN_D8: 48.0, 68.0, "din-d8")
-
- // SIS (used in academia)
- (SIS_G5: 169.0, 239.0, "sis-g5")
- (SIS_E5: 115.0, 220.0, "sis-e5")
-
- // ANSI Extensions
- (ANSI_A: 216.0, 279.0, "ansi-a")
- (ANSI_B: 279.0, 432.0, "ansi-b")
- (ANSI_C: 432.0, 559.0, "ansi-c")
- (ANSI_D: 559.0, 864.0, "ansi-d")
- (ANSI_E: 864.0, 1118.0, "ansi-e")
-
- // ANSI Architectural Paper
- (ARCH_A: 229.0, 305.0, "arch-a")
- (ARCH_B: 305.0, 457.0, "arch-b")
- (ARCH_C: 457.0, 610.0, "arch-c")
- (ARCH_D: 610.0, 914.0, "arch-d")
- (ARCH_E1: 762.0, 1067.0, "arch-e1")
- (ARCH_E: 914.0, 1219.0, "arch-e")
-
- // JIS B Series
- (JIS_B0: 1030.0, 1456.0, "jis-b0")
- (JIS_B1: 728.0, 1030.0, "jis-b1")
- (JIS_B2: 515.0, 728.0, "jis-b2")
- (JIS_B3: 364.0, 515.0, "jis-b3")
- (JIS_B4: 257.0, 364.0, "jis-b4")
- (JIS_B5: 182.0, 257.0, "jis-b5")
- (JIS_B6: 128.0, 182.0, "jis-b6")
- (JIS_B7: 91.0, 128.0, "jis-b7")
- (JIS_B8: 64.0, 91.0, "jis-b8")
- (JIS_B9: 45.0, 64.0, "jis-b9")
- (JIS_B10: 32.0, 45.0, "jis-b10")
- (JIS_B11: 22.0, 32.0, "jis-b11")
-
- // SAC D Series
- (SAC_D0: 764.0, 1064.0, "sac-d0")
- (SAC_D1: 532.0, 760.0, "sac-d1")
- (SAC_D2: 380.0, 528.0, "sac-d2")
- (SAC_D3: 264.0, 376.0, "sac-d3")
- (SAC_D4: 188.0, 260.0, "sac-d4")
- (SAC_D5: 130.0, 184.0, "sac-d5")
- (SAC_D6: 92.0, 126.0, "sac-d6")
-
- // ISO 7810 ID
- (ISO_ID_1: 85.6, 53.98, "iso-id-1")
- (ISO_ID_2: 74.0, 105.0, "iso-id-2")
- (ISO_ID_3: 88.0, 125.0, "iso-id-3")
-
- // ---------------------------------------------------------------------- //
- // Asia
- (ASIA_F4: 210.0, 330.0, "asia-f4")
-
- // Japan
- (JP_SHIROKU_BAN_4: 264.0, 379.0, "jp-shiroku-ban-4")
- (JP_SHIROKU_BAN_5: 189.0, 262.0, "jp-shiroku-ban-5")
- (JP_SHIROKU_BAN_6: 127.0, 188.0, "jp-shiroku-ban-6")
- (JP_KIKU_4: 227.0, 306.0, "jp-kiku-4")
- (JP_KIKU_5: 151.0, 227.0, "jp-kiku-5")
- (JP_BUSINESS_CARD: 91.0, 55.0, "jp-business-card")
-
- // China
- (CN_BUSINESS_CARD: 90.0, 54.0, "cn-business-card")
-
- // Europe
- (EU_BUSINESS_CARD: 85.0, 55.0, "eu-business-card")
-
- // French Traditional (AFNOR)
- (FR_TELLIERE: 340.0, 440.0, "fr-tellière")
- (FR_COURONNE_ECRITURE: 360.0, 460.0, "fr-couronne-écriture")
- (FR_COURONNE_EDITION: 370.0, 470.0, "fr-couronne-édition")
- (FR_RAISIN: 500.0, 650.0, "fr-raisin")
- (FR_CARRE: 450.0, 560.0, "fr-carré")
- (FR_JESUS: 560.0, 760.0, "fr-jésus")
-
- // United Kingdom Imperial
- (UK_BRIEF: 406.4, 342.9, "uk-brief")
- (UK_DRAFT: 254.0, 406.4, "uk-draft")
- (UK_FOOLSCAP: 203.2, 330.2, "uk-foolscap")
- (UK_QUARTO: 203.2, 254.0, "uk-quarto")
- (UK_CROWN: 508.0, 381.0, "uk-crown")
- (UK_BOOK_A: 111.0, 178.0, "uk-book-a")
- (UK_BOOK_B: 129.0, 198.0, "uk-book-b")
-
- // Unites States
- (US_LETTER: 215.9, 279.4, "us-letter")
- (US_LEGAL: 215.9, 355.6, "us-legal")
- (US_TABLOID: 279.4, 431.8, "us-tabloid")
- (US_EXECUTIVE: 84.15, 266.7, "us-executive")
- (US_FOOLSCAP_FOLIO: 215.9, 342.9, "us-foolscap-folio")
- (US_STATEMENT: 139.7, 215.9, "us-statement")
- (US_LEDGER: 431.8, 279.4, "us-ledger")
- (US_OFICIO: 215.9, 340.36, "us-oficio")
- (US_GOV_LETTER: 203.2, 266.7, "us-gov-letter")
- (US_GOV_LEGAL: 215.9, 330.2, "us-gov-legal")
- (US_BUSINESS_CARD: 88.9, 50.8, "us-business-card")
- (US_DIGEST: 139.7, 215.9, "us-digest")
- (US_TRADE: 152.4, 228.6, "us-trade")
-
- // ---------------------------------------------------------------------- //
- // Other
- (NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact")
- (NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner")
- (NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet")
- (PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9")
- (PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3")
-}
diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs
deleted file mode 100644
index ee38ebe6..00000000
--- a/src/library/layout/place.rs
+++ /dev/null
@@ -1,56 +0,0 @@
-use super::AlignNode;
-use crate::library::prelude::*;
-
-/// Place content at an absolute position.
-#[derive(Debug, Hash)]
-pub struct PlaceNode(pub Content);
-
-#[node(LayoutBlock)]
-impl PlaceNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
- let dx = args.named("dx")?.unwrap_or_default();
- let dy = args.named("dy")?.unwrap_or_default();
- let body = args.expect::<Content>("body")?;
- Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns)).pack())
- }
-}
-
-impl LayoutBlock for PlaceNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let out_of_flow = self.out_of_flow();
-
- // The pod is the base area of the region because for absolute
- // placement we don't really care about the already used area.
- let pod = {
- let finite = regions.base.map(Abs::is_finite);
- let expand = finite & (regions.expand | out_of_flow);
- Regions::one(regions.base, regions.base, expand)
- };
-
- let mut frames = self.0.layout_block(world, &pod, styles)?;
-
- // If expansion is off, zero all sizes so that we don't take up any
- // space in our parent. Otherwise, respect the expand settings.
- let target = regions.expand.select(regions.first, Size::zero());
- frames[0].resize(target, Align::LEFT_TOP);
-
- Ok(frames)
- }
-}
-
-impl PlaceNode {
- /// Whether this node wants to be placed relative to its its parent's base
- /// origin. Instead of relative to the parent's current flow/cursor
- /// position.
- pub fn out_of_flow(&self) -> bool {
- self.0
- .downcast::<AlignNode>()
- .map_or(false, |node| node.aligns.y.is_some())
- }
-}
diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs
deleted file mode 100644
index c410eee7..00000000
--- a/src/library/layout/spacing.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-use std::cmp::Ordering;
-
-use crate::library::prelude::*;
-use crate::library::text::ParNode;
-
-/// Horizontal spacing.
-#[derive(Debug, Clone, Hash)]
-pub struct HNode {
- pub amount: Spacing,
- pub weak: bool,
-}
-
-#[node]
-impl HNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let amount = args.expect("spacing")?;
- let weak = args.named("weak")?.unwrap_or(false);
- Ok(Self { amount, weak }.pack())
- }
-}
-
-/// Vertical spacing.
-#[derive(Debug, Clone, Hash)]
-pub struct VNode {
- pub amount: Spacing,
- pub weak: bool,
- pub generated: bool,
-}
-
-#[node]
-impl VNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let amount = args.expect("spacing")?;
- let weak = args.named("weak")?.unwrap_or(false);
- Ok(Self { amount, weak, generated: false }.pack())
- }
-}
-
-/// Kinds of spacing.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Spacing {
- /// Spacing specified in absolute terms and relative to the parent's size.
- Relative(Rel<Length>),
- /// Spacing specified as a fraction of the remaining free space in the
- /// parent.
- Fractional(Fr),
-}
-
-impl Spacing {
- /// Whether this is fractional spacing.
- pub fn is_fractional(self) -> bool {
- matches!(self, Self::Fractional(_))
- }
-}
-
-impl From<Abs> for Spacing {
- fn from(abs: Abs) -> Self {
- Self::Relative(abs.into())
- }
-}
-
-impl PartialOrd for Spacing {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- match (self, other) {
- (Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b),
- (Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b),
- _ => None,
- }
- }
-}
-
-castable! {
- Spacing,
- Expected: "relative length or fraction",
- Value::Length(v) => Self::Relative(v.into()),
- Value::Ratio(v) => Self::Relative(v.into()),
- Value::Relative(v) => Self::Relative(v),
- Value::Fraction(v) => Self::Fractional(v),
-}
-
-/// Spacing around and between blocks, relative to paragraph spacing.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct BlockSpacing(Rel<Length>);
-
-castable!(BlockSpacing: Rel<Length>);
-
-impl Resolve for BlockSpacing {
- type Output = Abs;
-
- fn resolve(self, styles: StyleChain) -> Self::Output {
- let whole = styles.get(ParNode::SPACING);
- self.0.resolve(styles).relative_to(whole)
- }
-}
-
-impl From<Ratio> for BlockSpacing {
- fn from(ratio: Ratio) -> Self {
- Self(ratio.into())
- }
-}
diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs
deleted file mode 100644
index e1e70de9..00000000
--- a/src/library/layout/stack.rs
+++ /dev/null
@@ -1,321 +0,0 @@
-use super::{AlignNode, Spacing};
-use crate::library::prelude::*;
-use crate::library::text::ParNode;
-use crate::model::StyledNode;
-
-/// Arrange content and spacing along an axis.
-#[derive(Debug, Hash)]
-pub struct StackNode {
- /// The stacking direction.
- pub dir: Dir,
- /// The spacing between non-spacing children.
- pub spacing: Option<Spacing>,
- /// The children to be stacked.
- pub children: Vec<StackChild>,
-}
-
-#[node(LayoutBlock)]
-impl StackNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self {
- dir: args.named("dir")?.unwrap_or(Dir::TTB),
- spacing: args.named("spacing")?,
- children: args.all()?,
- }
- .pack())
- }
-}
-
-impl LayoutBlock for StackNode {
- fn layout_block(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let mut layouter = StackLayouter::new(self.dir, regions, styles);
-
- // Spacing to insert before the next block.
- let mut deferred = None;
-
- for child in &self.children {
- match child {
- StackChild::Spacing(kind) => {
- layouter.layout_spacing(*kind);
- deferred = None;
- }
- StackChild::Block(block) => {
- if let Some(kind) = deferred {
- layouter.layout_spacing(kind);
- }
-
- layouter.layout_block(world, block, styles)?;
- deferred = self.spacing;
- }
- }
- }
-
- Ok(layouter.finish())
- }
-}
-
-/// A child of a stack node.
-#[derive(Hash)]
-pub enum StackChild {
- /// Spacing between other children.
- Spacing(Spacing),
- /// Arbitrary block-level content.
- Block(Content),
-}
-
-impl Debug for StackChild {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Spacing(kind) => kind.fmt(f),
- Self::Block(block) => block.fmt(f),
- }
- }
-}
-
-castable! {
- StackChild,
- Expected: "relative length, fraction, or content",
- Value::Length(v) => Self::Spacing(Spacing::Relative(v.into())),
- Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())),
- Value::Relative(v) => Self::Spacing(Spacing::Relative(v)),
- Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)),
- Value::Content(v) => Self::Block(v),
-}
-
-/// Performs stack layout.
-pub struct StackLayouter<'a> {
- /// The stacking direction.
- dir: Dir,
- /// The axis of the stacking direction.
- axis: Axis,
- /// The regions to layout children into.
- regions: Regions,
- /// The inherited styles.
- styles: StyleChain<'a>,
- /// Whether the stack itself should expand to fill the region.
- expand: Axes<bool>,
- /// The full size of the current region that was available at the start.
- full: Size,
- /// The generic size used by the frames for the current region.
- used: Gen<Abs>,
- /// The sum of fractions in the current region.
- fr: Fr,
- /// Already layouted items whose exact positions are not yet known due to
- /// fractional spacing.
- items: Vec<StackItem>,
- /// Finished frames for previous regions.
- finished: Vec<Frame>,
-}
-
-/// A prepared item in a stack layout.
-enum StackItem {
- /// Absolute spacing between other items.
- Absolute(Abs),
- /// Fractional spacing between other items.
- Fractional(Fr),
- /// A frame for a layouted block.
- Frame(Frame, Align),
-}
-
-impl<'a> StackLayouter<'a> {
- /// Create a new stack layouter.
- pub fn new(dir: Dir, regions: &Regions, styles: StyleChain<'a>) -> Self {
- let axis = dir.axis();
- let expand = regions.expand;
- let full = regions.first;
-
- // Disable expansion along the block axis for children.
- let mut regions = regions.clone();
- regions.expand.set(axis, false);
-
- Self {
- dir,
- axis,
- regions,
- styles,
- expand,
- full,
- used: Gen::zero(),
- fr: Fr::zero(),
- items: vec![],
- finished: vec![],
- }
- }
-
- /// Add spacing along the spacing direction.
- pub fn layout_spacing(&mut self, spacing: Spacing) {
- match spacing {
- Spacing::Relative(v) => {
- // Resolve the spacing and limit it to the remaining space.
- let resolved =
- v.resolve(self.styles).relative_to(self.regions.base.get(self.axis));
- let remaining = self.regions.first.get_mut(self.axis);
- let limited = resolved.min(*remaining);
- *remaining -= limited;
- self.used.main += limited;
- self.items.push(StackItem::Absolute(resolved));
- }
- Spacing::Fractional(v) => {
- self.fr += v;
- self.items.push(StackItem::Fractional(v));
- }
- }
- }
-
- /// Layout an arbitrary block.
- pub fn layout_block(
- &mut self,
- world: Tracked<dyn World>,
- block: &Content,
- styles: StyleChain,
- ) -> SourceResult<()> {
- if self.regions.is_full() {
- self.finish_region();
- }
-
- // Block-axis alignment of the `AlignNode` is respected
- // by the stack node.
- let align = block
- .downcast::<AlignNode>()
- .and_then(|node| node.aligns.get(self.axis))
- .map(|align| align.resolve(styles))
- .unwrap_or_else(|| {
- if let Some(styled) = block.downcast::<StyledNode>() {
- let map = &styled.map;
- if map.contains(ParNode::ALIGN) {
- return StyleChain::with_root(map).get(ParNode::ALIGN);
- }
- }
-
- self.dir.start().into()
- });
-
- let frames = block.layout_block(world, &self.regions, styles)?;
- let len = frames.len();
- for (i, mut frame) in frames.into_iter().enumerate() {
- // Set the generic block role.
- frame.apply_role(Role::GenericBlock);
-
- // Grow our size, shrink the region and save the frame for later.
- let size = frame.size();
- let size = match self.axis {
- Axis::X => Gen::new(size.y, size.x),
- Axis::Y => Gen::new(size.x, size.y),
- };
-
- self.used.main += size.main;
- self.used.cross.set_max(size.cross);
- *self.regions.first.get_mut(self.axis) -= size.main;
- self.items.push(StackItem::Frame(frame, align));
-
- if i + 1 < len {
- self.finish_region();
- }
- }
-
- Ok(())
- }
-
- /// Advance to the next region.
- pub fn finish_region(&mut self) {
- // Determine the size of the stack in this region dependening on whether
- // the region expands.
- let used = self.used.to_axes(self.axis);
- let mut size = self.expand.select(self.full, used);
-
- // Expand fully if there are fr spacings.
- let full = self.full.get(self.axis);
- let remaining = full - self.used.main;
- if self.fr.get() > 0.0 && full.is_finite() {
- self.used.main = full;
- size.set(self.axis, full);
- }
-
- let mut output = Frame::new(size);
- let mut cursor = Abs::zero();
- let mut ruler: Align = self.dir.start().into();
-
- // Place all frames.
- for item in self.items.drain(..) {
- match item {
- StackItem::Absolute(v) => cursor += v,
- StackItem::Fractional(v) => cursor += v.share(self.fr, remaining),
- StackItem::Frame(frame, align) => {
- if self.dir.is_positive() {
- ruler = ruler.max(align);
- } else {
- ruler = ruler.min(align);
- }
-
- // Align along the block axis.
- let parent = size.get(self.axis);
- let child = frame.size().get(self.axis);
- let block = ruler.position(parent - self.used.main)
- + if self.dir.is_positive() {
- cursor
- } else {
- self.used.main - child - cursor
- };
-
- let pos = Gen::new(Abs::zero(), block).to_point(self.axis);
- cursor += child;
- output.push_frame(pos, frame);
- }
- }
- }
-
- // Advance to the next region.
- self.regions.next();
- self.full = self.regions.first;
- self.used = Gen::zero();
- self.fr = Fr::zero();
- self.finished.push(output);
- }
-
- /// Finish layouting and return the resulting frames.
- pub fn finish(mut self) -> Vec<Frame> {
- self.finish_region();
- self.finished
- }
-}
-
-/// A container with a main and cross component.
-#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct Gen<T> {
- /// The main component.
- pub cross: T,
- /// The cross component.
- pub main: T,
-}
-
-impl<T> Gen<T> {
- /// Create a new instance from the two components.
- pub const fn new(cross: T, main: T) -> Self {
- Self { cross, main }
- }
-
- /// Convert to the specific representation, given the current main axis.
- pub fn to_axes(self, main: Axis) -> Axes<T> {
- match main {
- Axis::X => Axes::new(self.main, self.cross),
- Axis::Y => Axes::new(self.cross, self.main),
- }
- }
-}
-
-impl Gen<Abs> {
- /// The zero value.
- pub fn zero() -> Self {
- Self { cross: Abs::zero(), main: Abs::zero() }
- }
-
- /// Convert to a point.
- pub fn to_point(self, main: Axis) -> Point {
- self.to_axes(main).to_point()
- }
-}
diff --git a/src/library/layout/transform.rs b/src/library/layout/transform.rs
deleted file mode 100644
index a73a1827..00000000
--- a/src/library/layout/transform.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use crate::geom::Transform;
-use crate::library::prelude::*;
-
-/// Move content without affecting layout.
-#[derive(Debug, Hash)]
-pub struct MoveNode {
- /// The offset by which to move the content.
- pub delta: Axes<Rel<Length>>,
- /// The content that should be moved.
- pub child: Content,
-}
-
-#[node(LayoutInline)]
-impl MoveNode {
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let dx = args.named("dx")?.unwrap_or_default();
- let dy = args.named("dy")?.unwrap_or_default();
- Ok(Self {
- delta: Axes::new(dx, dy),
- child: args.expect("body")?,
- }
- .pack())
- }
-}
-
-impl LayoutInline for MoveNode {
- fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let mut frames = self.child.layout_inline(world, regions, styles)?;
-
- let delta = self.delta.resolve(styles);
- for frame in &mut frames {
- let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s));
- frame.translate(delta.to_point());
- }
-
- Ok(frames)
- }
-}
-
-/// Transform content without affecting layout.
-#[derive(Debug, Hash)]
-pub struct TransformNode<const T: TransformKind> {
- /// Transformation to apply to the content.
- pub transform: Transform,
- /// The content that should be transformed.
- pub child: Content,
-}
-
-/// Rotate content without affecting layout.
-pub type RotateNode = TransformNode<ROTATE>;
-
-/// Scale content without affecting layout.
-pub type ScaleNode = TransformNode<SCALE>;
-
-#[node(LayoutInline)]
-impl<const T: TransformKind> TransformNode<T> {
- /// The origin of the transformation.
- #[property(resolve)]
- pub const ORIGIN: Axes<Option<RawAlign>> = Axes::default();
-
- fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
- let transform = match T {
- ROTATE => {
- let angle = args.named_or_find("angle")?.unwrap_or_default();
- Transform::rotate(angle)
- }
- SCALE | _ => {
- let all = args.find()?;
- let sx = args.named("x")?.or(all).unwrap_or(Ratio::one());
- let sy = args.named("y")?.or(all).unwrap_or(Ratio::one());
- Transform::scale(sx, sy)
- }
- };
-
- Ok(Self { transform, child: args.expect("body")? }.pack())
- }
-}
-
-impl<const T: TransformKind> LayoutInline for TransformNode<T> {
- fn layout_inline(
- &self,
- world: Tracked<dyn World>,
- regions: &Regions,
- styles: StyleChain,
- ) -> SourceResult<Vec<Frame>> {
- let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
- let mut frames = self.child.layout_inline(world, regions, styles)?;
-
- for frame in &mut frames {
- let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
- let transform = Transform::translate(x, y)
- .pre_concat(self.transform)
- .pre_concat(Transform::translate(-x, -y));
-
- frame.transform(transform);
- }
-
- Ok(frames)
- }
-}
-
-/// Kinds of transformations.
-///
-/// The move transformation is handled separately.
-pub type TransformKind = usize;
-
-/// A rotational transformation.
-const ROTATE: TransformKind = 1;
-
-/// A scale transformation.
-const SCALE: TransformKind = 2;