summaryrefslogtreecommitdiff
path: root/crates/typst-pdf
diff options
context:
space:
mode:
authorHeinenen <37484430+Heinenen@users.noreply.github.com>2024-02-12 13:57:31 +0100
committerGitHub <noreply@github.com>2024-02-12 12:57:31 +0000
commitf776f0a75fb36deabab8e8cdf880389e4e2eb6e8 (patch)
tree3c93c358d46e7a6fb4693e9491e2f81d227ed8eb /crates/typst-pdf
parent63b73ee98c75b26b7adb4b7517732e0d2bb7551c (diff)
Named destinations (#2954)
Diffstat (limited to 'crates/typst-pdf')
-rw-r--r--crates/typst-pdf/src/lib.rs57
-rw-r--r--crates/typst-pdf/src/page.rs27
2 files changed, 77 insertions, 7 deletions
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;