Named destinations (#2954)
This commit is contained in:
parent
63b73ee98c
commit
f776f0a75f
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user