Add Page struct

To get rid of the Meta hack where numbering and things like that are stored in the frame.
This commit is contained in:
Laurenz 2024-01-17 21:50:35 +01:00
parent 50741209a8
commit 6ac71eeaf7
17 changed files with 203 additions and 199 deletions

View File

@ -219,7 +219,7 @@ fn export_image(
.pages
.par_iter()
.enumerate()
.map(|(i, frame)| {
.map(|(i, page)| {
let storage;
let path = if numbered {
storage = string.replace("{n}", &format!("{:0width$}", i + 1));
@ -231,20 +231,23 @@ fn export_image(
// If we are not watching, don't use the cache.
// If the frame is in the cache, skip it.
// If the file does not exist, always create it.
if watching && cache.is_cached(i, frame) && path.exists() {
if watching && cache.is_cached(i, &page.frame) && path.exists() {
return Ok(());
}
match fmt {
ImageExportFormat::Png => {
let pixmap =
typst_render::render(frame, command.ppi / 72.0, Color::WHITE);
let pixmap = typst_render::render(
&page.frame,
command.ppi / 72.0,
Color::WHITE,
);
pixmap
.save_png(path)
.map_err(|err| eco_format!("failed to write PNG file ({err})"))?;
}
ImageExportFormat::Svg => {
let svg = typst_svg::svg(frame);
let svg = typst_svg::svg(&page.frame);
fs::write(path, svg.as_bytes())
.map_err(|err| eco_format!("failed to write SVG file ({err})"))?;
}

View File

@ -388,8 +388,8 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
let world = DocWorld(source);
let mut tracer = Tracer::new();
let mut frames = match typst::compile(&world, &mut tracer) {
Ok(doc) => doc.pages,
let mut document = match typst::compile(&world, &mut tracer) {
Ok(doc) => doc,
Err(err) => {
let msg = &err[0].message;
panic!("while trying to compile:\n{text}:\n\nerror: {msg}");
@ -397,16 +397,16 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
};
if let Some([x, y, w, h]) = zoom {
frames[0].translate(Point::new(-x, -y));
*frames[0].size_mut() = Size::new(w, h);
document.pages[0].frame.translate(Point::new(-x, -y));
*document.pages[0].frame.size_mut() = Size::new(w, h);
}
if single {
frames.truncate(1);
document.pages.truncate(1);
}
let hash = typst::util::hash128(text);
resolver.example(hash, highlighted, &frames)
resolver.example(hash, highlighted, &document)
}
/// Extract an attribute value from an HTML element.

View File

@ -25,9 +25,10 @@ use typst::foundations::{
FOUNDATIONS,
};
use typst::introspection::INTROSPECTION;
use typst::layout::{Abs, Frame, Margin, PageElem, LAYOUT};
use typst::layout::{Abs, Margin, PageElem, LAYOUT};
use typst::loading::DATA_LOADING;
use typst::math::MATH;
use typst::model::Document;
use typst::model::MODEL;
use typst::symbols::SYMBOLS;
use typst::text::{Font, FontBook, TEXT};
@ -97,7 +98,7 @@ pub trait Resolver {
fn image(&self, filename: &str, data: &[u8]) -> String;
/// Produce HTML for an example.
fn example(&self, hash: u128, source: Option<Html>, frames: &[Frame]) -> Html;
fn example(&self, hash: u128, source: Option<Html>, document: &Document) -> Html;
/// Determine the commits between two tags.
fn commits(&self, from: &str, to: &str) -> Vec<Commit>;
@ -789,7 +790,7 @@ mod tests {
None
}
fn example(&self, _: u128, _: Option<Html>, _: &[Frame]) -> Html {
fn example(&self, _: u128, _: Option<Html>, _: &Document) -> Html {
Html::new(String::new())
}

View File

@ -121,8 +121,8 @@ pub fn jump_from_cursor(
}
let span = node.span();
for (i, frame) in document.pages.iter().enumerate() {
if let Some(pos) = find_in_frame(frame, span) {
for (i, page) in document.pages.iter().enumerate() {
if let Some(pos) = find_in_frame(&page.frame, span) {
return Some(Position {
page: NonZeroUsize::new(i + 1).unwrap(),
point: pos,

View File

@ -30,7 +30,7 @@ use crate::color::ColorSpaces;
use crate::extg::ExtGState;
use crate::gradient::PdfGradient;
use crate::image::EncodedImage;
use crate::page::Page;
use crate::page::EncodedPage;
use crate::pattern::PdfPattern;
/// Export a document into a PDF file.
@ -72,7 +72,7 @@ struct PdfContext<'a> {
/// The writer we are writing the PDF into.
pdf: Pdf,
/// Content of exported pages.
pages: Vec<Page>,
pages: Vec<EncodedPage>,
/// For each font a mapping from used glyphs to their text representation.
/// May contain multiple chars in case of ligatures or similar things. The
/// same glyph can have a different text representation within one document,

View File

@ -10,11 +10,10 @@ use pdf_writer::writers::PageLabel;
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
use typst::introspection::Meta;
use typst::layout::{
Abs, Em, Frame, FrameItem, GroupItem, PdfPageLabel, PdfPageLabelStyle, Point, Ratio,
Size, Transform,
Abs, Em, Frame, FrameItem, GroupItem, Page, Point, Ratio, Size, Transform,
};
use typst::model::Destination;
use typst::text::{Font, TextItem};
use typst::model::{Destination, Numbering};
use typst::text::{Case, Font, TextItem};
use typst::util::{Deferred, Numeric};
use typst::visualize::{
FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem, Shape,
@ -27,34 +26,36 @@ use crate::{deflate_deferred, AbsExt, EmExt, PdfContext};
/// Construct page objects.
#[typst_macros::time(name = "construct pages")]
pub(crate) fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) {
for frame in frames {
let (page_ref, page) = construct_page(ctx, frame);
pub(crate) fn construct_pages(ctx: &mut PdfContext, pages: &[Page]) {
for page in pages {
let (page_ref, mut encoded) = construct_page(ctx, &page.frame);
encoded.label = page
.numbering
.as_ref()
.and_then(|num| PdfPageLabel::generate(num, page.number));
ctx.page_refs.push(page_ref);
ctx.pages.push(page);
ctx.pages.push(encoded);
}
}
/// Construct a page object.
#[typst_macros::time(name = "construct page")]
pub(crate) fn construct_page(ctx: &mut PdfContext, frame: &Frame) -> (Ref, Page) {
pub(crate) fn construct_page(ctx: &mut PdfContext, frame: &Frame) -> (Ref, EncodedPage) {
let page_ref = ctx.alloc.bump();
let size = frame.size();
let mut ctx = PageContext {
parent: ctx,
page_ref,
label: None,
uses_opacities: false,
content: Content::new(),
state: State::new(frame.size()),
state: State::new(size),
saves: vec![],
bottom: 0.0,
links: vec![],
resources: HashMap::default(),
};
let size = frame.size();
// Make the coordinate system start at the top-left.
ctx.bottom = size.y.to_f32();
ctx.transform(Transform {
@ -69,13 +70,13 @@ pub(crate) fn construct_page(ctx: &mut PdfContext, frame: &Frame) -> (Ref, Page)
// Encode the page into the content stream.
write_frame(&mut ctx, frame);
let page = Page {
let page = EncodedPage {
size,
content: deflate_deferred(ctx.content.finish()),
id: ctx.page_ref,
uses_opacities: ctx.uses_opacities,
links: ctx.links,
label: ctx.label,
label: None,
resources: ctx.resources,
};
@ -249,8 +250,86 @@ pub(crate) fn write_page_labels(ctx: &mut PdfContext) -> Vec<(NonZeroUsize, Ref)
result
}
/// Specification for a PDF page label.
#[derive(Debug, Clone, PartialEq, Hash, Default)]
struct PdfPageLabel {
/// Can be any string or none. Will always be prepended to the numbering style.
prefix: Option<EcoString>,
/// Based on the numbering pattern.
///
/// If `None` or numbering is a function, the field will be empty.
style: Option<PdfPageLabelStyle>,
/// Offset for the page label start.
///
/// Describes where to start counting from when setting a style.
/// (Has to be greater or equal than 1)
offset: Option<NonZeroUsize>,
}
/// A PDF page label number style.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum PdfPageLabelStyle {
/// Decimal arabic numerals (1, 2, 3).
Arabic,
/// Lowercase roman numerals (i, ii, iii).
LowerRoman,
/// Uppercase roman numerals (I, II, III).
UpperRoman,
/// Lowercase letters (`a` to `z` for the first 26 pages,
/// `aa` to `zz` and so on for the next).
LowerAlpha,
/// Uppercase letters (`A` to `Z` for the first 26 pages,
/// `AA` to `ZZ` and so on for the next).
UpperAlpha,
}
impl PdfPageLabel {
/// Create a new `PdfNumbering` from a `Numbering` applied to a page
/// number.
fn generate(numbering: &Numbering, number: usize) -> Option<PdfPageLabel> {
let Numbering::Pattern(pat) = numbering else {
return None;
};
let Some((prefix, kind, case)) = pat.pieces.first() else {
return None;
};
// If there is a suffix, we cannot use the common style optimisation,
// since PDF does not provide a suffix field.
let mut style = None;
if pat.suffix.is_empty() {
use {typst::model::NumberingKind as Kind, PdfPageLabelStyle as Style};
match (kind, case) {
(Kind::Arabic, _) => style = Some(Style::Arabic),
(Kind::Roman, Case::Lower) => style = Some(Style::LowerRoman),
(Kind::Roman, Case::Upper) => style = Some(Style::UpperRoman),
(Kind::Letter, Case::Lower) if number <= 26 => {
style = Some(Style::LowerAlpha)
}
(Kind::Letter, Case::Upper) if number <= 26 => {
style = Some(Style::UpperAlpha)
}
_ => {}
}
}
// Prefix and offset depend on the style: If it is supported by the PDF
// spec, we use the given prefix and an offset. Otherwise, everything
// goes into prefix.
let prefix = if style.is_none() {
Some(pat.apply(&[number]))
} else {
(!prefix.is_empty()).then(|| prefix.clone())
};
let offset = style.and(NonZeroUsize::new(number));
Some(PdfPageLabel { prefix, style, offset })
}
}
/// Data for an exported page.
pub struct Page {
pub struct EncodedPage {
/// The indirect object id of the page.
pub id: Ref,
/// The page's dimensions.
@ -261,10 +340,10 @@ pub struct Page {
pub uses_opacities: bool,
/// Links in the PDF coordinate system.
pub links: Vec<(Destination, Rect)>,
/// The page's PDF label.
pub label: Option<PdfPageLabel>,
/// The page's used resources
pub resources: HashMap<PageResource, usize>,
/// The page's PDF label.
label: Option<PdfPageLabel>,
}
/// Represents a resource being used in a PDF page by its name.
@ -326,7 +405,6 @@ impl PageResource {
pub struct PageContext<'a, 'b> {
pub(crate) parent: &'a mut PdfContext<'b>,
page_ref: Ref,
label: Option<PdfPageLabel>,
pub content: Content,
state: State,
saves: Vec<State>,
@ -557,7 +635,6 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
for &(pos, ref item) in frame.items() {
let x = pos.x.to_f32();
let y = pos.y.to_f32();
match item {
FrameItem::Group(group) => write_group(ctx, pos, group),
FrameItem::Text(text) => write_text(ctx, pos, text),
@ -567,8 +644,6 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) {
Meta::Link(dest) => write_link(ctx, pos, dest, *size),
Meta::Elem(_) => {}
Meta::Hide => {}
Meta::PageNumbering(_) => {}
Meta::PdfPageLabel(label) => ctx.label = Some(label.clone()),
},
}
}

View File

@ -13,6 +13,7 @@ use typst::introspection::Meta;
use typst::layout::{
Abs, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Ratio, Size, Transform,
};
use typst::model::Document;
use typst::text::{Font, TextItem};
use typst::visualize::{
Color, DashPattern, FixedStroke, Geometry, Gradient, Image, ImageKind, LineCap,
@ -39,19 +40,20 @@ pub fn render(frame: &Frame, pixel_per_pt: f32, fill: Color) -> sk::Pixmap {
canvas
}
/// Export multiple frames into a single raster image.
/// Export a document with potentially multiple pages into a single raster image.
///
/// The padding will be added around and between the individual frames.
pub fn render_merged(
frames: &[Frame],
document: &Document,
pixel_per_pt: f32,
frame_fill: Color,
padding: Abs,
padding_fill: Color,
) -> sk::Pixmap {
let pixmaps: Vec<_> = frames
let pixmaps: Vec<_> = document
.pages
.iter()
.map(|frame| render(frame, pixel_per_pt, frame_fill))
.map(|page| render(&page.frame, pixel_per_pt, frame_fill))
.collect();
let padding = (pixel_per_pt * padding.to_f32()).round() as u32;
@ -165,8 +167,6 @@ fn render_frame(canvas: &mut sk::Pixmap, state: State, frame: &Frame) {
FrameItem::Meta(meta, _) => match meta {
Meta::Link(_) => {}
Meta::Elem(_) => {}
Meta::PageNumbering(_) => {}
Meta::PdfPageLabel(_) => {}
Meta::Hide => {}
},
}

View File

@ -11,6 +11,7 @@ use typst::layout::{
Abs, Angle, Axes, Frame, FrameItem, FrameKind, GroupItem, Point, Quadrant, Ratio,
Size, Transform,
};
use typst::model::Document;
use typst::text::{Font, TextItem};
use typst::util::hash128;
use typst::visualize::{
@ -35,24 +36,33 @@ pub fn svg(frame: &Frame) -> String {
renderer.finalize()
}
/// Export multiple frames into a single SVG file.
/// Export a document with potentially multiple pages into a single SVG file.
///
/// The padding will be added around and between the individual frames.
pub fn svg_merged(frames: &[Frame], padding: Abs) -> String {
pub fn svg_merged(document: &Document, padding: Abs) -> String {
let width = 2.0 * padding
+ frames.iter().map(|frame| frame.width()).max().unwrap_or_default();
let height = padding + frames.iter().map(|page| page.height() + padding).sum::<Abs>();
let size = Size::new(width, height);
+ document
.pages
.iter()
.map(|page| page.frame.width())
.max()
.unwrap_or_default();
let height = padding
+ document
.pages
.iter()
.map(|page| page.frame.height() + padding)
.sum::<Abs>();
let mut renderer = SVGRenderer::new();
renderer.write_header(size);
renderer.write_header(Size::new(width, height));
let [x, mut y] = [padding; 2];
for frame in frames {
for page in &document.pages {
let ts = Transform::translate(x, y);
let state = State::new(frame.size(), Transform::identity());
renderer.render_frame(state, ts, frame);
y += frame.height() + padding;
let state = State::new(page.frame.size(), Transform::identity());
renderer.render_frame(state, ts, &page.frame);
y += page.frame.height() + padding;
}
renderer.finalize()

View File

@ -12,7 +12,7 @@ use smallvec::SmallVec;
use crate::diag::{bail, StrResult};
use crate::foundations::{Content, Label, Repr, Selector};
use crate::introspection::{Location, Meta};
use crate::layout::{Frame, FrameItem, Point, Position, Transform};
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
use crate::model::Numbering;
use crate::util::NonZeroExt;
@ -38,16 +38,17 @@ pub struct Introspector {
impl Introspector {
/// Applies new frames in-place, reusing the existing allocations.
#[typst_macros::time(name = "introspect")]
pub fn rebuild(&mut self, frames: &[Frame]) {
self.pages = frames.len();
pub fn rebuild(&mut self, pages: &[Page]) {
self.pages = pages.len();
self.elems.clear();
self.labels.clear();
self.page_numberings.clear();
self.queries.clear();
for (i, frame) in frames.iter().enumerate() {
let page = NonZeroUsize::new(1 + i).unwrap();
self.extract(frame, page, Transform::identity());
for (i, page) in pages.iter().enumerate() {
let page_nr = NonZeroUsize::new(1 + i).unwrap();
self.extract(&page.frame, page_nr, Transform::identity());
self.page_numberings.push(page.numbering.clone());
}
}
@ -77,9 +78,6 @@ impl Introspector {
self.labels.entry(label).or_default().push(self.elems.len() - 1);
}
}
FrameItem::Meta(Meta::PageNumbering(numbering), _) => {
self.page_numberings.push(numbering.clone());
}
_ => {}
}
}

View File

@ -29,8 +29,7 @@ use crate::foundations::Packed;
use crate::foundations::{
category, elem, ty, Behave, Behaviour, Category, Content, Repr, Scope, Unlabellable,
};
use crate::layout::PdfPageLabel;
use crate::model::{Destination, Numbering};
use crate::model::Destination;
/// Interactions between document parts.
///
@ -88,10 +87,6 @@ pub enum Meta {
/// An identifiable element that produces something within the area this
/// metadata is attached to.
Elem(Content),
/// The numbering of the current page.
PageNumbering(Option<Numbering>),
/// A PDF page label of the current page.
PdfPageLabel(PdfPageLabel),
/// Indicates that content should be hidden. This variant doesn't appear
/// in the final frames as it is removed alongside the content that should
/// be hidden.
@ -103,8 +98,6 @@ impl Debug for Meta {
match self {
Self::Link(dest) => write!(f, "Link({dest:?})"),
Self::Elem(content) => write!(f, "Elem({:?})", content.func()),
Self::PageNumbering(value) => write!(f, "PageNumbering({value:?})"),
Self::PdfPageLabel(label) => write!(f, "PdfPageLabel({label:?})"),
Self::Hide => f.pad("Hide"),
}
}

View File

@ -4,9 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::num::NonZeroUsize;
use std::sync::Arc;
use ecow::{eco_format, EcoString};
use crate::foundations::{cast, dict, Dict, Repr, StyleChain, Value};
use crate::foundations::{cast, dict, Dict, StyleChain, Value};
use crate::introspection::{Meta, MetaElem};
use crate::layout::{
Abs, Axes, Corners, FixedAlignment, Length, Point, Rel, Sides, Size, Transform,
@ -552,42 +550,3 @@ impl From<Position> for Dict {
}
}
}
/// Specification for a PDF page label.
#[derive(Debug, Clone, PartialEq, Hash, Default)]
pub struct PdfPageLabel {
/// Can be any string or none. Will always be prepended to the numbering style.
pub prefix: Option<EcoString>,
/// Based on the numbering pattern.
///
/// If `None` or numbering is a function, the field will be empty.
pub style: Option<PdfPageLabelStyle>,
/// Offset for the page label start.
///
/// Describes where to start counting from when setting a style.
/// (Has to be greater or equal than 1)
pub offset: Option<NonZeroUsize>,
}
impl Repr for PdfPageLabel {
fn repr(&self) -> EcoString {
eco_format!("{self:?}")
}
}
/// A PDF page label number style.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PdfPageLabelStyle {
/// Decimal arabic numerals (1, 2, 3).
Arabic,
/// Lowercase roman numerals (i, ii, iii).
LowerRoman,
/// Uppercase roman numerals (I, II, III).
UpperRoman,
/// Lowercase letters (`a` to `z` for the first 26 pages,
/// `aa` to `zz` and so on for the next).
LowerAlpha,
/// Uppercase letters (`A` to `Z` for the first 26 pages,
/// `AA` to `ZZ` and so on for the next).
UpperAlpha,
}

View File

@ -9,10 +9,10 @@ use crate::foundations::{
cast, elem, AutoValue, Cast, Content, Dict, Fold, Func, NativeElement, Packed,
Resolve, Smart, StyleChain, Value,
};
use crate::introspection::{Counter, CounterKey, ManualPageCounter, Meta};
use crate::introspection::{Counter, CounterKey, ManualPageCounter};
use crate::layout::{
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Fragment, Frame, HAlignment,
Layout, Length, Point, Ratio, Regions, Rel, Sides, Size, VAlignment,
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Layout, Length,
Point, Ratio, Regions, Rel, Sides, Size, VAlignment,
};
use crate::model::Numbering;
@ -347,7 +347,7 @@ impl Packed<PageElem> {
styles: StyleChain,
page_counter: &mut ManualPageCounter,
extend_to: Option<Parity>,
) -> SourceResult<Fragment> {
) -> SourceResult<Vec<Page>> {
// When one of the lengths is infinite the page fits its content along
// that axis.
let width = self.width(styles).unwrap_or(Abs::inf());
@ -413,7 +413,6 @@ impl Packed<PageElem> {
let header_ascent = self.header_ascent(styles);
let footer_descent = self.footer_descent(styles);
let numbering = self.numbering(styles);
let numbering_meta = Meta::PageNumbering(numbering.clone());
let number_align = self.number_align(styles);
let mut header = Cow::Borrowed(self.header(styles));
let mut footer = Cow::Borrowed(self.footer(styles));
@ -447,7 +446,8 @@ impl Packed<PageElem> {
}
// Post-process pages.
for frame in frames.iter_mut() {
let mut pages = Vec::with_capacity(frames.len());
for mut frame in frames {
// The padded width of the page's content without margins.
let pw = frame.width();
@ -462,7 +462,6 @@ impl Packed<PageElem> {
// Realize margins.
frame.set_size(frame.size() + margin.sum_by_axis());
frame.translate(Point::new(margin.left, margin.top));
frame.push_positionless_meta(numbering_meta.clone());
// The page size with margins.
let size = frame.size();
@ -506,22 +505,32 @@ impl Packed<PageElem> {
frame.fill(fill.clone());
}
page_counter.visit(engine, frame)?;
// Add a PDF page label if there is a numbering.
if let Some(num) = numbering {
if let Some(page_label) = num.apply_pdf(page_counter.logical()) {
frame.push_positionless_meta(Meta::PdfPageLabel(page_label));
}
}
page_counter.visit(engine, &frame)?;
pages.push(Page {
frame,
numbering: numbering.clone(),
number: page_counter.logical(),
});
page_counter.step();
}
Ok(Fragment::frames(frames))
Ok(pages)
}
}
/// A finished page.
#[derive(Debug, Default, Clone)]
pub struct Page {
/// The frame that defines the page.
pub frame: Frame,
/// The page's numbering.
pub numbering: Option<Numbering>,
/// The logical page number (controlled by `counter(page)` and may thus not
/// match the physical number).
pub number: usize,
}
/// Specification of the page's margins.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Margin {

View File

@ -7,7 +7,7 @@ use crate::foundations::{
Value,
};
use crate::introspection::{Introspector, ManualPageCounter};
use crate::layout::{Frame, LayoutRoot, PageElem};
use crate::layout::{LayoutRoot, Page, PageElem};
/// The root element of a document and its metadata.
///
@ -95,9 +95,8 @@ impl LayoutRoot for Packed<DocumentElem> {
.to_packed::<PageElem>()?
.clear_to(styles)
});
let fragment =
page.layout(engine, styles, &mut page_counter, extend_to)?;
pages.extend(fragment);
let run = page.layout(engine, styles, &mut page_counter, extend_to)?;
pages.extend(run);
} else {
bail!(child.span(), "unexpected document child");
}
@ -139,8 +138,8 @@ cast! {
/// A finished document with metadata and page frames.
#[derive(Debug, Default, Clone)]
pub struct Document {
/// The page frames.
pub pages: Vec<Frame>,
/// The document's finished pages.
pub pages: Vec<Page>,
/// The document's title.
pub title: Option<EcoString>,
/// The document's author.

View File

@ -1,4 +1,3 @@
use std::num::NonZeroUsize;
use std::str::FromStr;
use chinese_number::{ChineseCase, ChineseCountMethod, ChineseVariant, NumberToChinese};
@ -7,7 +6,6 @@ use ecow::{eco_format, EcoString, EcoVec};
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{cast, func, Func, Str, Value};
use crate::layout::{PdfPageLabel, PdfPageLabelStyle};
use crate::text::Case;
/// Applies a numbering to a sequence of numbers.
@ -89,49 +87,6 @@ impl Numbering {
})
}
/// Create a new `PdfNumbering` from a `Numbering` applied to a page
/// number.
pub fn apply_pdf(&self, number: usize) -> Option<PdfPageLabel> {
let Numbering::Pattern(pat) = self else {
return None;
};
let Some((prefix, kind, case)) = pat.pieces.first() else {
return None;
};
// If there is a suffix, we cannot use the common style optimisation,
// since PDF does not provide a suffix field.
let mut style = None;
if pat.suffix.is_empty() {
use {NumberingKind as Kind, PdfPageLabelStyle as Style};
match (kind, case) {
(Kind::Arabic, _) => style = Some(Style::Arabic),
(Kind::Roman, Case::Lower) => style = Some(Style::LowerRoman),
(Kind::Roman, Case::Upper) => style = Some(Style::UpperRoman),
(Kind::Letter, Case::Lower) if number <= 26 => {
style = Some(Style::LowerAlpha)
}
(Kind::Letter, Case::Upper) if number <= 26 => {
style = Some(Style::UpperAlpha)
}
_ => {}
}
}
// Prefix and offset depend on the style: If it is supported by the PDF
// spec, we use the given prefix and an offset. Otherwise, everything
// goes into prefix.
let prefix = if style.is_none() {
Some(pat.apply(&[number]))
} else {
(!prefix.is_empty()).then(|| prefix.clone())
};
let offset = style.and(NonZeroUsize::new(number));
Some(PdfPageLabel { prefix, style, offset })
}
/// Trim the prefix suffix if this is a pattern.
pub fn trimmed(mut self) -> Self {
if let Self::Pattern(pattern) = &mut self {

View File

@ -67,7 +67,7 @@ fuzz_target!(|text: &str| {
let mut tracer = Tracer::new();
if let Ok(document) = typst::compile(&world, &mut tracer) {
if let Some(page) = document.pages.first() {
std::hint::black_box(typst_render::render(page, 1.0, Color::WHITE));
std::hint::black_box(typst_render::render(&page.frame, 1.0, Color::WHITE));
}
}
comemo::evict(10);

View File

@ -75,7 +75,7 @@ fn bench_render(iai: &mut Iai) {
let world = BenchWorld::new();
let mut tracer = Tracer::new();
let document = typst::compile(&world, &mut tracer).unwrap();
iai.run(|| typst_render::render(&document.pages[0], 1.0, Color::WHITE))
iai.run(|| typst_render::render(&document.pages[0].frame, 1.0, Color::WHITE))
}
struct BenchWorld {

View File

@ -33,7 +33,7 @@ use typst::diag::{bail, FileError, FileResult, Severity, SourceDiagnostic, StrRe
use typst::eval::Tracer;
use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value};
use typst::introspection::Meta;
use typst::layout::{Abs, Frame, FrameItem, Margin, PageElem, Transform};
use typst::layout::{Abs, Frame, FrameItem, Margin, Page, PageElem, Transform};
use typst::model::Document;
use typst::syntax::{FileId, Source, SyntaxNode, VirtualPath};
use typst::text::{Font, FontBook, TextElem, TextSize};
@ -423,7 +423,7 @@ fn test(
let mut output = String::new();
let mut ok = true;
let mut updated = false;
let mut frames = vec![];
let mut pages = vec![];
let mut line = 0;
let mut header_configuration = None;
let mut compare_ever = false;
@ -490,13 +490,13 @@ fn test(
ok &= part_ok;
compare_ever |= compare_here;
frames.extend(part_frames);
pages.extend(part_frames);
}
line += part.lines().count() + 1;
}
let document = Document { pages: frames, ..Default::default() };
let document = Document { pages, ..Default::default() };
if compare_ever {
if let Some(pdf_path) = pdf_path {
let pdf_data = typst_pdf::pdf(
@ -514,11 +514,12 @@ fn test(
}
}
let canvas = render(&document.pages);
let canvas = render(&document);
fs::create_dir_all(png_path.parent().unwrap()).unwrap();
canvas.save_png(png_path).unwrap();
let svg = typst_svg::svg_merged(&document.pages, Abs::pt(5.0));
let svg = typst_svg::svg_merged(&document, Abs::pt(5.0));
fs::create_dir_all(svg_path.parent().unwrap()).unwrap();
std::fs::write(svg_path, svg.as_bytes()).unwrap();
@ -595,7 +596,7 @@ fn test_part(
header_configuration: &TestConfig,
rng: &mut LinearShift,
verbose: bool,
) -> (bool, bool, Vec<Frame>) {
) -> (bool, bool, Vec<Page>) {
let source = world.set(src_path, text);
if world.print.syntax {
writeln!(output, "Syntax Tree:\n{:#?}\n", source.root()).unwrap();
@ -1038,19 +1039,19 @@ fn test_spans_impl(output: &mut String, node: &SyntaxNode, within: Range<u64>) -
}
/// Draw all frames into one image with padding in between.
fn render(frames: &[Frame]) -> sk::Pixmap {
fn render(document: &Document) -> sk::Pixmap {
let pixel_per_pt = 2.0;
let padding = Abs::pt(5.0);
for frame in frames {
for page in &document.pages {
let limit = Abs::cm(100.0);
if frame.width() > limit || frame.height() > limit {
panic!("overlarge frame: {:?}", frame.size());
if page.frame.width() > limit || page.frame.height() > limit {
panic!("overlarge frame: {:?}", page.frame.size());
}
}
let mut pixmap = typst_render::render_merged(
frames,
document,
pixel_per_pt,
Color::WHITE,
padding,
@ -1059,11 +1060,12 @@ fn render(frames: &[Frame]) -> sk::Pixmap {
let padding = (pixel_per_pt * padding.to_pt() as f32).round();
let [x, mut y] = [padding; 2];
for frame in frames {
for page in &document.pages {
let ts =
sk::Transform::from_scale(pixel_per_pt, pixel_per_pt).post_translate(x, y);
render_links(&mut pixmap, ts, frame);
y += (pixel_per_pt * frame.height().to_pt() as f32).round().max(1.0) + padding;
render_links(&mut pixmap, ts, &page.frame);
y += (pixel_per_pt * page.frame.height().to_pt() as f32).round().max(1.0)
+ padding;
}
pixmap