1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990 |
- #Copyright ReportLab Europe Ltd. 2000-2017
- #see license.txt for license details
- __version__='3.3.0'
- __doc__="""
- The Canvas object is the primary interface for creating PDF files. See
- doc/reportlab-userguide.pdf for copious examples.
- """
- __all__ = ['Canvas']
- ENABLE_TRACKING = 1 # turn this off to do profile testing w/o tracking
- import os
- import sys
- import re
- import hashlib
- from string import digits
- import tempfile
- from math import sin, cos, tan, pi, ceil
- from reportlab import rl_config
- from reportlab.pdfbase import pdfutils
- from reportlab.pdfbase import pdfdoc
- from reportlab.pdfbase import pdfmetrics
- from reportlab.pdfgen import pdfgeom, pathobject
- from reportlab.pdfgen.textobject import PDFTextObject, _PDFColorSetter
- from reportlab.lib.colors import black, _chooseEnforceColorSpace, Color, CMYKColor, toColor
- from reportlab.lib.utils import import_zlib, ImageReader, isSeq, isStr, isUnicode, _digester, asUnicode
- from reportlab.lib.rl_accel import fp_str, escapePDF
- from reportlab.lib.boxstuff import aspectRatioFix
- digitPat = re.compile(r'\d') #used in decimal alignment
- zlib = import_zlib()
- # Robert Kern
- # Constants for closing paths.
- # May be useful if one changes 'arc' and 'rect' to take a
- # default argument that tells how to close the path.
- # That way we can draw filled shapes.
- FILL_EVEN_ODD = 0
- FILL_NON_ZERO = 1
- #this is used by path-closing routines.
- #map stroke, fill, fillmode -> operator
- # fillmode: 1 = non-Zero (obviously), 0 = evenOdd
- PATH_OPS = {(0, 0, FILL_EVEN_ODD) : 'n', #no op
- (0, 0, FILL_NON_ZERO) : 'n', #no op
- (1, 0, FILL_EVEN_ODD) : 'S', #stroke only
- (1, 0, FILL_NON_ZERO) : 'S', #stroke only
- (0, 1, FILL_EVEN_ODD) : 'f*', #Fill only
- (0, 1, FILL_NON_ZERO) : 'f', #Fill only
- (1, 1, FILL_EVEN_ODD) : 'B*', #Stroke and Fill
- (1, 1, FILL_NON_ZERO) : 'B', #Stroke and Fill
- }
- def _annFormat(D,color,thickness,dashArray,hradius=0,vradius=0):
- from reportlab.pdfbase.pdfdoc import PDFArray, PDFDictionary
- if color and 'C' not in D:
- D["C"] = PDFArray([color.red, color.green, color.blue])
- if 'Border' not in D:
- border = [hradius,vradius,thickness or 0]
- if dashArray:
- border.append(PDFArray(dashArray))
- D["Border"] = PDFArray(border)
- # BS = PDFDictionary()
- # bss = 'S'
- # if dashArray:
- # BS['D'] = PDFArray(dashArray)
- # bss = 'D'
- # BS['W'] = thickness or 0
- # BS['S'] = bss
- # D['BS'] = BS
- # helpers to guess color space for gradients
- def _normalizeColor(aColor):
- if isinstance(aColor, CMYKColor):
- d = aColor.density
- return "DeviceCMYK", tuple(c*d for c in aColor.cmyk())
- elif isinstance(aColor, Color):
- return "DeviceRGB", aColor.rgb()
- elif isinstance(aColor, (tuple, list)):
- l = len(aColor)
- if l == 3:
- return "DeviceRGB", aColor
- elif l == 4:
- return "DeviceCMYK", aColor
- elif isinstance(aColor, str):
- return _normalizeColor(toColor(aColor))
- raise ValueError("Unknown color %r" % aColor)
- def _normalizeColors(colors):
- space = None
- outcolors = []
- for aColor in colors:
- nspace, outcolor = _normalizeColor(aColor)
- if space is not None and space != nspace:
- raise ValueError("Mismatch in color spaces: %s and %s" % (space, nspace))
- space = nspace
- outcolors.append(outcolor)
- return space, outcolors
- def _buildColorFunction(colors, positions):
- from reportlab.pdfbase.pdfdoc import PDFExponentialFunction, PDFStitchingFunction
- if positions is not None and len(positions) != len(colors):
- raise ValueError("need to have the same number of colors and positions")
- # simplified functions for edge cases
- if len(colors) == 1:
- # for completeness
- return PDFExponentialFunction(N=1, C0=colors[0], C1=colors[0])
- if len(colors) == 2:
- if positions is None or (positions[0] == 0 and positions[1] == 1):
- return PDFExponentialFunction(N=1, C0=colors[0], C1=colors[1])
- # equally distribute if positions not specified
- if positions is None:
- nc = len(colors)
- positions = [float(x)/(nc-1) for x in range(nc)]
- else:
- # sort positions and colors in increasing order
- poscolors = list(zip(positions, colors))
- poscolors.sort(key=lambda x: x[0])
- # add endpoint positions if not already present
- if poscolors[0][0] != 0:
- poscolors.insert(0, (0.0, poscolors[0][1]))
- if poscolors[-1][0] != 1:
- poscolors.append((1.0, poscolors[-1][1]))
- positions, colors = list(zip(*poscolors)) # unzip
- # build stitching function
- functions = []
- bounds = [pos for pos in positions[1:-1]]
- encode = []
- lastcolor = colors[0]
- for color in colors[1:]:
- functions.append(PDFExponentialFunction(N=1, C0=lastcolor, C1=color))
- lastcolor = color
- encode.append(0.0)
- encode.append(1.0)
- return PDFStitchingFunction(functions, bounds, encode, Domain="[0.0 1.0]")
- class ExtGState:
- defaults = dict(
- CA=1,
- ca=1,
- OP=False,
- op=False,
- OPM=0,
- BM='Normal',
- )
- allowed = dict(
- BM = {
- 'Normal', 'Multiply', 'Screen', 'Overlay',
- 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn',
- 'HardLight', 'SoftLight', 'Difference', 'Exclusion',
- 'Hue', 'Saturation', 'Color', 'Luminosity',
- },
- )
- pdfNameValues = {'BM'}
- @staticmethod
- def _boolTransform(v):
- return str(v).lower()
- @staticmethod
- def _identityTransform(v):
- return v
- @staticmethod
- def _pdfNameTransform(v):
- return '/'+v
- def __init__(self):
- self._d = {}
- self._c = {}
- def set(self,canv,a,v):
- d = self.defaults[a]
- if isinstance(d,bool):
- v=bool(v)
- vTransform = self._boolTransform
- elif a in self.pdfNameValues:
- if v not in self.allowed[a]:
- raise ValueError('ExtGstate[%r] = %r not in allowed values %r' % (
- a,v,self.allowed[a]))
- vTransform = self._pdfNameTransform
- else:
- vTransform = self._identityTransform
- if v!=self._d.get(a,d) or (a=='op' and self.getValue('OP')!=d):
- self._d[a] = v
- t = a,vTransform(v)
- if t in self._c:
- name = self._c[t]
- else:
- name = 'gRLs'+str(len(self._c))
- self._c[t] = name
- canv._code.append('/%s gs' % name)
- def getValue(self,a):
- return self._d.get(a,self.defaults[a])
- def getState(self):
- S = {}
- for t,name in self._c.items():
- S[name] = pdfdoc.PDFDictionary(dict((t,)))
- return S and pdfdoc.PDFDictionary(S) or None
- def pushCopy(self):
- '''the states must be shared across push/pop, but the values not'''
- x = self.__class__()
- x._d = self._d.copy()
- x._c = self._c
- return x
- class Canvas(_PDFColorSetter):
- """This class is the programmer's interface to the PDF file format. Methods
- are (or will be) provided here to do just about everything PDF can do.
- The underlying model to the canvas concept is that of a graphics state machine
- that at any given point in time has a current font, fill color (for figure
- interiors), stroke color (for figure borders), line width and geometric transform, among
- many other characteristics.
- Canvas methods generally either draw something (like canvas.line) using the
- current state of the canvas or change some component of the canvas
- state (like canvas.setFont). The current state can be saved and restored
- using the saveState/restoreState methods.
- Objects are "painted" in the order they are drawn so if, for example
- two rectangles overlap the last draw will appear "on top". PDF form
- objects (supported here) are used to draw complex drawings only once,
- for possible repeated use.
- There are other features of canvas which are not visible when printed,
- such as outlines and bookmarks which are used for navigating a document
- in a viewer.
- Here is a very silly example usage which generates a Hello World pdf document.
- Example::
-
- from reportlab.pdfgen import canvas
- c = canvas.Canvas("hello.pdf")
- from reportlab.lib.units import inch
- # move the origin up and to the left
- c.translate(inch,inch)
- # define a large font
- c.setFont("Helvetica", 80)
- # choose some colors
- c.setStrokeColorRGB(0.2,0.5,0.3)
- c.setFillColorRGB(1,0,1)
- # draw a rectangle
- c.rect(inch,inch,6*inch,9*inch, fill=1)
- # make text go straight up
- c.rotate(90)
- # change color
- c.setFillColorRGB(0,0,0.77)
- # say hello (note after rotate the y coord needs to be negative!)
- c.drawString(3*inch, -3*inch, "Hello World")
- c.showPage()
- c.save()
- """
- def __init__(self,filename,
- pagesize=None,
- bottomup = 1,
- pageCompression=None,
- invariant = None,
- verbosity=0,
- encrypt=None,
- cropMarks=None,
- pdfVersion=None,
- enforceColorSpace=None,
- initialFontName=None,
- initialFontSize=None,
- initialLeading=None,
- cropBox=None,
- artBox=None,
- trimBox=None,
- bleedBox=None,
- lang=None,
- ):
- """Create a canvas of a given size. etc.
- You may pass a file-like object to filename as an alternative to
- a string.
- For more information about the encrypt parameter refer to the setEncrypt method.
-
- Most of the attributes are private - we will use set/get methods
- as the preferred interface. Default page size is A4.
- cropMarks may be True/False or an object with parameters borderWidth, markColor, markWidth
- and markLength
-
- if enforceColorSpace is in ('cmyk', 'rgb', 'sep','sep_black','sep_cmyk') then one of
- the standard _PDFColorSetter callables will be used to enforce appropriate color settings.
- If it is a callable then that will be used.
- """
- if pagesize is None: pagesize = rl_config.defaultPageSize
- if invariant is None: invariant = rl_config.invariant
- self._initialFontName = initialFontName if initialFontName else rl_config.canvas_basefontname
- self._initialFontSize = initialFontSize if initialFontSize is not None else 12
- self._initialLeading = initialLeading if initialLeading is not None else self._initialFontSize*1.2
- self._filename = filename
- self._doc = pdfdoc.PDFDocument(compression=pageCompression,
- invariant=invariant, filename=filename,
- pdfVersion=pdfVersion or pdfdoc.PDF_VERSION_DEFAULT,
- lang=lang
- )
- self._enforceColorSpace = _chooseEnforceColorSpace(enforceColorSpace)
- #this only controls whether it prints 'saved ...' - 0 disables
- self._verbosity = verbosity
- #this is called each time a page is output if non-null
- self._onPage = None
- self._cropMarks = cropMarks
- self._pagesize = pagesize
- self._hanging_pagesize = None
- self._pageRotation = 0
- #self._currentPageHasImages = 0
- self._pageTransition = None
- self._pageDuration = None
- self._destinations = {} # dictionary of destinations for cross indexing.
- self.setPageCompression(pageCompression)
- self._pageNumber = 1 # keep a count
- # when we create a form we need to save operations not in the form
- self._codeStack = []
- self._restartAccumulators() # restart all accumulation state (generalized, arw)
- self._annotationCount = 0
- self._outlines = [] # list for a name tree
- self._psCommandsBeforePage = [] #for postscript tray/font commands
- self._psCommandsAfterPage = [] #for postscript tray/font commands
- #PostScript has the origin at bottom left. It is easy to achieve a top-
- #down coord system by translating to the top of the page and setting y
- #scale to -1, but then text is inverted. So self.bottomup is used
- #to also set the text matrix accordingly. You can now choose your
- #drawing coordinates.
- self.bottomup = bottomup
- self.imageCaching = rl_config.defaultImageCaching
- self._cropBox = cropBox #we don't do semantics for these at all
- self._artBox = artBox
- self._trimBox = trimBox
- self._bleedBox = bleedBox
- self.init_graphics_state()
- self._make_preamble()
- self.state_stack = []
- self.setEncrypt(encrypt)
- def setEncrypt(self, encrypt):
- '''
- Set the encryption used for the pdf generated by this canvas.
- If encrypt is a string object, it is used as the user password for the pdf.
- If encrypt is an instance of reportlab.lib.pdfencrypt.StandardEncryption, this object is
- used to encrypt the pdf. This allows more finegrained control over the encryption settings.
- '''
- if encrypt:
- from reportlab.lib import pdfencrypt
- if isStr(encrypt): #encrypt is the password itself
- if isUnicode(encrypt):
- encrypt = encrypt.encode('utf-8')
- encrypt = pdfencrypt.StandardEncryption(encrypt) #now it's the encrypt object
- encrypt.setAllPermissions(1)
- elif not isinstance(encrypt, pdfencrypt.StandardEncryption):
- raise TypeError('Expected string or instance of reportlab.lib.pdfencrypt.StandardEncryption as encrypt parameter but got %r' % encrypt)
- self._doc.encrypt = encrypt
- else:
- try:
- del self._doc.encrypt
- except AttributeError:
- pass
- def init_graphics_state(self):
- #initial graphics state, never modify any of these in place
- self._x = 0
- self._y = 0
- self._fontname = self._initialFontName
- self._fontsize = self._initialFontSize
- self._textMode = 0 #track if between BT/ET
- self._leading = self._initialLeading
- self._currentMatrix = (1., 0., 0., 1., 0., 0.)
- self._fillMode = FILL_EVEN_ODD
- #text state
- self._charSpace = 0
- self._wordSpace = 0
- self._horizScale = 100
- self._textRenderMode = 0
- self._rise = 0
- self._textLineMatrix = (1., 0., 0., 1., 0., 0.)
- self._textMatrix = (1., 0., 0., 1., 0., 0.)
- # line drawing
- self._lineCap = 0
- self._lineJoin = 0
- self._lineDash = None #not done
- self._lineWidth = 1
- self._mitreLimit = 0
- self._fillColorObj = self._strokeColorObj = rl_config.canvas_baseColor or (0,0,0)
- self._extgstate = ExtGState()
- def push_state_stack(self):
- state = {}
- d = self.__dict__
- for name in self.STATE_ATTRIBUTES:
- state[name] = d[name] #getattr(self, name)
- self.state_stack.append(state)
- self._extgstate = self._extgstate.pushCopy()
- def pop_state_stack(self):
- self.__dict__.update(self.state_stack.pop())
- STATE_ATTRIBUTES = """_x _y _fontname _fontsize _textMode _leading _currentMatrix _fillMode
- _charSpace _wordSpace _horizScale _textRenderMode _rise _textLineMatrix
- _textMatrix _lineCap _lineJoin _lineDash _lineWidth _mitreLimit _fillColorObj
- _strokeColorObj _extgstate""".split()
- STATE_RANGE = list(range(len(STATE_ATTRIBUTES)))
- #self._addStandardFonts()
- def _make_preamble(self):
- P = [].append
- if self.bottomup:
- P('1 0 0 1 0 0 cm')
- else:
- P('1 0 0 -1 0 %s cm' % fp_str(self._pagesize[1]))
- C = self._code
- n = len(C)
- if self._fillColorObj != (0,0,0):
- self.setFillColor(self._fillColorObj)
- if self._strokeColorObj != (0,0,0):
- self.setStrokeColor(self._strokeColorObj)
- P(' '.join(C[n:]))
- del C[n:]
- font = pdfmetrics.getFont(self._fontname)
- if not font._dynamicFont:
- #set an initial font
- if font.face.builtIn or not getattr(self,'_drawTextAsPath',False):
- P('BT %s 12 Tf 14.4 TL ET' % self._doc.getInternalFontName(self._fontname))
- self._preamble = ' '.join(P.__self__)
- def _escape(self, s):
- return escapePDF(s)
- #info functions - non-standard
- def setAuthor(self, author):
- """identify the author for invisible embedding inside the PDF document.
- the author annotation will appear in the the text of the file but will
- not automatically be seen when the document is viewed, but is visible
- in document properties etc etc."""
- self._doc.setAuthor(author)
- def setDateFormatter(self, dateFormatter):
- """accepts a func(yyyy,mm,dd,hh,m,s) used to create embedded formatted date"""
- self._doc.setDateFormatter(dateFormatter)
- def addOutlineEntry(self, title, key, level=0, closed=None):
- """Adds a new entry to the outline at given level. If LEVEL not specified,
- entry goes at the top level. If level specified, it must be
- no more than 1 greater than the outline level in the last call.
- The key must be the (unique) name of a bookmark.
- the title is the (non-unique) name to be displayed for the entry.
- If closed is set then the entry should show no subsections by default
- when displayed.
- Example::
-
- c.addOutlineEntry("first section", "section1")
- c.addOutlineEntry("introduction", "s1s1", 1, closed=1)
- c.addOutlineEntry("body", "s1s2", 1)
- c.addOutlineEntry("detail1", "s1s2s1", 2)
- c.addOutlineEntry("detail2", "s1s2s2", 2)
- c.addOutlineEntry("conclusion", "s1s3", 1)
- c.addOutlineEntry("further reading", "s1s3s1", 2)
- c.addOutlineEntry("second section", "section1")
- c.addOutlineEntry("introduction", "s2s1", 1)
- c.addOutlineEntry("body", "s2s2", 1, closed=1)
- c.addOutlineEntry("detail1", "s2s2s1", 2)
- c.addOutlineEntry("detail2", "s2s2s2", 2)
- c.addOutlineEntry("conclusion", "s2s3", 1)
- c.addOutlineEntry("further reading", "s2s3s1", 2)
- generated outline looks like::
-
- - first section
- |- introduction
- |- body
- | |- detail1
- | |- detail2
- |- conclusion
- | |- further reading
- - second section
- |- introduction
- |+ body
- |- conclusion
- | |- further reading
- Note that the second "body" is closed.
- Note that you can jump from level 5 to level 3 but not
- from 3 to 5: instead you need to provide all intervening
- levels going down (4 in this case). Note that titles can
- collide but keys cannot.
- """
- #to be completed
- #self._outlines.append(title)
- self._doc.outline.addOutlineEntry(key, level, title, closed=closed)
- def setOutlineNames0(self, *nametree): # keep this for now (?)
- """nametree should can be a recursive tree like so::
-
- c.setOutlineNames(
- "chapter1dest",
- ("chapter2dest",
- ["chapter2section1dest",
- "chapter2section2dest",
- "chapter2conclusiondest"]
- ), # end of chapter2 description
- "chapter3dest",
- ("chapter4dest", ["c4s1", "c4s2"])
- )
-
- each of the string names inside must be bound to a bookmark
- before the document is generated.
- """
- self._doc.outline.setNames(*((self,)+nametree))
- def setTitle(self, title):
- """write a title into the PDF file that won't automatically display
- in the document itself."""
- self._doc.setTitle(title)
- def setSubject(self, subject):
- """write a subject into the PDF file that won't automatically display
- in the document itself."""
- self._doc.setSubject(subject)
- def setCreator(self, creator):
- """write a creator into the PDF file that won't automatically display
- in the document itself. This should be used to name the original app
- which is passing data into ReportLab, if you wish to name it."""
- self._doc.setCreator(creator)
- def setProducer(self, producer):
- """change the default producer value"""
- self._doc.setProducer(producer)
- def setKeywords(self, keywords):
- """write a list of keywords into the PDF file which shows in document properties.
- Either submit a single string or a list/tuple"""
- if isinstance(keywords,(list,tuple)):
- keywords = ', '.join(keywords)
- self._doc.setKeywords(keywords)
- def pageHasData(self):
- "Info function - app can call it after showPage to see if it needs a save"
- return len(self._code) == 0
- def showOutline(self):
- """Specify that Acrobat Reader should start with the outline tree visible.
- showFullScreen() and showOutline() conflict; the one called last
- wins."""
- self._doc._catalog.showOutline()
- def showFullScreen0(self):
- """Specify that Acrobat Reader should start in full screen mode.
- showFullScreen() and showOutline() conflict; the one called last
- wins."""
- self._doc._catalog.showFullScreen()
- def _setStrokeAlpha(self,v):
- """
- Define the transparency/opacity of strokes. 0 is fully
- transparent, 1 is fully opaque.
- Note that calling this function will cause a version 1.4 PDF
- to be generated (rather than 1.3).
- """
- self._doc.ensureMinPdfVersion('transparency')
- self._extgstate.set(self,'CA',v)
- def _setFillAlpha(self,v):
- """
- Define the transparency/opacity of non-strokes. 0 is fully
- transparent, 1 is fully opaque.
- Note that calling this function will cause a version 1.4 PDF
- to be generated (rather than 1.3).
- """
- self._doc.ensureMinPdfVersion('transparency')
- self._extgstate.set(self,'ca',v)
- def _setStrokeOverprint(self,v):
- self._extgstate.set(self,'OP',v)
- def _setFillOverprint(self,v):
- self._extgstate.set(self,'op',v)
- def _setOverprintMask(self,v):
- self._extgstate.set(self,'OPM',v and 1 or 0)
- def setBlendMode(self, v):
- self._extgstate.set(self,'BM',v)
- def _getCmShift(self):
- cM = self._cropMarks
- if cM:
- bleedW = max(0,getattr(cM,'bleedWidth',0))
- bw = max(0,getattr(cM,'borderWidth',36))
- if bleedW:
- bw -= bleedW
- return bw
- def showPage(self):
- """Close the current page and possibly start on a new page."""
- # ensure a space at the end of the stream - Acrobat does
- # not mind, but Ghostscript dislikes 'Qendstream' even if
- # the length marker finishes after 'Q'
- pageWidth = self._pagesize[0]
- pageHeight = self._pagesize[1]
- cM = self._cropMarks
- code = self._code
- if cM:
- bw = max(0,getattr(cM,'borderWidth',36))
- if bw:
- markLast = getattr(cM,'markLast',1)
- ml = min(bw,max(0,getattr(cM,'markLength',18)))
- mw = getattr(cM,'markWidth',0.5)
- mc = getattr(cM,'markColor',black)
- mg = 2*bw-ml
- cx0 = len(code)
- if ml and mc:
- self.saveState()
- self.setStrokeColor(mc)
- self.setLineWidth(mw)
- self.lines([
- (bw,0,bw,ml),
- (pageWidth+bw,0,pageWidth+bw,ml),
- (bw,pageHeight+mg,bw,pageHeight+2*bw),
- (pageWidth+bw,pageHeight+mg,pageWidth+bw,pageHeight+2*bw),
- (0,bw,ml,bw),
- (pageWidth+mg,bw,pageWidth+2*bw,bw),
- (0,pageHeight+bw,ml,pageHeight+bw),
- (pageWidth+mg,pageHeight+bw,pageWidth+2*bw,pageHeight+bw),
- ])
- self.restoreState()
- if markLast:
- #if the marks are to be drawn after the content
- #save the code we just drew for later use
- L = code[cx0:]
- del code[cx0:]
- cx0 = len(code)
- bleedW = max(0,getattr(cM,'bleedWidth',0))
- self.saveState()
- self.translate(bw-bleedW,bw-bleedW)
- if bleedW:
- #scale everything
- self.scale(1+(2.0*bleedW)/pageWidth,1+(2.0*bleedW)/pageHeight)
- #move our translation/expansion code to the beginning
- C = code[cx0:]
- del code[cx0:]
- code[0:0] = C
- self.restoreState()
- if markLast:
- code.extend(L)
- pageWidth = 2*bw + pageWidth
- pageHeight = 2*bw + pageHeight
- code.append(' ')
- page = pdfdoc.PDFPage()
- page.pagewidth = pageWidth
- page.pageheight = pageHeight
- page.Rotate = self._pageRotation
- page.hasImages = self._currentPageHasImages
- page.setPageTransition(self._pageTransition)
- page.setCompression(self._pageCompression)
- for box in ('crop','art','bleed','trim'):
- size = getattr(self,'_%sBox'%box,None)
- if size:
- setattr(page,box.capitalize()+'Box',pdfdoc.PDFArray(size))
- if self._pageDuration is not None:
- page.Dur = self._pageDuration
- strm = self._psCommandsBeforePage + [self._preamble] + code + self._psCommandsAfterPage
- page.setStream(strm)
- self._setColorSpace(page)
- self._setExtGState(page)
- self._setXObjects(page)
- self._setShadingUsed(page)
- self._setAnnotations(page)
- self._doc.addPage(page)
- if self._onPage: self._onPage(self._pageNumber)
- self._startPage()
- def _startPage(self):
- #now get ready for the next one
- if self._hanging_pagesize:
- self.setPageSize(self._hanging_pagesize)
- self._hanging_pagesize = None
- self._pageNumber += 1
- self._restartAccumulators()
- self.init_graphics_state()
- self.state_stack = []
- def setPageCallBack(self, func):
- """func(pageNum) will be called on each page end.
- This is mainly a hook for progress monitoring.
- Call setPageCallback(None) to clear a callback."""
- self._onPage = func
- def _setAnnotations(self,page):
- page.Annots = self._annotationrefs
- def _setColorSpace(self,obj):
- obj._colorsUsed = self._colorsUsed
- def _setShadingUsed(self, page):
- page._shadingUsed = self._shadingUsed
- def _setXObjects(self, thing):
- """for pages and forms, define the XObject dictionary for resources, if needed"""
- forms = self._formsinuse
- if forms:
- xobjectsdict = self._doc.xobjDict(forms)
- thing.XObjects = xobjectsdict
- else:
- thing.XObjects = None
- def _bookmarkReference(self, name):
- """get a reference to a (possibly undefined, possibly unbound) bookmark"""
- d = self._destinations
- try:
- return d[name]
- except:
- result = d[name] = pdfdoc.Destination(name) # newly defined, unbound
- return result
- def bookmarkPage(self, key,
- fit="Fit",
- left=None,
- top=None,
- bottom=None,
- right=None,
- zoom=None
- ):
- """
- This creates a bookmark to the current page which can
- be referred to with the given key elsewhere.
- PDF offers very fine grained control over how Acrobat
- reader is zoomed when people link to this. The default
- is to keep the user's current zoom settings. the last
- arguments may or may not be needed depending on the
- choice of 'fitType'.
- Fit types and the other arguments they use are:
-
- - XYZ left top zoom - fine grained control. null
- or zero for any of the parameters means 'leave
- as is', so "0,0,0" will keep the reader's settings.
- NB. Adobe Reader appears to prefer "null" to 0's.
- - Fit - entire page fits in window
- - FitH top - top coord at top of window, width scaled
- to fit.
- - FitV left - left coord at left of window, height
- scaled to fit
- - FitR left bottom right top - scale window to fit
- the specified rectangle
- (question: do we support /FitB, FitBH and /FitBV
- which are hangovers from version 1.1 / Acrobat 3.0?)"""
- dest = self._bookmarkReference(key)
- self._doc.inPage() # try to enable page-only features
- pageref = self._doc.thisPageRef()
- #None = "null" for PDF
- if left is None:
- left = "null"
- if top is None:
- top = "null"
- if bottom is None:
- bottom = "null"
- if right is None:
- right = "null"
- if zoom is None:
- zoom = "null"
- if fit == "XYZ":
- dest.xyz(left,top,zoom)
- elif fit == "Fit":
- dest.fit()
- elif fit == "FitH":
- dest.fith(top)
- elif fit == "FitV":
- dest.fitv(left)
- elif fit == "FitR":
- dest.fitr(left,bottom,right,top)
- #Do we need these (version 1.1 / Acrobat 3 versions)?
- elif fit == "FitB":
- dest.fitb()
- elif fit == "FitBH":
- dest.fitbh(top)
- elif fit == "FitBV":
- dest.fitbv(left)
- else:
- raise ValueError("Unknown Fit type %s" % ascii(fit))
- dest.setPage(pageref)
- return dest
- def bookmarkHorizontalAbsolute(self, key, top, left=0, fit='XYZ', **kw):
- """Bind a bookmark (destination) to the current page at a horizontal position.
- Note that the yhorizontal of the book mark is with respect to the default
- user space (where the origin is at the lower left corner of the page)
- and completely ignores any transform (translation, scale, skew, rotation,
- etcetera) in effect for the current graphics state. The programmer is
- responsible for making sure the bookmark matches an appropriate item on
- the page."""
- #This method should probably be deprecated since it is just a sub-set of bookmarkPage
- return self.bookmarkPage(key, fit=fit, top=top, left=left, zoom=0)
- def bookmarkHorizontal(self, key, relativeX, relativeY, **kw):
- """w.r.t. the current transformation, bookmark this horizontal."""
- (left, top) = self.absolutePosition(relativeX,relativeY)
- self.bookmarkHorizontalAbsolute(key, top, left=left, **kw)
- #def _inPage0(self): disallowed!
- # """declare a page, enable page features"""
- # self._doc.inPage()
- #def _inForm0(self):
- # "deprecated in favore of beginForm...endForm"
- # self._doc.inForm()
- def doForm(self, name):
- """use a form XObj in current operation stream.
- The form should either have been defined previously using
- beginForm ... endForm, or may be defined later. If it is not
- defined at save time, an exception will be raised. The form
- will be drawn within the context of the current graphics
- state."""
- self._code.append("/%s Do" % self._doc.getXObjectName(name))
- self._formsinuse.append(name)
- def hasForm(self, name):
- """Query whether form XObj really exists yet."""
- return self._doc.hasForm(name)
- ######################################################
- #
- # Image routines
- #
- ######################################################
- def drawInlineImage(self, image, x,y, width=None,height=None,
- preserveAspectRatio=False,anchor='c', anchorAtXY=False, showBoundary=False):
- """See drawImage, which should normally be used instead...
-
- drawInlineImage behaves like drawImage, but stores the image content
- within the graphics stream for the page. This means that the mask
- parameter for transparency is not available. It also means that there
- is no saving in file size or time if the same image is reused.
-
- In theory it allows images to be displayed slightly faster; however,
- we doubt if the difference is noticeable to any human user these days.
- Only use this if you have studied the PDF specification and know the
- implications.
- """
-
- self._currentPageHasImages = 1
- from reportlab.pdfgen.pdfimages import PDFImage
- img_obj = PDFImage(image, x,y, width, height)
- img_obj.drawInlineImage(self,
- preserveAspectRatio=preserveAspectRatio,
- anchor=anchor,anchorAtXY=anchorAtXY,showBoundary=showBoundary)
- return (img_obj.width, img_obj.height)
- def drawImage(self, image, x, y, width=None, height=None, mask=None,
- preserveAspectRatio=False, anchor='c', anchorAtXY=False, showBoundary=False):
- """Draws the image (ImageReader object or filename) as specified.
- "image" may be an image filename or an ImageReader object.
-
- x and y define the lower left corner of the image you wish to
- draw (or of its bounding box, if using preserveAspectRation below).
-
- If width and height are not given, the width and height of the
- image in pixels is used at a scale of 1 point to 1 pixel.
-
- If width and height are given, the image will be stretched to fill
- the given rectangle bounded by (x, y, x+width, y-height).
-
- If you supply negative widths and/or heights, it inverts them and adjusts
- x and y accordingly.
- The method returns the width and height of the underlying image, since
- this is often useful for layout algorithms and saves you work if you have
- not specified them yourself.
- The mask parameter supports transparent backgrounds. It takes 6 numbers
- and defines the range of RGB values which will be masked out or treated
- as transparent. For example with [0,2,40,42,136,139], it will mask out
- any pixels with a Red value from 0-2, Green from 40-42 and
- Blue from 136-139 (on a scale of 0-255).
- New post version 2.0: drawImage can center an image in a box you
- provide, while preserving its aspect ratio. For example, you might
- have a fixed square box in your design, and a collection of photos
- which might be landscape or portrait that you want to appear within
- the box. If preserveAspectRatio is true, your image will appear within
- the box specified.
-
- If preserveAspectRatio is True, the anchor property can be used to
- specify how images should fit into the given box. It should
- be set to one of the following values, taken from the points of
- the compass (plus 'c' for 'centre'):
- nw n ne
- w c e
- sw s se
- The default value is 'c' for 'centre'. Thus, if you want your
- bitmaps to always be centred and appear at the top of the given box,
- set anchor='n'. There are good examples of this in the output
- of test_pdfgen_general.py
- Unlike drawInlineImage, this creates 'external images' which
- are only stored once in the PDF file but can be drawn many times.
- If you give it the same filename twice, even at different locations
- and sizes, it will reuse the first occurrence, resulting in a saving
- in file size and generation time. If you use ImageReader objects,
- it tests whether the image content has changed before deciding
- whether to reuse it.
- In general you should use drawImage in preference to drawInlineImage
- unless you have read the PDF Spec and understand the tradeoffs."""
-
- self._currentPageHasImages = 1
- # first, generate a unique name/signature for the image. If ANYTHING
- # is different, even the mask, this should be different.
- if isinstance(image,ImageReader):
- rawdata = image.getRGBData()
- smask = image._dataA
- if mask=='auto' and smask:
- mdata = smask.getRGBData()
- else:
- mdata = str(mask)
- if isUnicode(mdata):
- mdata = mdata.encode('utf8')
- name = _digester(rawdata+mdata)
- else:
- #filename, use it
- s = '%s%s' % (image, mask)
- if isUnicode(s):
- s = s.encode('utf-8')
- name = _digester(s)
- # in the pdf document, this will be prefixed with something to
- # say it is an XObject. Does it exist yet?
- regName = self._doc.getXObjectName(name)
- imgObj = self._doc.idToObject.get(regName, None)
- if not imgObj:
- #first time seen, create and register the PDFImageXobject
- imgObj = pdfdoc.PDFImageXObject(name, image, mask=mask)
- imgObj.name = name
- self._setXObjects(imgObj)
- self._doc.Reference(imgObj, regName)
- self._doc.addForm(name, imgObj)
- smask = getattr(imgObj,'_smask',None)
- if smask: #set up the softmask obtained above
- mRegName = self._doc.getXObjectName(smask.name)
- mImgObj = self._doc.idToObject.get(mRegName, None)
- if not mImgObj:
- self._setXObjects(smask)
- imgObj.smask = self._doc.Reference(smask,mRegName)
- else:
- imgObj.smask = pdfdoc.PDFObjectReference(mRegName)
- del imgObj._smask
- # ensure we have a size, as PDF will make it 1x1 pixel otherwise!
- x,y,width,height,scaled = aspectRatioFix(preserveAspectRatio,anchor,x,y,width,height,imgObj.width,imgObj.height,anchorAtXY)
- # scale and draw
- self.saveState()
- self.translate(x, y)
- self.scale(width, height)
- self._code.append("/%s Do" % regName)
- self.restoreState()
- if showBoundary:
- self.rect(x,y,width,height,stroke=1,fill=0)
- # track what's been used on this page
- self._formsinuse.append(name)
- return (imgObj.width, imgObj.height)
- def _restartAccumulators(self):
- if self._codeStack:
- # restore the saved code
- self._code, self._formsinuse, self._annotationrefs, self._formData,self._colorsUsed, self._shadingUsed = self._codeStack.pop(-1)
- else:
- self._code = [] # ready for more...
- self._psCommandsAfterPage = []
- self._psCommandsBeforePage = []
- self._currentPageHasImages = 1 # for safety...
- self._formsinuse = []
- self._annotationrefs = []
- self._formData = None
- self._colorsUsed = {}
- self._shadingUsed = {}
- def _pushAccumulators(self):
- "when you enter a form, save accumulator info not related to the form for page (if any)"
- saved = (self._code, self._formsinuse, self._annotationrefs, self._formData, self._colorsUsed, self._shadingUsed)
- self._codeStack.append(saved)
- self._code = [] # ready for more...
- self._currentPageHasImages = 1 # for safety...
- self._formsinuse = []
- self._annotationrefs = []
- self._formData = None
- self._colorsUsed = {}
- self._shadingUsed = {}
- def _setExtGState(self, obj):
- obj.ExtGState = self._extgstate.getState()
- def beginForm(self, name, lowerx=0, lowery=0, upperx=None, uppery=None):
- """declare the current graphics stream to be a named form.
- A graphics stream can either be a page or a form, not both.
- Some operations (like bookmarking) are permitted for pages
- but not forms. The form will not automatically be shown in the
- document but must be explicitly referenced using doForm in pages
- that require the form."""
- self.push_state_stack()
- self.init_graphics_state()
- if self._code or self._formData:
- # save the code that is not in the formf
- self._pushAccumulators()
- #self._codeStack.append(self._code)
- #self._code = []
- self._formData = (name, lowerx, lowery, upperx, uppery)
- self._doc.inForm()
- #self._inForm0()
- def endForm(self,**extra_attributes):
- """emit the current collection of graphics operations as a Form
- as declared previously in beginForm."""
- (name, lowerx, lowery, upperx, uppery) = self._formData
- #self.makeForm0(name, lowerx, lowery, upperx, uppery)
- # fall through! makeForm0 disallowed
- #def makeForm0(self, name, lowerx=0, lowery=0, upperx=None, uppery=None):
- """Like showpage, but make a form using accumulated operations instead"""
- # deprecated in favor or beginForm(...)... endForm()
- (w,h) = self._pagesize
- if upperx is None: upperx=w
- if uppery is None: uppery=h
- form = pdfdoc.PDFFormXObject(lowerx=lowerx, lowery=lowery, upperx=upperx, uppery=uppery)
- form.compression = self._pageCompression
- form.setStreamList([self._preamble] + self._code) # ??? minus preamble (seems to be needed!)
- for k, v in extra_attributes.items():
- setattr(form,k,v)
- self._setColorSpace(form)
- self._setExtGState(form)
- self._setXObjects(form)
- self._setAnnotations(form)
- self._doc.addForm(name, form)
- self._restartAccumulators()
- self.pop_state_stack()
- def addPostScriptCommand(self, command, position=1):
- """Embed literal Postscript in the document.
- With position=0, it goes at very beginning of page stream;
- with position=1, at current point; and
- with position=2, at very end of page stream. What that does
- to the resulting Postscript depends on Adobe's header :-)
- Use with extreme caution, but sometimes needed for printer tray commands.
- Acrobat 4.0 will export Postscript to a printer or file containing
- the given commands. Adobe Reader 6.0 no longer does as this feature is
- deprecated. 5.0, I don't know about (please let us know!). This was
- funded by Bob Marshall of Vector.co.uk and tested on a Lexmark 750.
- See test_pdfbase_postscript.py for 2 test cases - one will work on
- any Postscript device, the other uses a 'setpapertray' command which
- will error in Distiller but work on printers supporting it.
- """
- #check if we've done this one already...
- if isUnicode(command):
- rawName = 'PS' + hashlib.md5(command.encode('utf-8')).hexdigest()
- else:
- rawName = 'PS' + hashlib.md5(command).hexdigest()
- regName = self._doc.getXObjectName(rawName)
- psObj = self._doc.idToObject.get(regName, None)
- if not psObj:
- #first use of this chunk of Postscript, make an object
- psObj = pdfdoc.PDFPostScriptXObject(command + '\n')
- self._setXObjects(psObj)
- self._doc.Reference(psObj, regName)
- self._doc.addForm(rawName, psObj)
- if position == 0:
- self._psCommandsBeforePage.append("/%s Do" % regName)
- elif position==1:
- self._code.append("/%s Do" % regName)
- else:
- self._psCommandsAfterPage.append("/%s Do" % regName)
- self._formsinuse.append(rawName)
- def _absRect(self,rect,relative=0):
- if not rect:
- w,h = self._pagesize
- rect = (0,0,w,h)
- elif relative:
- lx, ly, ux, uy = rect
- xll,yll = self.absolutePosition(lx,ly)
- xur,yur = self.absolutePosition(ux, uy)
- xul,yul = self.absolutePosition(lx, uy)
- xlr,ylr = self.absolutePosition(ux, ly)
- xs = xll, xur, xul, xlr
- ys = yll, yur, yul, ylr
- xmin, ymin = min(xs), min(ys)
- xmax, ymax = max(xs), max(ys)
- rect = xmin, ymin, xmax, ymax
- bw = self._getCmShift()
- if bw:
- rect = rect[0]+bw,rect[1]+bw,rect[2]+bw,rect[3]+bw
- return rect
- def freeTextAnnotation(self, contents, DA, Rect=None, addtopage=1, name=None, relative=0, **kw):
- """DA is the default appearance string???"""
- Rect = self._absRect(Rect,relative)
- self._addAnnotation(pdfdoc.FreeTextAnnotation(Rect, contents, DA, **kw), name, addtopage)
- def textAnnotation(self, contents, Rect=None, addtopage=1, name=None, relative=0, **kw):
- """Experimental, but works.
- """
- Rect = self._absRect(Rect,relative)
- self._addAnnotation(pdfdoc.TextAnnotation(Rect, contents, **kw), name, addtopage)
- textAnnotation0 = textAnnotation #deprecated
- def highlightAnnotation(self, contents, Rect, QuadPoints=None, Color=[0.83, 0.89, 0.95], addtopage=1,
- name=None, relative=0, **kw):
- """
- Allows adding of a highlighted annotation.
- Rect: Mouseover area to show contents of annotation
- QuadPoints: List of four x/y points [TOP-LEFT, TOP-RIGHT, BOTTOM-LEFT, BOTTOM-RIGHT]
- These points outline the areas to highlight.
- You can have multiple groups of four to allow multiple highlighted areas.
- Is in the format [x1, y1, x2, y2, x3, y3, x4, y4, x1, y1, x2, y2, x3, y3, x4, y4] etc
- QuadPoints defaults to be area inside of passed in Rect
- Color: The color of the highlighting.
- """
- Rect = self._absRect(Rect, relative)
- if not QuadPoints:
- QuadPoints = pdfdoc.rect_to_quad(Rect)
- self._addAnnotation(pdfdoc.HighlightAnnotation(Rect, contents, QuadPoints, Color, **kw), name, addtopage)
- def inkAnnotation(self, contents, InkList=None, Rect=None, addtopage=1, name=None, relative=0, **kw):
- raise NotImplementedError
- "Experimental"
- Rect = self._absRect(Rect,relative)
- if not InkList:
- InkList = ((100,100,100,h-100,w-100,h-100,w-100,100),)
- self._addAnnotation(pdfdoc.InkAnnotation(Rect, contents, InkList, **kw), name, addtopage)
- inkAnnotation0 = inkAnnotation #deprecated
- def linkAbsolute(self, contents, destinationname, Rect=None, addtopage=1, name=None,
- thickness=0, color=None, dashArray=None, **kw):
- """rectangular link annotation positioned wrt the default user space.
- The identified rectangle on the page becomes a "hot link" which
- when clicked will send the viewer to the page and position identified
- by the destination.
- Rect identifies (lowerx, lowery, upperx, uppery) for lower left
- and upperright points of the rectangle. Translations and other transforms
- are IGNORED (the rectangular position is given with respect
- to the default user space.
- destinationname should be the name of a bookmark (which may be defined later
- but must be defined before the document is generated).
- You may want to use the keyword argument Border='[0 0 0]' to
- suppress the visible rectangle around the during viewing link."""
- return self.linkRect(contents, destinationname, Rect, addtopage, name, relative=0,
- thickness=thickness, color=color, dashArray=dashArray, **kw)
- def linkRect(self, contents, destinationname, Rect=None, addtopage=1, name=None, relative=1,
- thickness=0, color=None, dashArray=None, **kw):
- """rectangular link annotation w.r.t the current user transform.
- if the transform is skewed/rotated the absolute rectangle will use the max/min x/y
- """
- destination = self._bookmarkReference(destinationname) # permitted to be undefined... must bind later...
- Rect = self._absRect(Rect,relative)
- kw["Rect"] = Rect
- kw["Contents"] = contents
- kw["Destination"] = destination
- _annFormat(kw,color,thickness,dashArray)
- return self._addAnnotation(pdfdoc.LinkAnnotation(**kw), name, addtopage)
- def linkURL(self, url, rect, relative=0, thickness=0, color=None, dashArray=None, kind="URI", **kw):
- """Create a rectangular URL 'hotspot' in the given rectangle.
- if relative=1, this is in the current coord system, otherwise
- in absolute page space.
- The remaining options affect the border appearance; the border is
- drawn by Acrobat, not us. Set thickness to zero to hide it.
- Any border drawn this way is NOT part of the page stream and
- will not show when printed to a Postscript printer or distilled;
- it is safest to draw your own."""
- from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFName, PDFArray, PDFString
- #tried the documented BS element in the pdf spec but it
- #does not work, and Acrobat itself does not appear to use it!
- ann = PDFDictionary(dict=kw)
- ann["Type"] = PDFName("Annot")
- ann["Subtype"] = PDFName("Link")
- ann["Rect"] = PDFArray(self._absRect(rect,relative)) # the whole page for testing
- # the action is a separate dictionary
- A = PDFDictionary()
- A["Type"] = PDFName("Action") # not needed?
- uri = PDFString(url)
- A['S'] = PDFName(kind)
- if kind=="URI":
- A["URI"] = uri
- elif kind=='GoToR':
- A["F"] = uri
- A["D"] = "[ 0 /XYZ null null null ]"
- else:
- raise ValueError("Unknown linkURI kind '%s'" % kind)
- ann["A"] = A
- _annFormat(ann,color,thickness,dashArray)
- self._addAnnotation(ann)
- def _addAnnotation(self, annotation, name=None, addtopage=1):
- count = self._annotationCount = self._annotationCount+1
- if not name: name="NUMBER"+repr(count)
- self._doc.addAnnotation(name, annotation)
- if addtopage:
- self._annotatePage(name)
- def _annotatePage(self, name):
- ref = self._doc.refAnnotation(name)
- self._annotationrefs.append(ref)
- def getPageNumber(self):
- "get the page number for the current page being generated."
- return self._pageNumber
- def save(self):
- """Saves and close the PDF document in the file.
- If there is current data a ShowPage is executed automatically.
- After this operation the canvas must not be used further."""
- if len(self._code): self.showPage()
- self._doc.SaveToFile(self._filename, self)
- def getpdfdata(self):
- """Returns the PDF data that would normally be written to a file.
- If there is current data a ShowPage is executed automatically.
- After this operation the canvas must not be used further."""
- if len(self._code): self.showPage()
- s = self._doc.GetPDFData(self)
- if isUnicode(s):
- s = s.encode('utf-8')
- return s
- def setPageSize(self, size):
- """accepts a 2-tuple in points for paper size for this
- and subsequent pages"""
- self._pagesize = size
- self._make_preamble()
- def setCropBox(self, size, name='crop'):
- """accepts a 2-tuple in points for name+'Box' size for this and subsequent pages"""
- name = name.lower()
- if name.endswith('box'): name = name[:-3]
- if name not in ('crop','art','trim','bleed'):
- raise ValueError('unknown box name: %r' % name)
- setattr(self,'_%sBox' % name, size)
- def setTrimBox(self,size):
- self.setCropBox(size,name='trim')
- def setArtBox(self,size):
- self.setCropBox(size,name='art')
- def setBleedBox(self,size):
- self.setCropBox(size,name='bleed')
- def setPageRotation(self, rot):
- """Instruct display device that this page is to be rotated"""
- assert rot % 90.0 == 0.0, "Rotation must be a multiple of 90 degrees"
- self._pageRotation = rot
- def addLiteral(self, s, escaped=1):
- """introduce the literal text of PDF operations s into the current stream.
- Only use this if you are an expert in the PDF file format."""
- s = str(s) # make sure its a string
- if escaped==0:
- s = self._escape(s) # convert to string for safety
- self._code.append(s)
- ######################################################################
- #
- # coordinate transformations
- #
- ######################################################################
- def resetTransforms(self):
- """I want to draw something (eg, string underlines) w.r.t. the default user space.
- Reset the matrix! This should be used usually as follows::
-
- canv.saveState()
- canv.resetTransforms()
- #...draw some stuff in default space coords...
- canv.restoreState() # go back!
- """
- # we have to adjoin the inverse, since reset is not a basic operation (without save/restore)
- (selfa, selfb, selfc, selfd, selfe, selff) = self._currentMatrix
- det = selfa*selfd - selfc*selfb
- resulta = selfd/det
- resultc = -selfc/det
- resulte = (selfc*selff - selfd*selfe)/det
- resultd = selfa/det
- resultb = -selfb/det
- resultf = (selfe*selfb - selff*selfa)/det
- self.transform(resulta, resultb, resultc, resultd, resulte, resultf)
- def transform(self, a,b,c,d,e,f):
- """adjoin a mathematical transform to the current graphics state matrix.
- Not recommended for beginners."""
- #How can Python track this?
- if ENABLE_TRACKING:
- a0,b0,c0,d0,e0,f0 = self._currentMatrix
- self._currentMatrix = (a0*a+c0*b, b0*a+d0*b,
- a0*c+c0*d, b0*c+d0*d,
- a0*e+c0*f+e0, b0*e+d0*f+f0)
- if self._code and self._code[-1][-3:]==' cm':
- L = self._code[-1].split()
- a0, b0, c0, d0, e0, f0 = list(map(float,L[-7:-1]))
- s = len(L)>7 and join(L)+ ' %s cm' or '%s cm'
- self._code[-1] = s % fp_str(a0*a+c0*b,b0*a+d0*b,a0*c+c0*d,b0*c+d0*d,a0*e+c0*f+e0,b0*e+d0*f+f0)
- else:
- self._code.append('%s cm' % fp_str(a,b,c,d,e,f))
- def absolutePosition(self, x, y):
- """return the absolute position of x,y in user space w.r.t. default user space"""
- if not ENABLE_TRACKING:
- raise ValueError("tracking not enabled! (canvas.ENABLE_TRACKING=0)")
- (a,b,c,d,e,f) = self._currentMatrix
- xp = a*x + c*y + e
- yp = b*x + d*y + f
- return (xp, yp)
- def translate(self, dx, dy):
- """move the origin from the current (0,0) point to the (dx,dy) point
- (with respect to the current graphics state)."""
- self.transform(1,0,0,1,dx,dy)
- def scale(self, x, y):
- """Scale the horizontal dimension by x and the vertical by y
- (with respect to the current graphics state).
- For example canvas.scale(2.0, 0.5) will make everything short and fat."""
- self.transform(x,0,0,y,0,0)
- def rotate(self, theta):
- """Canvas.rotate(theta)
- Rotate the canvas by the angle theta (in degrees)."""
- c = cos(theta * pi / 180)
- s = sin(theta * pi / 180)
- self.transform(c, s, -s, c, 0, 0)
- def skew(self, alpha, beta):
- tanAlpha = tan(alpha * pi / 180)
- tanBeta = tan(beta * pi / 180)
- self.transform(1, tanAlpha, tanBeta, 1, 0, 0)
- ######################################################################
- #
- # graphics state management
- #
- ######################################################################
- def saveState(self):
- """Save the current graphics state to be restored later by restoreState.
- For example:
- canvas.setFont("Helvetica", 20)
- canvas.saveState()
- ...
- canvas.setFont("Courier", 9)
- ...
- canvas.restoreState()
- # if the save/restore pairs match then font is Helvetica 20 again.
- """
- self.push_state_stack()
- self._code.append('q')
- def restoreState(self):
- """restore the graphics state to the matching saved state (see saveState)."""
- self._code.append('Q')
- self.pop_state_stack()
- ###############################################################
- #
- # Drawing methods. These draw things directly without
- # fiddling around with Path objects. We can add any geometry
- # methods we wish as long as their meaning is precise and
- # they are of general use.
- #
- # In general there are two patterns. Closed shapes
- # have the pattern shape(self, args, stroke=1, fill=0);
- # by default they draw an outline only. Line segments come
- # in three flavours: line, bezier, arc (which is a segment
- # of an elliptical arc, approximated by up to four bezier
- # curves, one for each quadrant.
- #
- # In the case of lines, we provide a 'plural' to unroll
- # the inner loop; it is useful for drawing big grids
- ################################################################
- #--------first the line drawing methods-----------------------
- def line(self, x1,y1, x2,y2):
- """draw a line segment from (x1,y1) to (x2,y2) (with color, thickness and
- other attributes determined by the current graphics state)."""
- self._code.append('n %s m %s l S' % (fp_str(x1, y1), fp_str(x2, y2)))
- def lines(self, linelist):
- """Like line(), permits many lines to be drawn in one call.
- for example for the figure::
-
- |
- -- --
- |
- crosshairs = [(20,0,20,10), (20,30,20,40), (0,20,10,20), (30,20,40,20)]
- canvas.lines(crosshairs)
- """
- self._code.append('n')
- for (x1,y1,x2,y2) in linelist:
- self._code.append('%s m %s l' % (fp_str(x1, y1), fp_str(x2, y2)))
- self._code.append('S')
- def cross(self, x, y, size=5, gap=1, text=None, strokeColor=None, strokeWidth=None, fontSize=3):
- size *= 0.5
- gap *= 0.5
- crosshairs = [(x,y-size,x,y-gap),(x,y+gap,x,y+size), (x-size,y,x-gap,y),(x+gap,y,x+size,y)]
- self.saveState()
- if strokeColor:
- self.setStrokeColor(strokeColor)
- if text:
- self.setFillColor(strokeColor)
- if strokeWidth:
- self.setLineWidth(StrokeWidth)
- self.lines(crosshairs)
- if text:
- if fontSize is not None: self.setFontSize(fontSize)
- self.drawRightString(x-size, y, text)
- self.restoreState()
- def grid(self, xlist, ylist):
- """Lays out a grid in current line style. Supply list of
- x an y positions."""
- assert len(xlist) > 1, "x coordinate list must have 2+ items"
- assert len(ylist) > 1, "y coordinate list must have 2+ items"
- lines = []
- y0, y1 = ylist[0], ylist[-1]
- x0, x1 = xlist[0], xlist[-1]
- for x in xlist:
- lines.append((x,y0,x,y1))
- for y in ylist:
- lines.append((x0,y,x1,y))
- self.lines(lines)
- def bezier(self, x1, y1, x2, y2, x3, y3, x4, y4):
- "Bezier curve with the four given control points"
- self._code.append('n %s m %s c S' %
- (fp_str(x1, y1), fp_str(x2, y2, x3, y3, x4, y4))
- )
- def arc(self, x1,y1, x2,y2, startAng=0, extent=90):
- """Draw a partial ellipse inscribed within the rectangle x1,y1,x2,y2,
- starting at startAng degrees and covering extent degrees. Angles
- start with 0 to the right (+x) and increase counter-clockwise.
- These should have x1<x2 and y1<y2."""
- pathobject.PDFPathObject(code=self._code).arc(x1,y1,x2,y2,startAng,extent)
- self._strokeAndFill(1,0)
- #--------now the shape drawing methods-----------------------
- def rect(self, x, y, width, height, stroke=1, fill=0):
- "draws a rectangle with lower left corner at (x,y) and width and height as given."
- self._code.append('n %s re ' % fp_str(x, y, width, height)
- + PATH_OPS[stroke, fill, self._fillMode])
- def ellipse(self, x1, y1, x2, y2, stroke=1, fill=0):
- """Draw an ellipse defined by an enclosing rectangle.
- Note that (x1,y1) and (x2,y2) are the corner points of
- the enclosing rectangle.
- """
- pathobject.PDFPathObject(code=self._code).ellipse(x1, y1, x2-x1, y2-y1)
- self._strokeAndFill(stroke, fill)
- def wedge(self, x1,y1, x2,y2, startAng, extent, stroke=1, fill=0):
- """Like arc, but connects to the centre of the ellipse.
- Most useful for pie charts and PacMan!"""
- p = pathobject.PDFPathObject(code=self._code)
- p.moveTo(0.5*(x1+x2),0.5*(y1+y2))
- p.arcTo(x1,y1,x2,y2,startAng,extent)
- p.close()
- self._strokeAndFill(stroke,fill)
- def circle(self, x_cen, y_cen, r, stroke=1, fill=0):
- """draw a cirle centered at (x_cen,y_cen) with radius r (special case of ellipse)"""
- x1 = x_cen - r
- x2 = x_cen + r
- y1 = y_cen - r
- y2 = y_cen + r
- self.ellipse(x1, y1, x2, y2, stroke, fill)
- def roundRect(self, x, y, width, height, radius, stroke=1, fill=0):
- """Draws a rectangle with rounded corners. The corners are
- approximately quadrants of a circle, with the given radius."""
- #make the path operators draw into our code
- pathobject.PDFPathObject(code=self._code).roundRect(x, y, width, height, radius)
- self._strokeAndFill(stroke,fill)
- def _addShading(self, shading):
- name = self._doc.addShading(shading)
- self._shadingUsed[name] = name
- return name
- def shade(self, shading):
- name = self._addShading(shading)
- self._code.append('/%s sh' % name)
- def linearGradient(self, x0, y0, x1, y1, colors, positions=None, extend=True):
- #this code contributed by Peter Johnson <johnson.peter@gmail.com>
- from reportlab.pdfbase.pdfdoc import PDFAxialShading
- colorSpace, ncolors = _normalizeColors(colors)
- fcn = _buildColorFunction(ncolors, positions)
- if extend:
- extendStr = "[true true]"
- else:
- extendStr = "[false false]"
- shading = PDFAxialShading(x0, y0, x1, y1, Function=fcn,
- ColorSpace=colorSpace, Extend=extendStr)
- self.shade(shading)
- def radialGradient(self, x, y, radius, colors, positions=None, extend=True):
- #this code contributed by Peter Johnson <johnson.peter@gmail.com>
- from reportlab.pdfbase.pdfdoc import PDFRadialShading
- colorSpace, ncolors = _normalizeColors(colors)
- fcn = _buildColorFunction(ncolors, positions)
- if extend:
- extendStr = "[true true]"
- else:
- extendStr = "[false false]"
- shading = PDFRadialShading(x, y, 0.0, x, y, radius, Function=fcn,
- ColorSpace=colorSpace, Extend=extendStr)
- self.shade(shading)
- ##################################################
- #
- # Text methods
- #
- # As with graphics, a separate object ensures that
- # everything is bracketed between text operators.
- # The methods below are a high-level convenience.
- # use PDFTextObject for multi-line text.
- ##################################################
- def drawString(self, x, y, text, mode=None, charSpace=0, direction=None, wordSpace=None):
- """Draws a string in the current text styles."""
- text = asUnicode(text)
- #we could inline this for speed if needed
- t = self.beginText(x, y, direction=direction)
- if mode is not None: t.setTextRenderMode(mode)
- if charSpace: t.setCharSpace(charSpace)
- if wordSpace: t.setWordSpace(wordSpace)
- t.textLine(text)
- if charSpace: t.setCharSpace(0)
- if wordSpace: t.setWordSpace(0)
- if mode is not None: t.setTextRenderMode(0)
- self.drawText(t)
- def drawRightString(self, x, y, text, mode=None, charSpace=0, direction=None, wordSpace=None):
- """Draws a string right-aligned with the x coordinate"""
- if sys.version_info[0] == 3 and not isinstance(text, str):
- text = text.decode('utf-8')
- width = self.stringWidth(text, self._fontname, self._fontsize)
- if charSpace: width += (len(text)-1)*charSpace
- if wordSpace: width += (text.count(u' ')+text.count(u'\xa0')-1)*wordSpace
- t = self.beginText(x - width, y, direction=direction)
- if mode is not None: t.setTextRenderMode(mode)
- if charSpace: t.setCharSpace(charSpace)
- if wordSpace: t.setWordSpace(wordSpace)
- t.textLine(text)
- if charSpace: t.setCharSpace(0)
- if wordSpace: t.setWordSpace(0)
- if mode is not None: t.setTextRenderMode(0)
- self.drawText(t)
- def drawCentredString(self, x, y, text, mode=None, charSpace=0, direction=None, wordSpace=None):
- """Draws a string centred on the x coordinate.
-
- We're British, dammit, and proud of our spelling!"""
- if sys.version_info[0] == 3 and not isinstance(text, str):
- text = text.decode('utf-8')
- width = self.stringWidth(text, self._fontname, self._fontsize)
- if charSpace: width += (len(text)-1)*charSpace
- if wordSpace: width += (text.count(u' ')+text.count(u'\xa0')-1)*wordSpace
- t = self.beginText(x - 0.5*width, y, direction=direction)
- if mode is not None: t.setTextRenderMode(mode)
- if charSpace: t.setCharSpace(charSpace)
- if wordSpace: t.setWordSpace(wordSpace)
- t.textLine(text)
- if charSpace: t.setCharSpace(0)
- if wordSpace: t.setWordSpace(0)
- if mode is not None: t.setTextRenderMode(0)
- self.drawText(t)
- def drawAlignedString(self, x, y, text, pivotChar=rl_config.decimalSymbol, mode=None, charSpace=0, direction=None, wordSpace=None):
- """Draws a string aligned on the first '.' (or other pivot character).
- The centre position of the pivot character will be used as x.
- So, you could draw a straight line down through all the decimals in a
- column of numbers, and anything without a decimal should be
- optically aligned with those that have.
- There is one special rule to help with accounting formatting. Here's
- how normal numbers should be aligned on the 'dot'. Look at the
- LAST two::
-
- 12,345,67
- 987.15
- 42
- -1,234.56
- (456.78)
- (456)
- 27 inches
- 13cm
-
- Since the last three do not contain a dot, a crude dot-finding
- rule would place them wrong. So we test for the special case
- where no pivot is found, digits are present, but the last character
- is not a digit. We then work back from the end of the string
- This case is a tad slower but hopefully rare.
-
- """
- parts = text.split(pivotChar,1)
- pivW = self.stringWidth(pivotChar, self._fontname, self._fontsize)
-
- if len(parts) == 1 and digitPat.search(text) is not None and text[-1] not in digits:
- #we have no decimal but it ends in a bracket, or 'in' or something.
- #the cut should be after the last digit.
- leftText = parts[0][0:-1]
- rightText = parts[0][-1]
- #any more?
- while leftText[-1] not in digits:
- rightText = leftText[-1] + rightText
- leftText = leftText[0:-1]
- self.drawRightString(x-0.5*pivW, y, leftText, mode=mode, charSpace=charSpace,
- direction=direction, wordSpace=wordSpace)
- self.drawString(x-0.5*pivW, y, rightText, mode=mode, charSpace=charSpace,
- direction=direction, wordSpace=wordSpace)
- else:
- #normal case
- leftText = parts[0]
- self.drawRightString(x-0.5*pivW, y, leftText, mode=mode, charSpace=charSpace,
- direction=direction, wordSpace=wordSpace)
- if len(parts) > 1:
- rightText = pivotChar + parts[1]
- self.drawString(x-0.5*pivW, y, rightText, mode=mode, charSpace=charSpace,
- direction=direction, wordSpace=wordSpace)
- def getAvailableFonts(self):
- """Returns the list of PostScript font names available.
- Standard set now, but may grow in future with font embedding."""
- fontnames = self._doc.getAvailableFonts()
- return fontnames
- def listLoadedFonts0(self):
- "Convenience function to list all loaded fonts"
- names = list(pdfmetrics.widths.keys())
- names.sort()
- return names
- def setFont(self, psfontname, size, leading = None):
- """Sets the font. If leading not specified, defaults to 1.2 x
- font size. Raises a readable exception if an illegal font
- is supplied. Font names are case-sensitive! Keeps track
- of font name and size for metrics."""
- self._fontname = psfontname
- self._fontsize = size
- if leading is None:
- leading = size * 1.2
- self._leading = leading
- font = pdfmetrics.getFont(self._fontname)
- if not font._dynamicFont:
- if font.face.builtIn or not getattr(self,'_drawTextAsPath',False):
- pdffontname = self._doc.getInternalFontName(psfontname)
- self._code.append('BT %s %s Tf %s TL ET' % (pdffontname, fp_str(size), fp_str(leading)))
- def setFontSize(self, size=None, leading=None):
- '''Sets font size or leading without knowing the font face'''
- if size is None: size = self._fontsize
- if leading is None: leading = self._leading
- self.setFont(self._fontname, size, leading)
- def stringWidth(self, text, fontName=None, fontSize=None):
- "gets width of a string in the given font and size"
- return pdfmetrics.stringWidth(text, fontName or self._fontname,
- (fontSize,self._fontsize)[fontSize is None])
- # basic graphics modes
- def setLineWidth(self, width):
- self._lineWidth = width
- self._code.append('%s w' % fp_str(width))
- def setLineCap(self, mode):
- """0=butt,1=round,2=square"""
- assert mode in (0,1,2), "Line caps allowed: 0=butt,1=round,2=square"
- self._lineCap = mode
- self._code.append('%d J' % mode)
- def setLineJoin(self, mode):
- """0=mitre, 1=round, 2=bevel"""
- assert mode in (0,1,2), "Line Joins allowed: 0=mitre, 1=round, 2=bevel"
- self._lineJoin = mode
- self._code.append('%d j' % mode)
- def setMiterLimit(self, limit):
- self._miterLimit = limit
- self._code.append('%s M' % fp_str(limit))
- def setDash(self, array=[], phase=0):
- """Two notations. pass two numbers, or an array and phase"""
- reason = ''
- if isinstance(array,(int,float)):
- array = (array, phase)
- phase = 0
- elif not isSeq(array):
- reason = 'array should be a sequence of numbers or a number'
- bad = [_ for _ in array if not isinstance(_,(int,float)) or _ < 0]
- if bad or not isinstance(phase,(int,float)) or phase<0:
- reason = 'array & phase should be non-negative numbers'
- elif array and sum(array)<=0:
- reason = 'dash cycle should be larger than zero'
- if reason:
- raise ValueError('setDash: array=%r phase=%r\n%s' % (array, phase, reason))
- self._code.append('[%s] %s d' % (fp_str(array), phase))
- # path stuff - the separate path object builds it
- def beginPath(self):
- """Returns a fresh path object. Paths are used to draw
- complex figures. The object returned follows the protocol
- for a pathobject.PDFPathObject instance"""
- return pathobject.PDFPathObject()
- def drawPath(self, aPath, stroke=1, fill=0, fillMode=None):
- "Draw the path object in the mode indicated"
- if fillMode is None:
- fillMode = getattr(aPath,'_fillMode',self._fillMode)
- self._code.append(str(aPath.getCode()))
- self._strokeAndFill(stroke,fill,fillMode)
- def _strokeAndFill(self,stroke,fill,fillMode=None):
- self._code.append(PATH_OPS[stroke, fill, fillMode if fillMode is not None else self._fillMode])
- def clipPath(self, aPath, stroke=1, fill=0, fillMode=None):
- "clip as well as drawing"
- if fillMode is None:
- fillMode = getattr(aPath,'_fillMode',self._fillMode)
- gc = aPath.getCode(); pathops = PATH_OPS[stroke, fill, fillMode]
- clip = (fillMode == FILL_EVEN_ODD and ' W* ' or ' W ')
- item = "%s%s%s" % (gc, clip, pathops) # ensure string conversion
- self._code.append(item)
- #self._code.append( aPath.getCode()
- # + (self._fillMode == FILL_EVEN_ODD and ' W* ' or ' W ')
- # + PATH_OPS[stroke,fill,self._fillMode])
- def beginText(self, x=0, y=0, direction=None):
- """Returns a fresh text object. Text objects are used
- to add large amounts of text. See PDFTextObject"""
- return PDFTextObject(self, x, y, direction=direction)
- def drawText(self, aTextObject):
- """Draws a text object"""
- self._code.append(str(aTextObject.getCode()))
- def setPageCompression(self, pageCompression=1):
- """Possible values None, 1 or 0
- If None the value from rl_config will be used.
- If on, the page data will be compressed, leading to much
- smaller files, but takes a little longer to create the files.
- This applies to all subsequent pages, or until setPageCompression()
- is next called."""
- if pageCompression is None: pageCompression = rl_config.pageCompression
- if pageCompression and not zlib:
- self._pageCompression = 0
- else:
- self._pageCompression = pageCompression
- self._doc.setCompression(self._pageCompression)
- def setPageDuration(self, duration=None):
- """Allows hands-off animation of presentations :-)
- If this is set to a number, in full screen mode, Acrobat Reader
- will advance to the next page after this many seconds. The
- duration of the transition itself (fade/flicker etc.) is controlled
- by the 'duration' argument to setPageTransition; this controls
- the time spent looking at the page. This is effective for all
- subsequent pages."""
- self._pageDuration = duration
- def setPageTransition(self, effectname=None, duration=1,
- direction=0,dimension='H',motion='I'):
- """PDF allows page transition effects for use when giving
- presentations. There are six possible effects. You can
- just guive the effect name, or supply more advanced options
- to refine the way it works. There are three types of extra
- argument permitted, and here are the allowed values::
-
- direction_arg = [0,90,180,270]
- dimension_arg = ['H', 'V']
- motion_arg = ['I','O'] (start at inside or outside)
-
- This table says which ones take which arguments::
- PageTransitionEffects = {
- 'Split': [direction_arg, motion_arg],
- 'Blinds': [dimension_arg],
- 'Box': [motion_arg],
- 'Wipe' : [direction_arg],
- 'Dissolve' : [],
- 'Glitter':[direction_arg]
- }
-
- Have fun!
- """
- # This builds a Python dictionary with the right arguments
- # for the Trans dictionary in the PDFPage object,
- # and stores it in the variable _pageTransition.
- # showPage later passes this to the setPageTransition method
- # of the PDFPage object, which turns it to a PDFDictionary.
- self._pageTransition = {}
- if not effectname:
- return
- #first check each optional argument has an allowed value
- if direction in [0,90,180,270]:
- direction_arg = ('Di', '/%d' % direction)
- else:
- raise pdfdoc.PDFError(' directions allowed are 0,90,180,270')
- if dimension in ['H', 'V']:
- dimension_arg = ('Dm', '/' + dimension)
- else:
- raise pdfdoc.PDFError('dimension values allowed are H and V')
- if motion in ['I','O']:
- motion_arg = ('M', '/' + motion)
- else:
- raise pdfdoc.PDFError('motion values allowed are I and O')
- # this says which effects require which argument types from above
- PageTransitionEffects = {
- 'Split': [direction_arg, motion_arg],
- 'Blinds': [dimension_arg],
- 'Box': [motion_arg],
- 'Wipe' : [direction_arg],
- 'Dissolve' : [],
- 'Glitter':[direction_arg]
- }
- try:
- args = PageTransitionEffects[effectname]
- except KeyError:
- raise pdfdoc.PDFError('Unknown Effect Name "%s"' % effectname)
- # now build the dictionary
- transDict = {}
- transDict['Type'] = '/Trans'
- transDict['D'] = '%d' % duration
- transDict['S'] = '/' + effectname
- for (key, value) in args:
- transDict[key] = value
- self._pageTransition = transDict
- def getCurrentPageContent(self):
- """Return uncompressed contents of current page buffer.
- This is useful in creating test cases and assertions of what
- got drawn, without necessarily saving pages to disk"""
- return '\n'.join(self._code)
- def setViewerPreference(self,pref,value):
- '''set one of the allowed enbtries in the documents viewer preferences'''
- catalog = self._doc.Catalog
- VP = getattr(catalog,'ViewerPreferences',None)
- if VP is None:
- from reportlab.pdfbase.pdfdoc import ViewerPreferencesPDFDictionary
- VP = catalog.ViewerPreferences = ViewerPreferencesPDFDictionary()
- VP[pref] = value
- def getViewerPreference(self,pref):
- '''you'll get an error here if none have been set'''
- return self._doc.Catalog.ViewerPreferences[pref]
- def delViewerPreference(self,pref):
- '''you'll get an error here if none have been set'''
- del self._doc.Catalog.ViewerPreferences[pref]
- def setCatalogEntry(self,key,value):
- from reportlab.pdfbase.pdfdoc import PDFDictionary, PDFArray, PDFString
- if isStr(value):
- value = PDFString(value)
- elif isinstance(value,(list,tuple)):
- value = PDFArray(value)
- elif isinstance(value,dict):
- value = PDFDictionary(value)
- setattr(self._doc.Catalog,key,value)
- def getCatalogEntry(self,key):
- return getattr(self._doc.Catalog,key)
- def delCatalogEntry(self,key):
- '''you'll get an error here if it's not been set'''
- delattr(self._doc.Catalog,key)
- def addPageLabel(self, pageNum, style=None, start=None, prefix=None):
- '''add a PDFPageLabel for pageNum'''
- catalog = self._doc.Catalog
- PL = getattr(catalog,'PageLabels',None)
- if PL is None:
- from reportlab.pdfbase.pdfdoc import PDFPageLabels
- PL = catalog.PageLabels = PDFPageLabels()
- from reportlab.pdfbase.pdfdoc import PDFPageLabel
- PL.addPageLabel(pageNum,PDFPageLabel(style,start,prefix))
- @property
- def acroForm(self):
- "get form from canvas, create the form if needed"
- try:
- return self.AcroForm
- except AttributeError:
- from reportlab.pdfbase.acroform import AcroForm
- self._doc._catalog.AcroForm = self.AcroForm = AcroForm(self)
- return self.AcroForm
- @property
- def drawBoundary(self):
- if not hasattr(self,'_drawBoundary'):
- from reportlab.platypus import Frame
- self._drawBoundary = lambda sb,x,y,w,h: Frame._drawBoundary(self,sb,x,y,w,h)
- return self._drawBoundary
- if __name__ == '__main__':
- print('For test scripts, look in tests')
|