Ref count the frames

This commit is contained in:
Martin 2021-06-18 13:01:55 +02:00
parent 7db78d83be
commit 80a9b300d1
14 changed files with 127 additions and 64 deletions

View File

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

View File

@ -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<u8> {
pub fn pdf(cache: &Cache, frames: &[Rc<Frame>]) -> Vec<u8> {
PdfExporter::new(cache, frames).write()
}
struct PdfExporter<'a> {
writer: PdfWriter,
frames: &'a [Frame],
frames: &'a [Rc<Frame>],
cache: &'a Cache,
refs: Refs,
fonts: Remapper<FaceId>,
@ -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<Frame>]) -> 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<Fill> = 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;

View File

@ -23,10 +23,11 @@ impl Layout for BackgroundNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
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
}
}

View File

@ -16,7 +16,7 @@ impl Layout for FixedNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
let Regions { current, base, .. } = regions;
let mut constraints = Constraints::new(regions.expand);
constraints.set_base_using_linears(Spec::new(self.width, self.height), &regions);

View File

@ -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<Self::Item> {
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>) {
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<Self> {
Constrained { item: self, constraints }
pub fn constrain(self, constraints: Constraints) -> Constrained<Rc<Self>> {
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<Frame>),
}
/// The building block frames are composed of.

View File

@ -32,7 +32,7 @@ impl Layout for GridNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
// 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<Constrained<Frame>>,
finished: Vec<Constrained<Rc<Frame>>>,
}
/// 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<Constrained<Frame>> {
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
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;
}

View File

@ -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<Constrained<Frame>>,
pub frames: Vec<Constrained<Rc<Frame>>>,
/// 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<Vec<Constrained<Frame>>> {
pub fn check(&self, mut regions: Regions) -> Option<Vec<Constrained<Rc<Frame>>>> {
for (i, frame) in self.frames.iter().enumerate() {
if (i != 0 && !regions.next()) || !frame.constraints.check(&regions) {
return None;

View File

@ -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<Frame> {
pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec<Rc<Frame>> {
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<Frame> {
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
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<Frame> {
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 Size { width, height } = self.size;
@ -97,7 +98,7 @@ impl Layout for AnyNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
ctx.level += 1;
let frames = ctx
.cache
@ -179,7 +180,7 @@ pub trait Layout {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>>;
) -> Vec<Constrained<Rc<Frame>>>;
}
/// 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,
}

View File

@ -14,21 +14,19 @@ impl Layout for PadNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
let mut regions = regions.map(|size| size - self.padding.resolve(size).size());
let mut frames = self.child.layout(ctx, &regions);
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
}
}

View File

@ -37,7 +37,7 @@ impl Layout for ParNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
// 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<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
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<Frame>, Align),
}
impl ParItem<'_> {
@ -306,7 +306,7 @@ struct LineStack<'a> {
regions: Regions,
size: Size,
lines: Vec<LineLayout<'a>>,
finished: Vec<Constrained<Frame>>,
finished: Vec<Constrained<Rc<Frame>>>,
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<Constrained<Frame>> {
fn finish(mut self, ctx: &LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
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);

View File

@ -32,7 +32,7 @@ impl Layout for StackNode {
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
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<Align>, Frame)>,
frames: Vec<(Length, Gen<Align>, Rc<Frame>)>,
/// Finished frames for previous regions.
finished: Vec<Constrained<Frame>>,
finished: Vec<Constrained<Rc<Frame>>>,
}
impl<'a> StackLayouter<'a> {
@ -98,7 +98,7 @@ impl<'a> StackLayouter<'a> {
}
/// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Frame>> {
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
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<Align>) {
fn push_frame(&mut self, frame: Rc<Frame>, aligns: Gen<Align>) {
let size = frame.size.to_gen(self.main);
// Don't allow `Start` after `End` in the same region.

View File

@ -83,7 +83,7 @@ pub fn typeset(
src: &str,
scope: &Scope,
state: State,
) -> Pass<Vec<Frame>> {
) -> Pass<Vec<Rc<Frame>>> {
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);

View File

@ -58,7 +58,7 @@ impl Layout for ImageNode {
&self,
_: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Frame>> {
) -> Vec<Constrained<Rc<Frame>>> {
let Regions { current, base, .. } = regions;
let mut constraints = Constraints::new(regions.expand);
constraints.set_base_using_linears(Spec::new(self.width, self.height), regions);

View File

@ -208,7 +208,7 @@ fn test_part(
i: usize,
compare_ref: bool,
lines: u32,
) -> (bool, bool, Vec<Frame>) {
) -> (bool, bool, Vec<Rc<Frame>>) {
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<Frame>], dpi: f32) -> Pixmap {
let pad = Length::pt(5.0);
let height = pad + frames.iter().map(|l| l.size.height + pad).sum::<Length>();
@ -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);