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) {
|
||||
let (mut ctx, id) = context();
|
||||
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) {
|
||||
|
@ -7,9 +7,10 @@ use std::rc::Rc;
|
||||
|
||||
use crate::diag::StrResult;
|
||||
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::{
|
||||
Decoration, FlowChild, FlowNode, PadNode, ParChild, ParNode, Spacing,
|
||||
Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode,
|
||||
Spacing,
|
||||
};
|
||||
use crate::style::Style;
|
||||
use crate::util::EcoString;
|
||||
@ -36,9 +37,9 @@ enum TemplateNode {
|
||||
/// A decorated template.
|
||||
Decorated(Decoration, Template),
|
||||
/// An inline node builder.
|
||||
Inline(Rc<dyn Fn(&Style) -> InlineNode>),
|
||||
/// An block node builder.
|
||||
Block(Rc<dyn Fn(&Style) -> BlockNode>),
|
||||
Inline(Rc<dyn Fn(&Style) -> PackedNode>),
|
||||
/// A block node builder.
|
||||
Block(Rc<dyn Fn(&Style) -> PackedNode>),
|
||||
/// Save the current style.
|
||||
Save,
|
||||
/// Restore the last saved style.
|
||||
@ -57,7 +58,7 @@ impl Template {
|
||||
pub fn from_inline<F, T>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Style) -> T + 'static,
|
||||
T: InlineLevel + Hash + 'static,
|
||||
T: Layout + Hash + 'static,
|
||||
{
|
||||
let node = TemplateNode::Inline(Rc::new(move |s| f(s).pack()));
|
||||
Self(Rc::new(vec![node]))
|
||||
@ -67,7 +68,7 @@ impl Template {
|
||||
pub fn from_block<F, T>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Style) -> T + 'static,
|
||||
T: BlockLevel + Hash + 'static,
|
||||
T: Layout + Hash + 'static,
|
||||
{
|
||||
let node = TemplateNode::Block(Rc::new(move |s| f(s).pack()));
|
||||
Self(Rc::new(vec![node]))
|
||||
@ -158,10 +159,10 @@ impl Template {
|
||||
|
||||
/// Build the layout tree resulting from instantiating the template with the
|
||||
/// 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);
|
||||
builder.template(self);
|
||||
builder.build_pages()
|
||||
builder.build_document()
|
||||
}
|
||||
|
||||
/// Repeat this template `n` times.
|
||||
@ -327,13 +328,13 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// 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;
|
||||
self.flow.par.push(ParChild::Node(node.into(), align));
|
||||
}
|
||||
|
||||
/// 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.flow.push(FlowChild::Node(node.into(), self.style.aligns.block));
|
||||
self.parbreak();
|
||||
@ -359,10 +360,10 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// 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());
|
||||
self.pagebreak(true, false);
|
||||
self.finished
|
||||
DocumentNode { pages: self.finished }
|
||||
}
|
||||
|
||||
/// 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 crate::diag::TypResult;
|
||||
use crate::geom::Spec;
|
||||
use crate::layout::BlockLevel;
|
||||
use crate::layout::Layout;
|
||||
use crate::library::{GridNode, ParChild, ParNode, TrackSizing};
|
||||
use crate::syntax::ast::*;
|
||||
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::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
|
||||
/// in the context used during compilation such that things like fonts and
|
||||
/// 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> {
|
||||
PdfExporter::new(ctx, frames).write()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::frame::Frame;
|
||||
use crate::geom::{Length, Size, Spec};
|
||||
use crate::geom::{Length, Linear, Size, Spec};
|
||||
|
||||
/// Constrain a frame with constraints.
|
||||
pub trait Constrain {
|
||||
@ -68,6 +68,18 @@ impl Constraints {
|
||||
&& verify(self.exact, current, 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.
|
||||
|
@ -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;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
mod incremental;
|
||||
mod levels;
|
||||
mod regions;
|
||||
|
||||
pub use constraints::*;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub use incremental::*;
|
||||
pub use levels::*;
|
||||
pub use regions::*;
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::font::FontStore;
|
||||
use crate::frame::Frame;
|
||||
use crate::image::ImageStore;
|
||||
use crate::library::DocumentNode;
|
||||
use crate::Context;
|
||||
|
||||
/// 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,
|
||||
{
|
||||
/// Layout a document node into a collection of frames.
|
||||
pub fn layout(ctx: &mut Context, node: &DocumentNode) -> Vec<Rc<Frame>> {
|
||||
let mut ctx = LayoutContext::new(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
|
||||
//! 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
|
||||
//! 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.
|
||||
//! styled representation, rooted in the [document node]. The nodes of this
|
||||
//! tree are self-contained and order-independent and thus much better suited
|
||||
//! for layouting than the raw markup.
|
||||
//! - **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
|
||||
//! per page), ready for exporting.
|
||||
@ -24,6 +24,7 @@
|
||||
//! [evaluate]: eval::eval
|
||||
//! [module]: eval::Module
|
||||
//! [layout tree]: layout::LayoutTree
|
||||
//! [document node]: library::DocumentNode
|
||||
//! [layouted]: layout::layout
|
||||
//! [PDF]: export::pdf
|
||||
|
||||
@ -53,9 +54,9 @@ use crate::eval::{Module, Scope};
|
||||
use crate::font::FontStore;
|
||||
use crate::frame::Frame;
|
||||
use crate::image::ImageStore;
|
||||
use crate::layout::PageNode;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use crate::layout::{EvictionPolicy, LayoutCache};
|
||||
use crate::library::DocumentNode;
|
||||
use crate::loading::Loader;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::style::Style;
|
||||
@ -107,9 +108,9 @@ impl Context {
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
Ok(module.template.to_pages(&self.style))
|
||||
Ok(module.template.to_document(&self.style))
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let width = args.named("width")?;
|
||||
let height = args.named("height")?;
|
||||
let fill = args.named("fill")?;
|
||||
let body: Template = args.find().unwrap_or_default();
|
||||
Ok(Value::Template(Template::from_inline(move |style| {
|
||||
ShapeNode {
|
||||
shape: ShapeKind::Rect,
|
||||
width,
|
||||
height,
|
||||
fill: fill.map(Paint::Color),
|
||||
fill: None,
|
||||
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>,
|
||||
}
|
||||
|
||||
impl BlockLevel for FlowNode {
|
||||
impl Layout for FlowNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -63,8 +63,8 @@ impl BlockLevel for FlowNode {
|
||||
pub enum FlowChild {
|
||||
/// Vertical spacing between other children.
|
||||
Spacing(Spacing),
|
||||
/// Any block node and how to align it in the flow.
|
||||
Node(BlockNode, Align),
|
||||
/// A node and how to align it in the flow.
|
||||
Node(PackedNode, Align),
|
||||
}
|
||||
|
||||
impl Debug for FlowChild {
|
||||
@ -157,8 +157,8 @@ impl<'a> FlowLayouter<'a> {
|
||||
self.items.push(FlowItem::Absolute(resolved));
|
||||
}
|
||||
|
||||
/// Layout a block node.
|
||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode, align: Align) {
|
||||
/// Layout a node.
|
||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode, align: Align) {
|
||||
let frames = node.layout(ctx, &self.regions);
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
|
@ -58,7 +58,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<BlockNode>,
|
||||
pub children: Vec<PackedNode>,
|
||||
}
|
||||
|
||||
/// Defines how to size a grid cell along an axis.
|
||||
@ -72,7 +72,7 @@ pub enum TrackSizing {
|
||||
Fractional(Fractional),
|
||||
}
|
||||
|
||||
impl BlockLevel for GridNode {
|
||||
impl Layout for GridNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -92,7 +92,7 @@ impl BlockLevel for GridNode {
|
||||
/// Performs grid layout.
|
||||
struct GridLayouter<'a> {
|
||||
/// The children of the grid.
|
||||
children: &'a [BlockNode],
|
||||
children: &'a [PackedNode],
|
||||
/// Whether the grid should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
/// The column tracks including gutter tracks.
|
||||
@ -591,7 +591,7 @@ impl<'a> GridLayouter<'a> {
|
||||
///
|
||||
/// Returns `None` if it's a gutter cell.
|
||||
#[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!(y < self.rows.len());
|
||||
|
||||
|
@ -36,23 +36,31 @@ pub struct ImageNode {
|
||||
pub height: Option<Linear>,
|
||||
}
|
||||
|
||||
impl InlineLevel for ImageNode {
|
||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||
impl Layout for ImageNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<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(base.w));
|
||||
let height = self.height.map(|w| w.resolve(base.h));
|
||||
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 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) => {
|
||||
if space.is_finite() {
|
||||
cts.exact.x = Some(regions.current.w);
|
||||
if regions.current.w.is_finite() {
|
||||
// Fit to width.
|
||||
Size::new(space, space / pixel_ratio)
|
||||
Size::new(regions.current.w, regions.current.w / pixel_ratio)
|
||||
} else {
|
||||
// Unbounded width, we have to make up something,
|
||||
// so it is 1pt per pixel.
|
||||
@ -63,6 +71,7 @@ impl InlineLevel for ImageNode {
|
||||
|
||||
let mut frame = Frame::new(size, size.h);
|
||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
||||
frame
|
||||
|
||||
vec![frame.constrain(cts)]
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
mod align;
|
||||
mod container;
|
||||
mod deco;
|
||||
mod document;
|
||||
mod flow;
|
||||
mod grid;
|
||||
mod image;
|
||||
@ -36,6 +37,7 @@ pub use self::image::*;
|
||||
pub use align::*;
|
||||
pub use container::*;
|
||||
pub use deco::*;
|
||||
pub use document::*;
|
||||
pub use flow::*;
|
||||
pub use grid::*;
|
||||
pub use pad::*;
|
||||
|
@ -16,7 +16,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
bottom.or(all).unwrap_or_default(),
|
||||
);
|
||||
|
||||
Ok(Value::Template(Template::from_block(move |style| {
|
||||
Ok(Value::Template(Template::from_inline(move |style| {
|
||||
PadNode {
|
||||
padding,
|
||||
child: body.to_flow(style).pack(),
|
||||
@ -30,10 +30,10 @@ pub struct PadNode {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Linear>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: BlockNode,
|
||||
pub child: PackedNode,
|
||||
}
|
||||
|
||||
impl BlockLevel for PadNode {
|
||||
impl Layout for PadNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
|
@ -73,3 +73,23 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
|
||||
template.pagebreak(true);
|
||||
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::{shape, Decoration, ShapedText, Spacing};
|
||||
use crate::style::TextStyle;
|
||||
use crate::util::{EcoString, RangeExt, SliceExt};
|
||||
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
|
||||
|
||||
/// `par`: Configure paragraphs.
|
||||
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
@ -63,7 +63,7 @@ pub struct ParNode {
|
||||
pub children: Vec<ParChild>,
|
||||
}
|
||||
|
||||
impl BlockLevel for ParNode {
|
||||
impl Layout for ParNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -77,7 +77,7 @@ impl BlockLevel for ParNode {
|
||||
|
||||
// Prepare paragraph layout by building a representation on which we can
|
||||
// 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.
|
||||
layouter.layout(ctx, regions.clone())
|
||||
@ -126,7 +126,7 @@ pub enum ParChild {
|
||||
/// A run of text and how to align it in its line.
|
||||
Text(EcoString, Align, Rc<TextStyle>),
|
||||
/// 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`.
|
||||
Decorate(Decoration),
|
||||
/// The end of a decoration.
|
||||
@ -182,9 +182,12 @@ impl<'a> ParLayouter<'a> {
|
||||
fn new(
|
||||
par: &'a ParNode,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
mut regions: Regions,
|
||||
bidi: BidiInfo<'a>,
|
||||
) -> Self {
|
||||
// Disable expansion for children.
|
||||
regions.expand = Spec::splat(false);
|
||||
|
||||
let mut items = vec![];
|
||||
let mut ranges = vec![];
|
||||
let mut starts = vec![];
|
||||
@ -216,8 +219,8 @@ impl<'a> ParLayouter<'a> {
|
||||
}
|
||||
}
|
||||
ParChild::Node(ref node, align) => {
|
||||
let frame = node.layout(ctx, regions.current.w, regions.base);
|
||||
items.push(ParItem::Frame(frame, align));
|
||||
let frame = node.layout(ctx, ®ions).remove(0);
|
||||
items.push(ParItem::Frame(Rc::take(frame.item), align));
|
||||
ranges.push(range);
|
||||
}
|
||||
ParChild::Decorate(ref deco) => {
|
||||
|
@ -94,7 +94,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<BlockNode>,
|
||||
pub child: Option<PackedNode>,
|
||||
}
|
||||
|
||||
/// The type of a shape.
|
||||
@ -110,15 +110,36 @@ pub enum ShapeKind {
|
||||
Ellipse,
|
||||
}
|
||||
|
||||
impl InlineLevel for ShapeNode {
|
||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||
impl Layout for ShapeNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
// Resolve width and height relative to the region's base.
|
||||
let width = self.width.map(|w| w.resolve(base.w));
|
||||
let height = self.height.map(|h| h.resolve(base.h));
|
||||
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 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.
|
||||
let mut frame = if let Some(child) = &self.child {
|
||||
let mut node: &dyn BlockLevel = child;
|
||||
let mut node: &dyn Layout = child;
|
||||
|
||||
let padded;
|
||||
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.
|
||||
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(
|
||||
if width.is_some() { size.w } else { base.w },
|
||||
if height.is_some() { size.h } else { base.h },
|
||||
if width.is_some() { size.w } else { regions.base.w },
|
||||
if height.is_some() { size.h } else { regions.base.h },
|
||||
);
|
||||
|
||||
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
|
||||
vec![frame.constrain(cts)]
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ pub struct StackNode {
|
||||
pub children: Vec<StackChild>,
|
||||
}
|
||||
|
||||
impl BlockLevel for StackNode {
|
||||
impl Layout for StackNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -76,7 +76,7 @@ pub enum StackChild {
|
||||
/// Spacing between other nodes.
|
||||
Spacing(Spacing),
|
||||
/// Any block node and how to align it in the stack.
|
||||
Node(BlockNode),
|
||||
Node(PackedNode),
|
||||
}
|
||||
|
||||
impl Debug for StackChild {
|
||||
@ -174,8 +174,8 @@ impl<'a> StackLayouter<'a> {
|
||||
self.items.push(StackItem::Absolute(resolved));
|
||||
}
|
||||
|
||||
/// Layout a block node.
|
||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &BlockNode) {
|
||||
/// Layout a node.
|
||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
|
||||
let frames = node.layout(ctx, &self.regions);
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
|
@ -1,5 +1,4 @@
|
||||
use super::prelude::*;
|
||||
use super::{ShapeKind, ShapeNode};
|
||||
|
||||
/// `move`: Move content without affecting layout.
|
||||
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| {
|
||||
MoveNode {
|
||||
offset: Spec::new(x, y),
|
||||
child: ShapeNode {
|
||||
shape: ShapeKind::Rect,
|
||||
width: None,
|
||||
height: None,
|
||||
fill: None,
|
||||
child: Some(body.to_flow(style).pack()),
|
||||
},
|
||||
child: body.to_flow(style).pack(),
|
||||
}
|
||||
})))
|
||||
}
|
||||
@ -24,21 +17,30 @@ pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
#[derive(Debug, Hash)]
|
||||
struct MoveNode {
|
||||
offset: Spec<Option<Linear>>,
|
||||
child: ShapeNode,
|
||||
child: PackedNode,
|
||||
}
|
||||
|
||||
impl InlineLevel for MoveNode {
|
||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||
let offset = Point::new(
|
||||
self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(),
|
||||
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
|
||||
);
|
||||
impl Layout for MoveNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
let mut frames = self.child.layout(ctx, regions);
|
||||
|
||||
let mut frame = self.child.layout(ctx, space, base);
|
||||
for (point, _) in &mut frame.children {
|
||||
*point += offset;
|
||||
for (Constrained { item: frame, .. }, (_, base)) in
|
||||
frames.iter_mut().zip(regions.iter())
|
||||
{
|
||||
let offset = Point::new(
|
||||
self.offset.x.map(|x| x.resolve(base.w)).unwrap_or_default(),
|
||||
self.offset.y.map(|y| y.resolve(base.h)).unwrap_or_default(),
|
||||
);
|
||||
|
||||
for (point, _) in &mut Rc::make_mut(frame).children {
|
||||
*point += offset;
|
||||
}
|
||||
}
|
||||
|
||||
frame
|
||||
frames
|
||||
}
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ fn try_main() -> anyhow::Result<()> {
|
||||
// Typeset.
|
||||
match ctx.typeset(id) {
|
||||
// Export the PDF.
|
||||
Ok(document) => {
|
||||
let buffer = export::pdf(&ctx, &document);
|
||||
Ok(frames) => {
|
||||
let buffer = export::pdf(&ctx, &frames);
|
||||
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::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 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]])
|
||||
// Pad can grow.
|
||||
#pad(left: 10pt, right: 10pt)[PL #h(1fr) PR]
|
||||
|
||||
---
|
||||
// Test that the pad node doesn't consume the whole region.
|
||||
|
@ -17,7 +17,7 @@ World! 🌍
|
||||
---
|
||||
#page(height: 2cm)
|
||||
#font(white)
|
||||
#box(fill: forest)[
|
||||
#rect(fill: forest)[
|
||||
#v(1fr)
|
||||
#h(1fr) Hi you! #h(5pt)
|
||||
#v(5pt)
|
||||
|
@ -19,7 +19,7 @@ use typst::geom::{
|
||||
use typst::image::Image;
|
||||
use typst::layout::layout;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use typst::layout::PageNode;
|
||||
use typst::library::DocumentNode;
|
||||
use typst::loading::FsLoader;
|
||||
use typst::parse::Scanner;
|
||||
use typst::source::SourceFile;
|
||||
@ -231,11 +231,11 @@ fn test_part(
|
||||
|
||||
let mut ok = true;
|
||||
let (frames, mut errors) = match ctx.execute(id) {
|
||||
Ok(tree) => {
|
||||
let mut frames = layout(ctx, &tree);
|
||||
Ok(document) => {
|
||||
let mut frames = layout(ctx, &document);
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
(ok &= test_incremental(ctx, i, &tree, &frames));
|
||||
(ok &= test_incremental(ctx, i, &document, &frames));
|
||||
|
||||
if !compare_ref {
|
||||
frames.clear();
|
||||
@ -283,7 +283,7 @@ fn test_part(
|
||||
fn test_incremental(
|
||||
ctx: &mut Context,
|
||||
i: usize,
|
||||
tree: &[PageNode],
|
||||
document: &DocumentNode,
|
||||
frames: &[Rc<Frame>],
|
||||
) -> bool {
|
||||
let mut ok = true;
|
||||
@ -298,7 +298,7 @@ fn test_incremental(
|
||||
|
||||
ctx.layouts.turnaround();
|
||||
|
||||
let cached = layout(ctx, tree);
|
||||
let cached = layout(ctx, document);
|
||||
let misses = ctx
|
||||
.layouts
|
||||
.entries()
|
||||
|
Loading…
x
Reference in New Issue
Block a user