summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-16 10:41:30 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-16 10:41:30 +0100
commit0e0f340502beada1cd9ee23857f48b91a0d11a90 (patch)
treee4e4087260720adf4bab57edeac6a3d3cb5f14cf /src
parentbc118634aca5de415d211cab38c4eaa3d3cca25f (diff)
Revert page and inline levels
Diffstat (limited to 'src')
-rw-r--r--src/eval/template.rs27
-rw-r--r--src/eval/walk.rs2
-rw-r--r--src/export/pdf.rs4
-rw-r--r--src/layout/constraints.rs14
-rw-r--r--src/layout/levels.rs199
-rw-r--r--src/layout/mod.rs109
-rw-r--r--src/lib.rs13
-rw-r--r--src/library/container.rs3
-rw-r--r--src/library/document.rs16
-rw-r--r--src/library/flow.rs10
-rw-r--r--src/library/grid.rs8
-rw-r--r--src/library/image.rs23
-rw-r--r--src/library/mod.rs2
-rw-r--r--src/library/pad.rs6
-rw-r--r--src/library/page.rs20
-rw-r--r--src/library/par.rs17
-rw-r--r--src/library/shape.rs44
-rw-r--r--src/library/stack.rs8
-rw-r--r--src/library/transform.rs40
-rw-r--r--src/main.rs4
-rw-r--r--src/source.rs2
21 files changed, 277 insertions, 294 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 8fec398d..82a069f9 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -7,9 +7,10 @@ use std::rc::Rc;
use crate::diag::StrResult;
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
-use crate::layout::{BlockLevel, BlockNode, InlineLevel, InlineNode, PageNode};
+use crate::layout::{Layout, PackedNode};
use crate::library::{
- Decoration, FlowChild, FlowNode, PadNode, ParChild, ParNode, Spacing,
+ Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode,
+ Spacing,
};
use crate::style::Style;
use crate::util::EcoString;
@@ -36,9 +37,9 @@ enum TemplateNode {
/// A decorated template.
Decorated(Decoration, Template),
/// An inline node builder.
- Inline(Rc<dyn Fn(&Style) -> InlineNode>),
- /// An block node builder.
- Block(Rc<dyn Fn(&Style) -> BlockNode>),
+ Inline(Rc<dyn Fn(&Style) -> PackedNode>),
+ /// A block node builder.
+ Block(Rc<dyn Fn(&Style) -> PackedNode>),
/// Save the current style.
Save,
/// Restore the last saved style.
@@ -57,7 +58,7 @@ impl Template {
pub fn from_inline<F, T>(f: F) -> Self
where
F: Fn(&Style) -> T + 'static,
- T: InlineLevel + Hash + 'static,
+ T: Layout + Hash + 'static,
{
let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack()));
Self(Rc::new(vec![node]))
@@ -67,7 +68,7 @@ impl Template {
pub fn from_block<F, T>(f: F) -> Self
where
F: Fn(&Style) -> T + 'static,
- T: BlockLevel + Hash + 'static,
+ T: Layout + Hash + 'static,
{
let node = TemplateNode::Block(Rc::new(move |s| f(s).pack()));
Self(Rc::new(vec![node]))
@@ -158,10 +159,10 @@ impl Template {
/// Build the layout tree resulting from instantiating the template with the
/// given style.
- pub fn to_pages(&self, style: &Style) -> Vec<PageNode> {
+ pub fn to_document(&self, style: &Style) -> DocumentNode {
let mut builder = Builder::new(style, true);
builder.template(self);
- builder.build_pages()
+ builder.build_document()
}
/// Repeat this template `n` times.
@@ -327,13 +328,13 @@ impl Builder {
}
/// Push an inline node into the active paragraph.
- fn inline(&mut self, node: impl Into<InlineNode>) {
+ fn inline(&mut self, node: impl Into<PackedNode>) {
let align = self.style.aligns.inline;
self.flow.par.push(ParChild::Node(node.into(), align));
}
/// Push a block node into the active flow, finishing the active paragraph.
- fn block(&mut self, node: impl Into<BlockNode>) {
+ fn block(&mut self, node: impl Into<PackedNode>) {
self.parbreak();
self.flow.push(FlowChild::Node(node.into(), self.style.aligns.block));
self.parbreak();
@@ -359,10 +360,10 @@ impl Builder {
}
/// Finish building and return the created layout tree.
- fn build_pages(mut self) -> Vec<PageNode> {
+ fn build_document(mut self) -> DocumentNode {
assert!(self.page.is_some());
self.pagebreak(true, false);
- self.finished
+ DocumentNode { pages: self.finished }
}
/// Construct a text node with the given text and settings from the current
diff --git a/src/eval/walk.rs b/src/eval/walk.rs
index 7b3fb7a4..134f10c7 100644
--- a/src/eval/walk.rs
+++ b/src/eval/walk.rs
@@ -3,7 +3,7 @@ use std::rc::Rc;
use super::{Eval, EvalContext, Template, Value};
use crate::diag::TypResult;
use crate::geom::Spec;
-use crate::layout::BlockLevel;
+use crate::layout::Layout;
use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
use crate::syntax::ast::*;
use crate::util::{BoolExt, EcoString};
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index d517aadf..24944246 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -19,13 +19,13 @@ use crate::geom::{self, Color, Em, Length, Paint, Size};
use crate::image::{Image, ImageId, ImageStore};
use crate::Context;
-/// Export a collection of frames into a PDF document.
+/// Export a collection of frames into a PDF file.
///
/// This creates one page per frame. In addition to the frames, you need to pass
/// in the context used during compilation such that things like fonts and
/// images can be included in the PDF.
///
-/// Returns the raw bytes making up the PDF document.
+/// Returns the raw bytes making up the PDF file.
pub fn pdf(ctx: &Context, frames: &[Rc<Frame>]) -> Vec<u8> {
PdfExporter::new(ctx, frames).write()
}
diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs
index fdcda276..36cfa582 100644
--- a/src/layout/constraints.rs
+++ b/src/layout/constraints.rs
@@ -1,7 +1,7 @@
use std::rc::Rc;
use crate::frame::Frame;
-use crate::geom::{Length, Size, Spec};
+use crate::geom::{Length, Linear, Size, Spec};
/// Constrain a frame with constraints.
pub trait Constrain {
@@ -68,6 +68,18 @@ impl Constraints {
&& verify(self.exact, current, Length::approx_eq)
&& verify(self.base, base, Length::approx_eq)
}
+
+ /// Set the appropriate base constraints for linear width and height sizing.
+ pub fn set_base_if_linear(&mut self, base: Size, sizing: Spec<Option<Linear>>) {
+ // The full sizes need to be equal if there is a relative component in
+ // the sizes.
+ if sizing.x.map_or(false, |l| l.is_relative()) {
+ self.base.x = Some(base.w);
+ }
+ if sizing.y.map_or(false, |l| l.is_relative()) {
+ self.base.y = Some(base.h);
+ }
+ }
}
/// Verify a single constraint.
diff --git a/src/layout/levels.rs b/src/layout/levels.rs
deleted file mode 100644
index a6b8d050..00000000
--- a/src/layout/levels.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-use std::fmt::{self, Debug, Formatter};
-use std::hash::{Hash, Hasher};
-use std::rc::Rc;
-
-use super::*;
-use crate::geom::{Length, Size};
-
-/// Page-level nodes directly produce frames representing pages.
-///
-/// Such nodes create their own regions instead of being supplied with them from
-/// some parent.
-pub trait PageLevel: Debug {
- /// Layout the node, producing one frame per page.
- fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>>;
-}
-
-/// Layouts its children onto one or multiple pages.
-#[derive(Debug)]
-pub struct PageNode {
- /// The size of the page.
- pub size: Size,
- /// The node that produces the actual pages.
- pub child: BlockNode,
-}
-
-impl PageLevel for PageNode {
- fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
- // When one of the lengths is infinite the page fits its content along
- // that axis.
- let expand = self.size.to_spec().map(Length::is_finite);
- let regions = Regions::repeat(self.size, self.size, expand);
- self.child.layout(ctx, &regions).into_iter().map(|c| c.item).collect()
- }
-}
-
-impl<T> PageLevel for T
-where
- T: AsRef<[PageNode]> + Debug + ?Sized,
-{
- fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
- self.as_ref().iter().flat_map(|node| node.layout(ctx)).collect()
- }
-}
-
-/// Block-level nodes can be layouted into a sequence of regions.
-///
-/// They return one frame per used region alongside constraints that define
-/// whether the result is reusable in other regions.
-pub trait BlockLevel: Debug {
- /// Layout the node into the given regions, producing constrained frames.
- fn layout(
- &self,
- ctx: &mut LayoutContext,
- regions: &Regions,
- ) -> Vec<Constrained<Rc<Frame>>>;
-
- /// Convert to a packed block-level node.
- fn pack(self) -> BlockNode
- where
- Self: Sized + Hash + 'static,
- {
- BlockNode {
- #[cfg(feature = "layout-cache")]
- hash: hash_node(&self),
- node: Rc::new(self),
- }
- }
-}
-
-/// A packed [block-level](BlockLevel) layouting node with precomputed hash.
-#[derive(Clone)]
-pub struct BlockNode {
- node: Rc<dyn BlockLevel>,
- #[cfg(feature = "layout-cache")]
- hash: u64,
-}
-
-impl BlockLevel for BlockNode {
- fn layout(
- &self,
- ctx: &mut LayoutContext,
- regions: &Regions,
- ) -> Vec<Constrained<Rc<Frame>>> {
- #[cfg(not(feature = "layout-cache"))]
- return self.node.layout(ctx, regions);
-
- #[cfg(feature = "layout-cache")]
- ctx.layouts.get(self.hash, regions).unwrap_or_else(|| {
- ctx.level += 1;
- let frames = self.node.layout(ctx, regions);
- ctx.level -= 1;
-
- let entry = FramesEntry::new(frames.clone(), ctx.level);
-
- #[cfg(debug_assertions)]
- if !entry.check(regions) {
- eprintln!("node: {:#?}", self.node);
- eprintln!("regions: {:#?}", regions);
- eprintln!(
- "constraints: {:#?}",
- frames.iter().map(|c| c.cts).collect::<Vec<_>>()
- );
- panic!("constraints did not match regions they were created for");
- }
-
- ctx.layouts.insert(self.hash, entry);
- frames
- })
- }
-
- fn pack(self) -> BlockNode
- where
- Self: Sized + Hash + 'static,
- {
- self
- }
-}
-
-impl Hash for BlockNode {
- fn hash<H: Hasher>(&self, _state: &mut H) {
- #[cfg(feature = "layout-cache")]
- _state.write_u64(self.hash);
- #[cfg(not(feature = "layout-cache"))]
- unimplemented!()
- }
-}
-
-impl Debug for BlockNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.node.fmt(f)
- }
-}
-
-/// Inline-level nodes are layouted as part of paragraph layout.
-///
-/// They only know the width and not the height of the paragraph's region and
-/// return only a single frame.
-pub trait InlineLevel: Debug {
- /// Layout the node into a frame.
- fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame;
-
- /// Convert to a packed inline-level node.
- fn pack(self) -> InlineNode
- where
- Self: Sized + Hash + 'static,
- {
- InlineNode {
- #[cfg(feature = "layout-cache")]
- hash: hash_node(&self),
- node: Rc::new(self),
- }
- }
-}
-
-/// A packed [inline-level](InlineLevel) layouting node with precomputed hash.
-#[derive(Clone)]
-pub struct InlineNode {
- node: Rc<dyn InlineLevel>,
- #[cfg(feature = "layout-cache")]
- hash: u64,
-}
-
-impl InlineLevel for InlineNode {
- fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
- self.node.layout(ctx, space, base)
- }
-
- fn pack(self) -> InlineNode
- where
- Self: Sized + Hash + 'static,
- {
- self
- }
-}
-
-impl Hash for InlineNode {
- fn hash<H: Hasher>(&self, _state: &mut H) {
- #[cfg(feature = "layout-cache")]
- _state.write_u64(self.hash);
- #[cfg(not(feature = "layout-cache"))]
- unimplemented!()
- }
-}
-
-impl Debug for InlineNode {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- self.node.fmt(f)
- }
-}
-
-/// Hash a node alongside its type id.
-#[cfg(feature = "layout-cache")]
-fn hash_node(node: &(impl Hash + 'static)) -> u64 {
- use std::any::Any;
- let mut state = fxhash::FxHasher64::default();
- node.type_id().hash(&mut state);
- node.hash(&mut state);
- state.finish()
-}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 49ceccf6..7d2837de 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,29 +1,27 @@
-//! Layouting.
+//! Layouting infrastructure.
mod constraints;
#[cfg(feature = "layout-cache")]
mod incremental;
-mod levels;
mod regions;
pub use constraints::*;
#[cfg(feature = "layout-cache")]
pub use incremental::*;
-pub use levels::*;
pub use regions::*;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
use std::rc::Rc;
use crate::font::FontStore;
use crate::frame::Frame;
use crate::image::ImageStore;
+use crate::library::DocumentNode;
use crate::Context;
-/// Layout a page-level node into a collection of frames.
-pub fn layout<T>(ctx: &mut Context, node: &T) -> Vec<Rc<Frame>>
-where
- T: PageLevel + ?Sized,
-{
+/// Layout a document node into a collection of frames.
+pub fn layout(ctx: &mut Context, node: &DocumentNode) -> Vec<Rc<Frame>> {
let mut ctx = LayoutContext::new(ctx);
node.layout(&mut ctx)
}
@@ -55,3 +53,98 @@ impl<'a> LayoutContext<'a> {
}
}
}
+
+/// A node that can be layouted into a sequence of regions.
+///
+/// Layout return one frame per used region alongside constraints that define
+/// whether the result is reusable in other regions.
+pub trait Layout: Debug {
+ /// Layout the node into the given regions, producing constrained frames.
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>>;
+
+ /// Convert to a packed node.
+ fn pack(self) -> PackedNode
+ where
+ Self: Sized + Hash + 'static,
+ {
+ PackedNode {
+ #[cfg(feature = "layout-cache")]
+ hash: {
+ use std::any::Any;
+ let mut state = fxhash::FxHasher64::default();
+ self.type_id().hash(&mut state);
+ self.hash(&mut state);
+ state.finish()
+ },
+ node: Rc::new(self),
+ }
+ }
+}
+
+/// A packed layouting node with precomputed hash.
+#[derive(Clone)]
+pub struct PackedNode {
+ node: Rc<dyn Layout>,
+ #[cfg(feature = "layout-cache")]
+ hash: u64,
+}
+
+impl Layout for PackedNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ #[cfg(not(feature = "layout-cache"))]
+ return self.node.layout(ctx, regions);
+
+ #[cfg(feature = "layout-cache")]
+ ctx.layouts.get(self.hash, regions).unwrap_or_else(|| {
+ ctx.level += 1;
+ let frames = self.node.layout(ctx, regions);
+ ctx.level -= 1;
+
+ let entry = FramesEntry::new(frames.clone(), ctx.level);
+
+ #[cfg(debug_assertions)]
+ if !entry.check(regions) {
+ eprintln!("node: {:#?}", self.node);
+ eprintln!("regions: {:#?}", regions);
+ eprintln!(
+ "constraints: {:#?}",
+ frames.iter().map(|c| c.cts).collect::<Vec<_>>()
+ );
+ panic!("constraints did not match regions they were created for");
+ }
+
+ ctx.layouts.insert(self.hash, entry);
+ frames
+ })
+ }
+
+ fn pack(self) -> PackedNode
+ where
+ Self: Sized + Hash + 'static,
+ {
+ self
+ }
+}
+
+impl Hash for PackedNode {
+ fn hash<H: Hasher>(&self, _state: &mut H) {
+ #[cfg(feature = "layout-cache")]
+ _state.write_u64(self.hash);
+ #[cfg(not(feature = "layout-cache"))]
+ unimplemented!()
+ }
+}
+
+impl Debug for PackedNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.node.fmt(f)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 895430a2..b05a57b3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,9 +9,9 @@
//! [module], consisting of a scope of values that were exported by the code
//! and a template with the contents of the module. This template can be
//! instantiated with a style to produce a layout tree, a high-level, fully
-//! styled representation of the document. The nodes of this tree are
-//! self-contained and order-independent and thus much better suited for
-//! layouting than the raw markup.
+//! styled representation, rooted in the [document node]. The nodes of this
+//! tree are self-contained and order-independent and thus much better suited
+//! for layouting than the raw markup.
//! - **Layouting:** Next, the tree is [layouted] into a portable version of the
//! typeset document. The output of this is a collection of [`Frame`]s (one
//! per page), ready for exporting.
@@ -24,6 +24,7 @@
//! [evaluate]: eval::eval
//! [module]: eval::Module
//! [layout tree]: layout::LayoutTree
+//! [document node]: library::DocumentNode
//! [layouted]: layout::layout
//! [PDF]: export::pdf
@@ -53,9 +54,9 @@ use crate::eval::{Module, Scope};
use crate::font::FontStore;
use crate::frame::Frame;
use crate::image::ImageStore;
-use crate::layout::PageNode;
#[cfg(feature = "layout-cache")]
use crate::layout::{EvictionPolicy, LayoutCache};
+use crate::library::DocumentNode;
use crate::loading::Loader;
use crate::source::{SourceId, SourceStore};
use crate::style::Style;
@@ -107,9 +108,9 @@ impl Context {
}
/// Execute a source file and produce the resulting page nodes.
- pub fn execute(&mut self, id: SourceId) -> TypResult<Vec<PageNode>> {
+ pub fn execute(&mut self, id: SourceId) -> TypResult<DocumentNode> {
let module = self.evaluate(id)?;
- Ok(module.template.to_pages(&self.style))
+ Ok(module.template.to_document(&self.style))
}
/// Typeset a source file into a collection of layouted frames.
diff --git a/src/library/container.rs b/src/library/container.rs
index 4b52139f..2bcbbd19 100644
--- a/src/library/container.rs
+++ b/src/library/container.rs
@@ -5,14 +5,13 @@ use super::{ShapeKind, ShapeNode};
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let width = args.named("width")?;
let height = args.named("height")?;
- let fill = args.named("fill")?;
let body: Template = args.find().unwrap_or_default();
Ok(Value::Template(Template::from_inline(move |style| {
ShapeNode {
shape: ShapeKind::Rect,
width,
height,
- fill: fill.map(Paint::Color),
+ fill: None,
child: Some(body.to_flow(style).pack()),
}
})))
diff --git a/src/library/document.rs b/src/library/document.rs
new file mode 100644
index 00000000..fe01d2df
--- /dev/null
+++ b/src/library/document.rs
@@ -0,0 +1,16 @@
+use super::prelude::*;
+use super::PageNode;
+
+/// The root layout node, a document consisting of top-level page runs.
+#[derive(Debug, Hash)]
+pub struct DocumentNode {
+ /// The page runs.
+ pub pages: Vec<PageNode>,
+}
+
+impl DocumentNode {
+ /// Layout the document into a sequence of frames, one per page.
+ pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
+ self.pages.iter().flat_map(|node| node.layout(ctx)).collect()
+ }
+}
diff --git a/src/library/flow.rs b/src/library/flow.rs
index cc53d520..c41fb62c 100644
--- a/src/library/flow.rs
+++ b/src/library/flow.rs
@@ -48,7 +48,7 @@ pub struct FlowNode {
pub children: Vec<FlowChild>,
}
-impl BlockLevel for FlowNode {
+impl Layout for FlowNode {
fn layout(
&self,
ctx: &mut LayoutContext,
@@ -63,8 +63,8 @@ impl BlockLevel for FlowNode {
pub enum FlowChild {
/// Vertical spacing between other children.
Spacing(Spacing),
- /// Any block node and how to align it in the flow.
- Node(BlockNode, Align),
+ /// A node and how to align it in the flow.
+ Node(PackedNode, Align),
}
impl Debug for FlowChild {
@@ -157,8 +157,8 @@ impl<'a> FlowLayouter<'a> {
self.items.push(FlowItem::Absolute(resolved));
}
- /// Layout a block node.
- fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode, align: Align) {
+ /// Layout a node.
+ fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode, align: Align) {
let frames = node.layout(ctx, &self.regions);
let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() {
diff --git a/src/library/grid.rs b/src/library/grid.rs
index bafd639c..692ed210 100644
--- a/src/library/grid.rs
+++ b/src/library/grid.rs
@@ -58,7 +58,7 @@ pub struct GridNode {
/// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>,
/// The nodes to be arranged in a grid.
- pub children: Vec<BlockNode>,
+ pub children: Vec<PackedNode>,
}
/// Defines how to size a grid cell along an axis.
@@ -72,7 +72,7 @@ pub enum TrackSizing {
Fractional(Fractional),
}
-impl BlockLevel for GridNode {
+impl Layout for GridNode {
fn layout(
&self,
ctx: &mut LayoutContext,
@@ -92,7 +92,7 @@ impl BlockLevel for GridNode {
/// Performs grid layout.
struct GridLayouter<'a> {
/// The children of the grid.
- children: &'a [BlockNode],
+ children: &'a [PackedNode],
/// Whether the grid should expand to fill the region.
expand: Spec<bool>,
/// The column tracks including gutter tracks.
@@ -591,7 +591,7 @@ impl<'a> GridLayouter<'a> {
///
/// Returns `None` if it's a gutter cell.
#[track_caller]
- fn cell(&self, x: usize, y: usize) -> Option<&'a BlockNode> {
+ fn cell(&self, x: usize, y: usize) -> Option<&'a PackedNode> {
assert!(x < self.cols.len());
assert!(y < self.rows.len());
diff --git a/src/library/image.rs b/src/library/image.rs
index b51e4e70..f93d4b54 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -36,23 +36,31 @@ pub struct ImageNode {
pub height: Option<Linear>,
}
-impl InlineLevel for ImageNode {
- fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
+impl Layout for ImageNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
let img = ctx.images.get(self.id);
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
let pixel_ratio = pixel_size.x / pixel_size.y;
- let width = self.width.map(|w| w.resolve(base.w));
- let height = self.height.map(|w| w.resolve(base.h));
+ let width = self.width.map(|w| w.resolve(regions.base.w));
+ let height = self.height.map(|w| w.resolve(regions.base.h));
+
+ let mut cts = Constraints::new(regions.expand);
+ cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
let size = match (width, height) {
(Some(width), Some(height)) => Size::new(width, height),
(Some(width), None) => Size::new(width, width / pixel_ratio),
(None, Some(height)) => Size::new(height * pixel_ratio, height),
(None, None) => {
- if space.is_finite() {
+ cts.exact.x = Some(regions.current.w);
+ if regions.current.w.is_finite() {
// Fit to width.
- Size::new(space, space / pixel_ratio)
+ Size::new(regions.current.w, regions.current.w / pixel_ratio)
} else {
// Unbounded width, we have to make up something,
// so it is 1pt per pixel.
@@ -63,6 +71,7 @@ impl InlineLevel for ImageNode {
let mut frame = Frame::new(size, size.h);
frame.push(Point::zero(), Element::Image(self.id, size));
- frame
+
+ vec![frame.constrain(cts)]
}
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 775fb51f..f11e05f3 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -6,6 +6,7 @@
mod align;
mod container;
mod deco;
+mod document;
mod flow;
mod grid;
mod image;
@@ -36,6 +37,7 @@ pub use self::image::*;
pub use align::*;
pub use container::*;
pub use deco::*;
+pub use document::*;
pub use flow::*;
pub use grid::*;
pub use pad::*;
diff --git a/src/library/pad.rs b/src/library/pad.rs
index d17a4ca2..88f8c562 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -16,7 +16,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
bottom.or(all).unwrap_or_default(),
);
- Ok(Value::Template(Template::from_block(move |style| {
+ Ok(Value::Template(Template::from_inline(move |style| {
PadNode {
padding,
child: body.to_flow(style).pack(),
@@ -30,10 +30,10 @@ pub struct PadNode {
/// The amount of padding.
pub padding: Sides<Linear>,
/// The child node whose sides to pad.
- pub child: BlockNode,
+ pub child: PackedNode,
}
-impl BlockLevel for PadNode {
+impl Layout for PadNode {
fn layout(
&self,
ctx: &mut LayoutContext,
diff --git a/src/library/page.rs b/src/library/page.rs
index 16e6283d..a5935002 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -73,3 +73,23 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
template.pagebreak(true);
Ok(Value::Template(template))
}
+
+/// Layouts its children onto one or multiple pages.
+#[derive(Debug, Hash)]
+pub struct PageNode {
+ /// The size of the page.
+ pub size: Size,
+ /// The node that produces the actual pages.
+ pub child: PackedNode,
+}
+
+impl PageNode {
+ /// Layout the page run into a sequence of frames, one per page.
+ pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
+ // When one of the lengths is infinite the page fits its content along
+ // that axis.
+ let expand = self.size.to_spec().map(Length::is_finite);
+ let regions = Regions::repeat(self.size, self.size, expand);
+ self.child.layout(ctx, &regions).into_iter().map(|c| c.item).collect()
+ }
+}
diff --git a/src/library/par.rs b/src/library/par.rs
index 216b7d41..c8befc2d 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -8,7 +8,7 @@ use xi_unicode::LineBreakIterator;
use super::prelude::*;
use super::{shape, Decoration, ShapedText, Spacing};
use crate::style::TextStyle;
-use crate::util::{EcoString, RangeExt, SliceExt};
+use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
/// `par`: Configure paragraphs.
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@@ -63,7 +63,7 @@ pub struct ParNode {
pub children: Vec<ParChild>,
}
-impl BlockLevel for ParNode {
+impl Layout for ParNode {
fn layout(
&self,
ctx: &mut LayoutContext,
@@ -77,7 +77,7 @@ impl BlockLevel for ParNode {
// Prepare paragraph layout by building a representation on which we can
// do line breaking without layouting each and every line from scratch.
- let layouter = ParLayouter::new(self, ctx, regions, bidi);
+ let layouter = ParLayouter::new(self, ctx, regions.clone(), bidi);
// Find suitable linebreaks.
layouter.layout(ctx, regions.clone())
@@ -126,7 +126,7 @@ pub enum ParChild {
/// A run of text and how to align it in its line.
Text(EcoString, Align, Rc<TextStyle>),
/// Any child node and how to align it in its line.
- Node(InlineNode, Align),
+ Node(PackedNode, Align),
/// A decoration that applies until a matching `Undecorate`.
Decorate(Decoration),
/// The end of a decoration.
@@ -182,9 +182,12 @@ impl<'a> ParLayouter<'a> {
fn new(
par: &'a ParNode,
ctx: &mut LayoutContext,
- regions: &Regions,
+ mut regions: Regions,
bidi: BidiInfo<'a>,
) -> Self {
+ // Disable expansion for children.
+ regions.expand = Spec::splat(false);
+
let mut items = vec![];
let mut ranges = vec![];
let mut starts = vec![];
@@ -216,8 +219,8 @@ impl<'a> ParLayouter<'a> {
}
}
ParChild::Node(ref node, align) => {
- let frame = node.layout(ctx, regions.current.w, regions.base);
- items.push(ParItem::Frame(frame, align));
+ let frame = node.layout(ctx, &regions).remove(0);
+ items.push(ParItem::Frame(Rc::take(frame.item), align));
ranges.push(range);
}
ParChild::Decorate(ref deco) => {
diff --git a/src/library/shape.rs b/src/library/shape.rs
index 5be26aa4..c64dedb3 100644
--- a/src/library/shape.rs
+++ b/src/library/shape.rs
@@ -94,7 +94,7 @@ pub struct ShapeNode {
/// How to fill the shape, if at all.
pub fill: Option<Paint>,
/// The child node to place into the shape, if any.
- pub child: Option<BlockNode>,
+ pub child: Option<PackedNode>,
}
/// The type of a shape.
@@ -110,15 +110,36 @@ pub enum ShapeKind {
Ellipse,
}
-impl InlineLevel for ShapeNode {
- fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
+impl Layout for ShapeNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
// Resolve width and height relative to the region's base.
- let width = self.width.map(|w| w.resolve(base.w));
- let height = self.height.map(|h| h.resolve(base.h));
+ let width = self.width.map(|w| w.resolve(regions.base.w));
+ let height = self.height.map(|h| h.resolve(regions.base.h));
+
+ // Generate constraints.
+ let mut cts = Constraints::new(regions.expand);
+ cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
+
+ // Set tight exact and base constraints if the child is
+ // automatically sized since we don't know what the child might do.
+ if self.width.is_none() {
+ cts.exact.x = Some(regions.current.w);
+ cts.base.x = Some(regions.base.w);
+ }
+
+ // Same here.
+ if self.height.is_none() {
+ cts.exact.y = Some(regions.current.h);
+ cts.base.y = Some(regions.base.h);
+ }
// Layout.
let mut frame = if let Some(child) = &self.child {
- let mut node: &dyn BlockLevel = child;
+ let mut node: &dyn Layout = child;
let padded;
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
@@ -133,11 +154,14 @@ impl InlineLevel for ShapeNode {
// The "pod" is the region into which the child will be layouted.
let mut pod = {
- let size = Size::new(width.unwrap_or(space), height.unwrap_or(base.h));
+ let size = Size::new(
+ width.unwrap_or(regions.current.w),
+ height.unwrap_or(regions.base.h),
+ );
let base = Size::new(
- if width.is_some() { size.w } else { base.w },
- if height.is_some() { size.h } else { base.h },
+ if width.is_some() { size.w } else { regions.base.w },
+ if height.is_some() { size.h } else { regions.base.h },
);
let expand = Spec::new(width.is_some(), height.is_some());
@@ -180,6 +204,6 @@ impl InlineLevel for ShapeNode {
frame.prepend(pos, Element::Geometry(geometry, fill));
}
- frame
+ vec![frame.constrain(cts)]
}
}
diff --git a/src/library/stack.rs b/src/library/stack.rs
index fe1deda0..46825939 100644
--- a/src/library/stack.rs
+++ b/src/library/stack.rs
@@ -60,7 +60,7 @@ pub struct StackNode {
pub children: Vec<StackChild>,
}
-impl BlockLevel for StackNode {
+impl Layout for StackNode {
fn layout(
&self,
ctx: &mut LayoutContext,
@@ -76,7 +76,7 @@ pub enum StackChild {
/// Spacing between other nodes.
Spacing(Spacing),
/// Any block node and how to align it in the stack.
- Node(BlockNode),
+ Node(PackedNode),
}
impl Debug for StackChild {
@@ -174,8 +174,8 @@ impl<'a> StackLayouter<'a> {
self.items.push(StackItem::Absolute(resolved));
}
- /// Layout a block node.
- fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode) {
+ /// Layout a node.
+ fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
let frames = node.layout(ctx, &self.regions);
let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() {
diff --git a/src/library/transform.rs b/src/library/transform.rs
index b7e5e36c..20d2bc1d 100644
--- a/src/library/transform.rs
+++ b/src/library/transform.rs
@@ -1,5 +1,4 @@
use super::prelude::*;
-use super::{ShapeKind, ShapeNode};
/// `move`: Move content without affecting layout.
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@@ -10,13 +9,7 @@ pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
Ok(Value::Template(Template::from_inline(move |style| {
MoveNode {
offset: Spec::new(x, y),
- child: ShapeNode {
- shape: ShapeKind::Rect,
- width: None,
- height: None,
- fill: None,
- child: Some(body.to_flow(style).pack()),
- },
+ child: body.to_flow(style).pack(),
}
})))
}
@@ -24,21 +17,30 @@ pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
#[derive(Debug, Hash)]
struct MoveNode {
offset: Spec<Option<Linear>>,
- child: ShapeNode,
+ child: PackedNode,
}
-impl InlineLevel for MoveNode {
- fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
- let offset = Point::new(
- self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(),
- self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
- );
+impl Layout for MoveNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ let mut frames = self.child.layout(ctx, regions);
- let mut frame = self.child.layout(ctx, space, base);
- for (point, _) in &mut frame.children {
- *point += offset;
+ for (Constrained { item: frame, .. }, (_, base)) in
+ frames.iter_mut().zip(regions.iter())
+ {
+ let offset = Point::new(
+ self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(),
+ self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
+ );
+
+ for (point, _) in &mut Rc::make_mut(frame).children {
+ *point += offset;
+ }
}
- frame
+ frames
}
}
diff --git a/src/main.rs b/src/main.rs
index b2ae1ea3..fa8b6103 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -60,8 +60,8 @@ fn try_main() -> anyhow::Result<()> {
// Typeset.
match ctx.typeset(id) {
// Export the PDF.
- Ok(document) => {
- let buffer = export::pdf(&ctx, &document);
+ Ok(frames) => {
+ let buffer = export::pdf(&ctx, &frames);
fs::write(&args.output, buffer).context("failed to write PDF file")?;
}
diff --git a/src/source.rs b/src/source.rs
index e692ac94..74fa8d55 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -1,4 +1,4 @@
-//! Source files.
+//! Source file management.
use std::collections::HashMap;
use std::io;