Document metadata
This commit is contained in:
parent
0579fd4409
commit
6bafc63910
@ -236,8 +236,8 @@ fn compile_once(world: &mut SystemWorld, command: &CompileCommand) -> StrResult<
|
||||
|
||||
match typst::compile(world, source) {
|
||||
// Export the PDF.
|
||||
Ok(frames) => {
|
||||
let buffer = typst::export::pdf(&frames);
|
||||
Ok(document) => {
|
||||
let buffer = typst::export::pdf(&document);
|
||||
fs::write(&command.output, buffer).map_err(|_| "failed to write PDF file")?;
|
||||
status(command, Status::Success).unwrap();
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ use std::mem;
|
||||
use comemo::Tracked;
|
||||
use typed_arena::Arena;
|
||||
use typst::diag::SourceResult;
|
||||
use typst::frame::Frame;
|
||||
use typst::doc::Frame;
|
||||
use typst::geom::*;
|
||||
use typst::model::{
|
||||
applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain,
|
||||
@ -40,7 +40,7 @@ use typst::World;
|
||||
use crate::prelude::*;
|
||||
use crate::shared::BehavedBuilder;
|
||||
use crate::structure::{
|
||||
DescNode, DocNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
|
||||
DescNode, DocumentNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST,
|
||||
};
|
||||
use crate::text::{
|
||||
LinebreakNode, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
|
||||
@ -54,7 +54,7 @@ pub trait LayoutRoot {
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>>;
|
||||
) -> SourceResult<Document>;
|
||||
}
|
||||
|
||||
impl LayoutRoot for Content {
|
||||
@ -63,7 +63,7 @@ impl LayoutRoot for Content {
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
) -> SourceResult<Document> {
|
||||
let scratch = Scratch::default();
|
||||
let (realized, styles) = realize_root(world, &scratch, self, styles)?;
|
||||
realized.with::<dyn LayoutRoot>().unwrap().layout_root(world, styles)
|
||||
@ -245,7 +245,7 @@ fn realize_root<'a>(
|
||||
builder.accept(content, styles)?;
|
||||
builder.interrupt_page(Some(styles))?;
|
||||
let (pages, shared) = builder.doc.unwrap().pages.finish();
|
||||
Ok((DocNode(pages).pack(), shared))
|
||||
Ok((DocumentNode(pages).pack(), shared))
|
||||
}
|
||||
|
||||
/// Realize into a node that is capable of block-level layout.
|
||||
@ -357,6 +357,10 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(span) = content.span() {
|
||||
bail!(span, "not allowed here");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -378,13 +382,26 @@ impl<'a> Builder<'a> {
|
||||
map: &StyleMap,
|
||||
styles: Option<StyleChain<'a>>,
|
||||
) -> SourceResult<()> {
|
||||
if map.interrupts::<PageNode>() {
|
||||
if let Some(Some(span)) = map.interruption::<DocumentNode>() {
|
||||
if self.doc.is_none() {
|
||||
bail!(span, "not allowed here");
|
||||
}
|
||||
if !self.flow.0.is_empty()
|
||||
|| !self.par.0.is_empty()
|
||||
|| !self.list.items.is_empty()
|
||||
{
|
||||
bail!(span, "must appear before any content");
|
||||
}
|
||||
} else if let Some(Some(span)) = map.interruption::<PageNode>() {
|
||||
if self.doc.is_none() {
|
||||
bail!(span, "not allowed here");
|
||||
}
|
||||
self.interrupt_page(styles)?;
|
||||
} else if map.interrupts::<ParNode>() {
|
||||
} else if map.interruption::<ParNode>().is_some() {
|
||||
self.interrupt_par()?;
|
||||
} else if map.interrupts::<ListNode>()
|
||||
|| map.interrupts::<EnumNode>()
|
||||
|| map.interrupts::<DescNode>()
|
||||
} else if map.interruption::<ListNode>().is_some()
|
||||
|| map.interruption::<EnumNode>().is_some()
|
||||
|| map.interruption::<DescNode>().is_some()
|
||||
{
|
||||
self.interrupt_list()?;
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ fn scope() -> Scope {
|
||||
std.def_fn("smallcaps", text::smallcaps);
|
||||
|
||||
// Structure.
|
||||
std.def_node::<structure::DocumentNode>("document");
|
||||
std.def_node::<structure::RefNode>("ref");
|
||||
std.def_node::<structure::HeadingNode>("heading");
|
||||
std.def_node::<structure::ListNode>("list");
|
||||
|
@ -10,7 +10,7 @@ pub use comemo::Tracked;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::diag::{bail, error, with_alternative, At, SourceResult, StrResult};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::frame::*;
|
||||
pub use typst::doc::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::geom::*;
|
||||
#[doc(no_inline)]
|
||||
|
@ -1,32 +0,0 @@
|
||||
use crate::layout::{LayoutRoot, PageNode};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A sequence of page runs.
|
||||
#[derive(Hash)]
|
||||
pub struct DocNode(pub StyleVec<PageNode>);
|
||||
|
||||
#[node(LayoutRoot)]
|
||||
impl DocNode {}
|
||||
|
||||
impl LayoutRoot for DocNode {
|
||||
/// Layout the document into a sequence of frames, one per page.
|
||||
fn layout_root(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut frames = vec![];
|
||||
for (page, map) in self.0.iter() {
|
||||
let number = 1 + frames.len();
|
||||
frames.extend(page.layout(world, number, styles.chain(map))?);
|
||||
}
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DocNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("Doc ")?;
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
47
library/src/structure/document.rs
Normal file
47
library/src/structure/document.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use crate::layout::{LayoutRoot, PageNode};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// The root node of the model.
|
||||
#[derive(Hash)]
|
||||
pub struct DocumentNode(pub StyleVec<PageNode>);
|
||||
|
||||
#[node(LayoutRoot)]
|
||||
impl DocumentNode {
|
||||
/// The document's title.
|
||||
#[property(referenced)]
|
||||
pub const TITLE: Option<EcoString> = None;
|
||||
|
||||
/// The document's author.
|
||||
#[property(referenced)]
|
||||
pub const AUTHOR: Option<EcoString> = None;
|
||||
}
|
||||
|
||||
impl LayoutRoot for DocumentNode {
|
||||
/// Layout the document into a sequence of frames, one per page.
|
||||
fn layout_root(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Document> {
|
||||
let mut pages = vec![];
|
||||
for (page, map) in self.0.iter() {
|
||||
let number = 1 + pages.len();
|
||||
pages.extend(page.layout(world, number, styles.chain(map))?);
|
||||
}
|
||||
|
||||
Ok(Document {
|
||||
metadata: Metadata {
|
||||
title: styles.get(Self::TITLE).clone(),
|
||||
author: styles.get(Self::AUTHOR).clone(),
|
||||
},
|
||||
pages,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DocumentNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("Document ")?;
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
//! Document structuring.
|
||||
|
||||
mod doc;
|
||||
mod document;
|
||||
mod heading;
|
||||
mod list;
|
||||
mod reference;
|
||||
mod table;
|
||||
|
||||
pub use self::doc::*;
|
||||
pub use self::document::*;
|
||||
pub use self::heading::*;
|
||||
pub use self::list::*;
|
||||
pub use self::reference::*;
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Finished layouts.
|
||||
//! Finished documents.
|
||||
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
use std::num::NonZeroUsize;
|
||||
@ -13,6 +13,24 @@ use crate::image::Image;
|
||||
use crate::model::{dict, Dict, Value};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// A finished document with metadata and page frames.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Document {
|
||||
/// The document's metadata.
|
||||
pub metadata: Metadata,
|
||||
/// The page frames.
|
||||
pub pages: Vec<Frame>,
|
||||
}
|
||||
|
||||
/// Document metadata.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Metadata {
|
||||
/// The document's title.
|
||||
pub title: Option<EcoString>,
|
||||
/// The document's author.
|
||||
pub author: Option<EcoString>,
|
||||
}
|
||||
|
||||
/// A finished layout with elements at fixed positions.
|
||||
#[derive(Default, Clone, Eq, PartialEq)]
|
||||
pub struct Frame {
|
@ -14,21 +14,21 @@ use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
||||
|
||||
use self::outline::{Heading, HeadingNode};
|
||||
use self::page::Page;
|
||||
use crate::doc::{Document, Lang, Metadata};
|
||||
use crate::font::Font;
|
||||
use crate::frame::{Frame, Lang};
|
||||
use crate::geom::{Abs, Dir, Em};
|
||||
use crate::image::Image;
|
||||
|
||||
/// Export a collection of frames into a PDF file.
|
||||
/// Export a document 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 so that fonts and images can be
|
||||
/// included in the PDF.
|
||||
///
|
||||
/// Returns the raw bytes making up the PDF file.
|
||||
pub fn pdf(frames: &[Frame]) -> Vec<u8> {
|
||||
let mut ctx = PdfContext::new();
|
||||
page::construct_pages(&mut ctx, frames);
|
||||
pub fn pdf(document: &Document) -> Vec<u8> {
|
||||
let mut ctx = PdfContext::new(&document.metadata);
|
||||
page::construct_pages(&mut ctx, &document.pages);
|
||||
font::write_fonts(&mut ctx);
|
||||
image::write_images(&mut ctx);
|
||||
page::write_page_tree(&mut ctx);
|
||||
@ -41,7 +41,8 @@ const SRGB: Name<'static> = Name(b"srgb");
|
||||
const D65_GRAY: Name<'static> = Name(b"d65gray");
|
||||
|
||||
/// Context for exporting a whole PDF document.
|
||||
pub struct PdfContext {
|
||||
pub struct PdfContext<'a> {
|
||||
metadata: &'a Metadata,
|
||||
writer: PdfWriter,
|
||||
pages: Vec<Page>,
|
||||
page_heights: Vec<f32>,
|
||||
@ -57,11 +58,12 @@ pub struct PdfContext {
|
||||
heading_tree: Vec<HeadingNode>,
|
||||
}
|
||||
|
||||
impl PdfContext {
|
||||
fn new() -> Self {
|
||||
impl<'a> PdfContext<'a> {
|
||||
fn new(metadata: &'a Metadata) -> Self {
|
||||
let mut alloc = Ref::new(1);
|
||||
let page_tree_ref = alloc.bump();
|
||||
Self {
|
||||
metadata,
|
||||
writer: PdfWriter::new(),
|
||||
pages: vec![],
|
||||
page_heights: vec![],
|
||||
@ -117,7 +119,15 @@ fn write_catalog(ctx: &mut PdfContext) {
|
||||
};
|
||||
|
||||
// Write the document information.
|
||||
ctx.writer.document_info(ctx.alloc.bump()).creator(TextStr("Typst"));
|
||||
let mut info = ctx.writer.document_info(ctx.alloc.bump());
|
||||
if let Some(title) = &ctx.metadata.title {
|
||||
info.title(TextStr(title));
|
||||
}
|
||||
if let Some(author) = &ctx.metadata.author {
|
||||
info.author(TextStr(author));
|
||||
}
|
||||
info.creator(TextStr("Typst"));
|
||||
info.finish();
|
||||
|
||||
// Write the document catalog.
|
||||
let mut catalog = ctx.writer.catalog(ctx.alloc.bump());
|
||||
@ -131,8 +141,6 @@ fn write_catalog(ctx: &mut PdfContext) {
|
||||
if let Some(lang) = lang {
|
||||
catalog.lang(TextStr(lang.as_str()));
|
||||
}
|
||||
|
||||
catalog.finish();
|
||||
}
|
||||
|
||||
/// Compress data with the DEFLATE algorithm.
|
||||
|
@ -5,8 +5,8 @@ use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
||||
use super::{
|
||||
deflate, AbsExt, EmExt, Heading, HeadingNode, PdfContext, RefExt, D65_GRAY, SRGB,
|
||||
};
|
||||
use crate::doc::{Destination, Element, Frame, Group, Role, Text};
|
||||
use crate::font::Font;
|
||||
use crate::frame::{Destination, Element, Frame, Group, Role, Text};
|
||||
use crate::geom::{
|
||||
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
||||
Transform,
|
||||
@ -155,8 +155,8 @@ pub struct Page {
|
||||
}
|
||||
|
||||
/// An exporter for the contents of a single PDF page.
|
||||
struct PageContext<'a> {
|
||||
parent: &'a mut PdfContext,
|
||||
struct PageContext<'a, 'b> {
|
||||
parent: &'a mut PdfContext<'b>,
|
||||
page_ref: Ref,
|
||||
content: Content,
|
||||
state: State,
|
||||
@ -177,7 +177,7 @@ struct State {
|
||||
stroke_space: Option<Name<'static>>,
|
||||
}
|
||||
|
||||
impl<'a> PageContext<'a> {
|
||||
impl PageContext<'_, '_> {
|
||||
fn save_state(&mut self) {
|
||||
self.saves.push(self.state.clone());
|
||||
self.content.save_state();
|
||||
|
@ -8,7 +8,7 @@ use tiny_skia as sk;
|
||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||
use usvg::FitTo;
|
||||
|
||||
use crate::frame::{Element, Frame, Group, Text};
|
||||
use crate::doc::{Element, Frame, Group, Text};
|
||||
use crate::geom::{
|
||||
self, Abs, Geometry, Paint, PathElement, Shape, Size, Stroke, Transform,
|
||||
};
|
||||
|
22
src/lib.rs
22
src/lib.rs
@ -11,9 +11,8 @@
|
||||
//! in the source file. The nodes of the content tree are well structured and
|
||||
//! order-independent and thus much better suited for further processing than
|
||||
//! the raw markup.
|
||||
//! - **Typesetting:** Next, the content is [typeset] into a collection of
|
||||
//! [`Frame`]s (one per page) with elements and fixed positions, ready for
|
||||
//! exporting.
|
||||
//! - **Typesetting:** Next, the content is [typeset] into a [document]
|
||||
//! containing one [frame] per page with elements and fixed positions.
|
||||
//! - **Exporting:** These frames can finally be exported into an output format
|
||||
//! (currently supported are [PDF] and [raster images]).
|
||||
//!
|
||||
@ -25,6 +24,8 @@
|
||||
//! [module]: model::Module
|
||||
//! [content]: model::Content
|
||||
//! [typeset]: model::typeset
|
||||
//! [document]: doc::Document
|
||||
//! [frame]: doc::Frame
|
||||
//! [PDF]: export::pdf
|
||||
//! [raster images]: export::render
|
||||
|
||||
@ -38,9 +39,9 @@ pub mod geom;
|
||||
pub mod diag;
|
||||
#[macro_use]
|
||||
pub mod model;
|
||||
pub mod doc;
|
||||
pub mod export;
|
||||
pub mod font;
|
||||
pub mod frame;
|
||||
pub mod image;
|
||||
pub mod syntax;
|
||||
|
||||
@ -49,21 +50,14 @@ use std::path::Path;
|
||||
use comemo::{Prehashed, Track};
|
||||
|
||||
use crate::diag::{FileResult, SourceResult};
|
||||
use crate::doc::Document;
|
||||
use crate::font::{Font, FontBook};
|
||||
use crate::frame::Frame;
|
||||
use crate::model::{Library, Route};
|
||||
use crate::syntax::{Source, SourceId};
|
||||
use crate::util::Buffer;
|
||||
|
||||
/// Compile a source file into a collection of layouted frames.
|
||||
///
|
||||
/// Returns either a vector of frames representing individual pages or
|
||||
/// diagnostics in the form of a vector of error message with file and span
|
||||
/// information.
|
||||
pub fn compile(
|
||||
world: &(dyn World + 'static),
|
||||
source: &Source,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
/// Compile a source file into a fully layouted document.
|
||||
pub fn compile(world: &(dyn World + 'static), source: &Source) -> SourceResult<Document> {
|
||||
// Evaluate the source file into a module.
|
||||
let route = Route::default();
|
||||
let module = model::eval(world.track(), route.track(), source)?;
|
||||
|
@ -3,8 +3,8 @@ use std::str::FromStr;
|
||||
|
||||
use super::{Content, Regex, Selector, Transform, Value};
|
||||
use crate::diag::{with_alternative, StrResult};
|
||||
use crate::doc::{Destination, Lang, Location, Region};
|
||||
use crate::font::{FontStretch, FontStyle, FontWeight};
|
||||
use crate::frame::{Destination, Lang, Location, Region};
|
||||
use crate::geom::{
|
||||
Axes, Corners, Dir, GenAlign, Get, Length, Paint, PartialStroke, Point, Rel, Sides,
|
||||
};
|
||||
|
@ -21,10 +21,6 @@ use crate::util::{format_eco, EcoString, PathExt};
|
||||
use crate::World;
|
||||
|
||||
/// Evaluate a source file and return the resulting module.
|
||||
///
|
||||
/// Returns either a module containing a scope with top-level bindings and
|
||||
/// layoutable contents or diagnostics in the form of a vector of error
|
||||
/// messages with file and span information.
|
||||
#[comemo::memoize]
|
||||
pub fn eval(
|
||||
world: Tracked<dyn World>,
|
||||
@ -934,7 +930,7 @@ impl Eval for ast::SetRule {
|
||||
let target = self.target();
|
||||
let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
|
||||
let args = self.args().eval(vm)?;
|
||||
target.set(args)
|
||||
Ok(target.set(args)?.spanned(self.span()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use once_cell::sync::OnceCell;
|
||||
|
||||
use super::{Content, NodeId, Scope, StyleChain, StyleMap};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::frame::Frame;
|
||||
use crate::doc::Document;
|
||||
use crate::geom::{Abs, Dir};
|
||||
use crate::util::{hash128, EcoString};
|
||||
use crate::World;
|
||||
@ -31,7 +31,7 @@ pub struct LangItems {
|
||||
world: Tracked<dyn World>,
|
||||
content: &Content,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>>,
|
||||
) -> SourceResult<Document>,
|
||||
/// Access the em size.
|
||||
pub em: fn(StyleChain) -> Abs,
|
||||
/// Access the text direction.
|
||||
|
@ -79,9 +79,25 @@ impl StyleMap {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether this map contains styles for the given `node.`
|
||||
pub fn interrupts<T: 'static>(&self) -> bool {
|
||||
self.0.iter().any(|entry| entry.is_of(NodeId::of::<T>()))
|
||||
/// Add an origin span to all contained properties.
|
||||
pub fn spanned(mut self, span: Span) -> Self {
|
||||
for entry in &mut self.0 {
|
||||
if let Style::Property(property) = entry {
|
||||
property.origin = Some(span);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `Some(_)` with an optional span if this map contains styles for
|
||||
/// the given `node`.
|
||||
pub fn interruption<T: 'static>(&self) -> Option<Option<Span>> {
|
||||
let node = NodeId::of::<T>();
|
||||
self.0.iter().find_map(|entry| match entry {
|
||||
Style::Property(property) => property.is_of(node).then(|| property.origin),
|
||||
Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,15 +143,6 @@ impl Style {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this entry contains styles for the given `node.`
|
||||
pub fn is_of(&self, node: NodeId) -> bool {
|
||||
match self {
|
||||
Self::Property(property) => property.is_of(node),
|
||||
Self::Recipe(recipe) => recipe.is_of(node),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Style {
|
||||
@ -162,6 +169,8 @@ pub struct Property {
|
||||
scoped: bool,
|
||||
/// The property's value.
|
||||
value: Arc<Prehashed<dyn Bounds>>,
|
||||
/// The span of the set rule the property stems from.
|
||||
origin: Option<Span>,
|
||||
/// The name of the property.
|
||||
#[cfg(debug_assertions)]
|
||||
name: &'static str,
|
||||
@ -175,6 +184,7 @@ impl Property {
|
||||
node: K::node(),
|
||||
value: Arc::new(Prehashed::new(value)),
|
||||
scoped: false,
|
||||
origin: None,
|
||||
#[cfg(debug_assertions)]
|
||||
name: K::NAME,
|
||||
}
|
||||
@ -330,8 +340,11 @@ impl Recipe {
|
||||
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
||||
let mut result = func.call_detached(world, args);
|
||||
if let Some(span) = content.span() {
|
||||
let point = || Tracepoint::Show(content.name().into());
|
||||
result = result.trace(world, point, span);
|
||||
// For selector-less show rules, a tracepoint makes no sense.
|
||||
if self.selector.is_some() {
|
||||
let point = || Tracepoint::Show(content.name().into());
|
||||
result = result.trace(world, point, span);
|
||||
}
|
||||
}
|
||||
Ok(result?.display())
|
||||
}
|
||||
|
@ -2,16 +2,12 @@ use comemo::Tracked;
|
||||
|
||||
use super::{Content, StyleChain};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::frame::Frame;
|
||||
use crate::doc::Document;
|
||||
use crate::World;
|
||||
|
||||
/// Typeset content into a collection of layouted frames.
|
||||
///
|
||||
/// Returns either a vector of frames representing individual pages or
|
||||
/// diagnostics in the form of a vector of error message with file and span
|
||||
/// information.
|
||||
/// Typeset content into a fully layouted document.
|
||||
#[comemo::memoize]
|
||||
pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec<Frame>> {
|
||||
pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Document> {
|
||||
let library = world.library();
|
||||
let styles = StyleChain::new(&library.styles);
|
||||
(library.items.layout)(world, content, styles)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 756 B |
@ -94,8 +94,8 @@ fn bench_compile(iai: &mut Iai) {
|
||||
|
||||
fn bench_render(iai: &mut Iai) {
|
||||
let world = BenchWorld::new();
|
||||
let frames = typst::compile(&world, &world.source).unwrap();
|
||||
iai.run(|| typst::export::render(&frames[0], 1.0))
|
||||
let document = typst::compile(&world, &world.source).unwrap();
|
||||
iai.run(|| typst::export::render(&document.pages[0], 1.0))
|
||||
}
|
||||
|
||||
struct BenchWorld {
|
||||
|
@ -12,8 +12,8 @@ use elsa::FrozenVec;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use tiny_skia as sk;
|
||||
use typst::diag::{bail, FileError, FileResult};
|
||||
use typst::doc::{Document, Element, Frame, Metadata};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::frame::{Element, Frame};
|
||||
use typst::geom::{Abs, RgbaColor, Sides};
|
||||
use typst::model::{Library, Smart, Value};
|
||||
use typst::syntax::{Source, SourceId, SyntaxNode};
|
||||
@ -349,20 +349,21 @@ fn test(
|
||||
line += part.lines().count() + 1;
|
||||
}
|
||||
|
||||
let document = Document { pages: frames, metadata: Metadata::default() };
|
||||
if compare_ever {
|
||||
if let Some(pdf_path) = pdf_path {
|
||||
let pdf_data = typst::export::pdf(&frames);
|
||||
let pdf_data = typst::export::pdf(&document);
|
||||
fs::create_dir_all(&pdf_path.parent().unwrap()).unwrap();
|
||||
fs::write(pdf_path, pdf_data).unwrap();
|
||||
}
|
||||
|
||||
if world.print.frames {
|
||||
for frame in &frames {
|
||||
for frame in &document.pages {
|
||||
println!("Frame:\n{:#?}\n", frame);
|
||||
}
|
||||
}
|
||||
|
||||
let canvas = render(&frames);
|
||||
let canvas = render(&document.pages);
|
||||
fs::create_dir_all(&png_path.parent().unwrap()).unwrap();
|
||||
canvas.save_png(png_path).unwrap();
|
||||
|
||||
@ -378,7 +379,7 @@ fn test(
|
||||
println!(" Does not match reference image. ❌");
|
||||
ok = false;
|
||||
}
|
||||
} else if !frames.is_empty() {
|
||||
} else if !document.pages.is_empty() {
|
||||
println!(" Failed to open reference image. ❌");
|
||||
ok = false;
|
||||
}
|
||||
@ -425,7 +426,7 @@ fn test_part(
|
||||
}
|
||||
|
||||
let (mut frames, errors) = match typst::compile(world, source) {
|
||||
Ok(frames) => (frames, vec![]),
|
||||
Ok(document) => (document.pages, vec![]),
|
||||
Err(errors) => (vec![], *errors),
|
||||
};
|
||||
|
||||
|
@ -1,14 +0,0 @@
|
||||
// Test that you can't do page related stuff in a container.
|
||||
|
||||
---
|
||||
A
|
||||
#box[
|
||||
B
|
||||
#pagebreak()
|
||||
#set page("a4")
|
||||
]
|
||||
C
|
||||
|
||||
// No consequences from the page("A4") call here.
|
||||
#pagebreak()
|
||||
D
|
30
tests/typ/style/document.typ
Normal file
30
tests/typ/style/document.typ
Normal file
@ -0,0 +1,30 @@
|
||||
// Test document and page-level styles.
|
||||
|
||||
---
|
||||
// This is okay.
|
||||
// Ref: false
|
||||
#set document(title: "Hello")
|
||||
|
||||
---
|
||||
Hello
|
||||
|
||||
// Error: 1-30 must appear before any content
|
||||
#set document(title: "Hello")
|
||||
|
||||
---
|
||||
#box[
|
||||
// Error: 3-32 not allowed here
|
||||
#set document(title: "Hello")
|
||||
]
|
||||
|
||||
---
|
||||
#box[
|
||||
// Error: 3-18 not allowed here
|
||||
#set page("a4")
|
||||
]
|
||||
|
||||
---
|
||||
#box[
|
||||
// Error: 3-15 not allowed here
|
||||
#pagebreak()
|
||||
]
|
Loading…
Reference in New Issue
Block a user