Refactor PDF export a bit
This commit is contained in:
parent
7dc74b7281
commit
077218db3a
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
@ -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,28 +45,21 @@ 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>,
|
||||
alloc: Ref,
|
||||
page_tree_ref: Ref,
|
||||
font_refs: Vec<Ref>,
|
||||
image_refs: Vec<Ref>,
|
||||
gradient_refs: Vec<Ref>,
|
||||
ext_gs_refs: Vec<Ref>,
|
||||
page_refs: Vec<Ref>,
|
||||
font_map: Remapper<Font>,
|
||||
image_map: Remapper<Image>,
|
||||
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,
|
||||
@ -74,7 +67,35 @@ pub struct PdfContext<'a> {
|
||||
/// 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>,
|
||||
/// 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>,
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user