summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-10-04 14:41:21 +0200
committerLaurenz <laurmaedje@gmail.com>2023-10-04 14:47:04 +0200
commit077218db3a45eeb7eaa265242d154368fb567f72 (patch)
treeed13da5cbf4f0a7c692d39b1f50a339499df6164
parent7dc74b7281105110e0ff8933e9ce264248f3d367 (diff)
Refactor PDF export a bit
-rw-r--r--crates/typst/src/export/pdf/color.rs93
-rw-r--r--crates/typst/src/export/pdf/extg.rs23
-rw-r--r--crates/typst/src/export/pdf/font.rs10
-rw-r--r--crates/typst/src/export/pdf/gradient.rs12
-rw-r--r--crates/typst/src/export/pdf/image.rs26
-rw-r--r--crates/typst/src/export/pdf/mod.rs90
-rw-r--r--crates/typst/src/export/pdf/outline.rs8
-rw-r--r--crates/typst/src/export/pdf/page.rs30
8 files changed, 132 insertions, 160 deletions
diff --git a/crates/typst/src/export/pdf/color.rs b/crates/typst/src/export/pdf/color.rs
index eb27f969..fccaafab 100644
--- a/crates/typst/src/export/pdf/color.rs
+++ b/crates/typst/src/export/pdf/color.rs
@@ -1,5 +1,4 @@
-use std::sync::Arc;
-
+use once_cell::sync::Lazy;
use pdf_writer::types::DeviceNSubtype;
use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref};
@@ -27,13 +26,18 @@ const HSL_S: Name<'static> = Name(b"S");
const HSL_L: Name<'static> = Name(b"L");
// The ICC profiles.
-const SRGB_ICC: &[u8] = include_bytes!("./icc/sRGB-v4.icc");
-const GRAY_ICC: &[u8] = include_bytes!("./icc/sGrey-v4.icc");
+static SRGB_ICC_DEFLATED: Lazy<Vec<u8>> =
+ Lazy::new(|| deflate(include_bytes!("icc/sRGB-v4.icc")));
+static GRAY_ICC_DEFLATED: Lazy<Vec<u8>> =
+ Lazy::new(|| deflate(include_bytes!("icc/sGrey-v4.icc")));
// The PostScript functions for color spaces.
-const OKLAB_SOURCE: &str = include_str!("./postscript/oklab.ps");
-const HSL_SOURCE: &str = include_str!("./postscript/hsl.ps");
-const HSV_SOURCE: &str = include_str!("./postscript/hsv.ps");
+static OKLAB_DEFLATED: Lazy<Vec<u8>> =
+ Lazy::new(|| deflate(minify(include_str!("postscript/oklab.ps")).as_bytes()));
+static HSV_DEFLATED: Lazy<Vec<u8>> =
+ Lazy::new(|| deflate(minify(include_str!("postscript/hsl.ps")).as_bytes()));
+static HSL_DEFLATED: Lazy<Vec<u8>> =
+ Lazy::new(|| deflate(minify(include_str!("postscript/hsv.ps")).as_bytes()));
/// The color spaces present in the PDF document
#[derive(Default)]
@@ -161,87 +165,54 @@ impl ColorSpaces {
/// Write the necessary color spaces functions and ICC profiles to the
/// PDF file.
- pub fn write_functions(&self, writer: &mut Chunk) {
- // Write the Oklab function & color space
+ pub fn write_functions(&self, chunk: &mut Chunk) {
+ // Write the Oklab function & color space.
if let Some(oklab) = self.oklab {
- let code = oklab_function();
- writer
- .post_script_function(oklab, &code)
+ chunk
+ .post_script_function(oklab, &OKLAB_DEFLATED)
.domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
.filter(Filter::FlateDecode);
}
- // Write the HSV function & color space
+ // Write the HSV function & color space.
if let Some(hsv) = self.hsv {
- let code = hsv_function();
- writer
- .post_script_function(hsv, &code)
+ chunk
+ .post_script_function(hsv, &HSV_DEFLATED)
.domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
.filter(Filter::FlateDecode);
}
- // Write the HSL function & color space
+ // Write the HSL function & color space.
if let Some(hsl) = self.hsl {
- let code = hsl_function();
- writer
- .post_script_function(hsl, &code)
+ chunk
+ .post_script_function(hsl, &HSL_DEFLATED)
.domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
.range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
.filter(Filter::FlateDecode);
}
- // Write the sRGB color space
+ // Write the sRGB color space.
if let Some(srgb) = self.srgb {
- let profile = srgb_icc();
- writer
- .icc_profile(srgb, &profile)
+ chunk
+ .icc_profile(srgb, &SRGB_ICC_DEFLATED)
.n(3)
- .range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
+ .range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0])
+ .filter(Filter::FlateDecode);
}
- // Write the gray color space
+ // Write the gray color space.
if let Some(gray) = self.d65_gray {
- let profile = gray_icc();
- writer.icc_profile(gray, &profile).n(1).range([0.0, 1.0]);
+ chunk
+ .icc_profile(gray, &GRAY_ICC_DEFLATED)
+ .n(1)
+ .range([0.0, 1.0])
+ .filter(Filter::FlateDecode);
}
}
}
-/// Deflated sRGB ICC profile
-#[comemo::memoize]
-fn srgb_icc() -> Arc<Vec<u8>> {
- Arc::new(deflate(SRGB_ICC))
-}
-
-/// Deflated gray ICC profile
-#[comemo::memoize]
-fn gray_icc() -> Arc<Vec<u8>> {
- Arc::new(deflate(GRAY_ICC))
-}
-
-/// Deflated Oklab PostScript function
-#[comemo::memoize]
-fn oklab_function() -> Arc<Vec<u8>> {
- let code = minify(OKLAB_SOURCE);
- Arc::new(deflate(code.as_bytes()))
-}
-
-/// Deflated HSV PostScript function
-#[comemo::memoize]
-fn hsv_function() -> Arc<Vec<u8>> {
- let code = minify(HSV_SOURCE);
- Arc::new(deflate(code.as_bytes()))
-}
-
-/// Deflated HSL PostScript function
-#[comemo::memoize]
-fn hsl_function() -> Arc<Vec<u8>> {
- let code = minify(HSL_SOURCE);
- Arc::new(deflate(code.as_bytes()))
-}
-
/// This function removes comments, line spaces and carriage returns from a
/// PostScript program. This is necessary to optimize the size of the PDF file.
fn minify(source: &str) -> String {
diff --git a/crates/typst/src/export/pdf/extg.rs b/crates/typst/src/export/pdf/extg.rs
index 47393b54..f7bd116d 100644
--- a/crates/typst/src/export/pdf/extg.rs
+++ b/crates/typst/src/export/pdf/extg.rs
@@ -1,23 +1,21 @@
-use pdf_writer::Finish;
-
-use crate::export::pdf::PdfContext;
+use super::PdfContext;
/// A PDF external graphics state.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
-pub struct ExternalGraphicsState {
+pub struct ExtGState {
// In the range 0-255, needs to be divided before being written into the graphics state!
pub stroke_opacity: u8,
// In the range 0-255, needs to be divided before being written into the graphics state!
pub fill_opacity: u8,
}
-impl Default for ExternalGraphicsState {
+impl Default for ExtGState {
fn default() -> Self {
Self { stroke_opacity: 255, fill_opacity: 255 }
}
}
-impl ExternalGraphicsState {
+impl ExtGState {
pub fn uses_opacities(&self) -> bool {
self.stroke_opacity != 255 || self.fill_opacity != 255
}
@@ -26,13 +24,12 @@ impl ExternalGraphicsState {
/// Embed all used external graphics states into the PDF.
#[tracing::instrument(skip_all)]
pub fn write_external_graphics_states(ctx: &mut PdfContext) {
- for external_gs in ctx.ext_gs_map.items() {
- let gs_ref = ctx.alloc.bump();
- ctx.ext_gs_refs.push(gs_ref);
-
- let mut gs = ctx.writer.ext_graphics(gs_ref);
- gs.non_stroking_alpha(external_gs.fill_opacity as f32 / 255.0)
+ for external_gs in ctx.extg_map.items() {
+ let id = ctx.alloc.bump();
+ ctx.ext_gs_refs.push(id);
+ ctx.pdf
+ .ext_graphics(id)
+ .non_stroking_alpha(external_gs.fill_opacity as f32 / 255.0)
.stroking_alpha(external_gs.stroke_opacity as f32 / 255.0);
- gs.finish();
}
}
diff --git a/crates/typst/src/export/pdf/font.rs b/crates/typst/src/export/pdf/font.rs
index e1faf1b1..bd7cfb31 100644
--- a/crates/typst/src/export/pdf/font.rs
+++ b/crates/typst/src/export/pdf/font.rs
@@ -58,7 +58,7 @@ pub fn write_fonts(ctx: &mut PdfContext) {
};
// Write the base font object referencing the CID font.
- ctx.writer
+ ctx.pdf
.type0_font(type0_ref)
.base_font(Name(base_font_type0.as_bytes()))
.encoding_predefined(Name(b"Identity-H"))
@@ -66,7 +66,7 @@ pub fn write_fonts(ctx: &mut PdfContext) {
.to_unicode(cmap_ref);
// Write the CID font referencing the font descriptor.
- let mut cid = ctx.writer.cid_font(cid_ref);
+ let mut cid = ctx.pdf.cid_font(cid_ref);
cid.subtype(if is_cff { CidFontType::Type0 } else { CidFontType::Type2 });
cid.base_font(Name(base_font.as_bytes()));
cid.system_info(SYSTEM_INFO);
@@ -125,7 +125,7 @@ pub fn write_fonts(ctx: &mut PdfContext) {
let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0);
// Write the font descriptor (contains metrics about the font).
- let mut font_descriptor = ctx.writer.font_descriptor(descriptor_ref);
+ let mut font_descriptor = ctx.pdf.font_descriptor(descriptor_ref);
font_descriptor
.name(Name(base_font.as_bytes()))
.flags(flags)
@@ -147,13 +147,13 @@ pub fn write_fonts(ctx: &mut PdfContext) {
// Write the /ToUnicode character map, which maps glyph ids back to
// unicode codepoints to enable copying out of the PDF.
let cmap = create_cmap(ttf, glyph_set);
- ctx.writer.cmap(cmap_ref, &cmap.finish());
+ ctx.pdf.cmap(cmap_ref, &cmap.finish());
// Subset and write the font's bytes.
let glyphs: Vec<_> = glyph_set.keys().copied().collect();
let data = subset_font(font, &glyphs);
- let mut stream = ctx.writer.stream(data_ref, &data);
+ let mut stream = ctx.pdf.stream(data_ref, &data);
stream.filter(Filter::FlateDecode);
if is_cff {
stream.pair(Name(b"Subtype"), Name(b"CIDFontType0C"));
diff --git a/crates/typst/src/export/pdf/gradient.rs b/crates/typst/src/export/pdf/gradient.rs
index 23ab687c..842c7666 100644
--- a/crates/typst/src/export/pdf/gradient.rs
+++ b/crates/typst/src/export/pdf/gradient.rs
@@ -35,7 +35,7 @@ pub fn write_gradients(ctx: &mut PdfContext) {
let mut shading_pattern = match &gradient {
Gradient::Linear(linear) => {
let shading_function = shading_function(ctx, &gradient);
- let mut shading_pattern = ctx.writer.shading_pattern(shading);
+ let mut shading_pattern = ctx.pdf.shading_pattern(shading);
let mut shading = shading_pattern.function_shading();
shading.shading_type(FunctionShadingType::Axial);
@@ -108,7 +108,7 @@ fn shading_function(ctx: &mut PdfContext, gradient: &Gradient) -> Ref {
// These need to be individual function to encode 360.0 correctly.
let func1 = ctx.alloc.bump();
- ctx.writer
+ ctx.pdf
.exponential_function(func1)
.range(gradient.space().range())
.c0(gradient.space().convert(first.0))
@@ -117,7 +117,7 @@ fn shading_function(ctx: &mut PdfContext, gradient: &Gradient) -> Ref {
.n(1.0);
let func2 = ctx.alloc.bump();
- ctx.writer
+ ctx.pdf
.exponential_function(func2)
.range(gradient.space().range())
.c0([1.0, s1 * (1.0 - t) + s2 * t, x1 * (1.0 - t) + x2 * t])
@@ -126,7 +126,7 @@ fn shading_function(ctx: &mut PdfContext, gradient: &Gradient) -> Ref {
.n(1.0);
let func3 = ctx.alloc.bump();
- ctx.writer
+ ctx.pdf
.exponential_function(func3)
.range(gradient.space().range())
.c0([0.0, s1 * (1.0 - t) + s2 * t, x1 * (1.0 - t) + x2 * t])
@@ -157,7 +157,7 @@ fn shading_function(ctx: &mut PdfContext, gradient: &Gradient) -> Ref {
bounds.pop();
// Create the stitching function.
- ctx.writer
+ ctx.pdf
.stitching_function(function)
.domain([0.0, 1.0])
.range(gradient.space().range())
@@ -178,7 +178,7 @@ fn single_gradient(
) -> Ref {
let reference = ctx.alloc.bump();
- ctx.writer
+ ctx.pdf
.exponential_function(reference)
.range(color_space.range())
.c0(color_space.convert(first_color))
diff --git a/crates/typst/src/export/pdf/image.rs b/crates/typst/src/export/pdf/image.rs
index 12c5fb34..cce75bc3 100644
--- a/crates/typst/src/export/pdf/image.rs
+++ b/crates/typst/src/export/pdf/image.rs
@@ -15,26 +15,28 @@ use crate::{
pub fn write_images(ctx: &mut PdfContext) {
for image in ctx.image_map.items() {
let image_ref = ctx.alloc.bump();
- let icc_ref = ctx.alloc.bump();
ctx.image_refs.push(image_ref);
- let width = image.width();
- let height = image.height();
-
// Add the primary image.
match image.kind() {
ImageKind::Raster(raster) => {
// TODO: Error if image could not be encoded.
- let (data, filter, has_color) = encode_image(raster);
- let mut image = ctx.writer.image_xobject(image_ref, &data);
+ let (data, filter, has_color) = encode_raster_image(raster);
+ let width = image.width();
+ let height = image.height();
+
+ let mut image = ctx.pdf.image_xobject(image_ref, &data);
image.filter(filter);
image.width(width as i32);
image.height(height as i32);
image.bits_per_component(8);
+ let mut icc_ref = None;
let space = image.color_space();
if raster.icc().is_some() {
- space.icc_based(icc_ref);
+ let id = ctx.alloc.bump();
+ space.icc_based(id);
+ icc_ref = Some(id);
} else if has_color {
ctx.colors.write(ColorSpace::Srgb, space, &mut ctx.alloc);
} else {
@@ -49,7 +51,7 @@ pub fn write_images(ctx: &mut PdfContext) {
image.s_mask(mask_ref);
image.finish();
- let mut mask = ctx.writer.image_xobject(mask_ref, &alpha_data);
+ let mut mask = ctx.pdf.image_xobject(mask_ref, &alpha_data);
mask.filter(alpha_filter);
mask.width(width as i32);
mask.height(height as i32);
@@ -59,9 +61,9 @@ pub fn write_images(ctx: &mut PdfContext) {
image.finish();
}
- if let Some(icc) = raster.icc() {
+ if let (Some(icc), Some(icc_ref)) = (raster.icc(), icc_ref) {
let compressed = deflate(icc);
- let mut stream = ctx.writer.icc_profile(icc_ref, &compressed);
+ let mut stream = ctx.pdf.icc_profile(icc_ref, &compressed);
stream.filter(Filter::FlateDecode);
if has_color {
stream.n(3);
@@ -79,7 +81,7 @@ pub fn write_images(ctx: &mut PdfContext) {
let next_ref = svg2pdf::convert_tree_into(
tree,
svg2pdf::Options::default(),
- &mut ctx.writer,
+ &mut ctx.pdf,
image_ref,
);
ctx.alloc = next_ref;
@@ -95,7 +97,7 @@ pub fn write_images(ctx: &mut PdfContext) {
/// Skips the alpha channel as that's encoded separately.
#[comemo::memoize]
#[tracing::instrument(skip_all)]
-fn encode_image(image: &RasterImage) -> (Arc<Vec<u8>>, Filter, bool) {
+fn encode_raster_image(image: &RasterImage) -> (Arc<Vec<u8>>, Filter, bool) {
let dynamic = image.dynamic();
match (image.format(), dynamic) {
// 8-bit gray JPEG.
diff --git a/crates/typst/src/export/pdf/mod.rs b/crates/typst/src/export/pdf/mod.rs
index 587c5361..51784fbc 100644
--- a/crates/typst/src/export/pdf/mod.rs
+++ b/crates/typst/src/export/pdf/mod.rs
@@ -30,7 +30,7 @@ use crate::geom::{Abs, Dir, Em};
use crate::image::Image;
use crate::model::Introspector;
-use extg::ExternalGraphicsState;
+use extg::ExtGState;
/// Export a document into a PDF file.
///
@@ -45,36 +45,57 @@ pub fn pdf(document: &Document) -> Vec<u8> {
extg::write_external_graphics_states(&mut ctx);
page::write_page_tree(&mut ctx);
write_catalog(&mut ctx);
- ctx.writer.finish()
+ ctx.pdf.finish()
}
/// Context for exporting a whole PDF document.
pub struct PdfContext<'a> {
+ /// The document that we're currently exporting.
document: &'a Document,
+ /// An introspector for the document, used to resolve locations links and
+ /// the document outline.
introspector: Introspector,
- writer: Pdf,
- colors: ColorSpaces,
+
+ /// The writer we are writing the PDF into.
+ pdf: Pdf,
+ /// Content of exported pages.
pages: Vec<Page>,
- page_heights: Vec<f32>,
+ /// 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,
+ /// then we just save the first one. The resulting strings are used for the
+ /// PDF's /ToUnicode map for glyphs that don't have an entry in the font's
+ /// cmap. This is important for copy-paste and searching.
+ glyph_sets: HashMap<Font, BTreeMap<u16, EcoString>>,
+ /// The number of glyphs for all referenced languages in the document.
+ /// We keep track of this to determine the main document language.
+ languages: HashMap<Lang, usize>,
+
+ /// Allocator for indirect reference IDs.
alloc: Ref,
+ /// The ID of the page tree.
page_tree_ref: Ref,
+ /// The IDs of written pages.
+ page_refs: Vec<Ref>,
+ /// The IDs of written fonts.
font_refs: Vec<Ref>,
+ /// The IDs of written images.
image_refs: Vec<Ref>,
+ /// The IDs of written gradients.
gradient_refs: Vec<Ref>,
+ /// The IDs of written external graphics states.
ext_gs_refs: Vec<Ref>,
- page_refs: Vec<Ref>,
+ /// Handles color space writing.
+ colors: ColorSpaces,
+
+ /// Deduplicates fonts used across the document.
font_map: Remapper<Font>,
+ /// Deduplicates images used across the document.
image_map: Remapper<Image>,
+ /// Deduplicates gradients used across the document.
gradient_map: Remapper<PdfGradient>,
- ext_gs_map: Remapper<ExternalGraphicsState>,
- /// 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,
- /// then we just save the first one. The resulting strings are used for the
- /// PDF's /ToUnicode map for glyphs that don't have an entry in the font's
- /// cmap. This is important for copy-paste and searching.
- glyph_sets: HashMap<Font, BTreeMap<u16, EcoString>>,
- languages: HashMap<Lang, usize>,
+ /// Deduplicates external graphics states used across the document.
+ extg_map: Remapper<ExtGState>,
}
impl<'a> PdfContext<'a> {
@@ -84,10 +105,10 @@ impl<'a> PdfContext<'a> {
Self {
document,
introspector: Introspector::new(&document.pages),
- writer: Pdf::new(),
- colors: ColorSpaces::default(),
+ pdf: Pdf::new(),
pages: vec![],
- page_heights: vec![],
+ glyph_sets: HashMap::new(),
+ languages: HashMap::new(),
alloc,
page_tree_ref,
page_refs: vec![],
@@ -95,12 +116,11 @@ impl<'a> PdfContext<'a> {
image_refs: vec![],
gradient_refs: vec![],
ext_gs_refs: vec![],
+ colors: ColorSpaces::default(),
font_map: Remapper::new(),
image_map: Remapper::new(),
gradient_map: Remapper::new(),
- ext_gs_map: Remapper::new(),
- glyph_sets: HashMap::new(),
- languages: HashMap::new(),
+ extg_map: Remapper::new(),
}
}
}
@@ -127,7 +147,7 @@ fn write_catalog(ctx: &mut PdfContext) {
let page_labels = write_page_labels(ctx);
// Write the document information.
- let mut info = ctx.writer.document_info(ctx.alloc.bump());
+ let mut info = ctx.pdf.document_info(ctx.alloc.bump());
let mut xmp = XmpWriter::new();
if let Some(title) = &ctx.document.title {
info.title(TextStr(title));
@@ -160,13 +180,13 @@ fn write_catalog(ctx: &mut PdfContext) {
let xmp_buf = xmp.finish(None);
let meta_ref = ctx.alloc.bump();
- let mut meta_stream = ctx.writer.stream(meta_ref, xmp_buf.as_bytes());
- meta_stream.pair(Name(b"Type"), Name(b"Metadata"));
- meta_stream.pair(Name(b"Subtype"), Name(b"XML"));
- meta_stream.finish();
+ ctx.pdf
+ .stream(meta_ref, xmp_buf.as_bytes())
+ .pair(Name(b"Type"), Name(b"Metadata"))
+ .pair(Name(b"Subtype"), Name(b"XML"));
// Write the document catalog.
- let mut catalog = ctx.writer.catalog(ctx.alloc.bump());
+ let mut catalog = ctx.pdf.catalog(ctx.alloc.bump());
catalog.pages(ctx.page_tree_ref);
catalog.viewer_preferences().direction(dir);
catalog.pair(Name(b"Metadata"), meta_ref);
@@ -215,7 +235,7 @@ fn write_page_labels(ctx: &mut PdfContext) -> Vec<(NonZeroUsize, Ref)> {
}
let id = ctx.alloc.bump();
- let mut entry = ctx.writer.indirect(id).start::<PageLabel>();
+ let mut entry = ctx.pdf.indirect(id).start::<PageLabel>();
// Only add what is actually provided. Don't add empty prefix string if
// it wasn't given for example.
@@ -309,17 +329,3 @@ impl EmExt for Em {
1000.0 * self.get() as f32
}
}
-
-/// Additional methods for [`Ref`].
-trait RefExt {
- /// Bump the reference up by one and return the previous one.
- fn bump(&mut self) -> Self;
-}
-
-impl RefExt for Ref {
- fn bump(&mut self) -> Self {
- let prev = *self;
- *self = Self::new(prev.get() + 1);
- prev
- }
-}
diff --git a/crates/typst/src/export/pdf/outline.rs b/crates/typst/src/export/pdf/outline.rs
index 58db7a93..7aa15c41 100644
--- a/crates/typst/src/export/pdf/outline.rs
+++ b/crates/typst/src/export/pdf/outline.rs
@@ -92,7 +92,7 @@ pub fn write_outline(ctx: &mut PdfContext) -> Option<Ref> {
prev_ref = Some(write_outline_item(ctx, node, root_id, prev_ref, i + 1 == len));
}
- ctx.writer
+ ctx.pdf
.outline(root_id)
.first(start_ref)
.last(Ref::new(ctx.alloc.get() - 1))
@@ -140,7 +140,7 @@ fn write_outline_item(
let id = ctx.alloc.bump();
let next_ref = Ref::new(id.get() + node.len() as i32);
- let mut outline = ctx.writer.outline_item(id);
+ let mut outline = ctx.pdf.outline_item(id);
outline.parent(parent_ref);
if !is_last {
@@ -164,11 +164,11 @@ fn write_outline_item(
let loc = node.element.location().unwrap();
let pos = ctx.introspector.position(loc);
let index = pos.page.get() - 1;
- if let Some(&height) = ctx.page_heights.get(index) {
+ if let Some(page) = ctx.pages.get(index) {
let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
outline.dest().page(ctx.page_refs[index]).xyz(
pos.point.x.to_f32(),
- height - y.to_f32(),
+ (page.size.y - y).to_f32(),
None,
);
}
diff --git a/crates/typst/src/export/pdf/page.rs b/crates/typst/src/export/pdf/page.rs
index ef023f22..412d753d 100644
--- a/crates/typst/src/export/pdf/page.rs
+++ b/crates/typst/src/export/pdf/page.rs
@@ -8,7 +8,7 @@ use pdf_writer::types::{
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
use super::color::PaintEncode;
-use super::extg::ExternalGraphicsState;
+use super::extg::ExtGState;
use super::{deflate, AbsExt, EmExt, PdfContext};
use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem};
use crate::eval::Repr;
@@ -32,7 +32,6 @@ pub fn construct_pages(ctx: &mut PdfContext, frames: &[Frame]) {
pub fn construct_page(ctx: &mut PdfContext, frame: &Frame) {
let page_ref = ctx.alloc.bump();
ctx.page_refs.push(page_ref);
- ctx.page_heights.push(frame.height().to_f32());
let mut ctx = PageContext {
parent: ctx,
@@ -81,7 +80,7 @@ pub fn write_page_tree(ctx: &mut PdfContext) {
write_page(ctx, i);
}
- let mut pages = ctx.writer.pages(ctx.page_tree_ref);
+ let mut pages = ctx.pdf.pages(ctx.page_tree_ref);
pages
.count(ctx.page_refs.len() as i32)
.kids(ctx.page_refs.iter().copied());
@@ -115,7 +114,7 @@ pub fn write_page_tree(ctx: &mut PdfContext) {
patterns.finish();
let mut ext_gs_states = resources.ext_g_states();
- for (gs_ref, gs) in ctx.ext_gs_map.pdf_indices(&ctx.ext_gs_refs) {
+ for (gs_ref, gs) in ctx.extg_map.pdf_indices(&ctx.ext_gs_refs) {
let name = eco_format!("Gs{}", gs);
ext_gs_states.pair(Name(name.as_bytes()), gs_ref);
}
@@ -125,7 +124,7 @@ pub fn write_page_tree(ctx: &mut PdfContext) {
pages.finish();
// Write all of the functions used by the document.
- ctx.colors.write_functions(&mut ctx.writer);
+ ctx.colors.write_functions(&mut ctx.pdf);
}
/// Write a page tree node.
@@ -134,7 +133,7 @@ fn write_page(ctx: &mut PdfContext, i: usize) {
let page = &ctx.pages[i];
let content_id = ctx.alloc.bump();
- let mut page_writer = ctx.writer.page(page.id);
+ let mut page_writer = ctx.pdf.page(page.id);
page_writer.parent(ctx.page_tree_ref);
let w = page.size.x.to_f32();
@@ -172,13 +171,13 @@ fn write_page(ctx: &mut PdfContext, i: usize) {
let index = pos.page.get() - 1;
let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero());
- if let Some(&height) = ctx.page_heights.get(index) {
+ if let Some(page) = ctx.pages.get(index) {
annotation
.action()
.action_type(ActionType::GoTo)
.destination()
.page(ctx.page_refs[index])
- .xyz(pos.point.x.to_f32(), height - y.to_f32(), None);
+ .xyz(pos.point.x.to_f32(), (page.size.y - y).to_f32(), None);
}
}
@@ -186,7 +185,7 @@ fn write_page(ctx: &mut PdfContext, i: usize) {
page_writer.finish();
let data = deflate(&page.content);
- ctx.writer.stream(content_id, &data).filter(Filter::FlateDecode);
+ ctx.pdf.stream(content_id, &data).filter(Filter::FlateDecode);
}
/// Data for an exported page.
@@ -231,7 +230,7 @@ struct State {
font: Option<(Font, Abs)>,
fill: Option<Paint>,
fill_space: Option<Name<'static>>,
- external_graphics_state: Option<ExternalGraphicsState>,
+ external_graphics_state: Option<ExtGState>,
stroke: Option<FixedStroke>,
stroke_space: Option<Name<'static>>,
}
@@ -287,11 +286,11 @@ impl PageContext<'_, '_> {
self.state = self.saves.pop().expect("missing state save");
}
- fn set_external_graphics_state(&mut self, graphics_state: &ExternalGraphicsState) {
+ fn set_external_graphics_state(&mut self, graphics_state: &ExtGState) {
let current_state = self.state.external_graphics_state.as_ref();
if current_state != Some(graphics_state) {
- self.parent.ext_gs_map.insert(*graphics_state);
- let name = eco_format!("Gs{}", self.parent.ext_gs_map.map(graphics_state));
+ self.parent.extg_map.insert(*graphics_state);
+ let name = eco_format!("Gs{}", self.parent.extg_map.map(graphics_state));
self.content.set_parameters(Name(name.as_bytes()));
if graphics_state.uses_opacities() {
@@ -321,10 +320,7 @@ impl PageContext<'_, '_> {
color.alpha().map_or(255, |v| (v * 255.0).round() as u8)
})
.unwrap_or(255);
- self.set_external_graphics_state(&ExternalGraphicsState {
- stroke_opacity,
- fill_opacity,
- });
+ self.set_external_graphics_state(&ExtGState { stroke_opacity, fill_opacity });
}
fn transform(&mut self, transform: Transform) {