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) {
let (mut ctx, id) = context();
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,70 @@ pub struct Frame {
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
/// nested frame.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
@ -29,6 +93,46 @@ pub enum FrameChild {
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.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Element {
@ -81,107 +185,3 @@ pub enum Geometry {
/// A filled bezier 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.
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());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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>,
}
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.

View File

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

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
//! [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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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