[reportlab-users] pdf linking

Eric Johnson reportlab-users@reportlab.com
Tue, 30 Jul 2002 00:50:37 -0400


--98e8jtXdkpgskNou
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Fri, May 03, 2002 at 02:34:42PM +0100, Robin Becker wrote:
> .....
> linkFile is good and submit as is or after improvements. Please try and
> do context diffs as a mime attachment. 

I thought I really ought to send this in at last even though it needs
tidying up.  Honestly, I wrote it ages ago but moving house and holidays
seemed to delay a posting.

Attached is a context diff to the canvas.py file in the pdfgen directory
(based on the current.tgz snapshot from 24 July).  It adds one new public
method: linkFile which has a similar format to the linkURL method.
This method allows hyperlinks to other PDF files to be made -- the
original motivation was to produce a catalog of brochures on a CD. We had
thumbnails of the brochure frontcovers and by clicking on the thumbnail
the actual brochure opened automatically.

To show the way you can call the method and how it differs from linkURL
please have a look at demo.py.  This also defines a flowable to make
things easier to demonstrate.

More work is needed to define what scaling is used on the opened document
(this is a problem with linkAbsolute as well) and if I understood Name
dictionaries then I could allow named destinations in the external file --
currently it has to be an absolute page number.

Also a better fit into the paragraph markup of flowables would be good --
maybe the href tag from HTML???


Eric

--98e8jtXdkpgskNou
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="link.diff"

*** canvas.py	Tue Jul 30 00:32:42 2002
--- canvas.py.orig	Wed Jul 24 15:56:38 2002
***************
*** 673,681 ****
          return apply(self.linkAbsolute, (contents, destinationname, Rect, addtopage, name), kw)
  
  
!     def _linkObject(self, target, destination, rect, relative=0, thickness=0, color=None,
!     		dashArray=None, actionDict=None):
!         """Create a rectangular URL/File 'hotspot' in the given rectangle.
  
          if relative=1, this is in the current coord system, otherwise
          in absolute page space.
--- 673,680 ----
          return apply(self.linkAbsolute, (contents, destinationname, Rect, addtopage, name), kw)
  
  
!     def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None):
!         """Create a rectangular URL 'hotspot' in the given rectangle.
  
          if relative=1, this is in the current coord system, otherwise
          in absolute page space.
***************
*** 706,713 ****
          ann["Subtype"] = PDFName("Link")
          ann["Rect"] = PDFArray(rect) # the whole page for testing
  
!         # the action is a separate dictionary            
!         ann["A"] = actionDict(target, destination)
  
          # now for formatting stuff.
          if color:
--- 705,716 ----
          ann["Subtype"] = PDFName("Link")
          ann["Rect"] = PDFArray(rect) # the whole page for testing
  
!         # the action is a separate dictionary
!         A = PDFDictionary()
!         A["Type"] = PDFName("Action") # not needed?
!         A["S"] = PDFName("URI")
!         A["URI"] = PDFString(url)
!         ann["A"] = A
  
          # now for formatting stuff.
          if color:
***************
*** 721,762 ****
  
          self._addAnnotation(ann)
  
-     def _makeURLActionDict(self, url, destination):
-         from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFName, PDFString
-         A = PDFDictionary()
-         A["Type"] = PDFName("Action") # not needed?
-         A["S"] = PDFName("URI")
-         A["URI"] = PDFString(url)
-         return A
- 
-     def _makeGoToActionDict(self, filename, destination):
-         from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFName, PDFArray, PDFString
-         A = PDFDictionary()
-         A["Type"] = PDFName("Action") # not needed?
-         A["S"] = PDFName("GoToR")
- 	if filename:
- 	    A["F"] = PDFString(filename)
- 	if type(destination) == type(int(1)):
- 	    A["D"] = PDFArray([destination, "/Fit"])	#open page scaled to fit the page
-         else:
- 	    print "Not yet Supported"
- 	    #Need to be able to refer to links using a Name dictionary...
- 	    #A["D"] = PDFString(destination)
- 	#print filename, destination
- 	#print type(destination)
-         return A
- 
-     def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None):
-     	"""Better comment ..."""
-         return self._linkObject(url, None, rect, relative, thickness, color,
-         	dashArray, self._makeURLActionDict)
- 
-     def linkFile(self, filename, destination, rect, relative=0, thickness=0, color=None, dashArray=None):
-     	"""Better comment ..."""
-         return self._linkObject(filename, destination, rect, relative, thickness, color,
-         	dashArray, self._makeGoToActionDict)
- 
- 
      def _addAnnotation(self, annotation, name=None, addtopage=1):
          count = self._annotationCount = self._annotationCount+1
          if not name: name="NUMBER"+repr(count)
--- 724,729 ----
***************
*** 1415,1418 ****
  
  
  if __name__ == '__main__':
!     print 'For test scripts, look in reportlab/test'
--- 1382,1385 ----
  
  
  if __name__ == '__main__':
!     print 'For test scripts, look in reportlab/test'
\ No newline at end of file

--98e8jtXdkpgskNou
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="demo.py"

#! /usr/bin/env python
#
# Simple demonstration script to show how to use the linkFile method.
# It also uses linkURI to show the difference in behaviour.
#
# joh	2002-05-19	Original
###################################################

from reportlab.platypus import Macro, Paragraph, PageBreak, Spacer
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4

import reportlab as rl

import os

##############################################################################
class LinkParagraph(Paragraph):
    """ Simple flowable which allows a paragraph of text to be a link.

        The complete paragraph is the active area and can have a rectangle
	drawn around it with a defined line thickness.
	The link can refer to a bookmark in the current file, a URI link or
	to a page in another pdf document.

	destination	target	   isFile	Type of link
	===========	======	   =====	============
	page number	filename   1		link to external PDF file
	3		"tst.pdf"  1		Link to page 3 of tst.pdf

	-		uri	   0		Link to URI document
	-		"http://python.org" 0	Link to Python website

	bookmark	-	   -		Link to current pdf

	As can be seen the interface of this flowable is very limited. It
	is really just to show how to use the different links.
    """

    def __init__(self, text, style, destination='', target=None, isFile=1,
		 thickness=0, color=None):
	Paragraph.__init__(self, text, style)
	self._destination = destination
	self._target = target
	self._isFile = isFile
	self._thickness = thickness
	self._color = color

    def wrap(self, availWidth, availHeight):
	return Paragraph.wrap(self, availWidth, availHeight)

    def draw(self):
	Paragraph.draw(self)
	#Try to determine the width for the link from some low-level Platypus
	#data structures.
	x = self.blPara.lines[0]
	if hasattr(x, 'extraSpace'):
	    extraSpace =getattr( x, 'extraSpace')
	else:
	    extraSpace = self.blPara.lines[0][0]
	width = self.width - extraSpace
	#print "Width: %s" % width
	#we now know the size of the active link region
	box = (0, 0, width, self.height)

	if self._target != None:
	    if self._isFile:
		self.canv.linkFile(self._target, self._destination, box,
			relative=1, thickness=self._thickness,
			color=self._color)
	    else:
		self.canv.linkURL(self._target, box, relative=1,
			thickness=self._thickness, color=self._color)
	elif self._destination:
	    self.canv.linkRect("", self._destination, box,
		 #Border='[0 0 0]'
		)


##############################################################################

#define the colours of the links and documents
colours = [
    'green',
    'blue',
    ]

#############################################################################
#Construct the name of the temporary file based on it's colour
#Used so the names are only configured in one place
#############################################################################
def makeDocName(colour):
    """Construct the temporary document name from it's colour"""
    return "tmp_%s.pdf" % colour
    

#############################################################################
#Construct an n-page document with the given name. These will be
#used as the target link for the linkFile method 
#############################################################################
def makeDoc(colour, masterName, n=5):
    """Construct an n-page document called name"""
    name = makeDocName(colour)
    doc = SimpleDocTemplate(name, pagesize=A4)
    story = []
    normal = ParagraphStyle('S1', fontName='Helvetica', fontSize=16, leading=18)

    for page in range(1, n+1):
	story.extend( [
	    Paragraph('File: <font color="%s">%s</font>' % (colour, name),
			normal),
	    Paragraph("Page: %s" % page, normal),
	    Macro('canvas.bookmarkPage("page.%s")' % page),
	    #Following uses string links and is not yet implemented
	    LinkParagraph('<para fg="blue">Jump to Page 1</para>', normal,
			'page.1'),
	    #Following uses integer links to explicit page number
	    #Note page numbering starts at 0
	    LinkParagraph('<para fg="blue">Jump to Page 2</para>', normal,
			 1, target=name),
	    #Following is a link to file, defaulting to first page
	    LinkParagraph('<para fg="darkred">Return to MasterDocument</para>',
	    		normal, target=masterName),
	    PageBreak()
	    ])
    doc.build(story)



#############################################################################
#Construct the master document which links to the other documents
#############################################################################
def makeMasterDocument(name):
    """Make the Master Document"""
    doc = SimpleDocTemplate(name, pagesize=A4)
    styles = getSampleStyleSheet()
    
    story = [
	Paragraph("<b>Example of PDF Document Links</b>", styles['Title']),
	]


    ######################################
    # Links to URI
    ######################################
    story.extend([
	Paragraph( 'URI Link', styles['Heading2'] ),
	Paragraph("""These links define a URI specifying the remote document.
		The target document is opened using the Web browser specified
		in Acrobat Reader's <i>Preferences/Weblink</i> dialog. """,
	     styles['BodyText'] ),
	Paragraph("""Each link may be surrounded by a bounding box of defined 
		line-thickness and colour""", styles['BodyText'] )
	])

    tableData = [ ]

    i = 0
    for c in colours:
	for uriType in ['http', 'file']:
	    urlName = "%s:/%s/%s" % (uriType, os.getcwd(), makeDocName(c))
	    row = [
		LinkParagraph("%s link to <font color=%s>%s</font> document" % 
			(uriType,c,c), styles['Normal'], target=urlName,
			isFile=0, thickness=i, color=getattr(rl.lib.colors, c) ),
		urlName
		]  
	    i += 1
	    tableData.append(row)

    t = Table(tableData, colWidths=[6*cm, 8*cm])
    t.setStyle( TableStyle( [
	('FONTNAME', (1,0), (1,-1), 'Courier')
	] )) 

    story.append(t)

    ######################################
    # Links to External File
    ######################################
    story.extend([
	Spacer(0, 0.5*cm),
	Paragraph( 'External PDF Link', styles['Heading2'] ),
	Paragraph("""These links define a link to a page in an external PDF
		file. The target document is opened using the Acrobat Reader.
		The target document is opened in the same window or a new
		window dependent on the settings in Acrobat Reader's
		<i>Preferences/General</i> dialog. """,
	    styles['BodyText'] ),
	Paragraph("""Each link may be surrounded by a bounding box of defined 
		line-thickness and colour""", styles['BodyText'] )
	])

    tableData = [ ]

    for i in range(4):
 	row = [
	    LinkParagraph("Link to <font color=%s>%s</font> document, Page: %d"
		 % (c,c,i), styles['Normal'], target=makeDocName(c),
		 destination=i, thickness=i, color=getattr(rl.lib.colors, c) )
	    for c in colours
	    ]
	tableData.append(row)

    t = Table(tableData, colWidths=[6*cm, 8*cm])
    story.append(t)

    ######################################
    # Internal Link
    ######################################
    story.extend([
	Spacer(0, 0.5*cm),
	Paragraph( 'Internal PDF Link', styles['Heading2'] ),
	Macro('canvas.bookmarkPage("thisPage")'),
	Paragraph("""These links are to a bookmark in the current PDF file.
		They are as defined in the <i>Userguide</i> and the
		destination is established as a bookmark.""",
	    styles['BodyText'] ),
	Paragraph("""Each link may be surrounded by a bounding box of defined 
		line-thickness and colour.""", styles['BodyText'] )
	])

    tableData = []

    for i in range(1):
	row = [
	    LinkParagraph("Link to this page", styles['BodyText'],
		    destination="thisPage", thickness=0),
	    LinkParagraph("Link to next page", styles['BodyText'],
		    destination="nextPage"),
	    ]  
	tableData.append(row)

    t = Table(tableData, colWidths=[6*cm, 8*cm])

    story.append(t)

    ######################################
    #Second page for link destinations
    ######################################
    story.append( PageBreak() )
    story.append( Macro('canvas.bookmarkPage("nextPage")') )
    story.append( Paragraph( 'This is the second page and the target of links',
			 styles['Normal']) )

    doc.build(story)


#############################################################################
# Main
#############################################################################
if __name__ == "__main__":
#Make the master document
    masterName = 'linkDemo.pdf'
    makeMasterDocument(masterName)

    #Make the pdfs which we want to make references to ...
    for c in colours:
	makeDoc(c, masterName, n=4)



--98e8jtXdkpgskNou--