14

How can I list the named destinations in a PDF file?

Named destinations are the formal name for what you might call anchors. Major browsers jump to the named destination foo when you follow a link to http://example.com/some.pdf#foo.

I have documents where I can see anchors working, but I can't seem to find a way to list the anchors. Evince, okular and xpdf will jump to them when instructed but don't seem to have an interface that lists them. pdftk dump_data lists bookmarks, but that's not the same thing (that's table of content entries, which may well be at the same position as named destinations but can't be used as anchors).

I'm looking for a command line solution (suitable, for example, for use in a completion function after the likes of evince -n). Inasmuch as this is meaningful, I'd like to list the destinations in the order in which they appear in the document. Bonus: show the target page number and other information that helps figure out approximately where the destination is.

See also View anchors in a PDF document on Software Recommendations for a GUI viewer.

5 Answers 5

16

Poppler's pdfinfo command-line utility will provide you with page number, position, and name for all named destinations in a PDF. You need at least version 0.58 of Poppler.

$ pdfinfo -dests input.pdf
Page  Destination                 Name
   1 [ XYZ null null null      ] "F1"
   1 [ XYZ  122  458 null      ] "G1.1500945"
   1 [ XYZ   79  107 null      ] "G1.1500953"
   1 [ XYZ   79   81 null      ] "G1.1500954"
   1 [ XYZ null null null      ] "P.1"
   2 [ XYZ null null null      ] "L1"
   2 [ XYZ null null null      ] "P.2"
(...)
8

The pyPDF library can list anchors:

#!/usr/bin/env python
import sys
from pyPdf import PdfFileReader
def pdf_list_anchors(fh):
    reader = PdfFileReader(fh)
    destinations = reader.getNamedDestinations()
    for name in destinations:
        print name
pdf_list_anchors(open(sys.argv[1]))

That's good enough for the completion use case, but the anchors are listed in a random order. With only the stable interfaces of pyPdf 1.13, I can't find a way to list the anchors in order. I haven't tried pyPdf2 yet.

1
  • How would you address an anchor in dest.pdf from src.pdf? or from the command line?
    – albert
    Feb 10 at 17:48
2

(Also answered here: View anchors in a PDF document)

I had the same question, and eventually found a great answer via How can I visually inspect the structure of a PDF to reverse engineer it?

The answer is to use the Python package pdfminer.six. It's even one of their examples in the documentation! Cut-and-paste this code into a terminal:

pip install pdfminer.six
cat >extract.py <<EOF
import sys
import pdfminer.pdfparser, pdfminer.pdfdocument
with open(sys.argv[1], "rb") as f:
  parser = pdfminer.pdfparser.PDFParser(f)
  document = pdfminer.pdfdocument.PDFDocument(parser)
  for (level, title, dest, a, se) in document.get_outlines():
    print('  ' * level, title, dest or a, se)
EOF
python extract.py myInputFile.pdf

On my particular PDF, the output looks like this:

$ python extract.py ~/Desktop/p2786r3.pdf | head
   Abstract {'S': /'GoTo', 'D': b'section.1'} None
   Revision History {'S': /'GoTo', 'D': b'section.2'} None
     R3: October 2023 (midterm mailing)r3-october-2023-midterm-mailing {'S': /'GoTo', 'D': b'section*.2'} None
     R2: June 2023 (Varna meeting)r2-june-2023-varna-meeting {'S': /'GoTo', 'D': b'section*.3'} None
     R1: May 2023 (pre-Varna mailing)r1-may-2023-pre-varna-mailing {'S': /'GoTo', 'D': b'section*.4'} None
     R0: Issaquah 2023r0-issaquah-2023 {'S': /'GoTo', 'D': b'section*.5'} None
   Introduction {'S': /'GoTo', 'D': b'section.3'} None
   Motivating Use Cases {'S': /'GoTo', 'D': b'section.4'} None
     Efficient vector growth {'S': /'GoTo', 'D': b'subsection.4.1'} None
     Moving types without empty states {'S': /'GoTo', 'D': b'subsection.4.2'} None

and indeed navigating to p2786r3.pdf#subsection.4.2 in my browser opens the PDF at that particular section.

1

This prints them (twice) sorted by name and then by page position down the pdf. A large sample pdf containing named-destinations

#!/usr/bin/env python
import sys
from pyPdf import PdfFileReader
def pdf_get_anchors(fh):
    reader = PdfFileReader(fh)
    destinations = reader.getNamedDestinations()                #completely unsorted order, does not include pagenums
    L=list();
    for PageNum in range(1,reader.numPages+1) :
        ThisPage = reader.getPage(PageNum-1)
        PageTop = ThisPage['/MediaBox'][3]
        for name in destinations:
            ThisDest = destinations[name]
            ThisDestPage = ThisDest.page.getObject()
            if ThisDestPage == ThisPage:                        #have to do this to identify the pagenum
                DownPage = (PageTop - ThisDest.top) / PageTop   # calc fraction of page down
                Position = PageNum + DownPage                   # a sortable number down the whole pdf
                L.append((name, PageNum, Position));            # put everything in a sortable list         
    return L, len (destinations), reader.getNumPages()

def pdf_print_anchors ( L ) :
    for dest in L :
        name=dest[0]
        PageNum=dest[1]
        Position= round(dest[2]*100)/100
        print "%-8.2f % %s" % Position % name #ThisDest.title
        #print ThisDest.title, "       ",  PageNum,  round(Position*100)/100

HeaderLine="\n Page   Name\n"                     
L, NumDests, NumPages =pdf_get_anchors(open(sys.argv[1],'rb'))
print HeaderLine
L.sort(key=lambda dest: dest[0])                        #sort name order
pdf_print_anchors(L);     
print HeaderLine
L.sort(key=lambda dest: dest[2])                        #sort in order down the pdf
pdf_print_anchors(L);
print HeaderLine
print "Number of NamedDestinations: ", NumDests, "NumPages: ", NumPages
2
  • 2
    Your answer builds on the work of an earlier one. That's permitted, but you should acknowledge it.
    – JigglyNaga
    Jul 30, 2016 at 8:56
  • 2
    My initial reply simply bug fixed the original, I would have thought that rather self evident, and certainly did not purport to be anything.
    – Henry Crun
    Jul 30, 2016 at 9:02
1

There were a few changes in the pypdf library. Here you can find version of the original script and modified script working with python3 and pypdf:

#!/usr/bin/env python3
import sys
from pypdf import PdfReader

def pdf_get_anchors(fh):
    reader = PdfReader(fh)
    destinations = reader.named_destinations                    #completely unsorted order, does not include pagenums
    L=list();
    np =  len(reader.pages)
    for PageNum in range(1,np+1) :
        ThisPage = reader.pages[PageNum-1]
        PageTop = ThisPage['/MediaBox'][3]
        for name in destinations:
            ThisDest = destinations[name]
            ThisDestPage = ThisDest.page.get_object()
            if ThisDestPage == ThisPage:                        #have to do this to identify the pagenum
                DownPage = (PageTop - ThisDest.top) / PageTop   # calc fraction of page down
                Position = PageNum + DownPage                   # a sortable number down the whole pdf
                L.append((name, PageNum, Position));            # put everything in a sortable list         
    return L, len (destinations), np

def pdf_print_anchors ( L ) :
    for dest in L :
        name=dest[0]
        PageNum=dest[1]
        Position= round(dest[2]*100)/100
        print ("%8.2f % %s" % Position % name) #ThisDest.title

HeaderLine="\n    Page Name"                     
pdf_get_anchors(open(sys.argv[1],'rb'))
L, NumDests, NumPages =pdf_get_anchors(open(sys.argv[1],'rb'))
print (HeaderLine)
L.sort(key=lambda dest: dest[0])                        #sort name order
pdf_print_anchors(L);     
print (HeaderLine)
L.sort(key=lambda dest: dest[2])                        #sort in order down the pdf
pdf_print_anchors(L);
print ("Number of NamedDestinations: ", NumDests, "NumPages: ", NumPages)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .