123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- """Support for Acrobat Forms in ReportLab documents
- This module is somewhat experimental at this time.
- Includes basic support for
- textfields,
- select fields (drop down lists), and
- check buttons.
- The public interface consists of functions at the moment.
- At some later date these operations may be made into canvas
- methods. (comments?)
- The ...Absolute(...) functions position the fields with respect
- to the absolute canvas coordinate space -- that is, they do not
- respect any coordinate transforms in effect for the canvas.
- The ...Relative(...) functions position the ONLY THE LOWER LEFT
- CORNER of the field using the coordinate transform in effect for
- the canvas. THIS WILL ONLY WORK CORRECTLY FOR TRANSLATED COORDINATES
- -- THE SHAPE, SIZE, FONTSIZE, AND ORIENTATION OF THE FIELD WILL NOT BE EFFECTED
- BY SCALING, ROTATION, SKEWING OR OTHER NON-TRANSLATION COORDINATE
- TRANSFORMS.
- Please note that all field names (titles) in a given document must be unique.
- Textfields and select fields only support the "base 14" canvas fonts
- at this time.
- See individual function docstrings below for more information.
- The function test1(...) generates a simple test file.
- THIS CONTRIBUTION WAS COMMISSIONED BY REPORTLAB USERS
- WHO WISH TO REMAIN ANONYMOUS.
- """
- ### NOTE: MAKE THE STRING FORMATS DYNAMIC IN PATTERNS TO SUPPORT ENCRYPTION XXXX
- import string
- from reportlab.pdfbase.pdfdoc import PDFString, PDFStream, PDFDictionary, PDFName, PDFObject
- from reportlab.lib.colors import obj_R_G_B
- #==========================public interfaces
- def textFieldAbsolute(canvas, title, x, y, width, height, value="", maxlen=1000000, multiline=0):
- """Place a text field on the current page
- with name title at ABSOLUTE position (x,y) with
- dimensions (width, height), using value as the default value and
- maxlen as the maximum permissible length. If multiline is set make
- it a multiline field.
- """
- theform = getForm(canvas)
- return theform.textField(canvas, title, x, y, x+width, y+height, value, maxlen, multiline)
- def textFieldRelative(canvas, title, xR, yR, width, height, value="", maxlen=1000000, multiline=0):
- "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
- (xA, yA) = canvas.absolutePosition(xR,yR)
- return textFieldAbsolute(canvas, title, xA, yA, width, height, value, maxlen, multiline)
- def buttonFieldAbsolute(canvas, title, value, x, y, width=16.7704, height=14.907):
- """Place a check button field on the current page
- with name title and default value value (one of "Yes" or "Off")
- at ABSOLUTE position (x,y).
- """
- theform = getForm(canvas)
- return theform.buttonField(canvas, title, value, x, y, width=width, height=height)
- def buttonFieldRelative(canvas, title, value, xR, yR, width=16.7704, height=14.907):
- "same as buttonFieldAbsolute except the x and y are relative to the canvas coordinate transform"
- (xA, yA) = canvas.absolutePosition(xR,yR)
- return buttonFieldAbsolute(canvas, title, value, xA, yA, width=width, height=height)
- def selectFieldAbsolute(canvas, title, value, options, x, y, width, height):
- """Place a select field (drop down list) on the current page
- with name title and
- with options listed in the sequence options
- default value value (must be one of options)
- at ABSOLUTE position (x,y) with dimensions (width, height)."""
- theform = getForm(canvas)
- theform.selectField(canvas, title, value, options, x, y, x+width, y+height)
- def selectFieldRelative(canvas, title, value, options, xR, yR, width, height):
- "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
- (xA, yA) = canvas.absolutePosition(xR,yR)
- return selectFieldAbsolute(canvas, title, value, options, xA, yA, width, height)
- #==========================end of public interfaces
- from reportlab.pdfbase.pdfpattern import PDFPattern, PDFPatternIf
- def getForm(canvas):
- "get form from canvas, create the form if needed"
- try:
- return canvas.AcroForm
- except AttributeError:
- theform = canvas.AcroForm = AcroForm()
- # install the form in the document
- d = canvas._doc
- cat = d._catalog
- cat.AcroForm = theform
- return theform
- class AcroForm(PDFObject):
- def __init__(self):
- self.fields = []
- def textField(self, canvas, title, xmin, ymin, xmax, ymax, value="", maxlen=1000000, multiline=0):
- # determine the page ref
- doc = canvas._doc
- page = doc.thisPageRef()
- # determine text info
- R, G, B = obj_R_G_B(canvas._fillColorObj)
- #print "rgb", (R,G,B)
- font = canvas. _fontname
- fontsize = canvas. _fontsize
- field = TextField(title, value, xmin, ymin, xmax, ymax, page, maxlen,
- font, fontsize, R, G, B, multiline)
- self.fields.append(field)
- canvas._addAnnotation(field)
- def selectField(self, canvas, title, value, options, xmin, ymin, xmax, ymax):
- # determine the page ref
- doc = canvas._doc
- page = doc.thisPageRef()
- # determine text info
- R, G, B = obj_R_G_B(canvas._fillColorObj)
- #print "rgb", (R,G,B)
- font = canvas. _fontname
- fontsize = canvas. _fontsize
- field = SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
- font=font, fontsize=fontsize, R=R, G=G, B=B)
- self.fields.append(field)
- canvas._addAnnotation(field)
- def buttonField(self, canvas, title, value, xmin, ymin, width=16.7704, height=14.907):
- # determine the page ref
- doc = canvas._doc
- page = doc.thisPageRef()
- field = ButtonField(title, value, xmin, ymin, page, width=width, height=height)
- self.fields.append(field)
- canvas._addAnnotation(field)
- def format(self, document):
- from reportlab.pdfbase.pdfdoc import PDFArray
- proxy = PDFPattern(FormPattern,
- Resources=getattr(self,'resources',None) or FormResources(),
- NeedAppearances=getattr(self,'needAppearances','false'),
- fields=PDFArray(self.fields), SigFlags=getattr(self,'sigFlags',0))
- return proxy.format(document)
- FormPattern = [
- '<<\r\n',
- '/NeedAppearances ',['NeedAppearances'],'\r\n'
- '/DA ', PDFString('/Helv 0 Tf 0 g '), '\r\n',
- '/DR ',["Resources"],'\r\n',
- '/Fields ', ["fields"],'\r\n',
- PDFPatternIf('SigFlags',['\r\n/SigFlags ',['SigFlags']]),
- '>>'
- ]
- def FormFontsDictionary():
- from reportlab.pdfbase.pdfdoc import PDFDictionary
- fontsdictionary = PDFDictionary()
- fontsdictionary.__RefOnly__ = 1
- for fullname, shortname in FORMFONTNAMES.items():
- fontsdictionary[shortname] = FormFont(fullname, shortname)
- fontsdictionary["ZaDb"] = PDFPattern(ZaDbPattern)
- return fontsdictionary
- def FormResources():
- return PDFPattern(FormResourcesDictionaryPattern,
- Encoding=PDFPattern(EncodingPattern,PDFDocEncoding=PDFPattern(PDFDocEncodingPattern)),
- Font=FormFontsDictionary())
- ZaDbPattern = [
- ' <<'
- ' /BaseFont'
- ' /ZapfDingbats'
- ' /Name'
- ' /ZaDb'
- ' /Subtype'
- ' /Type1'
- ' /Type'
- ' /Font'
- '>>']
- FormResourcesDictionaryPattern = [
- '<<',
- ' /Encoding ',
- ["Encoding"], '\r\n',
- ' /Font ',
- ["Font"], '\r\n',
- '>>'
- ]
- FORMFONTNAMES = {
- "Helvetica": "Helv",
- "Helvetica-Bold": "HeBo",
- 'Courier': "Cour",
- 'Courier-Bold': "CoBo",
- 'Courier-Oblique': "CoOb",
- 'Courier-BoldOblique': "CoBO",
- 'Helvetica-Oblique': "HeOb",
- 'Helvetica-BoldOblique': "HeBO",
- 'Times-Roman': "Time",
- 'Times-Bold': "TiBo",
- 'Times-Italic': "TiIt",
- 'Times-BoldItalic': "TiBI",
- }
- EncodingPattern = [
- '<<',
- ' /PDFDocEncoding ',
- ["PDFDocEncoding"], '\r\n',
- '>>',
- ]
- PDFDocEncodingPattern = [
- '<<'
- ' /Differences'
- ' ['
- ' 24'
- ' /breve'
- ' /caron'
- ' /circumflex'
- ' /dotaccent'
- ' /hungarumlaut'
- ' /ogonek'
- ' /ring'
- ' /tilde'
- ' 39'
- ' /quotesingle'
- ' 96'
- ' /grave'
- ' 128'
- ' /bullet'
- ' /dagger'
- ' /daggerdbl'
- ' /ellipsis'
- ' /emdash'
- ' /endash'
- ' /florin'
- ' /fraction'
- ' /guilsinglleft'
- ' /guilsinglright'
- ' /minus'
- ' /perthousand'
- ' /quotedblbase'
- ' /quotedblleft'
- ' /quotedblright'
- ' /quoteleft'
- ' /quoteright'
- ' /quotesinglbase'
- ' /trademark'
- ' /fi'
- ' /fl'
- ' /Lslash'
- ' /OE'
- ' /Scaron'
- ' /Ydieresis'
- ' /Zcaron'
- ' /dotlessi'
- ' /lslash'
- ' /oe'
- ' /scaron'
- ' /zcaron'
- ' 160'
- ' /Euro'
- ' 164'
- ' /currency'
- ' 166'
- ' /brokenbar'
- ' 168'
- ' /dieresis'
- ' /copyright'
- ' /ordfeminine'
- ' 172'
- ' /logicalnot'
- ' /.notdef'
- ' /registered'
- ' /macron'
- ' /degree'
- ' /plusminus'
- ' /twosuperior'
- ' /threesuperior'
- ' /acute'
- ' /mu'
- ' 183'
- ' /periodcentered'
- ' /cedilla'
- ' /onesuperior'
- ' /ordmasculine'
- ' 188'
- ' /onequarter'
- ' /onehalf'
- ' /threequarters'
- ' 192'
- ' /Agrave'
- ' /Aacute'
- ' /Acircumflex'
- ' /Atilde'
- ' /Adieresis'
- ' /Aring'
- ' /AE'
- ' /Ccedilla'
- ' /Egrave'
- ' /Eacute'
- ' /Ecircumflex'
- ' /Edieresis'
- ' /Igrave'
- ' /Iacute'
- ' /Icircumflex'
- ' /Idieresis'
- ' /Eth'
- ' /Ntilde'
- ' /Ograve'
- ' /Oacute'
- ' /Ocircumflex'
- ' /Otilde'
- ' /Odieresis'
- ' /multiply'
- ' /Oslash'
- ' /Ugrave'
- ' /Uacute'
- ' /Ucircumflex'
- ' /Udieresis'
- ' /Yacute'
- ' /Thorn'
- ' /germandbls'
- ' /agrave'
- ' /aacute'
- ' /acircumflex'
- ' /atilde'
- ' /adieresis'
- ' /aring'
- ' /ae'
- ' /ccedilla'
- ' /egrave'
- ' /eacute'
- ' /ecircumflex'
- ' /edieresis'
- ' /igrave'
- ' /iacute'
- ' /icircumflex'
- ' /idieresis'
- ' /eth'
- ' /ntilde'
- ' /ograve'
- ' /oacute'
- ' /ocircumflex'
- ' /otilde'
- ' /odieresis'
- ' /divide'
- ' /oslash'
- ' /ugrave'
- ' /uacute'
- ' /ucircumflex'
- ' /udieresis'
- ' /yacute'
- ' /thorn'
- ' /ydieresis'
- ' ]'
- ' /Type'
- ' /Encoding'
- '>>']
- def FormFont(BaseFont, Name):
- from reportlab.pdfbase.pdfdoc import PDFName
- return PDFPattern(FormFontPattern, BaseFont=PDFName(BaseFont), Name=PDFName(Name), Encoding=PDFPattern(PDFDocEncodingPattern))
- FormFontPattern = [
- '<<',
- ' /BaseFont ',
- ["BaseFont"], '\r\n',
- ' /Encoding ',
- ["Encoding"], '\r\n',
- ' /Name ',
- ["Name"], '\r\n',
- ' /Subtype '
- ' /Type1 '
- ' /Type '
- ' /Font '
- '>>' ]
- def resetPdfForm():
- pass
- from reportlab.rl_config import register_reset
- register_reset(resetPdfForm)
- resetPdfForm()
- def TextField(title, value, xmin, ymin, xmax, ymax, page,
- maxlen=1000000, font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627, multiline=0):
- from reportlab.pdfbase.pdfdoc import PDFString, PDFName
- Flags = 0
- if multiline:
- Flags = Flags | (1<<12) # bit 13 is at position 12 :)
- fontname = FORMFONTNAMES[font]
- return PDFPattern(TextFieldPattern,
- value=PDFString(value), maxlen=maxlen, page=page,
- title=PDFString(title),
- xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
- fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B, Flags=Flags)
- TextFieldPattern = [
- '<<'
- ' /DA'
- ' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)'
- ' /DV ',
- ["value"], '\r\n',
- ' /F 4 /FT /Tx'
- '/MK << /BC [ 0 0 0 ] >>'
- ' /MaxLen ',
- ["maxlen"], '\r\n',
- ' /P ',
- ["page"], '\r\n',
- ' /Rect '
- ' [', ["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], ' ]'
- '/Subtype /Widget'
- ' /T ',
- ["title"], '\r\n',
- ' /Type'
- ' /Annot'
- ' /V ',
- ["value"], '\r\n',
- ' /Ff ',
- ["Flags"],'\r\n',
- '>>']
- def SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
- font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627):
- #print "ARGS", (title, value, options, xmin, ymin, xmax, ymax, page, font, fontsize, R, G, B)
- from reportlab.pdfbase.pdfdoc import PDFString, PDFName, PDFArray
- if value not in options:
- raise ValueError("value %s must be one of options %s" % (repr(value), repr(options)))
- fontname = FORMFONTNAMES[font]
- optionstrings = list(map(PDFString, options))
- optionarray = PDFArray(optionstrings)
- return PDFPattern(SelectFieldPattern,
- Options=optionarray,
- Selected=PDFString(value), Page=page,
- Name=PDFString(title),
- xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
- fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B)
- SelectFieldPattern = [
- '<< % a select list\r\n'
- ' /DA ',
- ' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)\r\n',
- #' (/Helv 12 Tf 0 g)\r\n',
- ' /DV ',
- ["Selected"],'\r\n',
- ' /F ',
- ' 4\r\n',
- ' /FT ',
- ' /Ch\r\n',
- ' /MK ',
- ' <<',
- ' /BC',
- ' [',
- ' 0',
- ' 0',
- ' 0',
- ' ]',
- ' /BG',
- ' [',
- ' 1',
- ' 1',
- ' 1',
- ' ]',
- ' >>\r\n',
- ' /Opt ',
- ["Options"],'\r\n',
- ' /P ',
- ["Page"],'\r\n',
- '/Rect',
- ' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
- ' ] \r\n',
- '/Subtype',
- ' /Widget\r\n',
- ' /T ',
- ["Name"],'\r\n',
- ' /Type ',
- ' /Annot',
- ' /V ',
- ["Selected"],'\r\n',
- '>>']
- def ButtonField(title, value, xmin, ymin, page, width=16.7704, height=14.907):
- if value not in ("Yes", "Off"):
- raise ValueError("button value must be 'Yes' or 'Off': "+repr(value))
- fontSize = (11.3086/14.907)*height
- dx = (3.6017/16.7704)*width
- dy = (3.3881/14.907)*height
- return PDFPattern(ButtonFieldPattern,
- Name=PDFString(title),
- xmin=xmin, ymin=ymin, xmax=xmin+width, ymax=ymin+width,
- Hide=PDFPattern(['<< /S /Hide >>']),
- APDOff=ButtonStream('0.749 g 0 0 %(width)s %(height)s re f\r\n' % vars(), width=width, height=height),
- APDYes=ButtonStream('0.749 g 0 0 %(width)s %(height)s re f q 1 1 %(width)s %(height)s re W n BT /ZaDb %(fontSize)s Tf 0 g 1 0 0 1 %(dx)s %(dy)s Tm (4) Tj ET\r\n' % vars(),
- width=width, height=height),
- APNYes=ButtonStream('q 1 1 %(width)s %(height)s re W n BT /ZaDb %(fontSize)s Tf 0 g 1 0 0 1 %(dx)s %(dy)s Tm (4) Tj ET Q\r\n' % vars(),
- width=width, height=height),
- Value=PDFName(value),
- Page=page)
- ButtonFieldPattern = ['<< ',
- '/AA',
- ' <<',
- ' /D ',
- ["Hide"],'\r\n',
- #' %(imported.18.0)s',
- ' >> ',
- '/AP ',
- ' <<',
- ' /D',
- ' <<',
- ' /Off ',
- #' %(imported.40.0)s',
- ["APDOff"], '\r\n',
- ' /Yes ',
- #' %(imported.39.0)s',
- ["APDYes"], '\r\n',
- ' >>', '\r\n',
- ' /N',
- ' << ',
- ' /Yes ',
- #' %(imported.38.0)s',
- ["APNYes"], '\r\n',
- ' >>',
- ' >>\r\n',
- ' /AS ',
- ["Value"], '\r\n',
- ' /DA ',
- PDFString('/ZaDb 0 Tf 0 g'), '\r\n',
- '/DV ',
- ["Value"], '\r\n',
- '/F ',
- ' 4 ',
- '/FT ',
- ' /Btn ',
- '/H ',
- ' /T ',
- '/MK ',
- ' <<',
- ' /AC (\\376\\377)',
- #PDFString('\376\377'),
- ' /CA ',
- PDFString('4'),
- ' /RC ',
- PDFString('\376\377'),
- ' >> ','\r\n',
- '/P ',
- ["Page"], '\r\n',
- '/Rect',
- ' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
- ' ] ','\r\n',
- '/Subtype',
- ' /Widget ',
- '/T ',
- ["Name"], '\r\n',
- '/Type',
- ' /Annot ',
- '/V ',
- ["Value"], '\r\n',
- ' >>']
- def buttonStreamDictionary(width=16.7704, height=14.907):
- "everything except the length for the button appearance streams"
- result = PDFDictionary()
- result["SubType"] = "/Form"
- result["BBox"] = "[0 0 %(width)s %(height)s]" % vars()
- font = PDFDictionary()
- font["ZaDb"] = PDFPattern(ZaDbPattern)
- resources = PDFDictionary()
- resources["ProcSet"] = "[ /PDF /Text ]"
- resources["Font"] = font
- result["Resources"] = resources
- return result
- def ButtonStream(content, width=16.7704, height=14.907):
- result = PDFStream(buttonStreamDictionary(width=width,height=height), content)
- result.filters = []
- return result
|