[cairo] PDF API for links and metadata

Bryce Harrington bryce at osg.samsung.com
Tue Jun 7 18:08:49 UTC 2016

On Sun, Jun 05, 2016 at 10:39:30PM +0930, Adrian Johnson wrote:
> I have previously indicated I intend adding support for PDF hyperlinks
> for 1.16. PDF supports a large range of non drawing related features.
> Based on the various PDF files I have seen over the last few years, the
> majority of these features are never used. There are only a small number
> of interactive and document interchange features that are regularly used
> and would be reasonably easy to support in cairo with a minimal amount
> of extra API.
> These features are:
> - metadata
> - page labels
> - thumbnails
> - links
> - bookmarks
> - tagged pdf
> The following outlines the API that I am planning to add to support
> these features.


I know pretty much zilch about the PDF file format and have interacted
with it only as a user, so take that as a huge caveat for the possibly
dumb comments to follow...

> Metadata
> --------
> PDF can contain document metadata that can be displayed by PDF viewers.
> The following API can be used to set the metadata.
> typedef enum _cairo_pdf_metadata {
> } cairo_pdf_metadata_t;
> void
> cairo_pdf_surface_set_metadata (cairo_pdf_metadata_t metadata,
>                                 const char *utf8);
> Setting utf8 to NULL removes any metadata previously set. The
> _CREATE_DATE defaults to the current date time. Date strings need to be
> a particular format: D:YYYYMMDDHHmmSSOHH'mm eg D:199812231952-08'00.
> Since most applications will use the "current time" default, I do not
> see the need for date specific API for setting the time.

Is this data kept globally or is it held by the surface that's going to
be written?  If the latter, shouldn't this get a cairo_surface_t pointer
passed in?

cairo_pdf_metadata_t sounds to me more like the name of a struct than an
enum...  cairo_pdf_metadata_element_t or something might be clearer.

I can see the point to requiring the date be passed in pre-formatted,
but how should the API indicate if the passed in date is an invalid
format?  Perhaps it should return or raise an error?  Are there length
limitations on any of these strings that might also need verified?

What happens if you call this API midway through your document creation?

> Page Labels
> -----------
> A PDF file may optionally define page labels that appear in the viewer
> instead of the page index number. For example the document may use roman
> numerals for the front matter and start the first chapter at page "1".
> The following function sets the page label for the current page. Setting
> utf8 to NULL removes any page label previously set.
> void
> cairo_pdf_surface_set_page_label (cairo_surface_t *surface,
>                                   const char *utf8);

Is the page label literal text or like a template?  I.e. in your example
where the front matter is roman numeraled, do you need to make each
individual page a separate surface with 'I', 'II', 'III', et al?  Or do
you specify 'I' and the pdf backend automatically does the appropriate
roman numbering?

> Thumbnails
> ----------
> PDF can store thumbnail images of the pages that can be displayed by the
> viewer.
> This function specifies the thumbnail size for the current page, and all
> subsequent pages until the next invocation of this function.
> void
> cairo_pdf_surface_set_thumbnail_size (int width, int height);
> Setting width and height to (0, 0) disables thumbnails. The default is
> (0, 0).

Also needs a cairo_surface_t * passed in right?

Would passing negative width/heights be errors or would that be treated
same as passing zero?

> Links
> -----
> PDF can contain hyperlinks to another location in the file, a location
> in another PDF file, or a URL.
> I initially started with the following API but then changed my mind. See
> the Tagged PDF section for the new API.
> The following function creates a link on the current page. In PDF links
> are defined by a one or more rectangles (more than one would be used
> when a link is split across two lines) defining the region that can be
> clicked on. Normally the application would set the rectangle to the
> extents of the link text.
> typedef enum _cairo_link_flags {
> } cairo_link_flags_t;


> void
> cairo_create_link (cairo_t *cr,
>                    int num_rectangles,
> 		   cairo_rectangle_t *rectangles,
> 		   const char *dest_name,
> 		   cairo_link_flags_t flags);
> If the appearance is not _NONE, use the current color and line style to
> draw the box/underline.
> For internal links we need a way to associate destination names with
> locations in the document. The following function creates a destination
> to the position x,y on the current page.
> typedef enum _cairo_destination_flags {
>     CAIRO_DESTINATION_FLAG_INTERNAL = 1, /* can optimize away name or
> the destination if unused */
> } cairo_destination_flags_t;
> void
> cairo_create_destination (cairo_t *cr,
>                           const char *dest_name,
> 			  double x, double y,
> 			  cairo_destination_flags_t flags);

> Bookmarks
> ---------
> A PDF file can contain bookmarks (also called document outline) that is
> a hierarchical set of links into the document. Using the
> cairo_create_destination() function it is easy to create a document
> outline with one API function.
> typedef enum _cairo_pdf_bookmark_flags {
> } cairo_pdf_bookmark_flags_t;
> int
> cairo_pdf_surface_add_bookmark (int parent_id,
>                                 const char *utf8,
>                                 const char *dest_name,
>                                 cairo_pdf_bookmark_flags_t flags);
> This function adds a bookmark with the name, utf8, that links to
> dest_name. It returns a bookmark id. The parent_id is the parent
> bookmark above this bookmark. Set to CAIRO_PDF_BOOKMARK_ROOT for the top
> level bookmark.

Can the flags be OR'd together and passed as a bitmask, so you can have
a bookmark be both bold and italic?  Maybe a bigger question is why does
this combine structural and stylistic formatting stuff?  I'm not
familiar with PDF document internals but this feels a bit hodge podge.

What are utf8 and dest_name exactly?  Is utf8 the text for the bookmark
and dest_name the anchor point?  Or vice versa?  These args may need
clearer naming.

I think I'm not really grokking what this feature is.  Am I
understanding correctly it's a way to define bookmarkable locations
inside the PDF, that can be referenced externally via URLs, sort of like
HTML anchors?  Or is it strictly for internal linking as would be used
by TOCs, footnotes, etc.?

If it is the latter, is there some mechanism to issue warnings if you
create a bookmark to a destination that never gets defined?

> Tagged PDF
> ----------
> A tagged PDF contains additional data that defines the logical structure
> of the page content. The logical structure includes information such as
> headings, paragraphs, tables, and figures. Tagged PDF is intended to be
> used for things like extraction of text and graphics into other
> applications, reflowing of text and graphics to fit a different page
> size, searching and indexing, and accessibility support.
> Cairo is already using one of the tagged PDF features, ActualText, to
> support the cairo_show_text_glyphs() function.
> The following API can be used for tagging the drawing operations
> enclosed by the cairo_tag_begin() and cairo_tag_end() functions with the
> specified tag. Tags can be nested.
> void
> cairo_tag_begin (cairo_t *cr, const char *tag_name);
> void
> cairo_tag_end (cairo_t *cr, const char *tag_name);
> The tag names are defined in PDF32000 section 14.8 [1]. Examples of tag
> names include:
> "P": paragraph
> "H1" - "H6": headings
> "Table": table
> "TR", "TH", "TD", "THead", "TBody" "TFoot": table elements
> "Link": hyperlink
> PDF32000 also defines an extensive range of attributes that can be
> include with each tag. I have omitted attributes from the API to keep it
> simple and because the tag name alone should be sufficient for the
> intended usage.

Yes, but looks like you changed your mind on this point in the next

> New Link API
> ------------
> The SVG backend also supports hyperlinks. SVG links are defined using
> the 'a' element. eg
>   <a xlink:href="http://www.w3.org">
>     <ellipse cx="2.5" cy="1.5" rx="2" ry="1"
>              fill="red" />
>   </a>
> Instead of requiring the application to provide a rectangle and then
> cairo has to figure out what text is inside the rectangle, we can use
> the tagged API to define the link text.

Yes, seems like a more sensible approach.
> #define CAIRO_TAG_LINK "Link"
> Then the application can wrap the link text drawing operations and the
> call to cairo_create_link() (with num rectangles = 0) with
> cairo_tag_begin(CAIRO_TAG_LINK) and cairo_tag_end(CAIRO_TAG_LINK).
> It then occurred to me that we could drop the
> cairo_create_link()/cairo_create_destination() API and extend the
> tagging API to also create links.

If we're already asserting that this isn't intending to implement every
nook and cranny of the PDF spec, then may as well.  It sounds to me like
the other alternative is the more primitive way of handling it?  Since
we're not parsing existing PDFs, I suppose the only reason to consider
it would be if you're at all worried that links in Cairo-generated PDFs
might not be correctly parsed by some PDF readers.

> #define CAIRO_TAG_LINK "Link"
> #define CAIRO_TAG_DEST "cairo.dest" /* cairo prefix because it is not a
> standard PDF tag */
> void
> cairo_tag_begin (cairo_t *cr,
>                  const char *tag_name,
>                  const char *attributes);
> void
> cairo_tag_end (cairo_t *cr, const char *tag_name);
> For example:
> Create a destination at position 100,20 on the current page.
>   cairo_tag_begin (cr, CAIRO_TAG_DEST, "pos=\"100 20\"");
> If the position is not specified it defaults to the top left of the
> extents of the drawing operations enclosed by this tag. If no drawing
> operations within the tag, the default position is the top left of the page.
> Create URL link:
>   cairo_tag_begin (cr, CAIRO_TAG_LINK,
>    "href=\"http:://cairographics.org/\" rect=\"0 0 100 20\"
>     appearance=\"underline\"");
> If the rectangle is not specified, it defaults to the extents of the
> drawing operations enclosed by this link tag.
> Create an internal link:
>   cairo_tag_begin (cr, CAIRO_TAG_LINK, "ref=\"section3\"
>     appearance=\"none\"");

All the quote escaping here makes me concerned this is a recipe for
death by typo...  but I do like that this provides a rather generic
interface, on top of which folks can put whatever string encoding
helpers they want to take care of escaping and whatnot.

More information about the cairo mailing list