diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c01b53f94..8afaa61ce 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -29,12 +29,12 @@ jobs: token: ${{ secrets.TYPSTC_ACTION_TOKEN }} path: fontdock - - name: Checkout tide + - name: Checkout pdf-writer uses: actions/checkout@v2 with: - repository: typst/tide + repository: typst/pdf-writer token: ${{ secrets.TYPSTC_ACTION_TOKEN }} - path: tide + path: pdf-writer - name: Install Rust uses: actions-rs/toolchain@v1 diff --git a/Cargo.toml b/Cargo.toml index 9d08c3522..50cfde1b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,8 @@ fs = ["fontdock/fs"] [dependencies] fontdock = { path = "../fontdock", default-features = false } -tide = { path = "../tide" } +pdf-writer = { path = "../pdf-writer" } +itoa = "0.4" ttf-parser = "0.8.2" unicode-xid = "0.2" diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 905ef9840..763cea8a2 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -1,16 +1,11 @@ //! Exporting into _PDF_ documents. use std::collections::HashMap; -use std::io::{self, Write}; use fontdock::FaceId; -use tide::content::Content; -use tide::doc::{Catalog, Page, PageTree, Resource, Text}; -use tide::font::{ - CIDFont, CIDFontType, CIDSystemInfo, CMap, CMapEncoding, FontDescriptor, FontFlags, - FontStream, GlyphUnit, Type0Font, WidthRecord, +use pdf_writer::{ + CIDFontType, FontFlags, Name, PdfWriter, Rect, Ref, Str, SystemInfo, TextStream, }; -use tide::{PdfWriter, Rect, Ref, Trailer, Version}; use ttf_parser::{name_id, GlyphId}; use crate::font::FontLoader; @@ -25,16 +20,12 @@ use crate::layout::{BoxLayout, LayoutElement}; /// /// The raw _PDF_ is written into the `target` writable, returning the number of /// bytes written. -pub fn export( - layouts: &[BoxLayout], - loader: &FontLoader, - target: W, -) -> io::Result { - PdfExporter::new(layouts, loader, target)?.write() +pub fn export(layouts: &[BoxLayout], loader: &FontLoader) -> Vec { + PdfExporter::new(layouts, loader).write() } -struct PdfExporter<'a, W: Write> { - writer: PdfWriter, +struct PdfExporter<'a> { + writer: PdfWriter, layouts: &'a [BoxLayout], loader: &'a FontLoader, /// We need to know exactly which indirect reference id will be used for @@ -50,57 +41,58 @@ struct PdfExporter<'a, W: Write> { struct Offsets { catalog: Ref, page_tree: Ref, - pages: (Ref, Ref), - contents: (Ref, Ref), - fonts: (Ref, Ref), + pages: (i32, i32), + contents: (i32, i32), + fonts: (i32, i32), } -const NUM_OBJECTS_PER_FONT: u32 = 5; +const NUM_OBJECTS_PER_FONT: i32 = 5; -impl<'a, W: Write> PdfExporter<'a, W> { - fn new( - layouts: &'a [BoxLayout], - loader: &'a FontLoader, - target: W, - ) -> io::Result { +impl<'a> PdfExporter<'a> { + fn new(layouts: &'a [BoxLayout], loader: &'a FontLoader) -> Self { let (to_pdf, to_fontdock) = remap_fonts(layouts); let offsets = calculate_offsets(layouts.len(), to_pdf.len()); + let mut writer = PdfWriter::new(1, 7); + writer.set_indent(2); - Ok(Self { - writer: PdfWriter::new(target), + Self { + writer, layouts, offsets, to_pdf, to_layout: to_fontdock, loader, - }) + } } - fn write(&mut self) -> io::Result { - self.writer.write_header(Version::new(1, 7))?; - self.write_preface()?; - self.write_pages()?; - self.write_fonts()?; - self.writer.write_xref_table()?; - self.writer.write_trailer(Trailer::new(self.offsets.catalog))?; - Ok(self.writer.written()) + fn write(mut self) -> Vec { + self.write_preface(); + self.write_pages(); + self.write_fonts(); + self.writer.end(self.offsets.catalog) } - fn write_preface(&mut self) -> io::Result<()> { + fn write_preface(&mut self) { // The document catalog. self.writer - .write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?; - - // The font resources. - let start = self.offsets.fonts.0; - let fonts = (0 .. self.to_pdf.len() as u32) - .map(|i| Resource::Font(i + 1, start + (NUM_OBJECTS_PER_FONT * i))); + .catalog(self.offsets.catalog) + .pages(self.offsets.page_tree); // The root page tree. - self.writer.write_obj( - self.offsets.page_tree, - PageTree::new().kids(ids(self.offsets.pages)).resources(fonts), - )?; + { + let mut pages = self.writer.pages(self.offsets.page_tree); + pages.kids(ids(self.offsets.pages)); + + let mut resources = pages.resources(); + let mut fonts = resources.fonts(); + for i in 0 .. self.to_pdf.len() { + let mut buf = itoa::Buffer::new(); + fonts.pair( + Name(buf.format(1 + i as i32).as_bytes()), + Ref::new(self.offsets.fonts.0 + NUM_OBJECTS_PER_FONT * i as i32), + ); + } + } // The page objects (non-root nodes in the page tree). for ((page_id, content_id), page) in ids(self.offsets.pages) @@ -114,24 +106,22 @@ impl<'a, W: Write> PdfExporter<'a, W> { page.size.height.to_pt() as f32, ); - self.writer.write_obj( - page_id, - Page::new(self.offsets.page_tree).media_box(rect).content(content_id), - )?; + self.writer + .page(page_id) + .parent(self.offsets.page_tree) + .media_box(rect) + .contents(content_id); } - - Ok(()) } - fn write_pages(&mut self) -> io::Result<()> { + fn write_pages(&mut self) { for (id, page) in ids(self.offsets.contents).zip(self.layouts) { - self.write_page(id, &page)?; + self.write_page(id, &page); } - Ok(()) } - fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> { - let mut text = Text::new(); + fn write_page(&mut self, id: Ref, page: &BoxLayout) { + let mut text = TextStream::new(); // Font switching actions are only written when the face used for // shaped text changes. Hence, we need to remember the active face. @@ -145,26 +135,26 @@ impl<'a, W: Write> PdfExporter<'a, W> { if shaped.face != face || shaped.font_size != size { face = shaped.face; size = shaped.font_size; - text.tf( - self.to_pdf[&shaped.face] as u32 + 1, + + let mut buf = itoa::Buffer::new(); + text = text.tf( + Name(buf.format(1 + self.to_pdf[&shaped.face]).as_bytes()), size.to_pt() as f32, ); } let x = pos.x.to_pt(); let y = (page.size.height - pos.y - size).to_pt(); - text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32); - text.tj(shaped.encode_glyphs_be()); + text = text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32); + text = text.tj(&shaped.encode_glyphs_be()); } } } - self.writer.write_obj(id, &text.to_stream())?; - - Ok(()) + self.writer.stream(id, &text.end()); } - fn write_fonts(&mut self) -> io::Result<()> { + fn write_fonts(&mut self) { let mut id = self.offsets.fonts.0; for &face_id in &self.to_layout { @@ -181,19 +171,23 @@ impl<'a, W: Write> PdfExporter<'a, W> { .unwrap_or_else(|| "unknown".to_string()); let base_font = format!("ABCDEF+{}", name); - let system_info = CIDSystemInfo::new("Adobe", "Identity", 0); + let base_font = Name(base_font.as_bytes()); + let system_info = SystemInfo { + registry: Str(b"Adobe"), + ordering: Str(b"Identity"), + supplement: 0, + }; - let units_per_em = face.units_per_em().unwrap_or(1000) as f64; + let units_per_em = face.units_per_em().unwrap_or(1000) as f32; let ratio = 1.0 / units_per_em; - let to_glyph_unit = - |font_unit: f64| (1000.0 * ratio * font_unit).round() as GlyphUnit; + let to_glyph_unit = |font_unit: f32| (1000.0 * ratio * font_unit).round(); let global_bbox = face.global_bounding_box(); let bbox = Rect::new( - to_glyph_unit(global_bbox.x_min as f64), - to_glyph_unit(global_bbox.y_min as f64), - to_glyph_unit(global_bbox.x_max as f64), - to_glyph_unit(global_bbox.y_max as f64), + to_glyph_unit(global_bbox.x_min as f32), + to_glyph_unit(global_bbox.y_min as f32), + to_glyph_unit(global_bbox.x_max as f32), + to_glyph_unit(global_bbox.y_max as f32), ); let monospace = face.is_monospaced(); @@ -212,10 +206,9 @@ impl<'a, W: Write> PdfExporter<'a, W> { flags.insert(FontFlags::SMALL_CAP); let num_glyphs = face.number_of_glyphs(); - let widths: Vec<_> = (0 .. num_glyphs) - .map(|g| face.glyph_hor_advance(GlyphId(g)).unwrap_or(0)) - .map(|w| to_glyph_unit(w as f64)) - .collect(); + let widths = (0 .. num_glyphs).map(|g| { + to_glyph_unit(face.glyph_hor_advance(GlyphId(g)).unwrap_or(0) as f32) + }); let mut mapping = vec![]; for subtable in face.character_mapping_subtables() { @@ -228,53 +221,51 @@ impl<'a, W: Write> PdfExporter<'a, W> { }) } + let type0_font_id = Ref::new(id); + let cid_font_id = Ref::new(id + 1); + let font_descriptor_id = Ref::new(id + 2); + let cmap_id = Ref::new(id + 3); + let data_id = Ref::new(id + 4); + // Write the base font object referencing the CID font. - self.writer.write_obj( - id, - Type0Font::new( - base_font.clone(), - CMapEncoding::Predefined("Identity-H".to_string()), - id + 1, - ) - .to_unicode(id + 3), - )?; + self.writer + .type0_font(type0_font_id) + .base_font(base_font) + .encoding_predefined(Name(b"Identity-H")) + .descendant_font(cid_font_id) + .to_unicode(cmap_id); // Write the CID font referencing the font descriptor. - self.writer.write_obj( - id + 1, - CIDFont::new( - CIDFontType::Type2, - base_font.clone(), - system_info.clone(), - id + 2, - ) - .widths(vec![WidthRecord::Start(0, widths)]), - )?; + self.writer + .cid_font(cid_font_id, CIDFontType::Type2) + .base_font(base_font) + .system_info(system_info) + .font_descriptor(font_descriptor_id) + .widths() + .individual(0, widths); // Write the font descriptor (contains metrics about the font). - self.writer.write_obj( - id + 2, - FontDescriptor::new(base_font, flags, italic_angle) - .font_bbox(bbox) - .ascent(to_glyph_unit(ascender as f64)) - .descent(to_glyph_unit(descender as f64)) - .cap_height(to_glyph_unit(cap_height as f64)) - .stem_v(stem_v as GlyphUnit) - .font_file_2(id + 4), - )?; + self.writer + .font_descriptor(font_descriptor_id) + .font_name(base_font) + .font_flags(flags) + .font_bbox(bbox) + .italic_angle(italic_angle) + .ascent(to_glyph_unit(ascender as f32)) + .descent(to_glyph_unit(descender as f32)) + .cap_height(to_glyph_unit(cap_height as f32)) + .stem_v(stem_v) + .font_file2(data_id); // Write the CMap, which maps glyph ids back to unicode codepoints // to enable copying out of the PDF. - self.writer - .write_obj(id + 3, &CMap::new("Custom", system_info, mapping))?; + self.writer.cmap(cmap_id, Name(b"Custom"), system_info, mapping); // Write the face's bytes. - self.writer.write_obj(id + 4, &FontStream::new(owned_face.data()))?; + self.writer.stream(data_id, owned_face.data()); id += NUM_OBJECTS_PER_FONT; } - - Ok(()) } } @@ -306,19 +297,19 @@ fn remap_fonts(layouts: &[BoxLayout]) -> (HashMap, Vec) { fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets { let catalog = 1; let page_tree = catalog + 1; - let pages = (page_tree + 1, page_tree + layout_count as Ref); - let contents = (pages.1 + 1, pages.1 + layout_count as Ref); - let font_offsets = (contents.1 + 1, contents.1 + 5 * font_count as Ref); + let pages = (page_tree + 1, page_tree + layout_count as i32); + let contents = (pages.1 + 1, pages.1 + layout_count as i32); + let font_offsets = (contents.1 + 1, contents.1 + 5 * font_count as i32); Offsets { - catalog, - page_tree, + catalog: Ref::new(catalog), + page_tree: Ref::new(page_tree), pages, contents, fonts: font_offsets, } } -fn ids((start, end): (Ref, Ref)) -> impl Iterator { - start ..= end +fn ids((start, end): (i32, i32)) -> impl Iterator { + (start ..= end).map(Ref::new) } diff --git a/src/main.rs b/src/main.rs index 6bd5a9b4b..715ee59fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; -use std::fs::{read_to_string, File}; -use std::io::BufWriter; +use std::fs; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -35,7 +34,7 @@ fn main() -> anyhow::Result<()> { bail!("Source and destination path are the same."); } - let src = read_to_string(src_path).context("Failed to read from source file.")?; + let src = fs::read_to_string(src_path).context("Failed to read from source file.")?; let mut index = FsIndex::new(); index.search_dir("fonts"); @@ -72,10 +71,8 @@ fn main() -> anyhow::Result<()> { } } - let loader = loader.borrow(); - let file = File::create(&dest_path).context("Failed to create output file.")?; - let writer = BufWriter::new(file); - pdf::export(&layouts, &loader, writer).context("Failed to export pdf.")?; + let pdf_data = pdf::export(&layouts, &loader.borrow()); + fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?; Ok(()) } diff --git a/tests/typeset.rs b/tests/typeset.rs index b3080a529..90e0d4fbb 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -2,7 +2,6 @@ use std::cell::RefCell; use std::env; use std::ffi::OsStr; use std::fs::{self, File}; -use std::io::BufWriter; use std::path::Path; use std::rc::Rc; @@ -135,8 +134,8 @@ fn test(src_path: &Path, pdf_path: &Path, png_path: &Path, loader: &SharedFontLo let surface = render(&layouts, &loader, 3.0); surface.write_png(png_path).unwrap(); - let file = BufWriter::new(File::create(pdf_path).unwrap()); - pdf::export(&layouts, &loader, file).unwrap(); + let pdf_data = pdf::export(&layouts, &loader); + fs::write(pdf_path, pdf_data).unwrap(); } struct TestFilter {