diff options
| author | Heinenen <37484430+Heinenen@users.noreply.github.com> | 2024-02-12 13:57:31 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-12 12:57:31 +0000 |
| commit | f776f0a75fb36deabab8e8cdf880389e4e2eb6e8 (patch) | |
| tree | 3c93c358d46e7a6fb4693e9491e2f81d227ed8eb | |
| parent | 63b73ee98c75b26b7adb4b7517732e0d2bb7551c (diff) | |
Named destinations (#2954)
| -rw-r--r-- | Cargo.lock | 5 | ||||
| -rw-r--r-- | Cargo.toml | 5 | ||||
| -rw-r--r-- | crates/typst-pdf/src/lib.rs | 57 | ||||
| -rw-r--r-- | crates/typst-pdf/src/page.rs | 27 |
4 files changed, 83 insertions, 11 deletions
@@ -1616,9 +1616,8 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pdf-writer" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644b654f2de28457bf1e25a4905a76a563d1128a33ce60cf042f721f6818feaf" +version = "0.9.0" +source = "git+https://github.com/heinenen/pdf-writer?branch=named_destinations#58c6dc1552aa72f5e2c07a37045526fcf365d34a" dependencies = [ "bitflags 1.3.2", "itoa", @@ -71,7 +71,7 @@ oxipng = { version = "9.0", default-features = false, features = ["filetime", "p palette = { version = "0.7.3", default-features = false, features = ["approx", "libm"] } parking_lot = "0.12.1" pathdiff = "0.2" -pdf-writer = "0.9.2" +pdf-writer = "0.9" phf = { version = "0.11", features = ["macros"] } pixglyph = "0.3" proc-macro2 = "1" @@ -122,6 +122,9 @@ xz2 = "0.1" yaml-front-matter = "0.1" zip = { version = "0.6", default-features = false, features = ["deflate"] } +[patch.crates-io] +pdf-writer = { git = 'https://github.com/heinenen/pdf-writer', branch = "named_destinations" } + [profile.dev.package."*"] opt-level = 2 diff --git a/crates/typst-pdf/src/lib.rs b/crates/typst-pdf/src/lib.rs index 80d4d67f..9cf345a7 100644 --- a/crates/typst-pdf/src/lib.rs +++ b/crates/typst-pdf/src/lib.rs @@ -10,17 +10,17 @@ mod page; mod pattern; use std::cmp::Eq; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::hash::Hash; use std::sync::Arc; use base64::Engine; use ecow::{eco_format, EcoString}; use pdf_writer::types::Direction; -use pdf_writer::{Finish, Name, Pdf, Ref, TextStr}; -use typst::foundations::Datetime; +use pdf_writer::{Finish, Name, Pdf, Ref, Str, TextStr}; +use typst::foundations::{Datetime, NativeElement}; use typst::layout::{Abs, Dir, Em, Transform}; -use typst::model::Document; +use typst::model::{Document, HeadingElem}; use typst::text::{Font, Lang}; use typst::util::Deferred; use typst::visualize::Image; @@ -252,12 +252,25 @@ fn write_catalog(ctx: &mut PdfContext, ident: Option<&str>, timestamp: Option<Da .pair(Name(b"Type"), Name(b"Metadata")) .pair(Name(b"Subtype"), Name(b"XML")); + let destinations = write_and_collect_destinations(ctx); + // Write the document catalog. let mut catalog = ctx.pdf.catalog(ctx.alloc.bump()); catalog.pages(ctx.page_tree_ref); catalog.viewer_preferences().direction(dir); catalog.metadata(meta_ref); + // Write the named destinations. + let mut name_dict = catalog.names(); + let mut dests_name_tree = name_dict.destinations(); + let mut names = dests_name_tree.names(); + for (name, dest_ref, _page_ref, _x, _y) in destinations { + names.insert(name, dest_ref); + } + names.finish(); + dests_name_tree.finish(); + name_dict.finish(); + // Insert the page labels. if !page_labels.is_empty() { let mut num_tree = catalog.page_labels(); @@ -274,6 +287,42 @@ fn write_catalog(ctx: &mut PdfContext, ident: Option<&str>, timestamp: Option<Da if let Some(lang) = lang { catalog.lang(TextStr(lang.as_str())); } + + catalog.finish(); +} + +fn write_and_collect_destinations<'a>( + ctx: &mut PdfContext, +) -> Vec<(Str<'a>, Ref, Ref, f32, f32)> { + let mut destinations = vec![]; + + let mut seen_labels = HashSet::new(); + let elements = ctx.document.introspector.query(&HeadingElem::elem().select()); + for elem in elements.iter() { + let heading = elem.to_packed::<HeadingElem>().unwrap(); + if let Some(label) = heading.label() { + if !seen_labels.contains(&label) { + let loc = heading.location().unwrap(); + let name = Str(label.as_str().as_bytes()); + let pos = ctx.document.introspector.position(loc); + let index = pos.page.get() - 1; + let y = (pos.point.y - Abs::pt(10.0)).max(Abs::zero()); + if let Some(page) = ctx.pages.get(index) { + seen_labels.insert(label); + let page_ref = ctx.page_refs[index]; + let x = pos.point.x.to_f32(); + let y = (page.size.y - y).to_f32(); + let dest_ref = ctx.alloc.bump(); + destinations.push((name, dest_ref, page_ref, x, y)) + } + } + } + } + destinations.sort_by_key(|i| i.0); + for (_name, dest_ref, page_ref, x, y) in destinations.iter().copied() { + ctx.pdf.destination(dest_ref).page(page_ref).xyz(x, y, None); + } + destinations } /// Compress data with the DEFLATE algorithm. diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs index 6f672848..b18c2878 100644 --- a/crates/typst-pdf/src/page.rs +++ b/crates/typst-pdf/src/page.rs @@ -8,11 +8,12 @@ use pdf_writer::types::{ }; use pdf_writer::writers::PageLabel; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr}; -use typst::introspection::Meta; +use typst::foundations::{NativeElement, Selector}; +use typst::introspection::{Location, Meta}; use typst::layout::{ Abs, Em, Frame, FrameItem, GroupItem, Page, Point, Ratio, Size, Transform, }; -use typst::model::{Destination, Numbering}; +use typst::model::{Destination, Document, HeadingElem, Numbering}; use typst::text::{Case, Font, TextItem}; use typst::util::{Deferred, Numeric}; use typst::visualize::{ @@ -141,6 +142,16 @@ pub(crate) fn write_page_tree(ctx: &mut PdfContext) { ctx.colors.write_functions(&mut ctx.pdf); } +fn name_from_loc<'a>(doc: &Document, loc: &Location) -> Option<Name<'a>> { + let elem = doc.introspector.query_first(&Selector::Location(*loc))?; + let label = elem.label()?; + debug_assert!(doc.introspector.query_label(label).is_ok()); + if elem.elem() != HeadingElem::elem() { + return None; + } + Some(Name(label.as_str().as_bytes())) +} + /// Write a page tree node. fn write_page(ctx: &mut PdfContext, i: usize) { let page = &ctx.pages[i]; @@ -179,7 +190,17 @@ fn write_page(ctx: &mut PdfContext, i: usize) { continue; } Destination::Position(pos) => *pos, - Destination::Location(loc) => ctx.document.introspector.position(*loc), + Destination::Location(loc) => { + if let Some(name) = name_from_loc(ctx.document, loc) { + annotation + .action() + .action_type(ActionType::GoTo) + .destination_named(name); + continue; + } else { + ctx.document.introspector.position(*loc) + } + } }; let index = pos.page.get() - 1; |
