pdfform.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. """Support for Acrobat Forms in ReportLab documents
  2. This module is somewhat experimental at this time.
  3. Includes basic support for
  4. textfields,
  5. select fields (drop down lists), and
  6. check buttons.
  7. The public interface consists of functions at the moment.
  8. At some later date these operations may be made into canvas
  9. methods. (comments?)
  10. The ...Absolute(...) functions position the fields with respect
  11. to the absolute canvas coordinate space -- that is, they do not
  12. respect any coordinate transforms in effect for the canvas.
  13. The ...Relative(...) functions position the ONLY THE LOWER LEFT
  14. CORNER of the field using the coordinate transform in effect for
  15. the canvas. THIS WILL ONLY WORK CORRECTLY FOR TRANSLATED COORDINATES
  16. -- THE SHAPE, SIZE, FONTSIZE, AND ORIENTATION OF THE FIELD WILL NOT BE EFFECTED
  17. BY SCALING, ROTATION, SKEWING OR OTHER NON-TRANSLATION COORDINATE
  18. TRANSFORMS.
  19. Please note that all field names (titles) in a given document must be unique.
  20. Textfields and select fields only support the "base 14" canvas fonts
  21. at this time.
  22. See individual function docstrings below for more information.
  23. The function test1(...) generates a simple test file.
  24. THIS CONTRIBUTION WAS COMMISSIONED BY REPORTLAB USERS
  25. WHO WISH TO REMAIN ANONYMOUS.
  26. """
  27. ### NOTE: MAKE THE STRING FORMATS DYNAMIC IN PATTERNS TO SUPPORT ENCRYPTION XXXX
  28. import string
  29. from reportlab.pdfbase.pdfdoc import PDFString, PDFStream, PDFDictionary, PDFName, PDFObject
  30. from reportlab.lib.colors import obj_R_G_B
  31. #==========================public interfaces
  32. def textFieldAbsolute(canvas, title, x, y, width, height, value="", maxlen=1000000, multiline=0):
  33. """Place a text field on the current page
  34. with name title at ABSOLUTE position (x,y) with
  35. dimensions (width, height), using value as the default value and
  36. maxlen as the maximum permissible length. If multiline is set make
  37. it a multiline field.
  38. """
  39. theform = getForm(canvas)
  40. return theform.textField(canvas, title, x, y, x+width, y+height, value, maxlen, multiline)
  41. def textFieldRelative(canvas, title, xR, yR, width, height, value="", maxlen=1000000, multiline=0):
  42. "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
  43. (xA, yA) = canvas.absolutePosition(xR,yR)
  44. return textFieldAbsolute(canvas, title, xA, yA, width, height, value, maxlen, multiline)
  45. def buttonFieldAbsolute(canvas, title, value, x, y, width=16.7704, height=14.907):
  46. """Place a check button field on the current page
  47. with name title and default value value (one of "Yes" or "Off")
  48. at ABSOLUTE position (x,y).
  49. """
  50. theform = getForm(canvas)
  51. return theform.buttonField(canvas, title, value, x, y, width=width, height=height)
  52. def buttonFieldRelative(canvas, title, value, xR, yR, width=16.7704, height=14.907):
  53. "same as buttonFieldAbsolute except the x and y are relative to the canvas coordinate transform"
  54. (xA, yA) = canvas.absolutePosition(xR,yR)
  55. return buttonFieldAbsolute(canvas, title, value, xA, yA, width=width, height=height)
  56. def selectFieldAbsolute(canvas, title, value, options, x, y, width, height):
  57. """Place a select field (drop down list) on the current page
  58. with name title and
  59. with options listed in the sequence options
  60. default value value (must be one of options)
  61. at ABSOLUTE position (x,y) with dimensions (width, height)."""
  62. theform = getForm(canvas)
  63. theform.selectField(canvas, title, value, options, x, y, x+width, y+height)
  64. def selectFieldRelative(canvas, title, value, options, xR, yR, width, height):
  65. "same as textFieldAbsolute except the x and y are relative to the canvas coordinate transform"
  66. (xA, yA) = canvas.absolutePosition(xR,yR)
  67. return selectFieldAbsolute(canvas, title, value, options, xA, yA, width, height)
  68. #==========================end of public interfaces
  69. from reportlab.pdfbase.pdfpattern import PDFPattern, PDFPatternIf
  70. def getForm(canvas):
  71. "get form from canvas, create the form if needed"
  72. try:
  73. return canvas.AcroForm
  74. except AttributeError:
  75. theform = canvas.AcroForm = AcroForm()
  76. # install the form in the document
  77. d = canvas._doc
  78. cat = d._catalog
  79. cat.AcroForm = theform
  80. return theform
  81. class AcroForm(PDFObject):
  82. def __init__(self):
  83. self.fields = []
  84. def textField(self, canvas, title, xmin, ymin, xmax, ymax, value="", maxlen=1000000, multiline=0):
  85. # determine the page ref
  86. doc = canvas._doc
  87. page = doc.thisPageRef()
  88. # determine text info
  89. R, G, B = obj_R_G_B(canvas._fillColorObj)
  90. #print "rgb", (R,G,B)
  91. font = canvas. _fontname
  92. fontsize = canvas. _fontsize
  93. field = TextField(title, value, xmin, ymin, xmax, ymax, page, maxlen,
  94. font, fontsize, R, G, B, multiline)
  95. self.fields.append(field)
  96. canvas._addAnnotation(field)
  97. def selectField(self, canvas, title, value, options, xmin, ymin, xmax, ymax):
  98. # determine the page ref
  99. doc = canvas._doc
  100. page = doc.thisPageRef()
  101. # determine text info
  102. R, G, B = obj_R_G_B(canvas._fillColorObj)
  103. #print "rgb", (R,G,B)
  104. font = canvas. _fontname
  105. fontsize = canvas. _fontsize
  106. field = SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
  107. font=font, fontsize=fontsize, R=R, G=G, B=B)
  108. self.fields.append(field)
  109. canvas._addAnnotation(field)
  110. def buttonField(self, canvas, title, value, xmin, ymin, width=16.7704, height=14.907):
  111. # determine the page ref
  112. doc = canvas._doc
  113. page = doc.thisPageRef()
  114. field = ButtonField(title, value, xmin, ymin, page, width=width, height=height)
  115. self.fields.append(field)
  116. canvas._addAnnotation(field)
  117. def format(self, document):
  118. from reportlab.pdfbase.pdfdoc import PDFArray
  119. proxy = PDFPattern(FormPattern,
  120. Resources=getattr(self,'resources',None) or FormResources(),
  121. NeedAppearances=getattr(self,'needAppearances','false'),
  122. fields=PDFArray(self.fields), SigFlags=getattr(self,'sigFlags',0))
  123. return proxy.format(document)
  124. FormPattern = [
  125. '<<\r\n',
  126. '/NeedAppearances ',['NeedAppearances'],'\r\n'
  127. '/DA ', PDFString('/Helv 0 Tf 0 g '), '\r\n',
  128. '/DR ',["Resources"],'\r\n',
  129. '/Fields ', ["fields"],'\r\n',
  130. PDFPatternIf('SigFlags',['\r\n/SigFlags ',['SigFlags']]),
  131. '>>'
  132. ]
  133. def FormFontsDictionary():
  134. from reportlab.pdfbase.pdfdoc import PDFDictionary
  135. fontsdictionary = PDFDictionary()
  136. fontsdictionary.__RefOnly__ = 1
  137. for fullname, shortname in FORMFONTNAMES.items():
  138. fontsdictionary[shortname] = FormFont(fullname, shortname)
  139. fontsdictionary["ZaDb"] = PDFPattern(ZaDbPattern)
  140. return fontsdictionary
  141. def FormResources():
  142. return PDFPattern(FormResourcesDictionaryPattern,
  143. Encoding=PDFPattern(EncodingPattern,PDFDocEncoding=PDFPattern(PDFDocEncodingPattern)),
  144. Font=FormFontsDictionary())
  145. ZaDbPattern = [
  146. ' <<'
  147. ' /BaseFont'
  148. ' /ZapfDingbats'
  149. ' /Name'
  150. ' /ZaDb'
  151. ' /Subtype'
  152. ' /Type1'
  153. ' /Type'
  154. ' /Font'
  155. '>>']
  156. FormResourcesDictionaryPattern = [
  157. '<<',
  158. ' /Encoding ',
  159. ["Encoding"], '\r\n',
  160. ' /Font ',
  161. ["Font"], '\r\n',
  162. '>>'
  163. ]
  164. FORMFONTNAMES = {
  165. "Helvetica": "Helv",
  166. "Helvetica-Bold": "HeBo",
  167. 'Courier': "Cour",
  168. 'Courier-Bold': "CoBo",
  169. 'Courier-Oblique': "CoOb",
  170. 'Courier-BoldOblique': "CoBO",
  171. 'Helvetica-Oblique': "HeOb",
  172. 'Helvetica-BoldOblique': "HeBO",
  173. 'Times-Roman': "Time",
  174. 'Times-Bold': "TiBo",
  175. 'Times-Italic': "TiIt",
  176. 'Times-BoldItalic': "TiBI",
  177. }
  178. EncodingPattern = [
  179. '<<',
  180. ' /PDFDocEncoding ',
  181. ["PDFDocEncoding"], '\r\n',
  182. '>>',
  183. ]
  184. PDFDocEncodingPattern = [
  185. '<<'
  186. ' /Differences'
  187. ' ['
  188. ' 24'
  189. ' /breve'
  190. ' /caron'
  191. ' /circumflex'
  192. ' /dotaccent'
  193. ' /hungarumlaut'
  194. ' /ogonek'
  195. ' /ring'
  196. ' /tilde'
  197. ' 39'
  198. ' /quotesingle'
  199. ' 96'
  200. ' /grave'
  201. ' 128'
  202. ' /bullet'
  203. ' /dagger'
  204. ' /daggerdbl'
  205. ' /ellipsis'
  206. ' /emdash'
  207. ' /endash'
  208. ' /florin'
  209. ' /fraction'
  210. ' /guilsinglleft'
  211. ' /guilsinglright'
  212. ' /minus'
  213. ' /perthousand'
  214. ' /quotedblbase'
  215. ' /quotedblleft'
  216. ' /quotedblright'
  217. ' /quoteleft'
  218. ' /quoteright'
  219. ' /quotesinglbase'
  220. ' /trademark'
  221. ' /fi'
  222. ' /fl'
  223. ' /Lslash'
  224. ' /OE'
  225. ' /Scaron'
  226. ' /Ydieresis'
  227. ' /Zcaron'
  228. ' /dotlessi'
  229. ' /lslash'
  230. ' /oe'
  231. ' /scaron'
  232. ' /zcaron'
  233. ' 160'
  234. ' /Euro'
  235. ' 164'
  236. ' /currency'
  237. ' 166'
  238. ' /brokenbar'
  239. ' 168'
  240. ' /dieresis'
  241. ' /copyright'
  242. ' /ordfeminine'
  243. ' 172'
  244. ' /logicalnot'
  245. ' /.notdef'
  246. ' /registered'
  247. ' /macron'
  248. ' /degree'
  249. ' /plusminus'
  250. ' /twosuperior'
  251. ' /threesuperior'
  252. ' /acute'
  253. ' /mu'
  254. ' 183'
  255. ' /periodcentered'
  256. ' /cedilla'
  257. ' /onesuperior'
  258. ' /ordmasculine'
  259. ' 188'
  260. ' /onequarter'
  261. ' /onehalf'
  262. ' /threequarters'
  263. ' 192'
  264. ' /Agrave'
  265. ' /Aacute'
  266. ' /Acircumflex'
  267. ' /Atilde'
  268. ' /Adieresis'
  269. ' /Aring'
  270. ' /AE'
  271. ' /Ccedilla'
  272. ' /Egrave'
  273. ' /Eacute'
  274. ' /Ecircumflex'
  275. ' /Edieresis'
  276. ' /Igrave'
  277. ' /Iacute'
  278. ' /Icircumflex'
  279. ' /Idieresis'
  280. ' /Eth'
  281. ' /Ntilde'
  282. ' /Ograve'
  283. ' /Oacute'
  284. ' /Ocircumflex'
  285. ' /Otilde'
  286. ' /Odieresis'
  287. ' /multiply'
  288. ' /Oslash'
  289. ' /Ugrave'
  290. ' /Uacute'
  291. ' /Ucircumflex'
  292. ' /Udieresis'
  293. ' /Yacute'
  294. ' /Thorn'
  295. ' /germandbls'
  296. ' /agrave'
  297. ' /aacute'
  298. ' /acircumflex'
  299. ' /atilde'
  300. ' /adieresis'
  301. ' /aring'
  302. ' /ae'
  303. ' /ccedilla'
  304. ' /egrave'
  305. ' /eacute'
  306. ' /ecircumflex'
  307. ' /edieresis'
  308. ' /igrave'
  309. ' /iacute'
  310. ' /icircumflex'
  311. ' /idieresis'
  312. ' /eth'
  313. ' /ntilde'
  314. ' /ograve'
  315. ' /oacute'
  316. ' /ocircumflex'
  317. ' /otilde'
  318. ' /odieresis'
  319. ' /divide'
  320. ' /oslash'
  321. ' /ugrave'
  322. ' /uacute'
  323. ' /ucircumflex'
  324. ' /udieresis'
  325. ' /yacute'
  326. ' /thorn'
  327. ' /ydieresis'
  328. ' ]'
  329. ' /Type'
  330. ' /Encoding'
  331. '>>']
  332. def FormFont(BaseFont, Name):
  333. from reportlab.pdfbase.pdfdoc import PDFName
  334. return PDFPattern(FormFontPattern, BaseFont=PDFName(BaseFont), Name=PDFName(Name), Encoding=PDFPattern(PDFDocEncodingPattern))
  335. FormFontPattern = [
  336. '<<',
  337. ' /BaseFont ',
  338. ["BaseFont"], '\r\n',
  339. ' /Encoding ',
  340. ["Encoding"], '\r\n',
  341. ' /Name ',
  342. ["Name"], '\r\n',
  343. ' /Subtype '
  344. ' /Type1 '
  345. ' /Type '
  346. ' /Font '
  347. '>>' ]
  348. def resetPdfForm():
  349. pass
  350. from reportlab.rl_config import register_reset
  351. register_reset(resetPdfForm)
  352. resetPdfForm()
  353. def TextField(title, value, xmin, ymin, xmax, ymax, page,
  354. maxlen=1000000, font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627, multiline=0):
  355. from reportlab.pdfbase.pdfdoc import PDFString, PDFName
  356. Flags = 0
  357. if multiline:
  358. Flags = Flags | (1<<12) # bit 13 is at position 12 :)
  359. fontname = FORMFONTNAMES[font]
  360. return PDFPattern(TextFieldPattern,
  361. value=PDFString(value), maxlen=maxlen, page=page,
  362. title=PDFString(title),
  363. xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
  364. fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B, Flags=Flags)
  365. TextFieldPattern = [
  366. '<<'
  367. ' /DA'
  368. ' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)'
  369. ' /DV ',
  370. ["value"], '\r\n',
  371. ' /F 4 /FT /Tx'
  372. '/MK << /BC [ 0 0 0 ] >>'
  373. ' /MaxLen ',
  374. ["maxlen"], '\r\n',
  375. ' /P ',
  376. ["page"], '\r\n',
  377. ' /Rect '
  378. ' [', ["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"], ' ]'
  379. '/Subtype /Widget'
  380. ' /T ',
  381. ["title"], '\r\n',
  382. ' /Type'
  383. ' /Annot'
  384. ' /V ',
  385. ["value"], '\r\n',
  386. ' /Ff ',
  387. ["Flags"],'\r\n',
  388. '>>']
  389. def SelectField(title, value, options, xmin, ymin, xmax, ymax, page,
  390. font="Helvetica-Bold", fontsize=9, R=0, G=0, B=0.627):
  391. #print "ARGS", (title, value, options, xmin, ymin, xmax, ymax, page, font, fontsize, R, G, B)
  392. from reportlab.pdfbase.pdfdoc import PDFString, PDFName, PDFArray
  393. if value not in options:
  394. raise ValueError("value %s must be one of options %s" % (repr(value), repr(options)))
  395. fontname = FORMFONTNAMES[font]
  396. optionstrings = list(map(PDFString, options))
  397. optionarray = PDFArray(optionstrings)
  398. return PDFPattern(SelectFieldPattern,
  399. Options=optionarray,
  400. Selected=PDFString(value), Page=page,
  401. Name=PDFString(title),
  402. xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
  403. fontname=PDFName(fontname), fontsize=fontsize, R=R, G=G, B=B)
  404. SelectFieldPattern = [
  405. '<< % a select list\r\n'
  406. ' /DA ',
  407. ' (', ["fontname"],' ',["fontsize"],' Tf ',["R"],' ',["G"],' ',["B"],' rg)\r\n',
  408. #' (/Helv 12 Tf 0 g)\r\n',
  409. ' /DV ',
  410. ["Selected"],'\r\n',
  411. ' /F ',
  412. ' 4\r\n',
  413. ' /FT ',
  414. ' /Ch\r\n',
  415. ' /MK ',
  416. ' <<',
  417. ' /BC',
  418. ' [',
  419. ' 0',
  420. ' 0',
  421. ' 0',
  422. ' ]',
  423. ' /BG',
  424. ' [',
  425. ' 1',
  426. ' 1',
  427. ' 1',
  428. ' ]',
  429. ' >>\r\n',
  430. ' /Opt ',
  431. ["Options"],'\r\n',
  432. ' /P ',
  433. ["Page"],'\r\n',
  434. '/Rect',
  435. ' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
  436. ' ] \r\n',
  437. '/Subtype',
  438. ' /Widget\r\n',
  439. ' /T ',
  440. ["Name"],'\r\n',
  441. ' /Type ',
  442. ' /Annot',
  443. ' /V ',
  444. ["Selected"],'\r\n',
  445. '>>']
  446. def ButtonField(title, value, xmin, ymin, page, width=16.7704, height=14.907):
  447. if value not in ("Yes", "Off"):
  448. raise ValueError("button value must be 'Yes' or 'Off': "+repr(value))
  449. fontSize = (11.3086/14.907)*height
  450. dx = (3.6017/16.7704)*width
  451. dy = (3.3881/14.907)*height
  452. return PDFPattern(ButtonFieldPattern,
  453. Name=PDFString(title),
  454. xmin=xmin, ymin=ymin, xmax=xmin+width, ymax=ymin+width,
  455. Hide=PDFPattern(['<< /S /Hide >>']),
  456. APDOff=ButtonStream('0.749 g 0 0 %(width)s %(height)s re f\r\n' % vars(), width=width, height=height),
  457. 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(),
  458. width=width, height=height),
  459. 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(),
  460. width=width, height=height),
  461. Value=PDFName(value),
  462. Page=page)
  463. ButtonFieldPattern = ['<< ',
  464. '/AA',
  465. ' <<',
  466. ' /D ',
  467. ["Hide"],'\r\n',
  468. #' %(imported.18.0)s',
  469. ' >> ',
  470. '/AP ',
  471. ' <<',
  472. ' /D',
  473. ' <<',
  474. ' /Off ',
  475. #' %(imported.40.0)s',
  476. ["APDOff"], '\r\n',
  477. ' /Yes ',
  478. #' %(imported.39.0)s',
  479. ["APDYes"], '\r\n',
  480. ' >>', '\r\n',
  481. ' /N',
  482. ' << ',
  483. ' /Yes ',
  484. #' %(imported.38.0)s',
  485. ["APNYes"], '\r\n',
  486. ' >>',
  487. ' >>\r\n',
  488. ' /AS ',
  489. ["Value"], '\r\n',
  490. ' /DA ',
  491. PDFString('/ZaDb 0 Tf 0 g'), '\r\n',
  492. '/DV ',
  493. ["Value"], '\r\n',
  494. '/F ',
  495. ' 4 ',
  496. '/FT ',
  497. ' /Btn ',
  498. '/H ',
  499. ' /T ',
  500. '/MK ',
  501. ' <<',
  502. ' /AC (\\376\\377)',
  503. #PDFString('\376\377'),
  504. ' /CA ',
  505. PDFString('4'),
  506. ' /RC ',
  507. PDFString('\376\377'),
  508. ' >> ','\r\n',
  509. '/P ',
  510. ["Page"], '\r\n',
  511. '/Rect',
  512. ' [',["xmin"], " ", ["ymin"], " ", ["xmax"], " ", ["ymax"],
  513. ' ] ','\r\n',
  514. '/Subtype',
  515. ' /Widget ',
  516. '/T ',
  517. ["Name"], '\r\n',
  518. '/Type',
  519. ' /Annot ',
  520. '/V ',
  521. ["Value"], '\r\n',
  522. ' >>']
  523. def buttonStreamDictionary(width=16.7704, height=14.907):
  524. "everything except the length for the button appearance streams"
  525. result = PDFDictionary()
  526. result["SubType"] = "/Form"
  527. result["BBox"] = "[0 0 %(width)s %(height)s]" % vars()
  528. font = PDFDictionary()
  529. font["ZaDb"] = PDFPattern(ZaDbPattern)
  530. resources = PDFDictionary()
  531. resources["ProcSet"] = "[ /PDF /Text ]"
  532. resources["Font"] = font
  533. result["Resources"] = resources
  534. return result
  535. def ButtonStream(content, width=16.7704, height=14.907):
  536. result = PDFStream(buttonStreamDictionary(width=width,height=height), content)
  537. result.filters = []
  538. return result