summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-pdf/src/catalog.rs14
-rw-r--r--crates/typst/src/foundations/styles.rs9
-rw-r--r--crates/typst/src/layout/mod.rs4
-rw-r--r--crates/typst/src/model/document.rs48
-rw-r--r--crates/typst/src/realize/mod.rs43
5 files changed, 75 insertions, 43 deletions
diff --git a/crates/typst-pdf/src/catalog.rs b/crates/typst-pdf/src/catalog.rs
index 7d52cc58..8f3253ee 100644
--- a/crates/typst-pdf/src/catalog.rs
+++ b/crates/typst-pdf/src/catalog.rs
@@ -44,12 +44,12 @@ pub fn write_catalog(
let info_ref = alloc.bump();
let mut info = pdf.document_info(info_ref);
let mut xmp = XmpWriter::new();
- if let Some(title) = &ctx.document.title {
+ if let Some(title) = &ctx.document.info.title {
info.title(TextStr(title));
xmp.title([(None, title.as_str())]);
}
- let authors = &ctx.document.author;
+ let authors = &ctx.document.info.author;
if !authors.is_empty() {
// Turns out that if the authors are given in both the document
// information dictionary and the XMP metadata, Acrobat takes a little
@@ -76,15 +76,15 @@ pub fn write_catalog(
info.creator(TextStr(&creator));
xmp.creator_tool(&creator);
- let keywords = &ctx.document.keywords;
+ let keywords = &ctx.document.info.keywords;
if !keywords.is_empty() {
let joined = keywords.join(", ");
info.keywords(TextStr(&joined));
xmp.pdf_keywords(&joined);
}
- if let Some(date) = ctx.document.date.unwrap_or(timestamp) {
- let tz = ctx.document.date.is_auto();
+ if let Some(date) = ctx.document.info.date.unwrap_or(timestamp) {
+ let tz = ctx.document.info.date.is_auto();
if let Some(pdf_date) = pdf_date(date, tz) {
info.creation_date(pdf_date);
info.modified_date(pdf_date);
@@ -109,10 +109,10 @@ pub fn write_catalog(
let doc_id = if let Smart::Custom(ident) = ident {
// We were provided with a stable ID. Yay!
hash_base64(&(PDF_VERSION, ident))
- } else if ctx.document.title.is_some() && !ctx.document.author.is_empty() {
+ } else if ctx.document.info.title.is_some() && !ctx.document.info.author.is_empty() {
// If not provided from the outside, but title and author were given, we
// compute a hash of them, which should be reasonably stable and unique.
- hash_base64(&(PDF_VERSION, &ctx.document.title, &ctx.document.author))
+ hash_base64(&(PDF_VERSION, &ctx.document.info.title, &ctx.document.info.author))
} else {
// The user provided no usable metadata which we can use as an `/ID`.
instance_id.clone()
diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs
index 48009c8c..55bb348a 100644
--- a/crates/typst/src/foundations/styles.rs
+++ b/crates/typst/src/foundations/styles.rs
@@ -133,6 +133,15 @@ impl Styles {
self
}
+ /// Whether there is a style for the given field of the given element.
+ pub fn has<T: NativeElement>(&self, field: u8) -> bool {
+ let elem = T::elem();
+ self.0
+ .iter()
+ .filter_map(|style| style.property())
+ .any(|property| property.is_of(elem) && property.id == field)
+ }
+
/// Returns `Some(_)` with an optional span if this list contains
/// styles for the given element.
pub fn interruption<T: NativeElement>(&self) -> Option<Option<Span>> {
diff --git a/crates/typst/src/layout/mod.rs b/crates/typst/src/layout/mod.rs
index 739d0922..f7f5c5b3 100644
--- a/crates/typst/src/layout/mod.rs
+++ b/crates/typst/src/layout/mod.rs
@@ -149,9 +149,9 @@ impl Content {
route: Route::extend(route).unnested(),
};
let arenas = Arenas::default();
- let (document, styles) =
+ let (document, styles, info) =
realize_doc(&mut engine, locator.next(&()), &arenas, content, styles)?;
- document.layout(&mut engine, locator.next(&()), styles)
+ document.layout(&mut engine, locator.next(&()), styles, info)
}
cached(
diff --git a/crates/typst/src/model/document.rs b/crates/typst/src/model/document.rs
index 77044112..71e9e796 100644
--- a/crates/typst/src/model/document.rs
+++ b/crates/typst/src/model/document.rs
@@ -3,8 +3,8 @@ use ecow::EcoString;
use crate::diag::{bail, HintedStrResult, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain,
- Value,
+ cast, elem, Args, Array, Construct, Content, Datetime, Fields, Packed, Smart,
+ StyleChain, Styles, Value,
};
use crate::introspection::{Introspector, Locator, ManualPageCounter};
use crate::layout::{Page, PageElem};
@@ -78,6 +78,7 @@ impl Packed<DocumentElem> {
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
+ info: DocumentInfo,
) -> SourceResult<Document> {
let children = self.children();
let mut peekable = children.chain(&styles).peekable();
@@ -107,14 +108,7 @@ impl Packed<DocumentElem> {
pages.extend(result?.finalize(engine, &mut page_counter)?);
}
- Ok(Document {
- pages,
- title: DocumentElem::title_in(styles).map(|content| content.plain_text()),
- author: DocumentElem::author_in(styles).0,
- keywords: DocumentElem::keywords_in(styles).0,
- date: DocumentElem::date_in(styles),
- introspector: Introspector::default(),
- })
+ Ok(Document { pages, info, introspector: Introspector::default() })
}
}
@@ -145,6 +139,15 @@ cast! {
pub struct Document {
/// The document's finished pages.
pub pages: Vec<Page>,
+ /// Details about the document.
+ pub info: DocumentInfo,
+ /// Provides the ability to execute queries on the document.
+ pub introspector: Introspector,
+}
+
+/// Details about the document.
+#[derive(Debug, Default, Clone, PartialEq, Hash)]
+pub struct DocumentInfo {
/// The document's title.
pub title: Option<EcoString>,
/// The document's author.
@@ -153,8 +156,29 @@ pub struct Document {
pub keywords: Vec<EcoString>,
/// The document's creation date.
pub date: Smart<Option<Datetime>>,
- /// Provides the ability to execute queries on the document.
- pub introspector: Introspector,
+}
+
+impl DocumentInfo {
+ /// Populate this document info with details from the given styles.
+ ///
+ /// Document set rules are a bit special, so we need to do this manually.
+ pub fn populate(&mut self, styles: &Styles) {
+ let chain = StyleChain::new(styles);
+ let has = |field| styles.has::<DocumentElem>(field as _);
+ if has(<DocumentElem as Fields>::Enum::Title) {
+ self.title =
+ DocumentElem::title_in(chain).map(|content| content.plain_text());
+ }
+ if has(<DocumentElem as Fields>::Enum::Author) {
+ self.author = DocumentElem::author_in(chain).0;
+ }
+ if has(<DocumentElem as Fields>::Enum::Keywords) {
+ self.keywords = DocumentElem::keywords_in(chain).0;
+ }
+ if has(<DocumentElem as Fields>::Enum::Date) {
+ self.date = DocumentElem::date_in(chain);
+ }
+ }
}
#[cfg(test)]
diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs
index a67660c1..721d4b4a 100644
--- a/crates/typst/src/realize/mod.rs
+++ b/crates/typst/src/realize/mod.rs
@@ -30,8 +30,8 @@ use crate::layout::{
};
use crate::math::{EquationElem, LayoutMath};
use crate::model::{
- CiteElem, CiteGroup, DocumentElem, EnumElem, EnumItem, ListElem, ListItem, ParElem,
- ParbreakElem, TermItem, TermsElem,
+ CiteElem, CiteGroup, DocumentElem, DocumentInfo, EnumElem, EnumItem, ListElem,
+ ListItem, ParElem, ParbreakElem, TermItem, TermsElem,
};
use crate::syntax::Span;
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
@@ -45,7 +45,7 @@ pub fn realize_doc<'a>(
arenas: &'a Arenas<'a>,
content: &'a Content,
styles: StyleChain<'a>,
-) -> SourceResult<(Packed<DocumentElem>, StyleChain<'a>)> {
+) -> SourceResult<(Packed<DocumentElem>, StyleChain<'a>, DocumentInfo)> {
let mut builder = Builder::new(engine, locator, arenas, true);
builder.accept(content, styles)?;
builder.interrupt_page(Some(styles), true)?;
@@ -198,11 +198,21 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
styled: &'a StyledElem,
styles: StyleChain<'a>,
) -> SourceResult<()> {
+ let local = &styled.styles;
let stored = self.arenas.store(styles);
- let styles = stored.chain(&styled.styles);
- self.interrupt_style(&styled.styles, None)?;
+ let styles = stored.chain(local);
+
+ if let Some(Some(span)) = local.interruption::<DocumentElem>() {
+ let Some(doc) = &mut self.doc else {
+ bail!(span, "document set rules are not allowed inside of containers");
+ };
+ doc.info.populate(local);
+ }
+
+ self.interrupt_style(local, None)?;
self.accept(&styled.child, styles)?;
- self.interrupt_style(&styled.styles, Some(styles))?;
+ self.interrupt_style(local, Some(styles))?;
+
Ok(())
}
@@ -211,20 +221,6 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
local: &Styles,
outer: Option<StyleChain<'a>>,
) -> SourceResult<()> {
- if let Some(Some(span)) = local.interruption::<DocumentElem>() {
- let Some(doc) = &self.doc else {
- bail!(span, "document set rules are not allowed inside of containers");
- };
- if outer.is_none()
- && (!doc.pages.is_empty()
- || !self.flow.0.is_empty()
- || !self.par.0.is_empty()
- || !self.list.items.is_empty()
- || !self.cites.items.is_empty())
- {
- bail!(span, "document set rules must appear before any content");
- }
- }
if let Some(Some(span)) = local.interruption::<PageElem>() {
if self.doc.is_none() {
bail!(span, "page configuration is not allowed inside of containers");
@@ -314,6 +310,8 @@ struct DocBuilder<'a> {
keep_next: bool,
/// Whether the next page should be cleared to an even or odd number.
clear_next: Option<Parity>,
+ /// Details about the document.
+ info: DocumentInfo,
}
impl<'a> DocBuilder<'a> {
@@ -354,9 +352,9 @@ impl<'a> DocBuilder<'a> {
/// Turns this builder into the resulting document, along with
/// its [style chain][StyleChain].
- fn finish(self) -> (Packed<DocumentElem>, StyleChain<'a>) {
+ fn finish(self) -> (Packed<DocumentElem>, StyleChain<'a>, DocumentInfo) {
let (children, trunk, span) = self.pages.finish();
- (Packed::new(DocumentElem::new(children)).spanned(span), trunk)
+ (Packed::new(DocumentElem::new(children)).spanned(span), trunk, self.info)
}
}
@@ -366,6 +364,7 @@ impl Default for DocBuilder<'_> {
pages: BehavedBuilder::new(),
keep_next: true,
clear_next: None,
+ info: DocumentInfo::default(),
}
}
}