Introduce page / block / inline levels

This commit is contained in:
Laurenz 2021-10-17 14:38:48 +02:00
parent c627847cb3
commit 5becb32ba4
25 changed files with 530 additions and 532 deletions

View File

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

View File

@ -7,6 +7,7 @@ use std::rc::Rc;
use super::Value; use super::Value;
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::util::RcExt;
/// Create a new [`Array`] from values. /// Create a new [`Array`] from values.
#[allow(unused_macros)] #[allow(unused_macros)]
@ -169,10 +170,7 @@ impl IntoIterator for Array {
type IntoIter = std::vec::IntoIter<Value>; type IntoIter = std::vec::IntoIter<Value>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
match Rc::try_unwrap(self.0) { Rc::take(self.0).into_iter()
Ok(vec) => vec.into_iter(),
Err(rc) => (*rc).clone().into_iter(),
}
} }
} }

View File

@ -6,6 +6,7 @@ use std::rc::Rc;
use super::{Str, Value}; use super::{Str, Value};
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::util::RcExt;
/// Create a new [`Dict`] from key-value pairs. /// Create a new [`Dict`] from key-value pairs.
#[allow(unused_macros)] #[allow(unused_macros)]
@ -135,10 +136,7 @@ impl IntoIterator for Dict {
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>; type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
match Rc::try_unwrap(self.0) { Rc::take(self.0).into_iter()
Ok(map) => map.into_iter(),
Err(rc) => (*rc).clone().into_iter(),
}
} }
} }

View File

@ -6,9 +6,9 @@ use std::rc::Rc;
use super::Str; use super::Str;
use crate::diag::StrResult; use crate::diag::StrResult;
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size, SpecAxis}; use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
use crate::layout::{ use crate::layout::{
Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, BlockNode, Decoration, InlineNode, PadNode, PageNode, ParChild, ParNode, StackChild,
StackNode, StackNode,
}; };
use crate::style::Style; use crate::style::Style;
@ -34,9 +34,9 @@ enum TemplateNode {
/// Spacing. /// Spacing.
Spacing(GenAxis, Linear), Spacing(GenAxis, Linear),
/// An inline node builder. /// An inline node builder.
Inline(Rc<dyn Fn(&Style) -> LayoutNode>, Vec<Decoration>), Inline(Rc<dyn Fn(&Style) -> InlineNode>, Vec<Decoration>),
/// An block node builder. /// An block node builder.
Block(Rc<dyn Fn(&Style) -> LayoutNode>), Block(Rc<dyn Fn(&Style) -> BlockNode>),
/// Save the current style. /// Save the current style.
Save, Save,
/// Restore the last saved style. /// Restore the last saved style.
@ -55,7 +55,7 @@ impl Template {
pub fn from_inline<F, T>(f: F) -> Self pub fn from_inline<F, T>(f: F) -> Self
where where
F: Fn(&Style) -> T + 'static, F: Fn(&Style) -> T + 'static,
T: Into<LayoutNode>, T: Into<InlineNode>,
{ {
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]); let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
Self(Rc::new(vec![node])) Self(Rc::new(vec![node]))
@ -65,7 +65,7 @@ impl Template {
pub fn from_block<F, T>(f: F) -> Self pub fn from_block<F, T>(f: F) -> Self
where where
F: Fn(&Style) -> T + 'static, F: Fn(&Style) -> T + 'static,
T: Into<LayoutNode>, T: Into<BlockNode>,
{ {
let node = TemplateNode::Block(Rc::new(move |s| f(s).into())); let node = TemplateNode::Block(Rc::new(move |s| f(s).into()));
Self(Rc::new(vec![node])) Self(Rc::new(vec![node]))
@ -164,10 +164,10 @@ impl Template {
/// Build the layout tree resulting from instantiating the template with the /// Build the layout tree resulting from instantiating the template with the
/// given style. /// given style.
pub fn to_tree(&self, style: &Style) -> LayoutTree { pub fn to_pages(&self, style: &Style) -> Vec<PageNode> {
let mut builder = Builder::new(style, true); let mut builder = Builder::new(style, true);
builder.template(self); builder.template(self);
builder.build_tree() builder.build_pages()
} }
/// Repeat this template `n` times. /// Repeat this template `n` times.
@ -243,8 +243,8 @@ struct Builder {
style: Style, style: Style,
/// Snapshots of the style. /// Snapshots of the style.
snapshots: Vec<Style>, snapshots: Vec<Style>,
/// The tree of finished page runs. /// The finished page nodes.
tree: LayoutTree, finished: Vec<PageNode>,
/// When we are building the top-level layout trees, this contains metrics /// When we are building the top-level layout trees, this contains metrics
/// of the page. While building a stack, this is `None`. /// of the page. While building a stack, this is `None`.
page: Option<PageBuilder>, page: Option<PageBuilder>,
@ -258,7 +258,7 @@ impl Builder {
Self { Self {
style: style.clone(), style: style.clone(),
snapshots: vec![], snapshots: vec![],
tree: LayoutTree { runs: vec![] }, finished: vec![],
page: pages.then(|| PageBuilder::new(style, true)), page: pages.then(|| PageBuilder::new(style, true)),
stack: StackBuilder::new(style), stack: StackBuilder::new(style),
} }
@ -309,7 +309,7 @@ impl Builder {
fn parbreak(&mut self) { fn parbreak(&mut self) {
let amount = self.style.par_spacing(); let amount = self.style.par_spacing();
self.stack.finish_par(&self.style); self.stack.finish_par(&self.style);
self.stack.push_soft(StackChild::spacing(amount, SpecAxis::Vertical)); self.stack.push_soft(StackChild::Spacing(amount.into()));
} }
/// Apply a forced page break. /// Apply a forced page break.
@ -317,7 +317,7 @@ impl Builder {
if let Some(builder) = &mut self.page { if let Some(builder) = &mut self.page {
let page = mem::replace(builder, PageBuilder::new(&self.style, hard)); let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.style)); let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.style));
self.tree.runs.extend(page.build(stack.build(), keep)); self.finished.extend(page.build(stack.build(), keep));
} }
} }
@ -327,15 +327,15 @@ impl Builder {
} }
/// Push an inline node into the active paragraph. /// Push an inline node into the active paragraph.
fn inline(&mut self, node: impl Into<LayoutNode>, decos: &[Decoration]) { fn inline(&mut self, node: impl Into<InlineNode>, decos: &[Decoration]) {
let align = self.style.aligns.inline; let align = self.style.aligns.inline;
self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec())); self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
} }
/// Push a block node into the active stack, finishing the active paragraph. /// Push a block node into the active stack, finishing the active paragraph.
fn block(&mut self, node: impl Into<LayoutNode>) { fn block(&mut self, node: impl Into<BlockNode>) {
self.parbreak(); self.parbreak();
self.stack.push(StackChild::new(node, self.style.aligns.block)); self.stack.push(StackChild::Any(node.into(), self.style.aligns.block));
self.parbreak(); self.parbreak();
} }
@ -344,7 +344,7 @@ impl Builder {
match axis { match axis {
GenAxis::Block => { GenAxis::Block => {
self.stack.finish_par(&self.style); self.stack.finish_par(&self.style);
self.stack.push_hard(StackChild::spacing(amount, SpecAxis::Vertical)); self.stack.push_hard(StackChild::Spacing(amount));
} }
GenAxis::Inline => { GenAxis::Inline => {
self.stack.par.push_hard(ParChild::Spacing(amount)); self.stack.par.push_hard(ParChild::Spacing(amount));
@ -359,10 +359,10 @@ impl Builder {
} }
/// Finish building and return the created layout tree. /// Finish building and return the created layout tree.
fn build_tree(mut self) -> LayoutTree { fn build_pages(mut self) -> Vec<PageNode> {
assert!(self.page.is_some()); assert!(self.page.is_some());
self.pagebreak(true, false); self.pagebreak(true, false);
self.tree self.finished
} }
/// Construct a text node with the given text and settings from the current /// Construct a text node with the given text and settings from the current
@ -396,9 +396,9 @@ impl PageBuilder {
} }
} }
fn build(self, child: StackNode, keep: bool) -> Option<PageRun> { fn build(self, child: StackNode, keep: bool) -> Option<PageNode> {
let Self { size, padding, hard } = self; let Self { size, padding, hard } = self;
(!child.children.is_empty() || (keep && hard)).then(|| PageRun { (!child.children.is_empty() || (keep && hard)).then(|| PageNode {
size, size,
child: PadNode { padding, child: child.into() }.into(), child: PadNode { padding, child: child.into() }.into(),
}) })
@ -456,7 +456,7 @@ impl StackBuilder {
struct ParBuilder { struct ParBuilder {
align: Align, align: Align,
dir: Dir, dir: Dir,
line_spacing: Length, leading: Length,
children: Vec<ParChild>, children: Vec<ParChild>,
last: Last<ParChild>, last: Last<ParChild>,
} }
@ -466,7 +466,7 @@ impl ParBuilder {
Self { Self {
align: style.aligns.block, align: style.aligns.block,
dir: style.dir, dir: style.dir,
line_spacing: style.line_spacing(), leading: style.leading(),
children: vec![], children: vec![],
last: Last::None, last: Last::None,
} }
@ -507,9 +507,9 @@ impl ParBuilder {
} }
fn build(self) -> Option<StackChild> { fn build(self) -> Option<StackChild> {
let Self { align, dir, line_spacing, children, .. } = self; let Self { align, dir, leading, children, .. } = self;
(!children.is_empty()) (!children.is_empty())
.then(|| StackChild::new(ParNode { dir, line_spacing, children }, align)) .then(|| StackChild::Any(ParNode { dir, leading, children }.into(), align))
} }
} }

View File

@ -2,7 +2,7 @@ use std::rc::Rc;
use super::{Eval, EvalContext, Str, Template, Value}; use super::{Eval, EvalContext, Str, Template, Value};
use crate::diag::TypResult; use crate::diag::TypResult;
use crate::geom::{Align, SpecAxis}; use crate::geom::Align;
use crate::layout::{ParChild, ParNode, StackChild, StackNode}; use crate::layout::{ParChild, ParNode, StackChild, StackNode};
use crate::syntax::*; use crate::syntax::*;
use crate::util::BoolExt; use crate::util::BoolExt;
@ -108,7 +108,7 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
ctx.template += Template::from_block(move |style| { ctx.template += Template::from_block(move |style| {
let label = ParNode { let label = ParNode {
dir: style.dir, dir: style.dir,
line_spacing: style.line_spacing(), leading: style.leading(),
children: vec![ParChild::Text( children: vec![ParChild::Text(
(&label).into(), (&label).into(),
style.aligns.inline, style.aligns.inline,
@ -119,9 +119,9 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
StackNode { StackNode {
dir: style.dir, dir: style.dir,
children: vec![ children: vec![
StackChild::new(label, Align::Start), StackChild::Any(label.into(), Align::Start),
StackChild::spacing(style.text.size / 2.0, SpecAxis::Horizontal), StackChild::Spacing((style.text.size / 2.0).into()),
StackChild::new(body.to_stack(&style), Align::Start), StackChild::Any(body.to_stack(&style).into(), Align::Start),
], ],
} }
}); });

View File

@ -7,7 +7,7 @@ pub struct Constrained<T> {
/// The item that is only valid if the constraints are fullfilled. /// The item that is only valid if the constraints are fullfilled.
pub item: T, pub item: T,
/// Constraints on regions in which the item is valid. /// Constraints on regions in which the item is valid.
pub constraints: Constraints, pub cts: Constraints,
} }
/// Describe regions that match them. /// Describe regions that match them.

View File

@ -19,6 +19,70 @@ pub struct Frame {
pub children: Vec<(Point, FrameChild)>, pub children: Vec<(Point, FrameChild)>,
} }
impl Frame {
/// Create a new, empty frame.
#[track_caller]
pub fn new(size: Size, baseline: Length) -> Self {
assert!(size.is_finite());
Self { size, baseline, children: vec![] }
}
/// Add an element at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) {
self.children.push((pos, FrameChild::Element(element)));
}
/// Add an element at a position in the background.
pub fn prepend(&mut self, pos: Point, element: Element) {
self.children.insert(0, (pos, FrameChild::Element(element)));
}
/// Add a frame element.
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
self.children.push((pos, FrameChild::Group(subframe)))
}
/// Add all elements of another frame, placing them relative to the given
/// position.
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
if pos == Point::zero() && self.children.is_empty() {
self.children = subframe.children;
} else {
for (subpos, child) in subframe.children {
self.children.push((pos + subpos, child));
}
}
}
/// An iterator over all elements in the frame and its children.
pub fn elements(&self) -> Elements {
Elements { stack: vec![(0, Point::zero(), self)] }
}
/// Wrap the frame with constraints.
pub fn constrain(self, cts: Constraints) -> Constrained<Rc<Self>> {
Constrained { item: Rc::new(self), cts }
}
}
impl Debug for Frame {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
struct Children<'a>(&'a [(Point, FrameChild)]);
impl Debug for Children<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_map().entries(self.0.iter().map(|(k, v)| (k, v))).finish()
}
}
f.debug_struct("Frame")
.field("size", &self.size)
.field("baseline", &self.baseline)
.field("children", &Children(&self.children))
.finish()
}
}
/// A frame can contain two different kinds of children: a leaf element or a /// A frame can contain two different kinds of children: a leaf element or a
/// nested frame. /// nested frame.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
@ -29,6 +93,46 @@ pub enum FrameChild {
Group(Rc<Frame>), Group(Rc<Frame>),
} }
impl Debug for FrameChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Element(element) => element.fmt(f),
Self::Group(frame) => frame.fmt(f),
}
}
}
/// An iterator over all elements in a frame, alongside with their positions.
pub struct Elements<'a> {
stack: Vec<(usize, Point, &'a Frame)>,
}
impl<'a> Iterator for Elements<'a> {
type Item = (Point, &'a Element);
fn next(&mut self) -> Option<Self::Item> {
let (cursor, offset, frame) = self.stack.last_mut()?;
match frame.children.get(*cursor) {
Some((pos, FrameChild::Group(f))) => {
let new_offset = *offset + *pos;
self.stack.push((0, new_offset, f.as_ref()));
self.next()
}
Some((pos, FrameChild::Element(e))) => {
*cursor += 1;
Some((*offset + *pos, e))
}
None => {
self.stack.pop();
if let Some((cursor, _, _)) = self.stack.last_mut() {
*cursor += 1;
}
self.next()
}
}
}
}
/// The building block frames are composed of. /// The building block frames are composed of.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Element { pub enum Element {
@ -81,107 +185,3 @@ pub enum Geometry {
/// A filled bezier path. /// A filled bezier path.
Path(Path), Path(Path),
} }
impl Frame {
/// Create a new, empty frame.
#[track_caller]
pub fn new(size: Size, baseline: Length) -> Self {
assert!(size.is_finite());
Self { size, baseline, children: vec![] }
}
/// Add an element at a position in the foreground.
pub fn push(&mut self, pos: Point, element: Element) {
self.children.push((pos, FrameChild::Element(element)));
}
/// Add an element at a position in the background.
pub fn prepend(&mut self, pos: Point, element: Element) {
self.children.insert(0, (pos, FrameChild::Element(element)));
}
/// Add a frame element.
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
self.children.push((pos, FrameChild::Group(subframe)))
}
/// Add all elements of another frame, placing them relative to the given
/// position.
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
if pos == Point::zero() && self.children.is_empty() {
self.children = subframe.children;
} else {
for (subpos, child) in subframe.children {
self.children.push((pos + subpos, child));
}
}
}
/// Wrap the frame with constraints.
pub fn constrain(self, constraints: Constraints) -> Constrained<Rc<Self>> {
Constrained { item: Rc::new(self), constraints }
}
/// An iterator over all elements in the frame and its children.
pub fn elements(&self) -> Elements {
Elements { stack: vec![(0, Point::zero(), self)] }
}
}
/// An iterator over all elements in a frame, alongside with their positions.
pub struct Elements<'a> {
stack: Vec<(usize, Point, &'a Frame)>,
}
impl<'a> Iterator for Elements<'a> {
type Item = (Point, &'a Element);
fn next(&mut self) -> Option<Self::Item> {
let (cursor, offset, frame) = self.stack.last_mut()?;
match frame.children.get(*cursor) {
Some((pos, FrameChild::Group(f))) => {
let new_offset = *offset + *pos;
self.stack.push((0, new_offset, f.as_ref()));
self.next()
}
Some((pos, FrameChild::Element(e))) => {
*cursor += 1;
Some((*offset + *pos, e))
}
None => {
self.stack.pop();
if let Some((cursor, _, _)) = self.stack.last_mut() {
*cursor += 1;
}
self.next()
}
}
}
}
impl Debug for Frame {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
struct Children<'a>(&'a [(Point, FrameChild)]);
impl Debug for Children<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_map().entries(self.0.iter().map(|(k, v)| (k, v))).finish()
}
}
f.debug_struct("Frame")
.field("size", &self.size)
.field("baseline", &self.baseline)
.field("children", &Children(&self.children))
.finish()
}
}
impl Debug for FrameChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Element(element) => element.fmt(f),
Self::Group(frame) => frame.fmt(f),
}
}
}

View File

@ -9,7 +9,7 @@ pub struct GridNode {
/// Defines sizing of gutter rows and columns between content. /// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>, pub gutter: Spec<Vec<TrackSizing>>,
/// The nodes to be arranged in a grid. /// The nodes to be arranged in a grid.
pub children: Vec<LayoutNode>, pub children: Vec<BlockNode>,
} }
/// Defines how to size a grid cell along an axis. /// Defines how to size a grid cell along an axis.
@ -23,7 +23,7 @@ pub enum TrackSizing {
Fractional(Fractional), Fractional(Fractional),
} }
impl Layout for GridNode { impl BlockLevel for GridNode {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
@ -40,9 +40,9 @@ impl Layout for GridNode {
} }
} }
impl From<GridNode> for LayoutNode { impl From<GridNode> for BlockNode {
fn from(grid: GridNode) -> Self { fn from(node: GridNode) -> Self {
Self::new(grid) Self::new(node)
} }
} }
@ -55,7 +55,7 @@ struct GridLayouter<'a> {
/// The row tracks including gutter tracks. /// The row tracks including gutter tracks.
rows: Vec<TrackSizing>, rows: Vec<TrackSizing>,
/// The children of the grid. /// The children of the grid.
children: &'a [LayoutNode], children: &'a [BlockNode],
/// The regions to layout into. /// The regions to layout into.
regions: Regions, regions: Regions,
/// Resolved column sizes. /// Resolved column sizes.
@ -546,7 +546,7 @@ impl<'a> GridLayouter<'a> {
/// ///
/// Returns `None` if it's a gutter cell. /// Returns `None` if it's a gutter cell.
#[track_caller] #[track_caller]
fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> { fn cell(&self, x: usize, y: usize) -> Option<&'a BlockNode> {
assert!(x < self.cols.len()); assert!(x < self.cols.len());
assert!(y < self.rows.len()); assert!(y < self.rows.len());

View File

@ -13,31 +13,23 @@ pub struct ImageNode {
pub height: Option<Linear>, pub height: Option<Linear>,
} }
impl Layout for ImageNode { impl InlineLevel for ImageNode {
fn layout( fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
let img = ctx.images.get(self.id); let img = ctx.images.get(self.id);
let pixel_size = Spec::new(img.width() as f64, img.height() as f64); let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
let pixel_ratio = pixel_size.x / pixel_size.y; let pixel_ratio = pixel_size.x / pixel_size.y;
let width = self.width.map(|w| w.resolve(regions.base.w)); let width = self.width.map(|w| w.resolve(base.w));
let height = self.height.map(|w| w.resolve(regions.base.h)); let height = self.height.map(|w| w.resolve(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) { let size = match (width, height) {
(Some(width), Some(height)) => Size::new(width, height), (Some(width), Some(height)) => Size::new(width, height),
(Some(width), None) => Size::new(width, width / pixel_ratio), (Some(width), None) => Size::new(width, width / pixel_ratio),
(None, Some(height)) => Size::new(height * pixel_ratio, height), (None, Some(height)) => Size::new(height * pixel_ratio, height),
(None, None) => { (None, None) => {
cts.exact.x = Some(regions.current.w); if space.is_finite() {
if regions.current.w.is_finite() {
// Fit to width. // Fit to width.
Size::new(regions.current.w, regions.current.w / pixel_ratio) Size::new(space, space / pixel_ratio)
} else { } else {
// Unbounded width, we have to make up something, // Unbounded width, we have to make up something,
// so it is 1pt per pixel. // so it is 1pt per pixel.
@ -48,12 +40,12 @@ impl Layout for ImageNode {
let mut frame = Frame::new(size, size.h); let mut frame = Frame::new(size, size.h);
frame.push(Point::zero(), Element::Image(self.id, size)); frame.push(Point::zero(), Element::Image(self.id, size));
vec![frame.constrain(cts)] frame
} }
} }
impl From<ImageNode> for LayoutNode { impl From<ImageNode> for InlineNode {
fn from(image: ImageNode) -> Self { fn from(node: ImageNode) -> Self {
Self::new(image) Self::new(node)
} }
} }

View File

@ -230,7 +230,7 @@ impl FramesEntry {
let mut iter = regions.iter(); let mut iter = regions.iter();
self.frames.iter().all(|frame| { self.frames.iter().all(|frame| {
iter.next().map_or(false, |(current, base)| { iter.next().map_or(false, |(current, base)| {
frame.constraints.check(current, base, regions.expand) frame.cts.check(current, base, regions.expand)
}) })
}) })
} }
@ -400,7 +400,7 @@ mod tests {
fn empty_frames() -> Vec<Constrained<Rc<Frame>>> { fn empty_frames() -> Vec<Constrained<Rc<Frame>>> {
vec![Constrained { vec![Constrained {
item: Rc::new(Frame::default()), item: Rc::new(Frame::default()),
constraints: Constraints::new(Spec::splat(false)), cts: Constraints::new(Spec::splat(false)),
}] }]
} }

View File

@ -10,10 +10,8 @@ mod pad;
mod par; mod par;
mod regions; mod regions;
mod shape; mod shape;
mod shaping;
mod spacing;
mod stack; mod stack;
mod tree; mod text;
pub use self::image::*; pub use self::image::*;
pub use constraints::*; pub use constraints::*;
@ -25,12 +23,10 @@ pub use pad::*;
pub use par::*; pub use par::*;
pub use regions::*; pub use regions::*;
pub use shape::*; pub use shape::*;
pub use shaping::*;
pub use spacing::*;
pub use stack::*; pub use stack::*;
pub use tree::*; pub use text::*;
use std::fmt::Debug; use std::fmt::{self, Debug, Formatter};
use std::rc::Rc; use std::rc::Rc;
use crate::font::FontStore; use crate::font::FontStore;
@ -39,10 +35,20 @@ use crate::image::ImageStore;
use crate::util::OptionExt; use crate::util::OptionExt;
use crate::Context; use crate::Context;
/// Layout a tree into a collection of frames. #[cfg(feature = "layout-cache")]
pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> { use {
fxhash::FxHasher64,
std::any::Any,
std::hash::{Hash, Hasher},
};
/// 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,
{
let mut ctx = LayoutContext::new(ctx); let mut ctx = LayoutContext::new(ctx);
tree.layout(&mut ctx) node.layout(&mut ctx)
} }
/// The context for layouting. /// The context for layouting.
@ -73,12 +79,198 @@ impl<'a> LayoutContext<'a> {
} }
} }
/// Layout a node. /// Page-level nodes directly produce frames representing pages.
pub trait Layout: Debug { ///
/// Layout the node into the given regions. /// 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( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>>; ) -> Vec<Constrained<Rc<Frame>>>;
} }
/// A dynamic [block-level](BlockLevel) layouting node.
#[derive(Clone)]
pub struct BlockNode {
node: Rc<dyn BlockLevel>,
#[cfg(feature = "layout-cache")]
hash: u64,
}
impl BlockNode {
/// Create a new dynamic node from any block-level node.
#[cfg(not(feature = "layout-cache"))]
pub fn new<T>(node: T) -> Self
where
T: BlockLevel + 'static,
{
Self { node: Rc::new(node) }
}
/// Create a new dynamic node from any block-level node.
#[cfg(feature = "layout-cache")]
pub fn new<T>(node: T) -> Self
where
T: BlockLevel + Hash + 'static,
{
Self {
hash: hash_node(&node),
node: Rc::new(node),
}
}
}
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
})
}
}
#[cfg(feature = "layout-cache")]
impl Hash for BlockNode {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
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;
}
/// A dynamic [inline-level](InlineLevel) layouting node.
#[derive(Clone)]
pub struct InlineNode {
node: Rc<dyn InlineLevel>,
#[cfg(feature = "layout-cache")]
hash: u64,
}
impl InlineNode {
/// Create a new dynamic node from any inline-level node.
#[cfg(not(feature = "layout-cache"))]
pub fn new<T>(node: T) -> Self
where
T: InlineLevel + 'static,
{
Self { node: Rc::new(node) }
}
/// Create a new dynamic node from any inline-level node.
#[cfg(feature = "layout-cache")]
pub fn new<T>(node: T) -> Self
where
T: InlineLevel + Hash + 'static,
{
Self {
hash: hash_node(&node),
node: Rc::new(node),
}
}
}
impl InlineLevel for InlineNode {
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
self.node.layout(ctx, space, base)
}
}
#[cfg(feature = "layout-cache")]
impl Hash for InlineNode {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
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 {
let mut state = FxHasher64::default();
node.type_id().hash(&mut state);
node.hash(&mut state);
state.finish()
}

View File

@ -7,10 +7,10 @@ pub struct PadNode {
/// The amount of padding. /// The amount of padding.
pub padding: Sides<Linear>, pub padding: Sides<Linear>,
/// The child node whose sides to pad. /// The child node whose sides to pad.
pub child: LayoutNode, pub child: BlockNode,
} }
impl Layout for PadNode { impl BlockLevel for PadNode {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
@ -22,7 +22,7 @@ impl Layout for PadNode {
&regions.map(|size| size - self.padding.resolve(size).size()), &regions.map(|size| size - self.padding.resolve(size).size()),
); );
for (Constrained { item: frame, constraints }, (current, base)) in for (Constrained { item: frame, cts }, (current, base)) in
frames.iter_mut().zip(regions.iter()) frames.iter_mut().zip(regions.iter())
{ {
fn solve_axis(length: Length, padding: Linear) -> Length { fn solve_axis(length: Length, padding: Linear) -> Length {
@ -46,7 +46,7 @@ impl Layout for PadNode {
new.push_frame(origin, prev); new.push_frame(origin, prev);
// Inflate min and max contraints by the padding. // Inflate min and max contraints by the padding.
for spec in [&mut constraints.min, &mut constraints.max] { for spec in [&mut cts.min, &mut cts.max] {
if let Some(x) = spec.x.as_mut() { if let Some(x) = spec.x.as_mut() {
*x += padding.size().w; *x += padding.size().w;
} }
@ -56,18 +56,18 @@ impl Layout for PadNode {
} }
// Set exact and base constraints if the child had them. // Set exact and base constraints if the child had them.
constraints.exact.x.and_set(Some(current.w)); cts.exact.x.and_set(Some(current.w));
constraints.exact.y.and_set(Some(current.h)); cts.exact.y.and_set(Some(current.h));
constraints.base.x.and_set(Some(base.w)); cts.base.x.and_set(Some(base.w));
constraints.base.y.and_set(Some(base.h)); cts.base.y.and_set(Some(base.h));
// Also set base constraints if the padding is relative. // Also set base constraints if the padding is relative.
if self.padding.left.is_relative() || self.padding.right.is_relative() { if self.padding.left.is_relative() || self.padding.right.is_relative() {
constraints.base.x = Some(base.w); cts.base.x = Some(base.w);
} }
if self.padding.top.is_relative() || self.padding.bottom.is_relative() { if self.padding.top.is_relative() || self.padding.bottom.is_relative() {
constraints.base.y = Some(base.h); cts.base.y = Some(base.h);
} }
} }
@ -75,8 +75,8 @@ impl Layout for PadNode {
} }
} }
impl From<PadNode> for LayoutNode { impl From<PadNode> for BlockNode {
fn from(pad: PadNode) -> Self { fn from(node: PadNode) -> Self {
Self::new(pad) Self::new(node)
} }
} }

View File

@ -18,7 +18,7 @@ pub struct ParNode {
/// The inline direction of this paragraph. /// The inline direction of this paragraph.
pub dir: Dir, pub dir: Dir,
/// The spacing to insert between each line. /// The spacing to insert between each line.
pub line_spacing: Length, pub leading: Length,
/// The nodes to be arranged in a paragraph. /// The nodes to be arranged in a paragraph.
pub children: Vec<ParChild>, pub children: Vec<ParChild>,
} }
@ -31,10 +31,10 @@ pub enum ParChild {
/// A run of text and how to align it in its line. /// A run of text and how to align it in its line.
Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>), Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>),
/// Any child node and how to align it in its line. /// Any child node and how to align it in its line.
Any(LayoutNode, Align, Vec<Decoration>), Any(InlineNode, Align, Vec<Decoration>),
} }
impl Layout for ParNode { impl BlockLevel for ParNode {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
@ -88,9 +88,9 @@ impl ParNode {
} }
} }
impl From<ParNode> for LayoutNode { impl From<ParNode> for BlockNode {
fn from(par: ParNode) -> Self { fn from(node: ParNode) -> Self {
Self::new(par) Self::new(node)
} }
} }
@ -110,7 +110,7 @@ struct ParLayouter<'a> {
/// The top-level direction. /// The top-level direction.
dir: Dir, dir: Dir,
/// The line spacing. /// The line spacing.
line_spacing: Length, leading: Length,
/// Bidirectional text embedding levels for the paragraph. /// Bidirectional text embedding levels for the paragraph.
bidi: BidiInfo<'a>, bidi: BidiInfo<'a>,
/// Layouted children and separated text runs. /// Layouted children and separated text runs.
@ -143,14 +143,14 @@ impl<'a> ParLayouter<'a> {
// TODO: Also split by language and script. // TODO: Also split by language and script.
for (subrange, dir) in split_runs(&bidi, range) { for (subrange, dir) in split_runs(&bidi, range) {
let text = &bidi.text[subrange.clone()]; let text = &bidi.text[subrange.clone()];
let shaped = shape(ctx, text, dir, style); let shaped = shape(ctx, text, style, dir);
items.push(ParItem::Text(shaped, *align, decos)); items.push(ParItem::Text(shaped, *align, decos));
ranges.push(subrange); ranges.push(subrange);
} }
} }
ParChild::Any(node, align, decos) => { ParChild::Any(node, align, decos) => {
let frame = node.layout(ctx, regions).remove(0); let frame = node.layout(ctx, regions.current.w, regions.base);
items.push(ParItem::Frame(frame.item, *align, decos)); items.push(ParItem::Frame(frame, *align, decos));
ranges.push(range); ranges.push(range);
} }
} }
@ -158,7 +158,7 @@ impl<'a> ParLayouter<'a> {
Self { Self {
dir: par.dir, dir: par.dir,
line_spacing: par.line_spacing, leading: par.leading,
bidi, bidi,
items, items,
ranges, ranges,
@ -171,7 +171,7 @@ impl<'a> ParLayouter<'a> {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: Regions, regions: Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
let mut stack = LineStack::new(self.line_spacing, regions); let mut stack = LineStack::new(self.leading, regions);
// The current line attempt. // The current line attempt.
// Invariant: Always fits into `stack.regions.current`. // Invariant: Always fits into `stack.regions.current`.
@ -196,18 +196,18 @@ impl<'a> ParLayouter<'a> {
// the width of the region must not fit the width of the // the width of the region must not fit the width of the
// tried line. // tried line.
if !stack.regions.current.w.fits(line.size.w) { if !stack.regions.current.w.fits(line.size.w) {
stack.constraints.max.x.set_min(line.size.w); stack.cts.max.x.set_min(line.size.w);
} }
// Same as above, but for height. // Same as above, but for height.
if !stack.regions.current.h.fits(line.size.h) { if !stack.regions.current.h.fits(line.size.h) {
let too_large = stack.size.h + self.line_spacing + line.size.h; let too_large = stack.size.h + self.leading + line.size.h;
stack.constraints.max.y.set_min(too_large); stack.cts.max.y.set_min(too_large);
} }
stack.push(last_line); stack.push(last_line);
stack.constraints.min.y = Some(stack.size.h); stack.cts.min.y = Some(stack.size.h);
start = last_end; start = last_end;
line = LineLayout::new(ctx, &self, start .. end); line = LineLayout::new(ctx, &self, start .. end);
} }
@ -223,8 +223,8 @@ impl<'a> ParLayouter<'a> {
// Again, the line must not fit. It would if the space taken up // Again, the line must not fit. It would if the space taken up
// plus the line height would fit, therefore the constraint // plus the line height would fit, therefore the constraint
// below. // below.
let too_large = stack.size.h + self.line_spacing + line.size.h; let too_large = stack.size.h + self.leading + line.size.h;
stack.constraints.max.y.set_min(too_large); stack.cts.max.y.set_min(too_large);
stack.finish_region(ctx); stack.finish_region(ctx);
} }
@ -247,18 +247,18 @@ impl<'a> ParLayouter<'a> {
} }
} }
stack.constraints.min.y = Some(stack.size.h); stack.cts.min.y = Some(stack.size.h);
} else { } else {
// Otherwise, the line fits both horizontally and vertically // Otherwise, the line fits both horizontally and vertically
// and we remember it. // and we remember it.
stack.constraints.min.x.set_max(line.size.w); stack.cts.min.x.set_max(line.size.w);
last = Some((line, end)); last = Some((line, end));
} }
} }
if let Some((line, _)) = last { if let Some((line, _)) = last {
stack.push(line); stack.push(line);
stack.constraints.min.y = Some(stack.size.h); stack.cts.min.y = Some(stack.size.h);
} }
stack.finish(ctx) stack.finish(ctx)
@ -292,7 +292,7 @@ enum ParItem<'a> {
/// A shaped text run with consistent direction. /// A shaped text run with consistent direction.
Text(ShapedText<'a>, Align, &'a [Decoration]), Text(ShapedText<'a>, Align, &'a [Decoration]),
/// A layouted child node. /// A layouted child node.
Frame(Rc<Frame>, Align, &'a [Decoration]), Frame(Frame, Align, &'a [Decoration]),
} }
impl ParItem<'_> { impl ParItem<'_> {
@ -463,10 +463,10 @@ impl<'a> LineLayout<'a> {
ParItem::Frame(ref frame, align, decos) => { ParItem::Frame(ref frame, align, decos) => {
let mut frame = frame.clone(); let mut frame = frame.clone();
for deco in decos { for deco in decos {
deco.apply(ctx, Rc::make_mut(&mut frame)); deco.apply(ctx, &mut frame);
} }
let pos = position(&frame, align); let pos = position(&frame, align);
output.push_frame(pos, frame); output.merge_frame(pos, frame);
} }
} }
} }
@ -522,23 +522,23 @@ impl<'a> LineLayout<'a> {
/// Stacks lines on top of each other. /// Stacks lines on top of each other.
struct LineStack<'a> { struct LineStack<'a> {
line_spacing: Length, leading: Length,
full: Size, full: Size,
regions: Regions, regions: Regions,
size: Size, size: Size,
lines: Vec<LineLayout<'a>>, lines: Vec<LineLayout<'a>>,
finished: Vec<Constrained<Rc<Frame>>>, finished: Vec<Constrained<Rc<Frame>>>,
constraints: Constraints, cts: Constraints,
overflowing: bool, overflowing: bool,
} }
impl<'a> LineStack<'a> { impl<'a> LineStack<'a> {
/// Create an empty line stack. /// Create an empty line stack.
fn new(line_spacing: Length, regions: Regions) -> Self { fn new(leading: Length, regions: Regions) -> Self {
Self { Self {
line_spacing, leading,
full: regions.current, full: regions.current,
constraints: Constraints::new(regions.expand), cts: Constraints::new(regions.expand),
regions, regions,
size: Size::zero(), size: Size::zero(),
lines: vec![], lines: vec![],
@ -549,12 +549,12 @@ impl<'a> LineStack<'a> {
/// Push a new line into the stack. /// Push a new line into the stack.
fn push(&mut self, line: LineLayout<'a>) { fn push(&mut self, line: LineLayout<'a>) {
self.regions.current.h -= line.size.h + self.line_spacing; self.regions.current.h -= line.size.h + self.leading;
self.size.w.set_max(line.size.w); self.size.w.set_max(line.size.w);
self.size.h += line.size.h; self.size.h += line.size.h;
if !self.lines.is_empty() { if !self.lines.is_empty() {
self.size.h += self.line_spacing; self.size.h += self.leading;
} }
self.lines.push(line); self.lines.push(line);
@ -564,13 +564,13 @@ impl<'a> LineStack<'a> {
fn finish_region(&mut self, ctx: &LayoutContext) { fn finish_region(&mut self, ctx: &LayoutContext) {
if self.regions.expand.x { if self.regions.expand.x {
self.size.w = self.regions.current.w; self.size.w = self.regions.current.w;
self.constraints.exact.x = Some(self.regions.current.w); self.cts.exact.x = Some(self.regions.current.w);
} }
if self.overflowing { if self.overflowing {
self.constraints.min.y = None; self.cts.min.y = None;
self.constraints.max.y = None; self.cts.max.y = None;
self.constraints.exact = self.full.to_spec().map(Some); self.cts.exact = self.full.to_spec().map(Some);
} }
let mut output = Frame::new(self.size, self.size.h); let mut output = Frame::new(self.size, self.size.h);
@ -586,14 +586,14 @@ impl<'a> LineStack<'a> {
first = false; first = false;
} }
offset += frame.size.h + self.line_spacing; offset += frame.size.h + self.leading;
output.merge_frame(pos, frame); output.merge_frame(pos, frame);
} }
self.finished.push(output.constrain(self.constraints)); self.finished.push(output.constrain(self.cts));
self.regions.next(); self.regions.next();
self.full = self.regions.current; self.full = self.regions.current;
self.constraints = Constraints::new(self.regions.expand); self.cts = Constraints::new(self.regions.expand);
self.size = Size::zero(); self.size = Size::zero();
} }

View File

@ -1,6 +1,7 @@
use std::f64::consts::SQRT_2; use std::f64::consts::SQRT_2;
use super::*; use super::*;
use crate::util::RcExt;
/// Places its child into a sizable and fillable shape. /// Places its child into a sizable and fillable shape.
#[derive(Debug)] #[derive(Debug)]
@ -15,7 +16,7 @@ pub struct ShapeNode {
/// How to fill the shape, if at all. /// How to fill the shape, if at all.
pub fill: Option<Paint>, pub fill: Option<Paint>,
/// The child node to place into the shape, if any. /// The child node to place into the shape, if any.
pub child: Option<LayoutNode>, pub child: Option<BlockNode>,
} }
/// The type of a shape. /// The type of a shape.
@ -31,40 +32,15 @@ pub enum ShapeKind {
Ellipse, Ellipse,
} }
impl Layout for ShapeNode { impl InlineLevel for ShapeNode {
fn layout( fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
// Resolve width and height relative to the region's base. // Resolve width and height relative to the region's base.
let width = self.width.map(|w| w.resolve(regions.base.w)); let width = self.width.map(|w| w.resolve(base.w));
let height = self.height.map(|h| h.resolve(regions.base.h)); let height = self.height.map(|h| h.resolve(base.h));
// Generate constraints.
let 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);
}
cts
};
// Layout. // Layout.
let mut frames = if let Some(child) = &self.child { let mut frame = if let Some(child) = &self.child {
let mut node: &dyn Layout = child; let mut node: &dyn BlockLevel = child;
let padded; let padded;
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) { if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
@ -79,14 +55,12 @@ impl Layout for ShapeNode {
// The "pod" is the region into which the child will be layouted. // The "pod" is the region into which the child will be layouted.
let mut pod = { let mut pod = {
let size = Size::new( let size =
width.unwrap_or(regions.current.w), Size::new(width.unwrap_or(space), height.unwrap_or(Length::inf()));
height.unwrap_or(regions.current.h),
);
let base = Size::new( let base = Size::new(
if width.is_some() { size.w } else { regions.base.w }, if width.is_some() { size.w } else { base.w },
if height.is_some() { size.h } else { regions.base.h }, if height.is_some() { size.h } else { base.h },
); );
let expand = Spec::new(width.is_some(), height.is_some()); let expand = Spec::new(width.is_some(), height.is_some());
@ -108,17 +82,15 @@ impl Layout for ShapeNode {
// Validate and set constraints. // Validate and set constraints.
assert_eq!(frames.len(), 1); assert_eq!(frames.len(), 1);
frames[0].constraints = constraints; Rc::take(frames.into_iter().next().unwrap().item)
frames
} else { } else {
// Resolve shape size. // Resolve shape size.
let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default()); let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default());
vec![Frame::new(size, size.h).constrain(constraints)] Frame::new(size, size.h)
}; };
// Add background shape if desired. // Add background shape if desired.
if let Some(fill) = self.fill { if let Some(fill) = self.fill {
let frame = Rc::make_mut(&mut frames[0].item);
let (pos, geometry) = match self.shape { let (pos, geometry) = match self.shape {
ShapeKind::Square | ShapeKind::Rect => { ShapeKind::Square | ShapeKind::Rect => {
(Point::zero(), Geometry::Rect(frame.size)) (Point::zero(), Geometry::Rect(frame.size))
@ -131,12 +103,12 @@ impl Layout for ShapeNode {
frame.prepend(pos, Element::Geometry(geometry, fill)); frame.prepend(pos, Element::Geometry(geometry, fill));
} }
frames frame
} }
} }
impl From<ShapeNode> for LayoutNode { impl From<ShapeNode> for InlineNode {
fn from(shape: ShapeNode) -> Self { fn from(node: ShapeNode) -> Self {
Self::new(shape) Self::new(node)
} }
} }

View File

@ -1,50 +0,0 @@
use super::*;
/// Spacing between other nodes.
#[derive(Debug)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct SpacingNode {
/// Which axis to space on.
pub axis: SpecAxis,
/// How much spacing to add.
pub amount: Linear,
}
impl Layout for SpacingNode {
fn layout(
&self,
_: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
let base = regions.base.get(self.axis);
let resolved = self.amount.resolve(base);
let limit = regions.current.get(self.axis);
// Generate constraints.
let mut cts = Constraints::new(regions.expand);
if self.amount.is_relative() {
cts.base.set(self.axis, Some(base));
}
// If the spacing fits into the region, any larger region would also do.
// If it was limited though, any change it region size might lead to
// different results.
if resolved < limit {
cts.min.set(self.axis, Some(resolved));
} else {
cts.exact.set(self.axis, Some(limit));
}
// Create frame with limited spacing size along spacing axis and zero
// extent along the other axis.
let mut size = Size::zero();
size.set(self.axis, resolved.min(limit));
vec![Frame::new(size, size.h).constrain(cts)]
}
}
impl From<SpacingNode> for LayoutNode {
fn from(spacing: SpacingNode) -> Self {
Self::new(spacing)
}
}

View File

@ -12,7 +12,16 @@ pub struct StackNode {
pub children: Vec<StackChild>, pub children: Vec<StackChild>,
} }
impl Layout for StackNode { /// A child of a stack node.
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub enum StackChild {
/// Spacing between other nodes.
Spacing(Linear),
/// Any block node and how to align it in the stack.
Any(BlockNode, Align),
}
impl BlockLevel for StackNode {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
@ -22,37 +31,18 @@ impl Layout for StackNode {
} }
} }
impl From<StackNode> for LayoutNode { impl From<StackNode> for BlockNode {
fn from(stack: StackNode) -> Self { fn from(node: StackNode) -> Self {
Self::new(stack) Self::new(node)
}
}
/// A child of a stack node.
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct StackChild {
/// The node itself.
pub node: LayoutNode,
/// How to align the node along the block axis.
pub align: Align,
}
impl StackChild {
/// Create a new stack child.
pub fn new(node: impl Into<LayoutNode>, align: Align) -> Self {
Self { node: node.into(), align }
}
/// Create a spacing stack child.
pub fn spacing(amount: impl Into<Linear>, axis: SpecAxis) -> Self {
Self::new(SpacingNode { amount: amount.into(), axis }, Align::Start)
} }
} }
impl Debug for StackChild { impl Debug for StackChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}: ", self.align)?; match self {
self.node.fmt(f) Self::Spacing(v) => write!(f, "Spacing({:?})", v),
Self::Any(node, _) => node.fmt(f),
}
} }
} }
@ -106,20 +96,41 @@ impl<'a> StackLayouter<'a> {
/// Layout all children. /// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> { fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
for child in &self.stack.children { for child in &self.stack.children {
let frames = child.node.layout(ctx, &self.regions); match *child {
StackChild::Spacing(amount) => self.space(amount),
StackChild::Any(ref node, align) => {
let frames = node.layout(ctx, &self.regions);
let len = frames.len(); let len = frames.len();
for (i, frame) in frames.into_iter().enumerate() { for (i, frame) in frames.into_iter().enumerate() {
self.push_frame(frame.item, child.align); self.push_frame(frame.item, align);
if i + 1 < len { if i + 1 < len {
self.finish_region(); self.finish_region();
} }
} }
} }
}
}
self.finish_region(); self.finish_region();
self.finished self.finished
} }
/// Add block-axis spacing into the current region.
fn space(&mut self, amount: Linear) {
// Resolve the linear.
let full = self.full.get(self.axis);
let resolved = amount.resolve(full);
// Cap the spacing to the remaining available space. This action does
// not directly affect the constraints because of the cap.
let remaining = self.regions.current.get_mut(self.axis);
let capped = resolved.min(*remaining);
// Grow our size and shrink the available space in the region.
self.used.block += capped;
*remaining -= capped;
}
/// Push a frame into the current region. /// Push a frame into the current region.
fn push_frame(&mut self, frame: Rc<Frame>, align: Align) { fn push_frame(&mut self, frame: Rc<Frame>, align: Align) {
// Grow our size. // Grow our size.

View File

@ -3,7 +3,7 @@ use std::ops::Range;
use rustybuzz::UnicodeBuffer; use rustybuzz::UnicodeBuffer;
use super::{Element, Frame, Glyph, LayoutContext, Text}; use super::*;
use crate::font::{Face, FaceId, FontVariant}; use crate::font::{Face, FaceId, FontVariant};
use crate::geom::{Dir, Em, Length, Point, Size}; use crate::geom::{Dir, Em, Length, Point, Size};
use crate::style::TextStyle; use crate::style::TextStyle;
@ -13,8 +13,8 @@ use crate::util::SliceExt;
pub fn shape<'a>( pub fn shape<'a>(
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
text: &'a str, text: &'a str,
dir: Dir,
style: &'a TextStyle, style: &'a TextStyle,
dir: Dir,
) -> ShapedText<'a> { ) -> ShapedText<'a> {
let mut glyphs = vec![]; let mut glyphs = vec![];
if !text.is_empty() { if !text.is_empty() {
@ -23,16 +23,15 @@ pub fn shape<'a>(
&mut glyphs, &mut glyphs,
0, 0,
text, text,
dir,
style.size, style.size,
style.variant(), style.variant(),
style.families(), style.families(),
None, None,
dir,
); );
} }
let (size, baseline) = measure(ctx, &glyphs, style); let (size, baseline) = measure(ctx, &glyphs, style);
ShapedText { ShapedText {
text, text,
dir, dir,
@ -134,7 +133,7 @@ impl<'a> ShapedText<'a> {
glyphs: Cow::Borrowed(glyphs), glyphs: Cow::Borrowed(glyphs),
} }
} else { } else {
shape(ctx, &self.text[text_range], self.dir, self.style) shape(ctx, &self.text[text_range], self.style, self.dir)
} }
} }
@ -209,11 +208,11 @@ fn shape_segment<'a>(
glyphs: &mut Vec<ShapedGlyph>, glyphs: &mut Vec<ShapedGlyph>,
base: usize, base: usize,
text: &str, text: &str,
dir: Dir,
size: Length, size: Length,
variant: FontVariant, variant: FontVariant,
mut families: impl Iterator<Item = &'a str> + Clone, mut families: impl Iterator<Item = &'a str> + Clone,
mut first_face: Option<FaceId>, mut first_face: Option<FaceId>,
dir: Dir,
) { ) {
// Select the font family. // Select the font family.
let (face_id, fallback) = loop { let (face_id, fallback) = loop {
@ -316,11 +315,11 @@ fn shape_segment<'a>(
glyphs, glyphs,
base + range.start, base + range.start,
&text[range], &text[range],
dir,
size, size,
variant, variant,
families.clone(), families.clone(),
first_face, first_face,
dir,
); );
face = ctx.fonts.get(face_id); face = ctx.fonts.get(face_id);

View File

@ -1,132 +0,0 @@
use std::fmt::{self, Debug, Formatter};
use super::*;
#[cfg(feature = "layout-cache")]
use {
fxhash::FxHasher64,
std::any::Any,
std::hash::{Hash, Hasher},
};
/// A tree of layout nodes.
pub struct LayoutTree {
/// Runs of pages with the same properties.
pub runs: Vec<PageRun>,
}
impl LayoutTree {
/// Layout the tree into a collection of frames.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
self.runs.iter().flat_map(|run| run.layout(ctx)).collect()
}
}
impl Debug for LayoutTree {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_list().entries(&self.runs).finish()
}
}
/// A run of pages that all have the same properties.
#[derive(Debug)]
pub struct PageRun {
/// The size of each page.
pub size: Size,
/// The layout node that produces the actual pages (typically a
/// [`StackNode`]).
pub child: LayoutNode,
}
impl PageRun {
/// Layout the page run.
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()
}
}
/// A dynamic layouting node.
#[derive(Clone)]
pub struct LayoutNode {
node: Rc<dyn Layout>,
#[cfg(feature = "layout-cache")]
hash: u64,
}
impl LayoutNode {
/// Create a new instance from any node that satisifies the required bounds.
#[cfg(not(feature = "layout-cache"))]
pub fn new<T>(node: T) -> Self
where
T: Layout + 'static,
{
Self { node: Rc::new(node) }
}
/// Create a new instance from any node that satisifies the required bounds.
#[cfg(feature = "layout-cache")]
pub fn new<T>(node: T) -> Self
where
T: Layout + Hash + 'static,
{
let hash = {
let mut state = FxHasher64::default();
node.type_id().hash(&mut state);
node.hash(&mut state);
state.finish()
};
Self { node: Rc::new(node), hash }
}
}
impl Layout for LayoutNode {
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.constraints).collect::<Vec<_>>()
);
panic!("constraints did not match regions they were created for");
}
ctx.layouts.insert(self.hash, entry);
frames
})
}
}
impl Debug for LayoutNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.node.fmt(f)
}
}
#[cfg(feature = "layout-cache")]
impl Hash for LayoutNode {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}

View File

@ -8,7 +8,7 @@
//! - **Evaluation:** The next step is to [evaluate] the markup. This produces a //! - **Evaluation:** The next step is to [evaluate] the markup. This produces a
//! [module], consisting of a scope of values that were exported by the code //! [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 //! 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 //! instantiated with a style to produce a layout tree, a high-level, fully
//! styled representation of the document. The nodes of this tree are //! styled representation of the document. The nodes of this tree are
//! self-contained and order-independent and thus much better suited for //! self-contained and order-independent and thus much better suited for
//! layouting than the raw markup. //! layouting than the raw markup.
@ -23,7 +23,6 @@
//! [markup]: syntax::Markup //! [markup]: syntax::Markup
//! [evaluate]: eval::eval //! [evaluate]: eval::eval
//! [module]: eval::Module //! [module]: eval::Module
//! [instantiated]: eval::Template::to_tree
//! [layout tree]: layout::LayoutTree //! [layout tree]: layout::LayoutTree
//! [layouted]: layout::layout //! [layouted]: layout::layout
//! [PDF]: export::pdf //! [PDF]: export::pdf
@ -53,7 +52,7 @@ use crate::font::FontStore;
use crate::image::ImageStore; use crate::image::ImageStore;
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
use crate::layout::{EvictionPolicy, LayoutCache}; use crate::layout::{EvictionPolicy, LayoutCache};
use crate::layout::{Frame, LayoutTree}; use crate::layout::{Frame, PageNode};
use crate::loading::Loader; use crate::loading::Loader;
use crate::source::{SourceId, SourceStore}; use crate::source::{SourceId, SourceStore};
use crate::style::Style; use crate::style::Style;
@ -110,10 +109,10 @@ impl Context {
eval::eval(self, id, &ast) eval::eval(self, id, &ast)
} }
/// Execute a source file and produce the resulting layout tree. /// Execute a source file and produce the resulting page nodes.
pub fn execute(&mut self, id: SourceId) -> TypResult<LayoutTree> { pub fn execute(&mut self, id: SourceId) -> TypResult<Vec<PageNode>> {
let module = self.evaluate(id)?; let module = self.evaluate(id)?;
Ok(module.template.to_tree(&self.style)) Ok(module.template.to_pages(&self.style))
} }
/// Typeset a source file into a collection of layouted frames. /// Typeset a source file into a collection of layouted frames.

View File

@ -220,16 +220,16 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
for child in &list { for child in &list {
match child { match child {
Child::Spacing(v) => { Child::Spacing(v) => {
children.push(StackChild::spacing(*v, dir.axis())); children.push(StackChild::Spacing(*v));
delayed = None; delayed = None;
} }
Child::Any(template) => { Child::Any(template) => {
if let Some(v) = delayed { if let Some(v) = delayed {
children.push(StackChild::spacing(v, dir.axis())); children.push(StackChild::Spacing(v));
} }
let node = template.to_stack(style); let node = template.to_stack(style).into();
children.push(StackChild::new(node, style.aligns.block)); children.push(StackChild::Any(node, style.aligns.block));
delayed = spacing; delayed = spacing;
} }
} }

View File

@ -110,18 +110,18 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// `par`: Configure paragraphs. /// `par`: Configure paragraphs.
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let par_spacing = args.named("spacing")?; let spacing = args.named("spacing")?;
let line_spacing = args.named("leading")?; let leading = args.named("leading")?;
ctx.template.modify(move |style| { ctx.template.modify(move |style| {
let par = style.par_mut(); let par = style.par_mut();
if let Some(par_spacing) = par_spacing { if let Some(spacing) = spacing {
par.par_spacing = par_spacing; par.spacing = spacing;
} }
if let Some(line_spacing) = line_spacing { if let Some(leading) = leading {
par.line_spacing = line_spacing; par.leading = leading;
} }
}); });

View File

@ -43,13 +43,13 @@ impl Style {
} }
/// The resolved line spacing. /// The resolved line spacing.
pub fn line_spacing(&self) -> Length { pub fn leading(&self) -> Length {
self.par.line_spacing.resolve(self.text.size) self.par.leading.resolve(self.text.size)
} }
/// The resolved paragraph spacing. /// The resolved paragraph spacing.
pub fn par_spacing(&self) -> Length { pub fn par_spacing(&self) -> Length {
self.par.par_spacing.resolve(self.text.size) self.par.spacing.resolve(self.text.size)
} }
} }
@ -105,16 +105,16 @@ impl Default for PageStyle {
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ParStyle { pub struct ParStyle {
/// The spacing between paragraphs (dependent on scaled font size). /// The spacing between paragraphs (dependent on scaled font size).
pub par_spacing: Linear, pub spacing: Linear,
/// The spacing between lines (dependent on scaled font size). /// The spacing between lines (dependent on scaled font size).
pub line_spacing: Linear, pub leading: Linear,
} }
impl Default for ParStyle { impl Default for ParStyle {
fn default() -> Self { fn default() -> Self {
Self { Self {
par_spacing: Relative::new(1.2).into(), spacing: Relative::new(1.2).into(),
line_spacing: Relative::new(0.65).into(), leading: Relative::new(0.65).into(),
} }
} }
} }

View File

@ -5,6 +5,8 @@ use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use super::RcExt;
/// An economical string with inline storage and clone-on-write semantics. /// An economical string with inline storage and clone-on-write semantics.
#[derive(Clone)] #[derive(Clone)]
pub struct EcoString(Repr); pub struct EcoString(Repr);
@ -293,10 +295,7 @@ impl From<EcoString> for String {
fn from(s: EcoString) -> Self { fn from(s: EcoString) -> Self {
match s.0 { match s.0 {
Repr::Small { .. } => s.as_str().to_owned(), Repr::Small { .. } => s.as_str().to_owned(),
Repr::Large(rc) => match Rc::try_unwrap(rc) { Repr::Large(rc) => Rc::take(rc),
Ok(string) => string,
Err(rc) => (*rc).clone(),
},
} }
} }
} }

View File

@ -10,6 +10,7 @@ use std::cell::RefMut;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::Range; use std::ops::Range;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::rc::Rc;
/// Additional methods for booleans. /// Additional methods for booleans.
pub trait BoolExt { pub trait BoolExt {
@ -67,6 +68,25 @@ impl<T> OptionExt<T> for Option<T> {
} }
} }
/// Additional methods for reference-counted pointers.
pub trait RcExt<T> {
/// Takes the inner value if there is exactly one strong reference and
/// clones it otherwise.
fn take(self) -> T;
}
impl<T> RcExt<T> for Rc<T>
where
T: Clone,
{
fn take(self) -> T {
match Rc::try_unwrap(self) {
Ok(v) => v,
Err(rc) => (*rc).clone(),
}
}
}
/// Additional methods for slices. /// Additional methods for slices.
pub trait SliceExt<T> { pub trait SliceExt<T> {
/// Split a slice into consecutive groups with the same key. /// Split a slice into consecutive groups with the same key.

View File

@ -17,7 +17,7 @@ use typst::geom::{
}; };
use typst::image::Image; use typst::image::Image;
#[cfg(feature = "layout-cache")] #[cfg(feature = "layout-cache")]
use typst::layout::LayoutTree; use typst::layout::PageNode;
use typst::layout::{layout, Element, Frame, Geometry, Text}; use typst::layout::{layout, Element, Frame, Geometry, Text};
use typst::loading::FsLoader; use typst::loading::FsLoader;
use typst::parse::Scanner; use typst::parse::Scanner;
@ -282,7 +282,7 @@ fn test_part(
fn test_incremental( fn test_incremental(
ctx: &mut Context, ctx: &mut Context,
i: usize, i: usize,
tree: &LayoutTree, tree: &[PageNode],
frames: &[Rc<Frame>], frames: &[Rc<Frame>],
) -> bool { ) -> bool {
let mut ok = true; let mut ok = true;