Revert page and inline levels

This commit is contained in:
Laurenz 2021-11-16 10:41:30 +01:00
parent bc118634ac
commit 0e0f340502
26 changed files with 288 additions and 313 deletions

View File

@ -60,7 +60,7 @@ fn bench_eval(iai: &mut Iai) {
fn bench_to_tree(iai: &mut Iai) {
let (mut ctx, id) = context();
let module = ctx.evaluate(id).unwrap();
iai.run(|| module.template.to_pages(ctx.style()));
iai.run(|| module.template.to_document(ctx.style()));
}
fn bench_layout(iai: &mut Iai) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

16
src/library/document.rs Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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::*;

View File

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

View File

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

View File

@ -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) => {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
//! Source files.
//! Source file management.
use std::collections::HashMap;
use std::io;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -11,19 +11,11 @@
)
)
Hi #box(pad(left: 10pt)[]) there
Hi #pad(left: 10pt)[A] there
---
#let pad(body) = pad(left: 10pt, right: 10pt, body)
// Pad inherits expansion behaviour from stack ....
#pad[PL #align(right)[PR]]
// ... block ...
#block(pad[PL #align(right)[PR]])
// ... and box.
#box(pad[PL #align(right)[PR]])
// Pad can grow.
#pad(left: 10pt, right: 10pt)[PL #h(1fr) PR]
---
// Test that the pad node doesn't consume the whole region.

View File

@ -17,7 +17,7 @@ World! 🌍
---
#page(height: 2cm)
#font(white)
#box(fill: forest)[
#rect(fill: forest)[
#v(1fr)
#h(1fr) Hi you! #h(5pt)
#v(5pt)

View File

@ -19,7 +19,7 @@ use typst::geom::{
use typst::image::Image;
use typst::layout::layout;
#[cfg(feature = "layout-cache")]
use typst::layout::PageNode;
use typst::library::DocumentNode;
use typst::loading::FsLoader;
use typst::parse::Scanner;
use typst::source::SourceFile;
@ -231,11 +231,11 @@ fn test_part(
let mut ok = true;
let (frames, mut errors) = match ctx.execute(id) {
Ok(tree) => {
let mut frames = layout(ctx, &tree);
Ok(document) => {
let mut frames = layout(ctx, &document);
#[cfg(feature = "layout-cache")]
(ok &= test_incremental(ctx, i, &tree, &frames));
(ok &= test_incremental(ctx, i, &document, &frames));
if !compare_ref {
frames.clear();
@ -283,7 +283,7 @@ fn test_part(
fn test_incremental(
ctx: &mut Context,
i: usize,
tree: &[PageNode],
document: &DocumentNode,
frames: &[Rc<Frame>],
) -> bool {
let mut ok = true;
@ -298,7 +298,7 @@ fn test_incremental(
ctx.layouts.turnaround();
let cached = layout(ctx, tree);
let cached = layout(ctx, document);
let misses = ctx
.layouts
.entries()