summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/array.rs6
-rw-r--r--src/eval/dict.rs6
-rw-r--r--src/eval/template.rs50
-rw-r--r--src/eval/walk.rs10
-rw-r--r--src/layout/constraints.rs2
-rw-r--r--src/layout/frame.rs178
-rw-r--r--src/layout/grid.rs14
-rw-r--r--src/layout/image.rs28
-rw-r--r--src/layout/incremental.rs4
-rw-r--r--src/layout/mod.rs218
-rw-r--r--src/layout/pad.rs26
-rw-r--r--src/layout/par.rs76
-rw-r--r--src/layout/shape.rs64
-rw-r--r--src/layout/spacing.rs50
-rw-r--r--src/layout/stack.rs77
-rw-r--r--src/layout/text.rs (renamed from src/layout/shaping.rs)13
-rw-r--r--src/layout/tree.rs132
-rw-r--r--src/lib.rs11
-rw-r--r--src/library/layout.rs8
-rw-r--r--src/library/text.rs12
-rw-r--r--src/style/mod.rs14
-rw-r--r--src/util/eco_string.rs7
-rw-r--r--src/util/mod.rs20
23 files changed, 512 insertions, 514 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs
index 17192cb3..f6adee6d 100644
--- a/src/eval/array.rs
+++ b/src/eval/array.rs
@@ -7,6 +7,7 @@ use std::rc::Rc;
use super::Value;
use crate::diag::StrResult;
+use crate::util::RcExt;
/// Create a new [`Array`] from values.
#[allow(unused_macros)]
@@ -169,10 +170,7 @@ impl IntoIterator for Array {
type IntoIter = std::vec::IntoIter<Value>;
fn into_iter(self) -> Self::IntoIter {
- match Rc::try_unwrap(self.0) {
- Ok(vec) => vec.into_iter(),
- Err(rc) => (*rc).clone().into_iter(),
- }
+ Rc::take(self.0).into_iter()
}
}
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
index c0ddf328..e7a46b40 100644
--- a/src/eval/dict.rs
+++ b/src/eval/dict.rs
@@ -6,6 +6,7 @@ use std::rc::Rc;
use super::{Str, Value};
use crate::diag::StrResult;
+use crate::util::RcExt;
/// Create a new [`Dict`] from key-value pairs.
#[allow(unused_macros)]
@@ -135,10 +136,7 @@ impl IntoIterator for Dict {
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
fn into_iter(self) -> Self::IntoIter {
- match Rc::try_unwrap(self.0) {
- Ok(map) => map.into_iter(),
- Err(rc) => (*rc).clone().into_iter(),
- }
+ Rc::take(self.0).into_iter()
}
}
diff --git a/src/eval/template.rs b/src/eval/template.rs
index 63916339..11ea3f56 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -6,9 +6,9 @@ use std::rc::Rc;
use super::Str;
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::{
- Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild,
+ BlockNode, Decoration, InlineNode, PadNode, PageNode, ParChild, ParNode, StackChild,
StackNode,
};
use crate::style::Style;
@@ -34,9 +34,9 @@ enum TemplateNode {
/// Spacing.
Spacing(GenAxis, Linear),
/// An inline node builder.
- Inline(Rc<dyn Fn(&Style) -> LayoutNode>, Vec<Decoration>),
+ Inline(Rc<dyn Fn(&Style) -> InlineNode>, Vec<Decoration>),
/// An block node builder.
- Block(Rc<dyn Fn(&Style) -> LayoutNode>),
+ Block(Rc<dyn Fn(&Style) -> BlockNode>),
/// Save the current style.
Save,
/// Restore the last saved style.
@@ -55,7 +55,7 @@ impl Template {
pub fn from_inline<F, T>(f: F) -> Self
where
F: Fn(&Style) -> T + 'static,
- T: Into<LayoutNode>,
+ T: Into<InlineNode>,
{
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
Self(Rc::new(vec![node]))
@@ -65,7 +65,7 @@ impl Template {
pub fn from_block<F, T>(f: F) -> Self
where
F: Fn(&Style) -> T + 'static,
- T: Into<LayoutNode>,
+ T: Into<BlockNode>,
{
let node = TemplateNode::Block(Rc::new(move |s| f(s).into()));
Self(Rc::new(vec![node]))
@@ -164,10 +164,10 @@ impl Template {
/// Build the layout tree resulting from instantiating the template with the
/// 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);
builder.template(self);
- builder.build_tree()
+ builder.build_pages()
}
/// Repeat this template `n` times.
@@ -243,8 +243,8 @@ struct Builder {
style: Style,
/// Snapshots of the style.
snapshots: Vec<Style>,
- /// The tree of finished page runs.
- tree: LayoutTree,
+ /// The finished page nodes.
+ finished: Vec<PageNode>,
/// When we are building the top-level layout trees, this contains metrics
/// of the page. While building a stack, this is `None`.
page: Option<PageBuilder>,
@@ -258,7 +258,7 @@ impl Builder {
Self {
style: style.clone(),
snapshots: vec![],
- tree: LayoutTree { runs: vec![] },
+ finished: vec![],
page: pages.then(|| PageBuilder::new(style, true)),
stack: StackBuilder::new(style),
}
@@ -309,7 +309,7 @@ impl Builder {
fn parbreak(&mut self) {
let amount = self.style.par_spacing();
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.
@@ -317,7 +317,7 @@ impl Builder {
if let Some(builder) = &mut self.page {
let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
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.
- 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;
self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
}
/// 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.stack.push(StackChild::new(node, self.style.aligns.block));
+ self.stack.push(StackChild::Any(node.into(), self.style.aligns.block));
self.parbreak();
}
@@ -344,7 +344,7 @@ impl Builder {
match axis {
GenAxis::Block => {
self.stack.finish_par(&self.style);
- self.stack.push_hard(StackChild::spacing(amount, SpecAxis::Vertical));
+ self.stack.push_hard(StackChild::Spacing(amount));
}
GenAxis::Inline => {
self.stack.par.push_hard(ParChild::Spacing(amount));
@@ -359,10 +359,10 @@ impl Builder {
}
/// 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());
self.pagebreak(true, false);
- self.tree
+ self.finished
}
/// 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;
- (!child.children.is_empty() || (keep && hard)).then(|| PageRun {
+ (!child.children.is_empty() || (keep && hard)).then(|| PageNode {
size,
child: PadNode { padding, child: child.into() }.into(),
})
@@ -456,7 +456,7 @@ impl StackBuilder {
struct ParBuilder {
align: Align,
dir: Dir,
- line_spacing: Length,
+ leading: Length,
children: Vec<ParChild>,
last: Last<ParChild>,
}
@@ -466,7 +466,7 @@ impl ParBuilder {
Self {
align: style.aligns.block,
dir: style.dir,
- line_spacing: style.line_spacing(),
+ leading: style.leading(),
children: vec![],
last: Last::None,
}
@@ -507,9 +507,9 @@ impl ParBuilder {
}
fn build(self) -> Option<StackChild> {
- let Self { align, dir, line_spacing, children, .. } = self;
+ let Self { align, dir, leading, children, .. } = self;
(!children.is_empty())
- .then(|| StackChild::new(ParNode { dir, line_spacing, children }, align))
+ .then(|| StackChild::Any(ParNode { dir, leading, children }.into(), align))
}
}
diff --git a/src/eval/walk.rs b/src/eval/walk.rs
index 24284e4e..2f44f5df 100644
--- a/src/eval/walk.rs
+++ b/src/eval/walk.rs
@@ -2,7 +2,7 @@ use std::rc::Rc;
use super::{Eval, EvalContext, Str, Template, Value};
use crate::diag::TypResult;
-use crate::geom::{Align, SpecAxis};
+use crate::geom::Align;
use crate::layout::{ParChild, ParNode, StackChild, StackNode};
use crate::syntax::*;
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| {
let label = ParNode {
dir: style.dir,
- line_spacing: style.line_spacing(),
+ leading: style.leading(),
children: vec![ParChild::Text(
(&label).into(),
style.aligns.inline,
@@ -119,9 +119,9 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
StackNode {
dir: style.dir,
children: vec![
- StackChild::new(label, Align::Start),
- StackChild::spacing(style.text.size / 2.0, SpecAxis::Horizontal),
- StackChild::new(body.to_stack(&style), Align::Start),
+ StackChild::Any(label.into(), Align::Start),
+ StackChild::Spacing((style.text.size / 2.0).into()),
+ StackChild::Any(body.to_stack(&style).into(), Align::Start),
],
}
});
diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs
index f88b2add..7dc16446 100644
--- a/src/layout/constraints.rs
+++ b/src/layout/constraints.rs
@@ -7,7 +7,7 @@ pub struct Constrained<T> {
/// The item that is only valid if the constraints are fullfilled.
pub item: T,
/// Constraints on regions in which the item is valid.
- pub constraints: Constraints,
+ pub cts: Constraints,
}
/// Describe regions that match them.
diff --git a/src/layout/frame.rs b/src/layout/frame.rs
index 0e986fe4..82f60e22 100644
--- a/src/layout/frame.rs
+++ b/src/layout/frame.rs
@@ -19,69 +19,6 @@ pub struct Frame {
pub children: Vec<(Point, FrameChild)>,
}
-/// A frame can contain two different kinds of children: a leaf element or a
-/// nested frame.
-#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
-pub enum FrameChild {
- /// A leaf node in the frame tree.
- Element(Element),
- /// An interior group.
- Group(Rc<Frame>),
-}
-
-/// The building block frames are composed of.
-#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
-pub enum Element {
- /// Shaped text.
- Text(Text),
- /// A geometric shape and the paint which with it should be filled or
- /// stroked (which one depends on the kind of geometry).
- Geometry(Geometry, Paint),
- /// A raster image.
- Image(ImageId, Size),
- /// A link to an external resource.
- Link(String, Size),
-}
-
-/// A run of shaped text.
-#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
-pub struct Text {
- /// The font face the glyphs are contained in.
- pub face_id: FaceId,
- /// The font size.
- pub size: Length,
- /// The width of the text run.
- pub width: Length,
- /// Glyph color.
- pub fill: Paint,
- /// The glyphs.
- pub glyphs: Vec<Glyph>,
-}
-
-/// A glyph in a run of shaped text.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
-pub struct Glyph {
- /// The glyph's index in the face.
- pub id: u16,
- /// The advance width of the glyph.
- pub x_advance: Em,
- /// The horizontal offset of the glyph.
- pub x_offset: Em,
-}
-
-/// A geometric shape.
-#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
-pub enum Geometry {
- /// A filled rectangle with its origin in the topleft corner.
- Rect(Size),
- /// A filled ellipse with its origin in the center.
- Ellipse(Size),
- /// A stroked line to a point (relative to its position) with a thickness.
- Line(Point, Length),
- /// A filled bezier path.
- Path(Path),
-}
-
impl Frame {
/// Create a new, empty frame.
#[track_caller]
@@ -117,15 +54,52 @@ impl Frame {
}
}
- /// 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)] }
}
+
+ /// 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
+/// nested frame.
+#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub enum FrameChild {
+ /// A leaf node in the frame tree.
+ Element(Element),
+ /// An interior group.
+ 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.
@@ -159,29 +133,55 @@ impl<'a> Iterator for Elements<'a> {
}
}
-impl Debug for Frame {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- struct Children<'a>(&'a [(Point, FrameChild)]);
+/// The building block frames are composed of.
+#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub enum Element {
+ /// Shaped text.
+ Text(Text),
+ /// A geometric shape and the paint which with it should be filled or
+ /// stroked (which one depends on the kind of geometry).
+ Geometry(Geometry, Paint),
+ /// A raster image.
+ Image(ImageId, Size),
+ /// A link to an external resource.
+ Link(String, Size),
+}
- 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()
- }
- }
+/// A run of shaped text.
+#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub struct Text {
+ /// The font face the glyphs are contained in.
+ pub face_id: FaceId,
+ /// The font size.
+ pub size: Length,
+ /// The width of the text run.
+ pub width: Length,
+ /// Glyph color.
+ pub fill: Paint,
+ /// The glyphs.
+ pub glyphs: Vec<Glyph>,
+}
- f.debug_struct("Frame")
- .field("size", &self.size)
- .field("baseline", &self.baseline)
- .field("children", &Children(&self.children))
- .finish()
- }
+/// A glyph in a run of shaped text.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub struct Glyph {
+ /// The glyph's index in the face.
+ pub id: u16,
+ /// The advance width of the glyph.
+ pub x_advance: Em,
+ /// The horizontal offset of the glyph.
+ pub x_offset: Em,
}
-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),
- }
- }
+/// A geometric shape.
+#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
+pub enum Geometry {
+ /// A filled rectangle with its origin in the topleft corner.
+ Rect(Size),
+ /// A filled ellipse with its origin in the center.
+ Ellipse(Size),
+ /// A stroked line to a point (relative to its position) with a thickness.
+ Line(Point, Length),
+ /// A filled bezier path.
+ Path(Path),
}
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
index 2553fc2f..22581696 100644
--- a/src/layout/grid.rs
+++ b/src/layout/grid.rs
@@ -9,7 +9,7 @@ pub struct GridNode {
/// Defines sizing of gutter rows and columns between content.
pub gutter: Spec<Vec<TrackSizing>>,
/// The nodes to be arranged in a grid.
- pub children: Vec<LayoutNode>,
+ pub children: Vec<BlockNode>,
}
/// Defines how to size a grid cell along an axis.
@@ -23,7 +23,7 @@ pub enum TrackSizing {
Fractional(Fractional),
}
-impl Layout for GridNode {
+impl BlockLevel for GridNode {
fn layout(
&self,
ctx: &mut LayoutContext,
@@ -40,9 +40,9 @@ impl Layout for GridNode {
}
}
-impl From<GridNode> for LayoutNode {
- fn from(grid: GridNode) -> Self {
- Self::new(grid)
+impl From<GridNode> for BlockNode {
+ fn from(node: GridNode) -> Self {
+ Self::new(node)
}
}
@@ -55,7 +55,7 @@ struct GridLayouter<'a> {
/// The row tracks including gutter tracks.
rows: Vec<TrackSizing>,
/// The children of the grid.
- children: &'a [LayoutNode],
+ children: &'a [BlockNode],
/// The regions to layout into.
regions: Regions,
/// Resolved column sizes.
@@ -546,7 +546,7 @@ impl<'a> GridLayouter<'a> {
///
/// Returns `None` if it's a gutter cell.
#[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!(y < self.rows.len());
diff --git a/src/layout/image.rs b/src/layout/image.rs
index eb2fc221..59b4c6ef 100644
--- a/src/layout/image.rs
+++ b/src/layout/image.rs
@@ -13,31 +13,23 @@ pub struct ImageNode {
pub height: Option<Linear>,
}
-impl Layout for ImageNode {
- fn layout(
- &self,
- ctx: &mut LayoutContext,
- regions: &Regions,
- ) -> Vec<Constrained<Rc<Frame>>> {
+impl InlineLevel for ImageNode {
+ fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
let img = ctx.images.get(self.id);
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
let pixel_ratio = pixel_size.x / pixel_size.y;
- let width = self.width.map(|w| w.resolve(regions.base.w));
- let height = self.height.map(|w| w.resolve(regions.base.h));
-
- let mut cts = Constraints::new(regions.expand);
- cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
+ let width = self.width.map(|w| w.resolve(base.w));
+ let height = self.height.map(|w| w.resolve(base.h));
let size = match (width, height) {
(Some(width), Some(height)) => Size::new(width, height),
(Some(width), None) => Size::new(width, width / pixel_ratio),
(None, Some(height)) => Size::new(height * pixel_ratio, height),
(None, None) => {
- cts.exact.x = Some(regions.current.w);
- if regions.current.w.is_finite() {
+ if space.is_finite() {
// Fit to width.
- Size::new(regions.current.w, regions.current.w / pixel_ratio)
+ Size::new(space, space / pixel_ratio)
} else {
// Unbounded width, we have to make up something,
// so it is 1pt per pixel.
@@ -48,12 +40,12 @@ impl Layout for ImageNode {
let mut frame = Frame::new(size, size.h);
frame.push(Point::zero(), Element::Image(self.id, size));
- vec![frame.constrain(cts)]
+ frame
}
}
-impl From<ImageNode> for LayoutNode {
- fn from(image: ImageNode) -> Self {
- Self::new(image)
+impl From<ImageNode> for InlineNode {
+ fn from(node: ImageNode) -> Self {
+ Self::new(node)
}
}
diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs
index 19a41a22..2f6dccd0 100644
--- a/src/layout/incremental.rs
+++ b/src/layout/incremental.rs
@@ -230,7 +230,7 @@ impl FramesEntry {
let mut iter = regions.iter();
self.frames.iter().all(|frame| {
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>>> {
vec![Constrained {
item: Rc::new(Frame::default()),
- constraints: Constraints::new(Spec::splat(false)),
+ cts: Constraints::new(Spec::splat(false)),
}]
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index ee74ff6f..5c5e2ffd 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -10,10 +10,8 @@ mod pad;
mod par;
mod regions;
mod shape;
-mod shaping;
-mod spacing;
mod stack;
-mod tree;
+mod text;
pub use self::image::*;
pub use constraints::*;
@@ -25,12 +23,10 @@ pub use pad::*;
pub use par::*;
pub use regions::*;
pub use shape::*;
-pub use shaping::*;
-pub use spacing::*;
pub use stack::*;
-pub use tree::*;
+pub use text::*;
-use std::fmt::Debug;
+use std::fmt::{self, Debug, Formatter};
use std::rc::Rc;
use crate::font::FontStore;
@@ -39,10 +35,20 @@ use crate::image::ImageStore;
use crate::util::OptionExt;
use crate::Context;
-/// Layout a tree into a collection of frames.
-pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> {
+#[cfg(feature = "layout-cache")]
+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);
- tree.layout(&mut ctx)
+ node.layout(&mut ctx)
}
/// The context for layouting.
@@ -73,12 +79,198 @@ impl<'a> LayoutContext<'a> {
}
}
-/// Layout a node.
-pub trait Layout: Debug {
- /// Layout the node into the given regions.
+/// Page-level nodes directly produce frames representing pages.
+///
+/// Such nodes create their own regions instead of being supplied with them from
+/// some parent.
+pub trait PageLevel: Debug {
+ /// Layout the node, producing one frame per page.
+ fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>>;
+}
+
+/// Layouts its children onto one or multiple pages.
+#[derive(Debug)]
+pub struct PageNode {
+ /// The size of the page.
+ pub size: Size,
+ /// The node that produces the actual pages.
+ pub child: BlockNode,
+}
+
+impl PageLevel for PageNode {
+ fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
+ // When one of the lengths is infinite the page fits its content along
+ // that axis.
+ let expand = self.size.to_spec().map(Length::is_finite);
+ let regions = Regions::repeat(self.size, self.size, expand);
+ self.child.layout(ctx, &regions).into_iter().map(|c| c.item).collect()
+ }
+}
+
+impl<T> PageLevel for T
+where
+ T: AsRef<[PageNode]> + Debug + ?Sized,
+{
+ fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
+ self.as_ref().iter().flat_map(|node| node.layout(ctx)).collect()
+ }
+}
+
+/// Block-level nodes can be layouted into a sequence of regions.
+///
+/// They return one frame per used region alongside constraints that define
+/// whether the result is reusable in other regions.
+pub trait BlockLevel: Debug {
+ /// Layout the node into the given regions, producing constrained frames.
fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>>;
}
+
+/// 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()
+}
diff --git a/src/layout/pad.rs b/src/layout/pad.rs
index dfe6dc47..20fcc161 100644
--- a/src/layout/pad.rs
+++ b/src/layout/pad.rs
@@ -7,10 +7,10 @@ pub struct PadNode {
/// The amount of padding.
pub padding: Sides<Linear>,
/// The child node whose sides to pad.
- pub child: LayoutNode,
+ pub child: BlockNode,
}
-impl Layout for PadNode {
+impl BlockLevel for PadNode {
fn layout(
&self,
ctx: &mut LayoutContext,
@@ -22,7 +22,7 @@ impl Layout for PadNode {
&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())
{
fn solve_axis(length: Length, padding: Linear) -> Length {
@@ -46,7 +46,7 @@ impl Layout for PadNode {
new.push_frame(origin, prev);
// 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() {
*x += padding.size().w;
}
@@ -56,18 +56,18 @@ impl Layout for PadNode {
}
// Set exact and base constraints if the child had them.
- constraints.exact.x.and_set(Some(current.w));
- constraints.exact.y.and_set(Some(current.h));
- constraints.base.x.and_set(Some(base.w));
- constraints.base.y.and_set(Some(base.h));
+ cts.exact.x.and_set(Some(current.w));
+ cts.exact.y.and_set(Some(current.h));
+ cts.base.x.and_set(Some(base.w));
+ cts.base.y.and_set(Some(base.h));
// Also set base constraints if the padding 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() {
- 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 {
- fn from(pad: PadNode) -> Self {
- Self::new(pad)
+impl From<PadNode> for BlockNode {
+ fn from(node: PadNode) -> Self {
+ Self::new(node)
}
}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index cf37f005..1c618fff 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -18,7 +18,7 @@ pub struct ParNode {
/// The inline direction of this paragraph.
pub dir: Dir,
/// The spacing to insert between each line.
- pub line_spacing: Length,
+ pub leading: Length,
/// The nodes to be arranged in a paragraph.
pub children: Vec<ParChild>,
}
@@ -31,10 +31,10 @@ pub enum ParChild {
/// A run of text and how to align it in its line.
Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>),
/// 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(
&self,
ctx: &mut LayoutContext,
@@ -88,9 +88,9 @@ impl ParNode {
}
}
-impl From<ParNode> for LayoutNode {
- fn from(par: ParNode) -> Self {
- Self::new(par)
+impl From<ParNode> for BlockNode {
+ fn from(node: ParNode) -> Self {
+ Self::new(node)
}
}
@@ -110,7 +110,7 @@ struct ParLayouter<'a> {
/// The top-level direction.
dir: Dir,
/// The line spacing.
- line_spacing: Length,
+ leading: Length,
/// Bidirectional text embedding levels for the paragraph.
bidi: BidiInfo<'a>,
/// Layouted children and separated text runs.
@@ -143,14 +143,14 @@ impl<'a> ParLayouter<'a> {
// TODO: Also split by language and script.
for (subrange, dir) in split_runs(&bidi, range) {
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));
ranges.push(subrange);
}
}
ParChild::Any(node, align, decos) => {
- let frame = node.layout(ctx, regions).remove(0);
- items.push(ParItem::Frame(frame.item, *align, decos));
+ let frame = node.layout(ctx, regions.current.w, regions.base);
+ items.push(ParItem::Frame(frame, *align, decos));
ranges.push(range);
}
}
@@ -158,7 +158,7 @@ impl<'a> ParLayouter<'a> {
Self {
dir: par.dir,
- line_spacing: par.line_spacing,
+ leading: par.leading,
bidi,
items,
ranges,
@@ -171,7 +171,7 @@ impl<'a> ParLayouter<'a> {
ctx: &mut LayoutContext,
regions: Regions,
) -> 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.
// 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
// tried line.
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.
if !stack.regions.current.h.fits(line.size.h) {
- let too_large = stack.size.h + self.line_spacing + line.size.h;
- stack.constraints.max.y.set_min(too_large);
+ let too_large = stack.size.h + self.leading + line.size.h;
+ stack.cts.max.y.set_min(too_large);
}
stack.push(last_line);
- stack.constraints.min.y = Some(stack.size.h);
+ stack.cts.min.y = Some(stack.size.h);
start = last_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
// plus the line height would fit, therefore the constraint
// below.
- let too_large = stack.size.h + self.line_spacing + line.size.h;
- stack.constraints.max.y.set_min(too_large);
+ let too_large = stack.size.h + self.leading + line.size.h;
+ stack.cts.max.y.set_min(too_large);
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 {
// Otherwise, the line fits both horizontally and vertically
// 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));
}
}
if let Some((line, _)) = last {
stack.push(line);
- stack.constraints.min.y = Some(stack.size.h);
+ stack.cts.min.y = Some(stack.size.h);
}
stack.finish(ctx)
@@ -292,7 +292,7 @@ enum ParItem<'a> {
/// A shaped text run with consistent direction.
Text(ShapedText<'a>, Align, &'a [Decoration]),
/// A layouted child node.
- Frame(Rc<Frame>, Align, &'a [Decoration]),
+ Frame(Frame, Align, &'a [Decoration]),
}
impl ParItem<'_> {
@@ -463,10 +463,10 @@ impl<'a> LineLayout<'a> {
ParItem::Frame(ref frame, align, decos) => {
let mut frame = frame.clone();
for deco in decos {
- deco.apply(ctx, Rc::make_mut(&mut frame));
+ deco.apply(ctx, &mut frame);
}
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.
struct LineStack<'a> {
- line_spacing: Length,
+ leading: Length,
full: Size,
regions: Regions,
size: Size,
lines: Vec<LineLayout<'a>>,
finished: Vec<Constrained<Rc<Frame>>>,
- constraints: Constraints,
+ cts: Constraints,
overflowing: bool,
}
impl<'a> LineStack<'a> {
/// Create an empty line stack.
- fn new(line_spacing: Length, regions: Regions) -> Self {
+ fn new(leading: Length, regions: Regions) -> Self {
Self {
- line_spacing,
+ leading,
full: regions.current,
- constraints: Constraints::new(regions.expand),
+ cts: Constraints::new(regions.expand),
regions,
size: Size::zero(),
lines: vec![],
@@ -549,12 +549,12 @@ impl<'a> LineStack<'a> {
/// Push a new line into the stack.
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.h += line.size.h;
if !self.lines.is_empty() {
- self.size.h += self.line_spacing;
+ self.size.h += self.leading;
}
self.lines.push(line);
@@ -564,13 +564,13 @@ impl<'a> LineStack<'a> {
fn finish_region(&mut self, ctx: &LayoutContext) {
if self.regions.expand.x {
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 {
- self.constraints.min.y = None;
- self.constraints.max.y = None;
- self.constraints.exact = self.full.to_spec().map(Some);
+ self.cts.min.y = None;
+ self.cts.max.y = None;
+ self.cts.exact = self.full.to_spec().map(Some);
}
let mut output = Frame::new(self.size, self.size.h);
@@ -586,14 +586,14 @@ impl<'a> LineStack<'a> {
first = false;
}
- offset += frame.size.h + self.line_spacing;
+ offset += frame.size.h + self.leading;
output.merge_frame(pos, frame);
}
- self.finished.push(output.constrain(self.constraints));
+ self.finished.push(output.constrain(self.cts));
self.regions.next();
self.full = self.regions.current;
- self.constraints = Constraints::new(self.regions.expand);
+ self.cts = Constraints::new(self.regions.expand);
self.size = Size::zero();
}
diff --git a/src/layout/shape.rs b/src/layout/shape.rs
index 3469f660..13d5418f 100644
--- a/src/layout/shape.rs
+++ b/src/layout/shape.rs
@@ -1,6 +1,7 @@
use std::f64::consts::SQRT_2;
use super::*;
+use crate::util::RcExt;
/// Places its child into a sizable and fillable shape.
#[derive(Debug)]
@@ -15,7 +16,7 @@ pub struct ShapeNode {
/// How to fill the shape, if at all.
pub fill: Option<Paint>,
/// The child node to place into the shape, if any.
- pub child: Option<LayoutNode>,
+ pub child: Option<BlockNode>,
}
/// The type of a shape.
@@ -31,40 +32,15 @@ pub enum ShapeKind {
Ellipse,
}
-impl Layout for ShapeNode {
- fn layout(
- &self,
- ctx: &mut LayoutContext,
- regions: &Regions,
- ) -> Vec<Constrained<Rc<Frame>>> {
+impl InlineLevel for ShapeNode {
+ fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
// Resolve width and height relative to the region's base.
- let width = self.width.map(|w| w.resolve(regions.base.w));
- let height = self.height.map(|h| h.resolve(regions.base.h));
-
- // Generate constraints.
- let 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
- };
+ let width = self.width.map(|w| w.resolve(base.w));
+ let height = self.height.map(|h| h.resolve(base.h));
// Layout.
- let mut frames = if let Some(child) = &self.child {
- let mut node: &dyn Layout = child;
+ let mut frame = if let Some(child) = &self.child {
+ let mut node: &dyn BlockLevel = child;
let padded;
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.
let mut pod = {
- let size = Size::new(
- width.unwrap_or(regions.current.w),
- height.unwrap_or(regions.current.h),
- );
+ let size =
+ Size::new(width.unwrap_or(space), height.unwrap_or(Length::inf()));
let base = Size::new(
- if width.is_some() { size.w } else { regions.base.w },
- if height.is_some() { size.h } else { regions.base.h },
+ if width.is_some() { size.w } else { base.w },
+ if height.is_some() { size.h } else { base.h },
);
let expand = Spec::new(width.is_some(), height.is_some());
@@ -108,17 +82,15 @@ impl Layout for ShapeNode {
// Validate and set constraints.
assert_eq!(frames.len(), 1);
- frames[0].constraints = constraints;
- frames
+ Rc::take(frames.into_iter().next().unwrap().item)
} else {
// Resolve shape size.
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.
if let Some(fill) = self.fill {
- let frame = Rc::make_mut(&mut frames[0].item);
let (pos, geometry) = match self.shape {
ShapeKind::Square | ShapeKind::Rect => {
(Point::zero(), Geometry::Rect(frame.size))
@@ -131,12 +103,12 @@ impl Layout for ShapeNode {
frame.prepend(pos, Element::Geometry(geometry, fill));
}
- frames
+ frame
}
}
-impl From<ShapeNode> for LayoutNode {
- fn from(shape: ShapeNode) -> Self {
- Self::new(shape)
+impl From<ShapeNode> for InlineNode {
+ fn from(node: ShapeNode) -> Self {
+ Self::new(node)
}
}
diff --git a/src/layout/spacing.rs b/src/layout/spacing.rs
deleted file mode 100644
index 68fab3e6..00000000
--- a/src/layout/spacing.rs
+++ /dev/null
@@ -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)
- }
-}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 4b9328a7..bbaf022b 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -12,7 +12,16 @@ pub struct StackNode {
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(
&self,
ctx: &mut LayoutContext,
@@ -22,37 +31,18 @@ impl Layout for StackNode {
}
}
-impl From<StackNode> for LayoutNode {
- fn from(stack: StackNode) -> Self {
- Self::new(stack)
- }
-}
-
-/// 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 From<StackNode> for BlockNode {
+ fn from(node: StackNode) -> Self {
+ Self::new(node)
}
}
impl Debug for StackChild {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{:?}: ", self.align)?;
- self.node.fmt(f)
+ match self {
+ Self::Spacing(v) => write!(f, "Spacing({:?})", v),
+ Self::Any(node, _) => node.fmt(f),
+ }
}
}
@@ -106,12 +96,17 @@ impl<'a> StackLayouter<'a> {
/// Layout all children.
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
for child in &self.stack.children {
- let frames = child.node.layout(ctx, &self.regions);
- let len = frames.len();
- for (i, frame) in frames.into_iter().enumerate() {
- self.push_frame(frame.item, child.align);
- if i + 1 < len {
- self.finish_region();
+ match *child {
+ StackChild::Spacing(amount) => self.space(amount),
+ StackChild::Any(ref node, align) => {
+ let frames = node.layout(ctx, &self.regions);
+ let len = frames.len();
+ for (i, frame) in frames.into_iter().enumerate() {
+ self.push_frame(frame.item, align);
+ if i + 1 < len {
+ self.finish_region();
+ }
+ }
}
}
}
@@ -120,6 +115,22 @@ impl<'a> StackLayouter<'a> {
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.
fn push_frame(&mut self, frame: Rc<Frame>, align: Align) {
// Grow our size.
diff --git a/src/layout/shaping.rs b/src/layout/text.rs
index 7bd2646d..a89d7e3b 100644
--- a/src/layout/shaping.rs
+++ b/src/layout/text.rs
@@ -3,7 +3,7 @@ use std::ops::Range;
use rustybuzz::UnicodeBuffer;
-use super::{Element, Frame, Glyph, LayoutContext, Text};
+use super::*;
use crate::font::{Face, FaceId, FontVariant};
use crate::geom::{Dir, Em, Length, Point, Size};
use crate::style::TextStyle;
@@ -13,8 +13,8 @@ use crate::util::SliceExt;
pub fn shape<'a>(
ctx: &mut LayoutContext,
text: &'a str,
- dir: Dir,
style: &'a TextStyle,
+ dir: Dir,
) -> ShapedText<'a> {
let mut glyphs = vec![];
if !text.is_empty() {
@@ -23,16 +23,15 @@ pub fn shape<'a>(
&mut glyphs,
0,
text,
- dir,
style.size,
style.variant(),
style.families(),
None,
+ dir,
);
}
let (size, baseline) = measure(ctx, &glyphs, style);
-
ShapedText {
text,
dir,
@@ -134,7 +133,7 @@ impl<'a> ShapedText<'a> {
glyphs: Cow::Borrowed(glyphs),
}
} 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>,
base: usize,
text: &str,
- dir: Dir,
size: Length,
variant: FontVariant,
mut families: impl Iterator<Item = &'a str> + Clone,
mut first_face: Option<FaceId>,
+ dir: Dir,
) {
// Select the font family.
let (face_id, fallback) = loop {
@@ -316,11 +315,11 @@ fn shape_segment<'a>(
glyphs,
base + range.start,
&text[range],
- dir,
size,
variant,
families.clone(),
first_face,
+ dir,
);
face = ctx.fonts.get(face_id);
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
deleted file mode 100644
index 181bb611..00000000
--- a/src/layout/tree.rs
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index ed90fed4..2ae87fc6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,7 +8,7 @@
//! - **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
//! 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
//! self-contained and order-independent and thus much better suited for
//! layouting than the raw markup.
@@ -23,7 +23,6 @@
//! [markup]: syntax::Markup
//! [evaluate]: eval::eval
//! [module]: eval::Module
-//! [instantiated]: eval::Template::to_tree
//! [layout tree]: layout::LayoutTree
//! [layouted]: layout::layout
//! [PDF]: export::pdf
@@ -53,7 +52,7 @@ use crate::font::FontStore;
use crate::image::ImageStore;
#[cfg(feature = "layout-cache")]
use crate::layout::{EvictionPolicy, LayoutCache};
-use crate::layout::{Frame, LayoutTree};
+use crate::layout::{Frame, PageNode};
use crate::loading::Loader;
use crate::source::{SourceId, SourceStore};
use crate::style::Style;
@@ -110,10 +109,10 @@ impl Context {
eval::eval(self, id, &ast)
}
- /// Execute a source file and produce the resulting layout tree.
- pub fn execute(&mut self, id: SourceId) -> TypResult<LayoutTree> {
+ /// Execute a source file and produce the resulting page nodes.
+ pub fn execute(&mut self, id: SourceId) -> TypResult<Vec<PageNode>> {
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.
diff --git a/src/library/layout.rs b/src/library/layout.rs
index bb9e5936..d412af67 100644
--- a/src/library/layout.rs
+++ b/src/library/layout.rs
@@ -220,16 +220,16 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
for child in &list {
match child {
Child::Spacing(v) => {
- children.push(StackChild::spacing(*v, dir.axis()));
+ children.push(StackChild::Spacing(*v));
delayed = None;
}
Child::Any(template) => {
if let Some(v) = delayed {
- children.push(StackChild::spacing(v, dir.axis()));
+ children.push(StackChild::Spacing(v));
}
- let node = template.to_stack(style);
- children.push(StackChild::new(node, style.aligns.block));
+ let node = template.to_stack(style).into();
+ children.push(StackChild::Any(node, style.aligns.block));
delayed = spacing;
}
}
diff --git a/src/library/text.rs b/src/library/text.rs
index fa334620..3c7055d3 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -110,18 +110,18 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
/// `par`: Configure paragraphs.
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let par_spacing = args.named("spacing")?;
- let line_spacing = args.named("leading")?;
+ let spacing = args.named("spacing")?;
+ let leading = args.named("leading")?;
ctx.template.modify(move |style| {
let par = style.par_mut();
- if let Some(par_spacing) = par_spacing {
- par.par_spacing = par_spacing;
+ if let Some(spacing) = spacing {
+ par.spacing = spacing;
}
- if let Some(line_spacing) = line_spacing {
- par.line_spacing = line_spacing;
+ if let Some(leading) = leading {
+ par.leading = leading;
}
});
diff --git a/src/style/mod.rs b/src/style/mod.rs
index f59ea32e..7917c81f 100644
--- a/src/style/mod.rs
+++ b/src/style/mod.rs
@@ -43,13 +43,13 @@ impl Style {
}
/// The resolved line spacing.
- pub fn line_spacing(&self) -> Length {
- self.par.line_spacing.resolve(self.text.size)
+ pub fn leading(&self) -> Length {
+ self.par.leading.resolve(self.text.size)
}
/// The resolved paragraph spacing.
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)]
pub struct ParStyle {
/// 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).
- pub line_spacing: Linear,
+ pub leading: Linear,
}
impl Default for ParStyle {
fn default() -> Self {
Self {
- par_spacing: Relative::new(1.2).into(),
- line_spacing: Relative::new(0.65).into(),
+ spacing: Relative::new(1.2).into(),
+ leading: Relative::new(0.65).into(),
}
}
}
diff --git a/src/util/eco_string.rs b/src/util/eco_string.rs
index f1dfdfaf..ab8d5642 100644
--- a/src/util/eco_string.rs
+++ b/src/util/eco_string.rs
@@ -5,6 +5,8 @@ use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::rc::Rc;
+use super::RcExt;
+
/// An economical string with inline storage and clone-on-write semantics.
#[derive(Clone)]
pub struct EcoString(Repr);
@@ -293,10 +295,7 @@ impl From<EcoString> for String {
fn from(s: EcoString) -> Self {
match s.0 {
Repr::Small { .. } => s.as_str().to_owned(),
- Repr::Large(rc) => match Rc::try_unwrap(rc) {
- Ok(string) => string,
- Err(rc) => (*rc).clone(),
- },
+ Repr::Large(rc) => Rc::take(rc),
}
}
}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 05dc9025..c4272c55 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -10,6 +10,7 @@ use std::cell::RefMut;
use std::cmp::Ordering;
use std::ops::Range;
use std::path::{Component, Path, PathBuf};
+use std::rc::Rc;
/// Additional methods for booleans.
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.
pub trait SliceExt<T> {
/// Split a slice into consecutive groups with the same key.