odoo/bin/reportlab/pdfbase/pdfform.py

632 lines
16 KiB
Python

"""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 LINEEND, PDFString, PDFStream, PDFDictionary, PDFName
#==========================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):
"""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)
def buttonFieldRelative(canvas, title, value, xR, yR):
"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)
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)
def test1():
from reportlab.pdfgen import canvas
fn = "formtest1.pdf"
c = canvas.Canvas(fn)
# first page
c.setFont("Courier", 10)
c.drawString(100, 500, "hello world")
textFieldAbsolute(c, "fieldA", 100, 600, 100, 20, "default value")
textFieldAbsolute(c, "fieldB", 100, 300, 100, 50, "another default value", multiline=1)
selectFieldAbsolute(c, "fieldC", "France", ["Canada", "France", "China"], 100, 200, 100, 20)
c.rect(100, 600, 100, 20)
buttonFieldAbsolute(c, "field2", "Yes", 100, 700)
c.rect(100, 700, 20, 20)
buttonFieldAbsolute(c, "field3", "Off", 100, 800)
c.rect(100, 800, 20, 20)
# second page
c.showPage()
c.setFont("Helvetica", 7)
c.translate(50, 20)
c.drawString(100, 500, "hello world")
textFieldRelative(c, "fieldA_1", 100, 600, 100, 20, "default value 2")
c.setStrokeColorRGB(1,0,0)
c.setFillColorRGB(0,1,0.5)
textFieldRelative(c, "fieldB_1", 100, 300, 100, 50, "another default value 2", multiline=1)
selectFieldRelative(c, "fieldC_1", "France 1", ["Canada 0", "France 1", "China 2"], 100, 200, 100, 20)
c.rect(100, 600, 100, 20)
buttonFieldRelative(c, "field2_1", "Yes", 100, 700)
c.rect(100, 700, 20, 20)
buttonFieldRelative(c, "field3_1", "Off", 100, 800)
c.rect(100, 800, 20, 20)
c.save()
print "wrote", fn
#==========================end of public interfaces
from pdfpattern import PDFPattern
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:
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) = canvas._fillColorRGB
#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) = canvas._fillColorRGB
#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):
# determine the page ref
doc = canvas._doc
page = doc.thisPageRef()
field = ButtonField(title, value, xmin, ymin, page)
self.fields.append(field)
canvas._addAnnotation(field)
def format(self, document):
from reportlab.pdfbase.pdfdoc import PDFArray
proxy = PDFPattern(FormPattern, Resources=GLOBALRESOURCES, fields=PDFArray(self.fields))
return proxy.format(document)
FormPattern = [
'<<', LINEEND,
' /NeedAppearances true ', LINEEND,
' /DA ', PDFString('/Helv 0 Tf 0 g '), LINEEND,
' /DR ', LINEEND,
["Resources"],
' /Fields ', LINEEND,
["fields"],
'>>'
]
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"] = ZADB
return fontsdictionary
def FormResources():
return PDFPattern(FormResourcesDictionaryPattern,
Encoding=ENCODING, Font=GLOBALFONTSDICTIONARY)
ZaDbPattern = [
' <<'
' /BaseFont'
' /ZapfDingbats'
' /Name'
' /ZaDb'
' /Subtype'
' /Type1'
' /Type'
' /Font'
'>>']
ZADB = PDFPattern(ZaDbPattern)
FormResourcesDictionaryPattern = [
'<<',
' /Encoding ',
["Encoding"], LINEEND,
' /Font ',
["Font"], LINEEND,
'>>'
]
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"], LINEEND,
'>>',
]
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'
'>>']
# global constant
PDFDOCENC = PDFPattern(PDFDocEncodingPattern)
# global constant
ENCODING = PDFPattern(EncodingPattern, PDFDocEncoding=PDFDOCENC)
def FormFont(BaseFont, Name):
from reportlab.pdfbase.pdfdoc import PDFName
return PDFPattern(FormFontPattern, BaseFont=PDFName(BaseFont), Name=PDFName(Name), Encoding=PDFDOCENC)
FormFontPattern = [
'<<',
' /BaseFont ',
["BaseFont"], LINEEND,
' /Encoding ',
["Encoding"], LINEEND,
' /Name ',
["Name"], LINEEND,
' /Subtype '
' /Type1 '
' /Type '
' /Font '
'>>' ]
# global constants
GLOBALFONTSDICTIONARY = FormFontsDictionary()
GLOBALRESOURCES = FormResources()
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"], LINEEND,
' /F 4 /FT /Tx'
'/MK << /BC [ 0 0 0 ] >>'
' /MaxLen ',
["maxlen"], LINEEND,
' /P ',
["page"], LINEEND,
' /Rect '
' [', ["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], ' ]'
'/Subtype /Widget'
' /T ',
["title"], LINEEND,
' /Type'
' /Annot'
' /V ',
["value"], LINEEND,
' /Ff ',
["Flags"],LINEEND,
'>>']
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 = 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',LINEEND,
' /DA ',
' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)',LINEEND,
#' (/Helv 12 Tf 0 g)',LINEEND,
' /DV ',
["Selected"],LINEEND,
' /F ',
' 4',LINEEND,
' /FT ',
' /Ch',LINEEND,
' /MK ',
' <<',
' /BC',
' [',
' 0',
' 0',
' 0',
' ]',
' /BG',
' [',
' 1',
' 1',
' 1',
' ]',
' >>',LINEEND,
' /Opt ',
["Options"],LINEEND,
' /P ',
["Page"],LINEEND,
'/Rect',
' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
' ] ',LINEEND,
'/Subtype',
' /Widget',LINEEND,
' /T ',
["Name"],LINEEND,
' /Type ',
' /Annot',
' /V ',
["Selected"],LINEEND,
'>>']
def ButtonField(title, value, xmin, ymin, page):
if value not in ("Yes", "Off"):
raise ValueError, "button value must be 'Yes' or 'Off': "+repr(value)
(dx, dy) = (16.77036, 14.90698)
return PDFPattern(ButtonFieldPattern,
Name=PDFString(title),
xmin=xmin, ymin=ymin, xmax=xmin+dx, ymax=ymin+dy,
Hide=HIDE,
APDOff=APDOFF,
APDYes=APDYES,
APNYes=APNYES,
Value=PDFName(value),
Page=page)
ButtonFieldPattern = ['<< ',
'/AA',
' <<',
' /D ',
["Hide"], LINEEND,
#' %(imported.18.0)s',
' >> ',
'/AP ',
' <<',
' /D',
' <<',
' /Off ',
#' %(imported.40.0)s',
["APDOff"], LINEEND,
' /Yes ',
#' %(imported.39.0)s',
["APDYes"], LINEEND,
' >>', LINEEND,
' /N',
' << ',
' /Yes ',
#' %(imported.38.0)s',
["APNYes"], LINEEND,
' >>',
' >>', LINEEND,
' /AS ',
["Value"], LINEEND,
' /DA ',
PDFString('/ZaDb 0 Tf 0 g'), LINEEND,
'/DV ',
["Value"], LINEEND,
'/F ',
' 4 ',
'/FT ',
' /Btn ',
'/H ',
' /T ',
'/MK ',
' <<',
' /AC (\\376\\377)',
#PDFString('\376\377'),
' /CA ',
PDFString('4'),
' /RC ',
PDFString('\376\377'),
' >> ',LINEEND,
'/P ',
["Page"], LINEEND,
'/Rect',
' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
' ] ',LINEEND,
'/Subtype',
' /Widget ',
'/T ',
["Name"], LINEEND,
'/Type',
' /Annot ',
'/V ',
["Value"], LINEEND,
' >>']
HIDE = PDFPattern([
'<< '
'/S '
' /Hide '
'>>'])
def buttonStreamDictionary():
"everything except the length for the button appearance streams"
result = PDFDictionary()
result["SubType"] = "/Form"
result["BBox"] = "[0 0 16.77036 14.90698]"
font = PDFDictionary()
font["ZaDb"] = ZADB
resources = PDFDictionary()
resources["ProcSet"] = "[ /PDF /Text ]"
resources["Font"] = font
result["Resources"] = resources
return result
def ButtonStream(content):
dict = buttonStreamDictionary()
result = PDFStream(dict, content)
result.filters = []
return result
APDOFF = ButtonStream('0.749 g 0 0 16.7704 14.907 re f'+LINEEND)
APDYES = ButtonStream(
'0.749 g 0 0 16.7704 14.907 re f q 1 1 14.7704 12.907 re W '+
'n BT /ZaDb 11.3086 Tf 0 g 1 0 0 1 3.6017 3.3881 Tm (4) Tj ET'+LINEEND)
APNYES = ButtonStream(
'q 1 1 14.7704 12.907 re W n BT /ZaDb 11.3086 Tf 0 g 1 0 0 1 3.6017 3.3881 Tm (4) Tj ET Q'+LINEEND)
#==== script interpretation
if __name__=="__main__":
test1()