fschutt / printpdf

A fully-featured PDF library for Rust, WASM-ready
https://fschutt.github.io/printpdf/
MIT License
829 stars 98 forks source link

Add link annotations to other pages #185

Closed chipsenkbeil closed 4 weeks ago

chipsenkbeil commented 2 months ago

Does printpdf support adding links between pages of the same, generated PDF? I've been using the library to make a new PDF template for the Supernote Nomad and realized that I'm not sure how to create hyperlinks to connect pages together.

The template idea for an eink tablet is to let you jump between days, weeks, and months like a calendar. You do this by generating a page per day/week/month and creating hyperlinks to connect between them.

I've got a link annotation working for something external like https://example.com/, but not sure if there's either

  1. A link convention like #page=2 that a PDF reader will understand or
  2. Some other way to create internal links

It seems like the only defined action is the URI, so not sure if I can use that or do something else.


I think what I want is a GoTo action.

image
chipsenkbeil commented 2 months ago

From my understanding, there are a couple of different types of destinations, which is denoted with the /D. For explicit destinations, the main thing we need is the page id, which seems to be a (u32, u16) that we don't calculate until saving the PDF to bytes.

This means that in order to add explicit destination annotations, we have to store the internal id used by printpdf and then translate that during save_to_bytes() invocations into the actual page id of (u32, u16), which I think we can get from the page_id_to_obj hashmap.


Skimming through the code for save_to_bytes(), the Actions instance is converted to a lopdf::creator::Object via the Into<Object> for Actions implementation. This probably needs to be changed to accept some sort of context when creating the object since we need to be able to translate an internal PdfPageIndex into an actual (u32, u16).

In addition, the page objects are created after the annotations have been converted into lopdf::creator::Object, meaning that the annotations wouldn't have access to a mapping of internal indexes to PDF references. To remedy that, we'd need to create the page objects in advance using doc.inner_doc.new_object_id(). Then, instead of creating the object later via doc.inner_doc.add_object(p), we'd use doc.inner_doc.set_object(id, p) to swap out the empty object with the actual version.

TLDR, we pre-allocate the page ids in advance so we can reference them when converting annotations into objects since they need to have a reference to the page mapping when doing explicit destinations.