From 80a9b300d1acb8821ac0600aad3d2135ad9587bd Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 18 Jun 2021 13:01:55 +0200 Subject: [PATCH] Ref count the frames --- Cargo.toml | 2 +- src/export/pdf.rs | 17 +++++---- src/layout/background.rs | 13 +++++-- src/layout/fixed.rs | 2 +- src/layout/frame.rs | 80 +++++++++++++++++++++++++++++++++------ src/layout/grid.rs | 8 ++-- src/layout/incremental.rs | 6 +-- src/layout/mod.rs | 13 ++++--- src/layout/pad.rs | 14 +++---- src/layout/par.rs | 14 +++---- src/layout/stack.rs | 10 ++--- src/lib.rs | 2 +- src/library/image.rs | 2 +- tests/typeset.rs | 8 ++-- 14 files changed, 127 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 066ca4945..b18b0af56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ image = { version = "0.23", default-features = false, features = ["png", "jpeg"] miniz_oxide = "0.3" pdf-writer = { path = "../pdf-writer" } rustybuzz = { git = "https://github.com/laurmaedje/rustybuzz" } -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = ["derive", "rc"] } ttf-parser = "0.12" unicode-bidi = "0.3.5" unicode-xid = "0.2" diff --git a/src/export/pdf.rs b/src/export/pdf.rs index f939d8abd..0439702fb 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -3,6 +3,7 @@ use std::cmp::Eq; use std::collections::HashMap; use std::hash::Hash; +use std::rc::Rc; use image::{DynamicImage, GenericImageView, ImageFormat, ImageResult, Rgba}; use miniz_oxide::deflate; @@ -26,13 +27,13 @@ use crate::layout::{Element, Fill, Frame, Shape}; /// can be included in the PDF. /// /// Returns the raw bytes making up the PDF document. -pub fn pdf(cache: &Cache, frames: &[Frame]) -> Vec { +pub fn pdf(cache: &Cache, frames: &[Rc]) -> Vec { PdfExporter::new(cache, frames).write() } struct PdfExporter<'a> { writer: PdfWriter, - frames: &'a [Frame], + frames: &'a [Rc], cache: &'a Cache, refs: Refs, fonts: Remapper, @@ -40,7 +41,7 @@ struct PdfExporter<'a> { } impl<'a> PdfExporter<'a> { - fn new(cache: &'a Cache, frames: &'a [Frame]) -> Self { + fn new(cache: &'a Cache, frames: &'a [Rc]) -> Self { let mut writer = PdfWriter::new(1, 7); writer.set_indent(2); @@ -49,7 +50,7 @@ impl<'a> PdfExporter<'a> { let mut alpha_masks = 0; for frame in frames { - for (_, element) in &frame.elements { + for (_, element) in frame.elements() { match *element { Element::Text(ref shaped) => fonts.insert(shaped.face_id), Element::Geometry(_, _) => {} @@ -143,7 +144,7 @@ impl<'a> PdfExporter<'a> { let mut size = Length::zero(); let mut fill: Option = None; - for (pos, element) in &page.elements { + for (pos, element) in page.elements() { let x = pos.x.to_pt() as f32; let y = (page.size.height - pos.y).to_pt() as f32; @@ -496,12 +497,12 @@ struct FontRefs { impl Refs { const OBJECTS_PER_FONT: usize = 5; - fn new(frames: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self { + fn new(pages: usize, fonts: usize, images: usize, alpha_masks: usize) -> Self { let catalog = 1; let page_tree = catalog + 1; let pages_start = page_tree + 1; - let contents_start = pages_start + frames as i32; - let fonts_start = contents_start + frames as i32; + let contents_start = pages_start + pages as i32; + let fonts_start = contents_start + pages as i32; let images_start = fonts_start + (Self::OBJECTS_PER_FONT * fonts) as i32; let alpha_masks_start = images_start + images as i32; let end = alpha_masks_start + alpha_masks as i32; diff --git a/src/layout/background.rs b/src/layout/background.rs index 41138bdf4..8390a7568 100644 --- a/src/layout/background.rs +++ b/src/layout/background.rs @@ -23,10 +23,11 @@ impl Layout for BackgroundNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { let mut frames = self.child.layout(ctx, regions); - for frame in &mut frames { + let mut new = Frame::new(frame.size, frame.baseline); + let (point, shape) = match self.shape { BackgroundShape::Rect => (Point::zero(), Shape::Rect(frame.size)), BackgroundShape::Ellipse => { @@ -35,9 +36,13 @@ impl Layout for BackgroundNode { }; let element = Element::Geometry(shape, self.fill); - frame.item.elements.insert(0, (point, element)); - } + new.push(point, element); + let prev = std::mem::take(&mut frame.item); + new.push_frame(Point::zero(), prev); + + *Rc::make_mut(&mut frame.item) = new; + } frames } } diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs index 19876987d..732351682 100644 --- a/src/layout/fixed.rs +++ b/src/layout/fixed.rs @@ -16,7 +16,7 @@ impl Layout for FixedNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { let Regions { current, base, .. } = regions; let mut constraints = Constraints::new(regions.expand); constraints.set_base_using_linears(Spec::new(self.width, self.height), ®ions); diff --git a/src/layout/frame.rs b/src/layout/frame.rs index 5e5bcfe86..b6cf46455 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use serde::{Deserialize, Serialize}; use super::{Constrained, Constraints}; @@ -14,37 +16,93 @@ pub struct Frame { /// The baseline of the frame measured from the top. pub baseline: Length, /// The elements composing this layout. - pub elements: Vec<(Point, Element)>, + children: Vec<(Point, Child)>, +} + +/// An iterator over all elements in a frame, alongside with their positions. +#[derive(Debug, Clone)] +pub struct ElementIter<'a> { + stack: Vec<(usize, Point, &'a Frame)>, +} + +impl<'a> Iterator for ElementIter<'a> { + type Item = (Point, &'a Element); + + /// Get the next element, if any. + fn next(&mut self) -> Option { + let (cursor, offset, frame) = self.stack.last_mut()?; + match frame.children.get(*cursor) { + Some((pos, Child::Frame(f))) => { + let new_offset = *offset + *pos; + self.stack.push((0, new_offset, f.as_ref())); + self.next() + } + Some((pos, Child::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 Frame { /// Create a new, empty frame. pub fn new(size: Size, baseline: Length) -> Self { assert!(size.is_finite()); - Self { size, baseline, elements: vec![] } + Self { size, baseline, children: vec![] } } - /// Add an element at a position. + /// Add an element at a position in the foreground. pub fn push(&mut self, pos: Point, element: Element) { - self.elements.push((pos, element)); + self.children.push((pos, Child::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, Child::Element(element))) + } + + /// Add a frame element. + pub fn push_frame(&mut self, pos: Point, subframe: Rc) { + self.children.push((pos, Child::Frame(subframe))) } /// Add all elements of another frame, placing them relative to the given /// position. - pub fn push_frame(&mut self, pos: Point, subframe: Self) { - if pos == Point::zero() && self.elements.is_empty() { - self.elements = subframe.elements; + 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, element) in subframe.elements { - self.push(pos + subpos, element); + for (subpos, child) in subframe.children { + self.children.push((pos + subpos, child)); } } } /// Wraps the frame with constraints. - pub fn constrain(self, constraints: Constraints) -> Constrained { - Constrained { item: self, constraints } + pub fn constrain(self, constraints: Constraints) -> Constrained> { + Constrained { item: Rc::new(self), constraints } } + + /// Returns an iterator over all elements in the frame and its children. + pub fn elements(&self) -> ElementIter { + ElementIter { stack: vec![(0, Point::zero(), self)] } + } +} + +/// A frame can contain multiple children: elements or other frames, complete +/// with their children. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +enum Child { + Element(Element), + Frame(Rc), } /// The building block frames are composed of. diff --git a/src/layout/grid.rs b/src/layout/grid.rs index e426d695e..d2976502a 100644 --- a/src/layout/grid.rs +++ b/src/layout/grid.rs @@ -32,7 +32,7 @@ impl Layout for GridNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { // Prepare grid layout by unifying content and gutter tracks. let mut layouter = GridLayouter::new(self, regions.clone()); @@ -78,7 +78,7 @@ struct GridLayouter<'a> { /// Constraints for the active region. constraints: Constraints, /// Frames for finished regions. - finished: Vec>, + finished: Vec>>, } /// Produced by initial row layout, auto and linear rows are already finished, @@ -314,7 +314,7 @@ impl<'a> GridLayouter<'a> { } /// Layout the grid row-by-row. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec> { + fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { for y in 0 .. self.rows.len() { match self.rows[y] { TrackSizing::Auto => { @@ -497,7 +497,7 @@ impl<'a> GridLayouter<'a> { }; let main = frame.size.get(self.main); - output.push_frame(pos.to_point(self.main), frame); + output.merge_frame(pos.to_point(self.main), frame); pos.main += main; } diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs index 7857f33c6..a5c3cea35 100644 --- a/src/layout/incremental.rs +++ b/src/layout/incremental.rs @@ -26,7 +26,7 @@ impl LayoutCache { where F: FnMut(usize) -> bool, { - self.frames.retain(|_, b| f(b.level)); + self.frames.retain(|_, entry| f(entry.level)); } /// Amount of items in the cache. @@ -39,14 +39,14 @@ impl LayoutCache { /// Cached frames from past layouting. pub struct FramesEntry { /// The cached frames for a node. - pub frames: Vec>, + pub frames: Vec>>, /// How nested the frame was in the context is was originally appearing in. pub level: usize, } impl FramesEntry { /// Checks if the cached [`Frame`] is valid for the given regions. - pub fn check(&self, mut regions: Regions) -> Option>> { + pub fn check(&self, mut regions: Regions) -> Option>>> { for (i, frame) in self.frames.iter().enumerate() { if (i != 0 && !regions.next()) || !frame.constraints.check(®ions) { return None; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 28e83da92..f1ae3e2af 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -23,6 +23,7 @@ pub use stack::*; use std::any::Any; use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; +use std::rc::Rc; use fxhash::FxHasher64; @@ -31,7 +32,7 @@ use crate::geom::*; use crate::loading::Loader; /// Layout a tree into a collection of frames. -pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec { +pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec> { tree.layout(&mut LayoutContext { loader, cache, level: 0 }) } @@ -44,7 +45,7 @@ pub struct Tree { impl Tree { /// Layout the tree into a collection of frames. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { + pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { self.runs.iter().flat_map(|run| run.layout(ctx)).collect() } } @@ -61,7 +62,7 @@ pub struct PageRun { impl PageRun { /// Layout the page run. - pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { + pub fn layout(&self, ctx: &mut LayoutContext) -> Vec> { // When one of the lengths is infinite the page fits its content along // that axis. let Size { width, height } = self.size; @@ -97,7 +98,7 @@ impl Layout for AnyNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { ctx.level += 1; let frames = ctx .cache @@ -179,7 +180,7 @@ pub trait Layout { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec>; + ) -> Vec>>; } /// The context for layouting. @@ -188,7 +189,7 @@ pub struct LayoutContext<'a> { pub loader: &'a mut dyn Loader, /// A cache for loaded fonts and artifacts from past layouting. pub cache: &'a mut Cache, - /// How deeply nested is the current layout tree position. + /// How deeply nested the current layout tree position is. pub level: usize, } diff --git a/src/layout/pad.rs b/src/layout/pad.rs index c212ec8a6..9d432e797 100644 --- a/src/layout/pad.rs +++ b/src/layout/pad.rs @@ -14,21 +14,19 @@ impl Layout for PadNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { let mut regions = regions.map(|size| size - self.padding.resolve(size).size()); let mut frames = self.child.layout(ctx, ®ions); + for frame in &mut frames { let padded = solve(self.padding, frame.size); let padding = self.padding.resolve(padded); let origin = Point::new(padding.left, padding.top); - frame.item.size = padded; - frame.item.baseline += origin.y; - - for (point, _) in &mut frame.item.elements { - *point += origin; - } + let mut new = Frame::new(padded, frame.baseline + origin.y); + let prev = std::mem::take(&mut frame.item); + new.push_frame(origin, prev); frame.constraints.mutate(padding.size() * -1.0); @@ -40,8 +38,8 @@ impl Layout for PadNode { } regions.next(); + *Rc::make_mut(&mut frame.item) = new; } - frames } } diff --git a/src/layout/par.rs b/src/layout/par.rs index 814f88edd..2208f9ee3 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -37,7 +37,7 @@ impl Layout for ParNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { // Collect all text into one string used for BiDi analysis. let text = self.collect_text(); @@ -169,7 +169,7 @@ impl<'a> ParLayouter<'a> { self, ctx: &mut LayoutContext, regions: Regions, - ) -> Vec> { + ) -> Vec>> { let mut stack = LineStack::new(self.line_spacing, regions); // The current line attempt. @@ -277,7 +277,7 @@ enum ParItem<'a> { /// A shaped text run with consistent direction. Text(ShapedText<'a>, Align), /// A layouted child node. - Frame(Frame, Align), + Frame(Rc, Align), } impl ParItem<'_> { @@ -306,7 +306,7 @@ struct LineStack<'a> { regions: Regions, size: Size, lines: Vec>, - finished: Vec>, + finished: Vec>>, constraints: Constraints, } @@ -357,7 +357,7 @@ impl<'a> LineStack<'a> { } offset += frame.size.height + self.line_spacing; - output.push_frame(pos, frame); + output.merge_frame(pos, frame); } self.finished.push(output.constrain(self.constraints)); @@ -367,7 +367,7 @@ impl<'a> LineStack<'a> { } /// Finish the last region and return the built frames. - fn finish(mut self, ctx: &LayoutContext) -> Vec> { + fn finish(mut self, ctx: &LayoutContext) -> Vec>> { self.finish_region(ctx); self.finished } @@ -504,7 +504,7 @@ impl<'a> LineLayout<'a> { } ParItem::Text(ref shaped, align) => { ruler = ruler.max(align); - shaped.build(ctx) + Rc::new(shaped.build(ctx)) } ParItem::Frame(ref frame, align) => { ruler = ruler.max(align); diff --git a/src/layout/stack.rs b/src/layout/stack.rs index b45adcb2a..2a0b28060 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -32,7 +32,7 @@ impl Layout for StackNode { &self, ctx: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { StackLayouter::new(self, regions.clone()).layout(ctx) } } @@ -64,9 +64,9 @@ struct StackLayouter<'a> { constraints: Constraints, /// Offset, alignment and frame for all children that fit into the current /// region. The exact positions are not known yet. - frames: Vec<(Length, Gen, Frame)>, + frames: Vec<(Length, Gen, Rc)>, /// Finished frames for previous regions. - finished: Vec>, + finished: Vec>>, } impl<'a> StackLayouter<'a> { @@ -98,7 +98,7 @@ impl<'a> StackLayouter<'a> { } /// Layout all children. - fn layout(mut self, ctx: &mut LayoutContext) -> Vec> { + fn layout(mut self, ctx: &mut LayoutContext) -> Vec>> { for child in &self.stack.children { match *child { StackChild::Spacing(amount) => self.space(amount), @@ -133,7 +133,7 @@ impl<'a> StackLayouter<'a> { /// Push a frame into the current or next fitting region, finishing regions /// if necessary. - fn push_frame(&mut self, frame: Frame, aligns: Gen) { + fn push_frame(&mut self, frame: Rc, aligns: Gen) { let size = frame.size.to_gen(self.main); // Don't allow `Start` after `End` in the same region. diff --git a/src/lib.rs b/src/lib.rs index 3c50230f6..053888f4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ pub fn typeset( src: &str, scope: &Scope, state: State, -) -> Pass> { +) -> Pass>> { let parsed = parse::parse(src); let evaluated = eval::eval(loader, cache, path, Rc::new(parsed.output), scope); let executed = exec::exec(&evaluated.output.template, state); diff --git a/src/library/image.rs b/src/library/image.rs index f8c04cbe8..54fa54c9e 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -58,7 +58,7 @@ impl Layout for ImageNode { &self, _: &mut LayoutContext, regions: &Regions, - ) -> Vec> { + ) -> Vec>> { let Regions { current, base, .. } = regions; let mut constraints = Constraints::new(regions.expand); constraints.set_base_using_linears(Spec::new(self.width, self.height), regions); diff --git a/tests/typeset.rs b/tests/typeset.rs index c6d3e8f10..8fc7712a0 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -208,7 +208,7 @@ fn test_part( i: usize, compare_ref: bool, lines: u32, -) -> (bool, bool, Vec) { +) -> (bool, bool, Vec>) { let map = LineMap::new(src); let (local_compare_ref, ref_diags) = parse_metadata(src, &map); let compare_ref = local_compare_ref.unwrap_or(compare_ref); @@ -345,7 +345,7 @@ fn print_diag(diag: &Diag, map: &LineMap, lines: u32) { println!("{}: {}-{}: {}", diag.level, start, end, diag.message); } -fn draw(cache: &Cache, frames: &[Frame], dpi: f32) -> Pixmap { +fn draw(cache: &Cache, frames: &[Rc], dpi: f32) -> Pixmap { let pad = Length::pt(5.0); let height = pad + frames.iter().map(|l| l.size.height + pad).sum::(); @@ -381,8 +381,8 @@ fn draw(cache: &Cache, frames: &[Frame], dpi: f32) -> Pixmap { None, ); - for (pos, element) in &frame.elements { - let global = origin + *pos; + for (pos, element) in frame.elements() { + let global = origin + pos; let x = global.x.to_pt() as f32; let y = global.y.to_pt() as f32; let ts = ts.pre_translate(x, y);