summaryrefslogtreecommitdiff
path: root/src/export/pdf/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/export/pdf/mod.rs')
-rw-r--r--src/export/pdf/mod.rs235
1 files changed, 0 insertions, 235 deletions
diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs
deleted file mode 100644
index 48485862..00000000
--- a/src/export/pdf/mod.rs
+++ /dev/null
@@ -1,235 +0,0 @@
-//! Exporting into PDF documents.
-
-mod font;
-mod image;
-mod outline;
-mod page;
-
-use std::cmp::Eq;
-use std::collections::{BTreeMap, HashMap};
-use std::hash::Hash;
-
-use ecow::EcoString;
-use pdf_writer::types::Direction;
-use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
-use xmp_writer::{LangId, RenditionClass, XmpWriter};
-
-use self::page::Page;
-use crate::doc::{Document, Lang};
-use crate::font::Font;
-use crate::geom::{Abs, Dir, Em};
-use crate::image::Image;
-use crate::model::Introspector;
-
-/// Export a document into a PDF file.
-///
-/// Returns the raw bytes making up the PDF file.
-#[tracing::instrument(skip_all)]
-pub fn pdf(document: &Document) -> Vec<u8> {
- let mut ctx = PdfContext::new(document);
- page::construct_pages(&mut ctx, &document.pages);
- font::write_fonts(&mut ctx);
- image::write_images(&mut ctx);
- page::write_page_tree(&mut ctx);
- write_catalog(&mut ctx);
- ctx.writer.finish()
-}
-
-/// Identifies the color space definitions.
-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<'a> {
- document: &'a Document,
- introspector: Introspector,
- writer: PdfWriter,
- pages: Vec<Page>,
- page_heights: Vec<f32>,
- alloc: Ref,
- page_tree_ref: Ref,
- font_refs: Vec<Ref>,
- image_refs: Vec<Ref>,
- page_refs: Vec<Ref>,
- font_map: Remapper<Font>,
- image_map: Remapper<Image>,
- /// 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>,
-}
-
-impl<'a> PdfContext<'a> {
- fn new(document: &'a Document) -> Self {
- let mut alloc = Ref::new(1);
- let page_tree_ref = alloc.bump();
- Self {
- document,
- introspector: Introspector::new(&document.pages),
- writer: PdfWriter::new(),
- pages: vec![],
- page_heights: vec![],
- alloc,
- page_tree_ref,
- page_refs: vec![],
- font_refs: vec![],
- image_refs: vec![],
- font_map: Remapper::new(),
- image_map: Remapper::new(),
- glyph_sets: HashMap::new(),
- languages: HashMap::new(),
- }
- }
-}
-
-/// Write the document catalog.
-#[tracing::instrument(skip_all)]
-fn write_catalog(ctx: &mut PdfContext) {
- let lang = ctx
- .languages
- .iter()
- .max_by_key(|(&lang, &count)| (count, lang))
- .map(|(&k, _)| k);
-
- let dir = if lang.map(Lang::dir) == Some(Dir::RTL) {
- Direction::R2L
- } else {
- Direction::L2R
- };
-
- // Write the outline tree.
- let outline_root_id = outline::write_outline(ctx);
-
- // Write the document information.
- let mut info = ctx.writer.document_info(ctx.alloc.bump());
- let mut xmp = XmpWriter::new();
- if let Some(title) = &ctx.document.title {
- info.title(TextStr(title));
- xmp.title([(None, title.as_str())]);
- }
-
- let authors = &ctx.document.author;
- if !authors.is_empty() {
- info.author(TextStr(&authors.join(", ")));
- xmp.creator(authors.iter().map(|s| s.as_str()));
- }
- info.creator(TextStr("Typst"));
- info.finish();
- xmp.creator_tool("Typst");
- xmp.num_pages(ctx.document.pages.len() as u32);
- xmp.format("application/pdf");
- xmp.language(ctx.languages.keys().map(|lang| LangId(lang.as_str())));
- xmp.rendition_class(RenditionClass::Proof);
- xmp.pdf_version("1.7");
-
- 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();
-
- // Write the document catalog.
- let mut catalog = ctx.writer.catalog(ctx.alloc.bump());
- catalog.pages(ctx.page_tree_ref);
- catalog.viewer_preferences().direction(dir);
- catalog.pair(Name(b"Metadata"), meta_ref);
-
- if let Some(outline_root_id) = outline_root_id {
- catalog.outlines(outline_root_id);
- }
-
- if let Some(lang) = lang {
- catalog.lang(TextStr(lang.as_str()));
- }
-}
-
-/// Compress data with the DEFLATE algorithm.
-#[tracing::instrument(skip_all)]
-fn deflate(data: &[u8]) -> Vec<u8> {
- const COMPRESSION_LEVEL: u8 = 6;
- miniz_oxide::deflate::compress_to_vec_zlib(data, COMPRESSION_LEVEL)
-}
-
-/// Assigns new, consecutive PDF-internal indices to items.
-struct Remapper<T> {
- /// Forwards from the items to the pdf indices.
- to_pdf: HashMap<T, usize>,
- /// Backwards from the pdf indices to the items.
- to_items: Vec<T>,
-}
-
-impl<T> Remapper<T>
-where
- T: Eq + Hash + Clone,
-{
- fn new() -> Self {
- Self { to_pdf: HashMap::new(), to_items: vec![] }
- }
-
- fn insert(&mut self, item: T) {
- let to_layout = &mut self.to_items;
- self.to_pdf.entry(item.clone()).or_insert_with(|| {
- let pdf_index = to_layout.len();
- to_layout.push(item);
- pdf_index
- });
- }
-
- fn map(&self, item: T) -> usize {
- self.to_pdf[&item]
- }
-
- fn pdf_indices<'a>(
- &'a self,
- refs: &'a [Ref],
- ) -> impl Iterator<Item = (Ref, usize)> + 'a {
- refs.iter().copied().zip(0..self.to_pdf.len())
- }
-
- fn items(&self) -> impl Iterator<Item = &T> + '_ {
- self.to_items.iter()
- }
-}
-
-/// Additional methods for [`Abs`].
-trait AbsExt {
- /// Convert an to a number of points.
- fn to_f32(self) -> f32;
-}
-
-impl AbsExt for Abs {
- fn to_f32(self) -> f32 {
- self.to_pt() as f32
- }
-}
-
-/// Additional methods for [`Em`].
-trait EmExt {
- /// Convert an em length to a number of PDF font units.
- fn to_font_units(self) -> f32;
-}
-
-impl EmExt for Em {
- fn to_font_units(self) -> f32 {
- 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
- }
-}