mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Revert page and inline levels
This commit is contained in:
parent
bc118634ac
commit
0e0f340502
@ -60,7 +60,7 @@ fn bench_eval(iai: &mut Iai) {
|
|||||||
fn bench_to_tree(iai: &mut Iai) {
|
fn bench_to_tree(iai: &mut Iai) {
|
||||||
let (mut ctx, id) = context();
|
let (mut ctx, id) = context();
|
||||||
let module = ctx.evaluate(id).unwrap();
|
let module = ctx.evaluate(id).unwrap();
|
||||||
iai.run(|| module.template.to_pages(ctx.style()));
|
iai.run(|| module.template.to_document(ctx.style()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_layout(iai: &mut Iai) {
|
fn bench_layout(iai: &mut Iai) {
|
||||||
|
@ -7,9 +7,10 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
|
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
|
||||||
use crate::layout::{BlockLevel, BlockNode, InlineLevel, InlineNode, PageNode};
|
use crate::layout::{Layout, PackedNode};
|
||||||
use crate::library::{
|
use crate::library::{
|
||||||
Decoration, FlowChild, FlowNode, PadNode, ParChild, ParNode, Spacing,
|
Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode,
|
||||||
|
Spacing,
|
||||||
};
|
};
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -36,9 +37,9 @@ enum TemplateNode {
|
|||||||
/// A decorated template.
|
/// A decorated template.
|
||||||
Decorated(Decoration, Template),
|
Decorated(Decoration, Template),
|
||||||
/// An inline node builder.
|
/// An inline node builder.
|
||||||
Inline(Rc<dyn Fn(&Style) -> InlineNode>),
|
Inline(Rc<dyn Fn(&Style) -> PackedNode>),
|
||||||
/// An block node builder.
|
/// A block node builder.
|
||||||
Block(Rc<dyn Fn(&Style) -> BlockNode>),
|
Block(Rc<dyn Fn(&Style) -> PackedNode>),
|
||||||
/// Save the current style.
|
/// Save the current style.
|
||||||
Save,
|
Save,
|
||||||
/// Restore the last saved style.
|
/// Restore the last saved style.
|
||||||
@ -57,7 +58,7 @@ impl Template {
|
|||||||
pub fn from_inline<F, T>(f: F) -> Self
|
pub fn from_inline<F, T>(f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&Style) -> T + 'static,
|
F: Fn(&Style) -> T + 'static,
|
||||||
T: InlineLevel + Hash + 'static,
|
T: Layout + Hash + 'static,
|
||||||
{
|
{
|
||||||
let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack()));
|
let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack()));
|
||||||
Self(Rc::new(vec![node]))
|
Self(Rc::new(vec![node]))
|
||||||
@ -67,7 +68,7 @@ impl Template {
|
|||||||
pub fn from_block<F, T>(f: F) -> Self
|
pub fn from_block<F, T>(f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&Style) -> T + 'static,
|
F: Fn(&Style) -> T + 'static,
|
||||||
T: BlockLevel + Hash + 'static,
|
T: Layout + Hash + 'static,
|
||||||
{
|
{
|
||||||
let node = TemplateNode::Block(Rc::new(move |s| f(s).pack()));
|
let node = TemplateNode::Block(Rc::new(move |s| f(s).pack()));
|
||||||
Self(Rc::new(vec![node]))
|
Self(Rc::new(vec![node]))
|
||||||
@ -158,10 +159,10 @@ impl Template {
|
|||||||
|
|
||||||
/// Build the layout tree resulting from instantiating the template with the
|
/// Build the layout tree resulting from instantiating the template with the
|
||||||
/// given style.
|
/// given style.
|
||||||
pub fn to_pages(&self, style: &Style) -> Vec<PageNode> {
|
pub fn to_document(&self, style: &Style) -> DocumentNode {
|
||||||
let mut builder = Builder::new(style, true);
|
let mut builder = Builder::new(style, true);
|
||||||
builder.template(self);
|
builder.template(self);
|
||||||
builder.build_pages()
|
builder.build_document()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Repeat this template `n` times.
|
/// Repeat this template `n` times.
|
||||||
@ -327,13 +328,13 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push an inline node into the active paragraph.
|
/// Push an inline node into the active paragraph.
|
||||||
fn inline(&mut self, node: impl Into<InlineNode>) {
|
fn inline(&mut self, node: impl Into<PackedNode>) {
|
||||||
let align = self.style.aligns.inline;
|
let align = self.style.aligns.inline;
|
||||||
self.flow.par.push(ParChild::Node(node.into(), align));
|
self.flow.par.push(ParChild::Node(node.into(), align));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a block node into the active flow, finishing the active paragraph.
|
/// Push a block node into the active flow, finishing the active paragraph.
|
||||||
fn block(&mut self, node: impl Into<BlockNode>) {
|
fn block(&mut self, node: impl Into<PackedNode>) {
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
self.flow.push(FlowChild::Node(node.into(), self.style.aligns.block));
|
self.flow.push(FlowChild::Node(node.into(), self.style.aligns.block));
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
@ -359,10 +360,10 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish building and return the created layout tree.
|
/// Finish building and return the created layout tree.
|
||||||
fn build_pages(mut self) -> Vec<PageNode> {
|
fn build_document(mut self) -> DocumentNode {
|
||||||
assert!(self.page.is_some());
|
assert!(self.page.is_some());
|
||||||
self.pagebreak(true, false);
|
self.pagebreak(true, false);
|
||||||
self.finished
|
DocumentNode { pages: self.finished }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a text node with the given text and settings from the current
|
/// Construct a text node with the given text and settings from the current
|
||||||
|
@ -3,7 +3,7 @@ use std::rc::Rc;
|
|||||||
use super::{Eval, EvalContext, Template, Value};
|
use super::{Eval, EvalContext, Template, Value};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::geom::Spec;
|
use crate::geom::Spec;
|
||||||
use crate::layout::BlockLevel;
|
use crate::layout::Layout;
|
||||||
use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
|
use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
|
||||||
use crate::syntax::ast::*;
|
use crate::syntax::ast::*;
|
||||||
use crate::util::{BoolExt, EcoString};
|
use crate::util::{BoolExt, EcoString};
|
||||||
|
@ -19,13 +19,13 @@ use crate::geom::{self, Color, Em, Length, Paint, Size};
|
|||||||
use crate::image::{Image, ImageId, ImageStore};
|
use crate::image::{Image, ImageId, ImageStore};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Export a collection of frames into a PDF document.
|
/// Export a collection of frames into a PDF file.
|
||||||
///
|
///
|
||||||
/// This creates one page per frame. In addition to the frames, you need to pass
|
/// This creates one page per frame. In addition to the frames, you need to pass
|
||||||
/// in the context used during compilation such that things like fonts and
|
/// in the context used during compilation such that things like fonts and
|
||||||
/// images can be included in the PDF.
|
/// images can be included in the PDF.
|
||||||
///
|
///
|
||||||
/// Returns the raw bytes making up the PDF document.
|
/// Returns the raw bytes making up the PDF file.
|
||||||
pub fn pdf(ctx: &Context, frames: &[Rc<Frame>]) -> Vec<u8> {
|
pub fn pdf(ctx: &Context, frames: &[Rc<Frame>]) -> Vec<u8> {
|
||||||
PdfExporter::new(ctx, frames).write()
|
PdfExporter::new(ctx, frames).write()
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::geom::{Length, Size, Spec};
|
use crate::geom::{Length, Linear, Size, Spec};
|
||||||
|
|
||||||
/// Constrain a frame with constraints.
|
/// Constrain a frame with constraints.
|
||||||
pub trait Constrain {
|
pub trait Constrain {
|
||||||
@ -68,6 +68,18 @@ impl Constraints {
|
|||||||
&& verify(self.exact, current, Length::approx_eq)
|
&& verify(self.exact, current, Length::approx_eq)
|
||||||
&& verify(self.base, base, Length::approx_eq)
|
&& verify(self.base, base, Length::approx_eq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the appropriate base constraints for linear width and height sizing.
|
||||||
|
pub fn set_base_if_linear(&mut self, base: Size, sizing: Spec<Option<Linear>>) {
|
||||||
|
// The full sizes need to be equal if there is a relative component in
|
||||||
|
// the sizes.
|
||||||
|
if sizing.x.map_or(false, |l| l.is_relative()) {
|
||||||
|
self.base.x = Some(base.w);
|
||||||
|
}
|
||||||
|
if sizing.y.map_or(false, |l| l.is_relative()) {
|
||||||
|
self.base.y = Some(base.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify a single constraint.
|
/// Verify a single constraint.
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::geom::{Length, Size};
|
|
||||||
|
|
||||||
/// 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, ®ions).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>>>;
|
|
||||||
|
|
||||||
/// Convert to a packed block-level node.
|
|
||||||
fn pack(self) -> BlockNode
|
|
||||||
where
|
|
||||||
Self: Sized + Hash + 'static,
|
|
||||||
{
|
|
||||||
BlockNode {
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
hash: hash_node(&self),
|
|
||||||
node: Rc::new(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A packed [block-level](BlockLevel) layouting node with precomputed hash.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BlockNode {
|
|
||||||
node: Rc<dyn BlockLevel>,
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
hash: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pack(self) -> BlockNode
|
|
||||||
where
|
|
||||||
Self: Sized + Hash + 'static,
|
|
||||||
{
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for BlockNode {
|
|
||||||
fn hash<H: Hasher>(&self, _state: &mut H) {
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
_state.write_u64(self.hash);
|
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/// Convert to a packed inline-level node.
|
|
||||||
fn pack(self) -> InlineNode
|
|
||||||
where
|
|
||||||
Self: Sized + Hash + 'static,
|
|
||||||
{
|
|
||||||
InlineNode {
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
hash: hash_node(&self),
|
|
||||||
node: Rc::new(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A packed [inline-level](InlineLevel) layouting node with precomputed hash.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InlineNode {
|
|
||||||
node: Rc<dyn InlineLevel>,
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
hash: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InlineLevel for InlineNode {
|
|
||||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
|
||||||
self.node.layout(ctx, space, base)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pack(self) -> InlineNode
|
|
||||||
where
|
|
||||||
Self: Sized + Hash + 'static,
|
|
||||||
{
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for InlineNode {
|
|
||||||
fn hash<H: Hasher>(&self, _state: &mut H) {
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
_state.write_u64(self.hash);
|
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
use std::any::Any;
|
|
||||||
let mut state = fxhash::FxHasher64::default();
|
|
||||||
node.type_id().hash(&mut state);
|
|
||||||
node.hash(&mut state);
|
|
||||||
state.finish()
|
|
||||||
}
|
|
@ -1,29 +1,27 @@
|
|||||||
//! Layouting.
|
//! Layouting infrastructure.
|
||||||
|
|
||||||
mod constraints;
|
mod constraints;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
mod incremental;
|
mod incremental;
|
||||||
mod levels;
|
|
||||||
mod regions;
|
mod regions;
|
||||||
|
|
||||||
pub use constraints::*;
|
pub use constraints::*;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
pub use incremental::*;
|
pub use incremental::*;
|
||||||
pub use levels::*;
|
|
||||||
pub use regions::*;
|
pub use regions::*;
|
||||||
|
|
||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
|
use crate::library::DocumentNode;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Layout a page-level node into a collection of frames.
|
/// Layout a document node into a collection of frames.
|
||||||
pub fn layout<T>(ctx: &mut Context, node: &T) -> Vec<Rc<Frame>>
|
pub fn layout(ctx: &mut Context, node: &DocumentNode) -> Vec<Rc<Frame>> {
|
||||||
where
|
|
||||||
T: PageLevel + ?Sized,
|
|
||||||
{
|
|
||||||
let mut ctx = LayoutContext::new(ctx);
|
let mut ctx = LayoutContext::new(ctx);
|
||||||
node.layout(&mut ctx)
|
node.layout(&mut ctx)
|
||||||
}
|
}
|
||||||
@ -55,3 +53,98 @@ impl<'a> LayoutContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A node that can be layouted into a sequence of regions.
|
||||||
|
///
|
||||||
|
/// Layout return one frame per used region alongside constraints that define
|
||||||
|
/// whether the result is reusable in other regions.
|
||||||
|
pub trait Layout: Debug {
|
||||||
|
/// Layout the node into the given regions, producing constrained frames.
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
regions: &Regions,
|
||||||
|
) -> Vec<Constrained<Rc<Frame>>>;
|
||||||
|
|
||||||
|
/// Convert to a packed node.
|
||||||
|
fn pack(self) -> PackedNode
|
||||||
|
where
|
||||||
|
Self: Sized + Hash + 'static,
|
||||||
|
{
|
||||||
|
PackedNode {
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
hash: {
|
||||||
|
use std::any::Any;
|
||||||
|
let mut state = fxhash::FxHasher64::default();
|
||||||
|
self.type_id().hash(&mut state);
|
||||||
|
self.hash(&mut state);
|
||||||
|
state.finish()
|
||||||
|
},
|
||||||
|
node: Rc::new(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A packed layouting node with precomputed hash.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PackedNode {
|
||||||
|
node: Rc<dyn Layout>,
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for PackedNode {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack(self) -> PackedNode
|
||||||
|
where
|
||||||
|
Self: Sized + Hash + 'static,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for PackedNode {
|
||||||
|
fn hash<H: Hasher>(&self, _state: &mut H) {
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
_state.write_u64(self.hash);
|
||||||
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for PackedNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.node.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
13
src/lib.rs
13
src/lib.rs
@ -9,9 +9,9 @@
|
|||||||
//! [module], consisting of a scope of values that were exported by the code
|
//! [module], consisting of a scope of values that were exported by the code
|
||||||
//! and a template with the contents of the module. This template can be
|
//! and a template with the contents of the module. This template can be
|
||||||
//! instantiated with a style to produce a layout tree, a high-level, fully
|
//! instantiated with a style to produce a layout tree, a high-level, fully
|
||||||
//! styled representation of the document. The nodes of this tree are
|
//! styled representation, rooted in the [document node]. The nodes of this
|
||||||
//! self-contained and order-independent and thus much better suited for
|
//! tree are self-contained and order-independent and thus much better suited
|
||||||
//! layouting than the raw markup.
|
//! for layouting than the raw markup.
|
||||||
//! - **Layouting:** Next, the tree is [layouted] into a portable version of the
|
//! - **Layouting:** Next, the tree is [layouted] into a portable version of the
|
||||||
//! typeset document. The output of this is a collection of [`Frame`]s (one
|
//! typeset document. The output of this is a collection of [`Frame`]s (one
|
||||||
//! per page), ready for exporting.
|
//! per page), ready for exporting.
|
||||||
@ -24,6 +24,7 @@
|
|||||||
//! [evaluate]: eval::eval
|
//! [evaluate]: eval::eval
|
||||||
//! [module]: eval::Module
|
//! [module]: eval::Module
|
||||||
//! [layout tree]: layout::LayoutTree
|
//! [layout tree]: layout::LayoutTree
|
||||||
|
//! [document node]: library::DocumentNode
|
||||||
//! [layouted]: layout::layout
|
//! [layouted]: layout::layout
|
||||||
//! [PDF]: export::pdf
|
//! [PDF]: export::pdf
|
||||||
|
|
||||||
@ -53,9 +54,9 @@ use crate::eval::{Module, Scope};
|
|||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::layout::PageNode;
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use crate::layout::{EvictionPolicy, LayoutCache};
|
use crate::layout::{EvictionPolicy, LayoutCache};
|
||||||
|
use crate::library::DocumentNode;
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
@ -107,9 +108,9 @@ impl Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a source file and produce the resulting page nodes.
|
/// Execute a source file and produce the resulting page nodes.
|
||||||
pub fn execute(&mut self, id: SourceId) -> TypResult<Vec<PageNode>> {
|
pub fn execute(&mut self, id: SourceId) -> TypResult<DocumentNode> {
|
||||||
let module = self.evaluate(id)?;
|
let module = self.evaluate(id)?;
|
||||||
Ok(module.template.to_pages(&self.style))
|
Ok(module.template.to_document(&self.style))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Typeset a source file into a collection of layouted frames.
|
/// Typeset a source file into a collection of layouted frames.
|
||||||
|
@ -5,14 +5,13 @@ use super::{ShapeKind, ShapeNode};
|
|||||||
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let fill = args.named("fill")?;
|
|
||||||
let body: Template = args.find().unwrap_or_default();
|
let body: Template = args.find().unwrap_or_default();
|
||||||
Ok(Value::Template(Template::from_inline(move |style| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
ShapeNode {
|
ShapeNode {
|
||||||
shape: ShapeKind::Rect,
|
shape: ShapeKind::Rect,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
fill: fill.map(Paint::Color),
|
fill: None,
|
||||||
child: Some(body.to_flow(style).pack()),
|
child: Some(body.to_flow(style).pack()),
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
16
src/library/document.rs
Normal file
16
src/library/document.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use super::prelude::*;
|
||||||
|
use super::PageNode;
|
||||||
|
|
||||||
|
/// The root layout node, a document consisting of top-level page runs.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct DocumentNode {
|
||||||
|
/// The page runs.
|
||||||
|
pub pages: Vec<PageNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocumentNode {
|
||||||
|
/// Layout the document into a sequence of frames, one per page.
|
||||||
|
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||||
|
self.pages.iter().flat_map(|node| node.layout(ctx)).collect()
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,7 @@ pub struct FlowNode {
|
|||||||
pub children: Vec<FlowChild>,
|
pub children: Vec<FlowChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevel for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -63,8 +63,8 @@ impl BlockLevel for FlowNode {
|
|||||||
pub enum FlowChild {
|
pub enum FlowChild {
|
||||||
/// Vertical spacing between other children.
|
/// Vertical spacing between other children.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// Any block node and how to align it in the flow.
|
/// A node and how to align it in the flow.
|
||||||
Node(BlockNode, Align),
|
Node(PackedNode, Align),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for FlowChild {
|
impl Debug for FlowChild {
|
||||||
@ -157,8 +157,8 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
self.items.push(FlowItem::Absolute(resolved));
|
self.items.push(FlowItem::Absolute(resolved));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a block node.
|
/// Layout a node.
|
||||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode, align: Align) {
|
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode, align: Align) {
|
||||||
let frames = node.layout(ctx, &self.regions);
|
let frames = node.layout(ctx, &self.regions);
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
|
@ -58,7 +58,7 @@ pub struct GridNode {
|
|||||||
/// Defines sizing of gutter rows and columns between content.
|
/// Defines sizing of gutter rows and columns between content.
|
||||||
pub gutter: Spec<Vec<TrackSizing>>,
|
pub gutter: Spec<Vec<TrackSizing>>,
|
||||||
/// The nodes to be arranged in a grid.
|
/// The nodes to be arranged in a grid.
|
||||||
pub children: Vec<BlockNode>,
|
pub children: Vec<PackedNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how to size a grid cell along an axis.
|
/// Defines how to size a grid cell along an axis.
|
||||||
@ -72,7 +72,7 @@ pub enum TrackSizing {
|
|||||||
Fractional(Fractional),
|
Fractional(Fractional),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevel for GridNode {
|
impl Layout for GridNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -92,7 +92,7 @@ impl BlockLevel for GridNode {
|
|||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
struct GridLayouter<'a> {
|
struct GridLayouter<'a> {
|
||||||
/// The children of the grid.
|
/// The children of the grid.
|
||||||
children: &'a [BlockNode],
|
children: &'a [PackedNode],
|
||||||
/// Whether the grid should expand to fill the region.
|
/// Whether the grid should expand to fill the region.
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The column tracks including gutter tracks.
|
/// The column tracks including gutter tracks.
|
||||||
@ -591,7 +591,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
///
|
///
|
||||||
/// Returns `None` if it's a gutter cell.
|
/// Returns `None` if it's a gutter cell.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn cell(&self, x: usize, y: usize) -> Option<&'a BlockNode> {
|
fn cell(&self, x: usize, y: usize) -> Option<&'a PackedNode> {
|
||||||
assert!(x < self.cols.len());
|
assert!(x < self.cols.len());
|
||||||
assert!(y < self.rows.len());
|
assert!(y < self.rows.len());
|
||||||
|
|
||||||
|
@ -36,23 +36,31 @@ pub struct ImageNode {
|
|||||||
pub height: Option<Linear>,
|
pub height: Option<Linear>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlineLevel for ImageNode {
|
impl Layout for ImageNode {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
regions: &Regions,
|
||||||
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let img = ctx.images.get(self.id);
|
let img = ctx.images.get(self.id);
|
||||||
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
|
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
|
||||||
let pixel_ratio = pixel_size.x / pixel_size.y;
|
let pixel_ratio = pixel_size.x / pixel_size.y;
|
||||||
|
|
||||||
let width = self.width.map(|w| w.resolve(base.w));
|
let width = self.width.map(|w| w.resolve(regions.base.w));
|
||||||
let height = self.height.map(|w| w.resolve(base.h));
|
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 size = match (width, height) {
|
let size = match (width, height) {
|
||||||
(Some(width), Some(height)) => Size::new(width, height),
|
(Some(width), Some(height)) => Size::new(width, height),
|
||||||
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
||||||
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
if space.is_finite() {
|
cts.exact.x = Some(regions.current.w);
|
||||||
|
if regions.current.w.is_finite() {
|
||||||
// Fit to width.
|
// Fit to width.
|
||||||
Size::new(space, space / pixel_ratio)
|
Size::new(regions.current.w, regions.current.w / pixel_ratio)
|
||||||
} else {
|
} else {
|
||||||
// Unbounded width, we have to make up something,
|
// Unbounded width, we have to make up something,
|
||||||
// so it is 1pt per pixel.
|
// so it is 1pt per pixel.
|
||||||
@ -63,6 +71,7 @@ impl InlineLevel for ImageNode {
|
|||||||
|
|
||||||
let mut frame = Frame::new(size, size.h);
|
let mut frame = Frame::new(size, size.h);
|
||||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
frame.push(Point::zero(), Element::Image(self.id, size));
|
||||||
frame
|
|
||||||
|
vec![frame.constrain(cts)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
mod align;
|
mod align;
|
||||||
mod container;
|
mod container;
|
||||||
mod deco;
|
mod deco;
|
||||||
|
mod document;
|
||||||
mod flow;
|
mod flow;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod image;
|
mod image;
|
||||||
@ -36,6 +37,7 @@ pub use self::image::*;
|
|||||||
pub use align::*;
|
pub use align::*;
|
||||||
pub use container::*;
|
pub use container::*;
|
||||||
pub use deco::*;
|
pub use deco::*;
|
||||||
|
pub use document::*;
|
||||||
pub use flow::*;
|
pub use flow::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
|
@ -16,7 +16,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
bottom.or(all).unwrap_or_default(),
|
bottom.or(all).unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Value::Template(Template::from_block(move |style| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
PadNode {
|
PadNode {
|
||||||
padding,
|
padding,
|
||||||
child: body.to_flow(style).pack(),
|
child: body.to_flow(style).pack(),
|
||||||
@ -30,10 +30,10 @@ pub struct PadNode {
|
|||||||
/// The amount of padding.
|
/// The amount of padding.
|
||||||
pub padding: Sides<Linear>,
|
pub padding: Sides<Linear>,
|
||||||
/// The child node whose sides to pad.
|
/// The child node whose sides to pad.
|
||||||
pub child: BlockNode,
|
pub child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevel for PadNode {
|
impl Layout for PadNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
|
@ -73,3 +73,23 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
|||||||
template.pagebreak(true);
|
template.pagebreak(true);
|
||||||
Ok(Value::Template(template))
|
Ok(Value::Template(template))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layouts its children onto one or multiple pages.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct PageNode {
|
||||||
|
/// The size of the page.
|
||||||
|
pub size: Size,
|
||||||
|
/// The node that produces the actual pages.
|
||||||
|
pub child: PackedNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageNode {
|
||||||
|
/// Layout the page run into a sequence of frames, one per page.
|
||||||
|
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, ®ions).into_iter().map(|c| c.item).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ use xi_unicode::LineBreakIterator;
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{shape, Decoration, ShapedText, Spacing};
|
use super::{shape, Decoration, ShapedText, Spacing};
|
||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
use crate::util::{EcoString, RangeExt, SliceExt};
|
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
|
||||||
|
|
||||||
/// `par`: Configure paragraphs.
|
/// `par`: Configure paragraphs.
|
||||||
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -63,7 +63,7 @@ pub struct ParNode {
|
|||||||
pub children: Vec<ParChild>,
|
pub children: Vec<ParChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevel for ParNode {
|
impl Layout for ParNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -77,7 +77,7 @@ impl BlockLevel for ParNode {
|
|||||||
|
|
||||||
// Prepare paragraph layout by building a representation on which we can
|
// Prepare paragraph layout by building a representation on which we can
|
||||||
// do line breaking without layouting each and every line from scratch.
|
// do line breaking without layouting each and every line from scratch.
|
||||||
let layouter = ParLayouter::new(self, ctx, regions, bidi);
|
let layouter = ParLayouter::new(self, ctx, regions.clone(), bidi);
|
||||||
|
|
||||||
// Find suitable linebreaks.
|
// Find suitable linebreaks.
|
||||||
layouter.layout(ctx, regions.clone())
|
layouter.layout(ctx, regions.clone())
|
||||||
@ -126,7 +126,7 @@ pub enum ParChild {
|
|||||||
/// A run of text and how to align it in its line.
|
/// A run of text and how to align it in its line.
|
||||||
Text(EcoString, Align, Rc<TextStyle>),
|
Text(EcoString, Align, Rc<TextStyle>),
|
||||||
/// Any child node and how to align it in its line.
|
/// Any child node and how to align it in its line.
|
||||||
Node(InlineNode, Align),
|
Node(PackedNode, Align),
|
||||||
/// A decoration that applies until a matching `Undecorate`.
|
/// A decoration that applies until a matching `Undecorate`.
|
||||||
Decorate(Decoration),
|
Decorate(Decoration),
|
||||||
/// The end of a decoration.
|
/// The end of a decoration.
|
||||||
@ -182,9 +182,12 @@ impl<'a> ParLayouter<'a> {
|
|||||||
fn new(
|
fn new(
|
||||||
par: &'a ParNode,
|
par: &'a ParNode,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
mut regions: Regions,
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
// Disable expansion for children.
|
||||||
|
regions.expand = Spec::splat(false);
|
||||||
|
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
let mut ranges = vec![];
|
let mut ranges = vec![];
|
||||||
let mut starts = vec![];
|
let mut starts = vec![];
|
||||||
@ -216,8 +219,8 @@ impl<'a> ParLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParChild::Node(ref node, align) => {
|
ParChild::Node(ref node, align) => {
|
||||||
let frame = node.layout(ctx, regions.current.w, regions.base);
|
let frame = node.layout(ctx, ®ions).remove(0);
|
||||||
items.push(ParItem::Frame(frame, align));
|
items.push(ParItem::Frame(Rc::take(frame.item), align));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
ParChild::Decorate(ref deco) => {
|
ParChild::Decorate(ref deco) => {
|
||||||
|
@ -94,7 +94,7 @@ pub struct ShapeNode {
|
|||||||
/// How to fill the shape, if at all.
|
/// How to fill the shape, if at all.
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
/// The child node to place into the shape, if any.
|
/// The child node to place into the shape, if any.
|
||||||
pub child: Option<BlockNode>,
|
pub child: Option<PackedNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of a shape.
|
/// The type of a shape.
|
||||||
@ -110,15 +110,36 @@ pub enum ShapeKind {
|
|||||||
Ellipse,
|
Ellipse,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlineLevel for ShapeNode {
|
impl Layout for ShapeNode {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
regions: &Regions,
|
||||||
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
// Resolve width and height relative to the region's base.
|
// Resolve width and height relative to the region's base.
|
||||||
let width = self.width.map(|w| w.resolve(base.w));
|
let width = self.width.map(|w| w.resolve(regions.base.w));
|
||||||
let height = self.height.map(|h| h.resolve(base.h));
|
let height = self.height.map(|h| h.resolve(regions.base.h));
|
||||||
|
|
||||||
|
// Generate 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);
|
||||||
|
}
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
let mut frame = if let Some(child) = &self.child {
|
let mut frame = if let Some(child) = &self.child {
|
||||||
let mut node: &dyn BlockLevel = child;
|
let mut node: &dyn Layout = child;
|
||||||
|
|
||||||
let padded;
|
let padded;
|
||||||
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
|
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
|
||||||
@ -133,11 +154,14 @@ impl InlineLevel for ShapeNode {
|
|||||||
|
|
||||||
// The "pod" is the region into which the child will be layouted.
|
// The "pod" is the region into which the child will be layouted.
|
||||||
let mut pod = {
|
let mut pod = {
|
||||||
let size = Size::new(width.unwrap_or(space), height.unwrap_or(base.h));
|
let size = Size::new(
|
||||||
|
width.unwrap_or(regions.current.w),
|
||||||
|
height.unwrap_or(regions.base.h),
|
||||||
|
);
|
||||||
|
|
||||||
let base = Size::new(
|
let base = Size::new(
|
||||||
if width.is_some() { size.w } else { base.w },
|
if width.is_some() { size.w } else { regions.base.w },
|
||||||
if height.is_some() { size.h } else { base.h },
|
if height.is_some() { size.h } else { regions.base.h },
|
||||||
);
|
);
|
||||||
|
|
||||||
let expand = Spec::new(width.is_some(), height.is_some());
|
let expand = Spec::new(width.is_some(), height.is_some());
|
||||||
@ -180,6 +204,6 @@ impl InlineLevel for ShapeNode {
|
|||||||
frame.prepend(pos, Element::Geometry(geometry, fill));
|
frame.prepend(pos, Element::Geometry(geometry, fill));
|
||||||
}
|
}
|
||||||
|
|
||||||
frame
|
vec![frame.constrain(cts)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ pub struct StackNode {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevel for StackNode {
|
impl Layout for StackNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -76,7 +76,7 @@ pub enum StackChild {
|
|||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// Any block node and how to align it in the stack.
|
/// Any block node and how to align it in the stack.
|
||||||
Node(BlockNode),
|
Node(PackedNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for StackChild {
|
impl Debug for StackChild {
|
||||||
@ -174,8 +174,8 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.items.push(StackItem::Absolute(resolved));
|
self.items.push(StackItem::Absolute(resolved));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a block node.
|
/// Layout a node.
|
||||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode) {
|
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
|
||||||
let frames = node.layout(ctx, &self.regions);
|
let frames = node.layout(ctx, &self.regions);
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
use super::{ShapeKind, ShapeNode};
|
|
||||||
|
|
||||||
/// `move`: Move content without affecting layout.
|
/// `move`: Move content without affecting layout.
|
||||||
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
@ -10,13 +9,7 @@ pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
Ok(Value::Template(Template::from_inline(move |style| {
|
Ok(Value::Template(Template::from_inline(move |style| {
|
||||||
MoveNode {
|
MoveNode {
|
||||||
offset: Spec::new(x, y),
|
offset: Spec::new(x, y),
|
||||||
child: ShapeNode {
|
child: body.to_flow(style).pack(),
|
||||||
shape: ShapeKind::Rect,
|
|
||||||
width: None,
|
|
||||||
height: None,
|
|
||||||
fill: None,
|
|
||||||
child: Some(body.to_flow(style).pack()),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@ -24,21 +17,30 @@ pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
struct MoveNode {
|
struct MoveNode {
|
||||||
offset: Spec<Option<Linear>>,
|
offset: Spec<Option<Linear>>,
|
||||||
child: ShapeNode,
|
child: PackedNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlineLevel for MoveNode {
|
impl Layout for MoveNode {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
regions: &Regions,
|
||||||
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
let mut frames = self.child.layout(ctx, regions);
|
||||||
|
|
||||||
|
for (Constrained { item: frame, .. }, (_, base)) in
|
||||||
|
frames.iter_mut().zip(regions.iter())
|
||||||
|
{
|
||||||
let offset = Point::new(
|
let offset = Point::new(
|
||||||
self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(),
|
self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(),
|
||||||
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
|
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut frame = self.child.layout(ctx, space, base);
|
for (point, _) in &mut Rc::make_mut(frame).children {
|
||||||
for (point, _) in &mut frame.children {
|
|
||||||
*point += offset;
|
*point += offset;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frame
|
frames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,8 +60,8 @@ fn try_main() -> anyhow::Result<()> {
|
|||||||
// Typeset.
|
// Typeset.
|
||||||
match ctx.typeset(id) {
|
match ctx.typeset(id) {
|
||||||
// Export the PDF.
|
// Export the PDF.
|
||||||
Ok(document) => {
|
Ok(frames) => {
|
||||||
let buffer = export::pdf(&ctx, &document);
|
let buffer = export::pdf(&ctx, &frames);
|
||||||
fs::write(&args.output, buffer).context("failed to write PDF file")?;
|
fs::write(&args.output, buffer).context("failed to write PDF file")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Source files.
|
//! Source file management.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
@ -11,19 +11,11 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Hi #box(pad(left: 10pt)[]) there
|
Hi #pad(left: 10pt)[A] there
|
||||||
|
|
||||||
---
|
---
|
||||||
#let pad(body) = pad(left: 10pt, right: 10pt, body)
|
// Pad can grow.
|
||||||
|
#pad(left: 10pt, right: 10pt)[PL #h(1fr) PR]
|
||||||
// Pad inherits expansion behaviour from stack ....
|
|
||||||
#pad[PL #align(right)[PR]]
|
|
||||||
|
|
||||||
// ... block ...
|
|
||||||
#block(pad[PL #align(right)[PR]])
|
|
||||||
|
|
||||||
// ... and box.
|
|
||||||
#box(pad[PL #align(right)[PR]])
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test that the pad node doesn't consume the whole region.
|
// Test that the pad node doesn't consume the whole region.
|
||||||
|
@ -17,7 +17,7 @@ World! 🌍
|
|||||||
---
|
---
|
||||||
#page(height: 2cm)
|
#page(height: 2cm)
|
||||||
#font(white)
|
#font(white)
|
||||||
#box(fill: forest)[
|
#rect(fill: forest)[
|
||||||
#v(1fr)
|
#v(1fr)
|
||||||
#h(1fr) Hi you! #h(5pt)
|
#h(1fr) Hi you! #h(5pt)
|
||||||
#v(5pt)
|
#v(5pt)
|
||||||
|
@ -19,7 +19,7 @@ use typst::geom::{
|
|||||||
use typst::image::Image;
|
use typst::image::Image;
|
||||||
use typst::layout::layout;
|
use typst::layout::layout;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use typst::layout::PageNode;
|
use typst::library::DocumentNode;
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::Scanner;
|
use typst::parse::Scanner;
|
||||||
use typst::source::SourceFile;
|
use typst::source::SourceFile;
|
||||||
@ -231,11 +231,11 @@ fn test_part(
|
|||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
let (frames, mut errors) = match ctx.execute(id) {
|
let (frames, mut errors) = match ctx.execute(id) {
|
||||||
Ok(tree) => {
|
Ok(document) => {
|
||||||
let mut frames = layout(ctx, &tree);
|
let mut frames = layout(ctx, &document);
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
(ok &= test_incremental(ctx, i, &tree, &frames));
|
(ok &= test_incremental(ctx, i, &document, &frames));
|
||||||
|
|
||||||
if !compare_ref {
|
if !compare_ref {
|
||||||
frames.clear();
|
frames.clear();
|
||||||
@ -283,7 +283,7 @@ fn test_part(
|
|||||||
fn test_incremental(
|
fn test_incremental(
|
||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
i: usize,
|
i: usize,
|
||||||
tree: &[PageNode],
|
document: &DocumentNode,
|
||||||
frames: &[Rc<Frame>],
|
frames: &[Rc<Frame>],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
@ -298,7 +298,7 @@ fn test_incremental(
|
|||||||
|
|
||||||
ctx.layouts.turnaround();
|
ctx.layouts.turnaround();
|
||||||
|
|
||||||
let cached = layout(ctx, tree);
|
let cached = layout(ctx, document);
|
||||||
let misses = ctx
|
let misses = ctx
|
||||||
.layouts
|
.layouts
|
||||||
.entries()
|
.entries()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user