add a convenience function for mering attrs with default attrs, checking, and None...
[PyX.git] / pdfextra.py
blob7d5b2653c67bfa3a40724b232d11c0c6e9db01d9
1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2006 Michael Schindler <m-schindler@users.sourceforge.net>
6 # This file is part of PyX (http://pyx.sourceforge.net/).
8 # PyX is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # PyX is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with PyX; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 import io, math
23 from . import baseclasses, bbox, pdfwriter, color, unit
24 from .font.font import PDFHelvetica, PDFZapfDingbats
26 # TODO:
27 # - discuss behaviour under transformations with André and Jörg
28 # - what about fillstyles here?
29 # - where should e.g. a font be added to the registry:
30 # in processPDF or in __init__ of the PDF-item?
31 # - test for double occurrance of field names:
32 # this leads to wrong/no display
34 # TODO horizontal alignment in textfields
37 # flags for annotations:
38 PDFannotflags = [("invisible", 0), ("hidden", 1), ("printable", 2),
39 ("nozoom", 3), ("norotate", 4), ("noview", 5), ("readonly", 6)]
41 # flags for form fields
42 PDFformflags = [("readonly", 0), ("required", 1), ("noexport", 2),
43 # flags for the button field
44 ("notoggletooff", 14), ("radio", 15), ("pushbutton", 16),
45 # flags for the choice list field
46 ("combo", 17), ("edit", 18), ("sort", 19), ("multiselect", 21),
47 # flags for the text field
48 ("multiline", 12), ("password", 13), ("fileselect", 20), ("donotspellcheck", 22),
49 ("donotscroll", 23)]
52 class flag: # <<<
53 """A helper class for handling flags in pdf forms and annotations"""
55 def __init__(self, value=None):
56 self.value = value
58 def is_set(self, bit):
59 return self.value is not None and (self.value & 1<<bit) == 1<<bit
61 def set(self, bit):
62 if self.value is None:
63 self.value = 1<<bit
64 else:
65 self.value = self.value | 1<<bit
67 def unset(self, bit):
68 if self.value is not None:
69 self.value = self.value & ~(1<<bit)
71 def __int__(self):
72 return self.value
74 def __str__(self):
75 return self.value.__str__()
76 # >>>
77 def _pdfflags(flags): # <<<
78 """Splits flags into annotation/form part
79 the flag for the annotation dictionary
80 the flag for the form (field) dictionary
82 All flags are handled equally here, independent of their use
83 for the specific form field.
84 """
86 # we initialize with 0 and set only those flags which are not 0
87 annotflag = flag(value=0)
88 formflag = flag(value=0)
90 for key, value in PDFannotflags:
91 if key in flags and flags[key]:
92 annotflag.set(value)
94 for key, value in PDFformflags:
95 if key in flags and flags[key]:
96 formflag.set(value)
98 return int(annotflag), int(formflag)
99 # >>>
100 def _pdfalignment(string): # <<<
101 alignflag = 0
102 if string == "c":
103 alignflag = 1
104 elif string == "r":
105 alignflag = 2
106 return alignflag
107 # >>>
108 def _topt(value, type="u", un="cm"): # <<<
109 if isinstance(value, unit.length):
110 return unit.topt(value)
111 else:
112 return unit.topt(unit.length(value, type, un))
113 # >>>
114 def _simplestring(text): # <<<
115 result = ""
116 for x in text:
117 if x.isalnum():
118 result += x
119 return result
120 # >>>
121 def _sizetrafo(s, tr): # <<<
122 x1, y1 = tr.apply_pt(s, s)
123 x0, y0 = tr.apply_pt(0, 0)
124 return math.hypot(x1 - x0, y1 - y0) * math.sqrt(0.5)
125 # >>>
128 class formfield(baseclasses.canvasitem): # <<<
129 """Base class for acroforms"""
131 defaultflags = dict()
133 def selectflags(self, flags):
134 newflags = dict(**self.defaultflags)
135 # overwrite the default flags with given values:
136 for key, value in list(flags.items()):
137 if key in newflags:
138 newflags[key] = value
139 else:
140 raise RuntimeError("unknown argument \"%s\" to formfield" % key)
141 return newflags
143 def bbox(self):
144 return bbox.bbox_pt(self.llx_pt, self.lly_pt, self.urx_pt, self.ury_pt)
146 def processPS(self, file, writer, context, registry, bbox):
147 raise RuntimeError("postscript output of forms is not supported")
148 # >>>
151 class textfield(formfield): # <<<
152 """An interactive pdf form field for text input.
154 The "name" is used for the graphical user interface and for exporing the input data.
155 Note that the behaviour under rotations is undefined."""
157 defaultflags = dict(invisible=0, hidden=0, printable=1, nozoom=0, norotate=0, noview=0,
158 readonly=0, required=0, noexport=0, multiline=0, password=0, fileselect=0,
159 donotspellcheck=1, donotscroll=0)
161 def __init__(self, x, y, width, height, name, defaultvalue="", fontsize=10, font=PDFHelvetica,
162 fontrelleading=1.16, borderwidth=0, align="l", **flags):
164 self.llx_pt, self.lly_pt = _topt(x), _topt(y)
165 self.urx_pt, self.ury_pt = _topt(x+width), _topt(y+height)
166 self.name = name
167 self.defaultvalue = defaultvalue
168 self.fontsize_pt = _topt(fontsize, "x", "pt")
169 self.font = font
170 self.fontrelleading = fontrelleading
171 self.borderwidth_pt = _topt(borderwidth, "x", "pt")
172 self.align = align
173 self.flags = self.selectflags(flags)
175 def processPDF(self, file, writer, context, registry, bbox):
176 # the bounding box is transformed by the canvas
177 bbox += self.bbox()
179 # the annotation rectangle must be transformed separately:
180 llx_pt, lly_pt = context.trafo.apply_pt(self.llx_pt, self.lly_pt)
181 urx_pt, ury_pt = context.trafo.apply_pt(self.urx_pt, self.ury_pt)
182 fontsize_pt = _sizetrafo(self.fontsize_pt, context.trafo)
183 borderwidth_pt = _sizetrafo(self.borderwidth_pt, context.trafo)
185 # we create numbers from the flags given
186 annotflag, formflag = _pdfflags(self.flags)
187 alignflag = _pdfalignment(self.align)
189 registry.add(PDFtextfield((llx_pt, lly_pt, urx_pt, ury_pt), self.name, self.defaultvalue,
190 fontsize_pt, self.font, self.fontrelleading*fontsize_pt,
191 borderwidth_pt, (not self.flags["multiline"]),
192 alignflag, annotflag, formflag, context.fillstyles, writer, registry))
193 # >>>
194 class PDFtextfield(pdfwriter.PDFobject): # <<<
196 def __init__(self, bb_pt, name, defaultvalue, fontsize, font, fontleading,
197 borderwidth, vcenter,
198 alignflag, annotflag, formflag, fillstyles, writer, registry):
200 pdfwriter.PDFobject.__init__(self, "formfield_text")
202 # append this formfield to the global document form
203 # and to the annotation list of the page:
204 self.PDFform = None
205 for object in registry.objects:
206 if object.type == "form":
207 object.append(self)
208 self.PDFform = object
209 elif object.type == "annotations":
210 object.append(self)
212 self.name = name
213 self.bb_pt = bb_pt
214 self.defaultvalue = defaultvalue
215 self.fontsize = fontsize
216 self.font = font
217 if self.font is None:
218 self.font = PDFHelvetica
219 self.fontleading = fontleading
220 self.borderwidth = borderwidth
221 self.alignflag = alignflag
222 self.formflag = formflag
223 self.annotflag = annotflag
225 self.registry = pdfwriter.PDFregistry()
226 self.registry.addresource("Font", self.font.name, self.font, procset="Text")
227 self.registry.add(self.font)
229 if self.defaultvalue:
230 text = self.defaultvalue.split("\n")
231 self.defaulttext = PDFdefaulttext(writer, registry, self.fontsize, self.font,
232 self.fontleading, text, self.bb_pt, self.borderwidth, vcenter)
233 self.registry.add(self.defaulttext)
234 else:
235 self.defaulttext = None
237 # process some fillstyles:
238 fillstring = io.StringIO()
239 for attr in fillstyles:
240 if 1:#isinstance(attr, color.color):
241 cont = pdfwriter.context()
242 cont.fillattr = 1
243 cont.strokeattr = 0
244 attr.processPDF(fillstring, writer, cont, self.registry, bbox)
245 self.fillstyles = fillstring.getvalue()
246 fillstring.close()
248 registry.mergeregistry(self.registry)
250 def write(self, file, writer, registry):
251 ### the dictionary entries for the annotation
252 file.write("<</Type /Annot\n")
253 file.write("/P %d 0 R\n" % registry.getrefno(self.PDFform)) # reference to the page objects
254 file.write("/Rect [%f %f %f %f]\n" % self.bb_pt) # the annotation rectangle
255 #ile.write("/BS <</W 0 /S /S>>\n") # border style dictionary
256 file.write("/Border [0 0 %f]\n" % self.borderwidth) # border style
257 file.write("/F %d\n" % self.annotflag)
258 ### the dictionary entries for the widget annotations
259 file.write("/Subtype /Widget\n")
260 file.write("/H /N\n") # highlight behaviour
261 if self.defaulttext:
262 file.write("/AP <</N %d 0 R >>\n" % registry.getrefno(self.defaulttext)) # appearance dictionary
263 ### the dictionary entries for the form field
264 file.write("/FT /Tx\n") # type of the form field
265 file.write("/T (%s)\n" % self.name) # partial field name
266 file.write("/TU (%s)\n" % self.name) # field name for the user-interface
267 file.write("/TM (%s)\n" % self.name) # field name for exporting the data
268 file.write("/V (%s)\n" % self.defaultvalue) # starting value
269 file.write("/DV (%s)\n" % self.defaultvalue) # reset value
270 file.write("/Ff %d\n" % self.formflag) # flags for various purposes
271 ### the dictionary entries for the text field
272 file.write("/DR ")
273 self.registry.writeresources(file) # default resources for appearance
274 file.write("/DA (%s /%s %f Tf %f TL)\n" % (self.fillstyles, self.font.name, self.fontsize, self.fontleading)) # default appearance string
275 file.write("/Q %d\n" % self.alignflag)
276 file.write(">>\n")
277 # >>>
278 class PDFdefaulttext(pdfwriter.PDFobject): # <<<
280 def __init__(self, writer, registry, fontsize, font, fontleading, texts, bb, borderwidth, vcenter):
282 pdfwriter.PDFobject.__init__(self, "defaulttext")
283 self.font = font
284 self.fontsize = fontsize
285 self.fontleading = fontleading
287 self.registry = pdfwriter.PDFregistry()
288 self.registry.addresource("Font", self.font.name, self.font, procset="Text")
289 self.registry.add(self.font)
291 self.bb = (0, 0, bb[2] - bb[0], bb[3] - bb[1])
292 self.texts = [t for t in texts if t]
294 self.borderwidth = borderwidth
295 # try to imitate the shifting of PDF:
296 # the font orientation point is on the baseline of the text
297 self.hshift = 2*self.borderwidth
298 if vcenter:
299 baselinevrel = 0.215
300 self.vshift = 0.5 * (bb[3] - bb[1]) + (len(self.texts) / 2.0 - 1)*self.fontleading + baselinevrel*self.fontsize
301 elif (bb[3] - bb[1]) < self.fontleading + 4*self.borderwidth:
302 baselinevrel = 0.215
303 self.vshift = 2*self.borderwidth + baselinevrel * self.fontsize
304 #self.vshift = 0.5 * (bb[3] - bb[1]) - (0.5 - baselinevrel - 0.5*addrelshift)*self.fontsize
305 else:
306 baselinevrel = 0.215
307 addrelshift = 0.215
308 self.vshift = (bb[3] - bb[1]) - 2*self.borderwidth - self.fontleading + (baselinevrel - addrelshift)*self.fontsize
310 registry.mergeregistry(self.registry)
313 def write(self, file, writer, registry):
314 content = "/Tx BMC q BT /%s %f Tf %f TL %f %f Td (%s) Tj" % (self.font.name, self.fontsize, self.fontleading, self.hshift, self.vshift, self.texts[0])
315 for text in self.texts[1:]:
316 content += " (%s)'" % (text)
317 content += " ET Q EMC\n"
318 if writer.compress:
319 import zlib
320 content = zlib.compress(content)
322 file.write("<<\n")
323 file.write("/Type /XObject\n")
324 file.write("/Subtype /Form\n")
325 file.write("/BBox [%f %f %f %f]\n" % self.bb)
326 #ile.write("/Matrix [0.98 0.17 -0.17 0.98 0 0]\n")
327 file.write("/Resources ")
328 self.registry.writeresources(file) # default resources for appearance
329 file.write("/Length %i\n" % len(content))
330 if writer.compress:
331 file.write("/Filter /FlateDecode\n")
332 file.write(">>\n"
333 "stream\n")
334 file.write(content)
335 file.write("endstream\n")
336 # >>>
339 class radiobuttons(formfield): # <<<
341 """A set of related buttons that can each be on or off.
343 Typically, at most one radio button in a set may be on at any
344 given time, and selecting any one of the buttons
345 automatically deselects all the others.
347 Note that the behaviour under rotations is undefined."""
349 defaultflags = dict(invisible=0, hidden=0, printable=1, nozoom=0,
350 norotate=0, noview=0, readonly=0, required=0, noexport=0, notoggletooff=0)
352 def __init__(self, positions, name, values, defaultvalue=None, size=10, baselinerelpos=0.2, **flags):
354 self.name = name
355 self.size_pt = _topt(size, "x", "pt")
356 self.positions_pt = [(_topt(x), _topt(y) - baselinerelpos*self.size_pt) for x, y in positions]
357 self.flags = self.selectflags(flags)
358 self.flags["radio"] = 1
359 self.values = values
360 self.defaultvalue = defaultvalue
362 def bbox(self):
363 llx = min([x[0] for x in self.positions_pt])
364 lly = min([x[1] for x in self.positions_pt])
365 urx = max([x[0] for x in self.positions_pt]) + self.size_pt
366 ury = max([x[1] for x in self.positions_pt]) + self.size_pt
367 return bbox.bbox_pt(llx, lly, urx, ury)
369 def processPDF(self, file, writer, context, registry, bbox):
370 # the bbox is transformed by the canvas
371 bbox += self.bbox()
373 # the annotation rectangle must be transformed separately:
374 positions_pt = [context.trafo.apply_pt(x, y) for x, y in self.positions_pt]
375 size_pt = _sizetrafo(self.size_pt, context.trafo)
377 # we create numbers from the flags given
378 annotflag, formflag = _pdfflags(self.flags)
380 onstate = PDFButtonState(writer, registry,
381 10, PDFZapfDingbats, bgchar="m", fgchar="8",
382 bgscale=1.1, bgrelshift=(0, 0.18), fgrelshift=(0.12, 0.26))
383 offstate = PDFButtonState(writer, registry,
384 10, PDFZapfDingbats, bgchar="m", fgchar=None,
385 bgscale=1.1, bgrelshift=(0, 0.18))
386 registry.add(onstate)
387 registry.add(offstate)
389 registry.add(PDFbuttonlist(positions_pt, self.name, size_pt, self.values, self.defaultvalue,
390 annotflag, formflag, onstate, offstate, writer, registry))
391 # >>>
392 class checkbox(formfield): # <<<
394 """Toggles between two states, on and off
396 Note that the behaviour under rotations is undefined."""
398 defaultflags = dict(invisible=0, hidden=0, printable=1, nozoom=0,
399 norotate=0, noview=0, readonly=0, required=0, noexport=0)
401 def __init__(self, x, y, name, defaulton=0, size=10, baselinerelpos=0.2, **flags):
403 self.name = name
404 self.size_pt = _topt(size, "x", "pt")
405 self.llx_pt, self.lly_pt = _topt(x), _topt(y) - baselinerelpos*self.size_pt
406 self.urx_pt, self.ury_pt = self.llx_pt + self.size_pt, self.lly_pt + self.size_pt
407 self.flags = self.selectflags(flags)
408 self.defaulton = defaulton
410 def processPDF(self, file, writer, context, registry, bbox):
411 # the bbox is transformed by the canvas
412 bbox += self.bbox()
414 # the annotation rectangle must be transformed separately:
415 positions_pt = [context.trafo.apply_pt(self.llx_pt, self.lly_pt)]
416 size_pt = _sizetrafo(self.size_pt, context.trafo)
418 # we create numbers from the flags given
419 annotflag, formflag = _pdfflags(self.flags)
421 onstate = PDFButtonState(writer, registry,
422 10, PDFZapfDingbats, bgchar="o", fgchar="4",
423 bgscale=1.2, bgrelshift=(0, 0.08), fgscale=0.9, fgrelshift=(0.15, 0.25))
424 offstate = PDFButtonState(writer, registry,
425 10, PDFZapfDingbats, bgchar="o", fgchar=None,
426 bgscale=1.2, bgrelshift=(0, 0.08))
427 registry.add(onstate)
428 registry.add(offstate)
430 if self.defaulton:
431 default = "Yes"
432 else:
433 default = "Off"
435 registry.add(PDFbuttonlist(positions_pt, self.name, size_pt, ["Yes"], default,
436 annotflag, formflag, onstate, offstate, writer, registry))
437 # >>>
438 class PDFbuttonlist(pdfwriter.PDFobject): # <<<
440 def __init__(self, positions_pt, name, size_pt, values, defaultvalue, annotflag, formflag,
441 onstate, offstate, writer, registry):
443 pdfwriter.PDFobject.__init__(self, "formfield_buttonlist")
445 # append this formfield to the global document form
446 # but we do not treat this as a fully valid annotation field
447 for object in registry.objects:
448 if object.type == "form":
449 object.append(self)
451 self.name = name
452 self.formflag = formflag
453 self.annotflag = annotflag
455 self.size_pt = size_pt
456 self.defaultvalue = defaultvalue
457 self.onstate = onstate
458 self.offstate = offstate
460 self.checkboxes = []
461 for i, pos_pt, value in zip(list(range(len(values))), positions_pt, values):
462 chbox = PDFcheckboxfield(pos_pt, value, size_pt, _simplestring(value), (value == defaultvalue),
463 self, self.onstate, self.offstate, self.annotflag, self.formflag, writer, registry)
464 self.checkboxes.append(chbox)
465 registry.add(chbox)
467 def write(self, file, writer, registry):
468 ### implementation note: There are some (undocumented) PDF flaws which
469 ### do not allow to inherit certain variables:
470 ### * The parent button may not have /Ff (otherwise, notoggletooff fails)
471 ### * The Kids of a radio button may not have a /T on their own (otherwise, they are not displayed)
472 ### * The /BS and /Border do not draw anything.
473 ### Nevertheless, the border width of /Border is used
475 ### the dictionary entries for the annotation
476 file.write("<<\n")
477 ### the dictionary entries for the form field
478 file.write("/FT /Btn\n") # type of the form field
479 file.write("/Kids [%s]\n" % " ".join(["%d 0 R" % registry.getrefno(x) for x in self.checkboxes]))
480 file.write("/T (%s)\n" % self.name) # partial field name
481 file.write("/TU (%s)\n" % self.name) # field name for the user-interface
482 file.write("/TM (%s)\n" % self.name) # field name for exporting the data
483 ### the dictionary entries for the radiobuttons field
484 file.write("/V /%s\n" % self.defaultvalue)
485 file.write(">>\n")
486 # >>>
487 class PDFcheckboxfield(pdfwriter.PDFobject): # <<<
489 def __init__(self, pos_pt, name, size_pt, valuename, defaulton, parent, onstate, offstate, annotflag, formflag, writer, registry):
491 pdfwriter.PDFobject.__init__(self, "formfield_checkbox")
493 # we treat this as an annotation only, since the parent is
494 # already in the form field
495 self.PDFform = None
496 for object in registry.objects:
497 if object.type == "form":
498 assert self.PDFform is None
499 self.PDFform = object
500 if object.type == "annotations":
501 object.append(self)
503 self.bb_pt = (pos_pt[0], pos_pt[1], pos_pt[0] + size_pt, pos_pt[1] + size_pt)
504 self.name = name
505 self.size_pt = size_pt
506 self.valuename = valuename
507 if defaulton:
508 self.defaultvalue = self.valuename
509 else:
510 self.defaultvalue = "Off"
511 self.parent = parent
512 self.onstate = onstate
513 self.offstate = offstate
514 self.annotflag = annotflag
515 self.formflag = formflag
517 def write(self, file, writer, registry):
518 ### the dictionary entries for the annotation
519 file.write("<<\n")
520 file.write("/Type /Annot\n")
521 file.write("/Subtype /Widget\n")
522 file.write("/P %d 0 R\n" % registry.getrefno(self.PDFform)) # reference to the page objects
523 file.write("/Rect [%f %f %f %f]\n" % self.bb_pt) # the annotation rectangle
524 file.write("/F %d\n" % self.annotflag) # flags
525 ### the dictionary entries for the widget annotations
526 file.write("/H /N\n") # hightlight behaviour
527 ### the dictionary entries for the form field
528 file.write("/FT /Btn\n") # type of the form field
529 file.write("/Parent %d 0 R\n" % registry.getrefno(self.parent)) # only for hierarchy
530 file.write("/AP << /N << /%s %d 0 R /Off %d 0 R >> >>\n" % (self.valuename, registry.getrefno(self.onstate), registry.getrefno(self.offstate)))
531 file.write("/AS /%s\n" % self.defaultvalue)
532 file.write("/Ff %d\n" % self.formflag) # Ff may not come from parent!
533 file.write(">>\n")
534 # >>>
535 class PDFButtonState(pdfwriter.PDFobject): # <<<
537 def __init__(self, writer, registry, fontsize, font, bgchar, fgchar,
538 bgscale=None, bgrelshift=None, fgscale=None, fgrelshift=None):
540 pdfwriter.PDFobject.__init__(self, "buttonstate", "buttonstate" + "_".join(map(str, list(map(id, [fontsize, font, bgchar, fgchar, bgscale, bgrelshift, fgscale, fgrelshift])))))
541 self.font = font
542 self.fontsize = fontsize
543 registry.addresource("Font", self.font.name, self.font, procset="Text")
544 registry.add(self.font)
545 self.bb = 0, 0, fontsize, fontsize
546 self.bgchar = bgchar
547 self.fgchar = fgchar
549 if bgscale is None and bgrelshift is not None:
550 bgscale = 1
551 if bgscale is not None and bgrelshift is None:
552 bgrelshift = 0, 0
553 if bgscale is not None:
554 self.bgtrafo = "%f 0 0 %f %f %f Tm" % (bgscale, bgscale, bgrelshift[0]*self.fontsize, bgrelshift[1]*self.fontsize)
555 else:
556 self.bgtrafo = ""
558 if fgscale is None and fgrelshift is not None:
559 fgscale = 1
560 if fgscale is not None and fgrelshift is None:
561 fgrelshift = 0, 0
562 if fgscale is not None:
563 self.fgtrafo = "%f 0 0 %f %f %f Tm" % (fgscale, fgscale, fgrelshift[0]*self.fontsize, fgrelshift[1]*self.fontsize)
564 else:
565 self.fgtrafo = ""
567 def write(self, file, writer, registry):
568 content = ""
569 if self.bgchar:
570 content += "q BT /%s %f Tf %s (%s) Tj ET Q\n" % (self.font.name, self.fontsize, self.bgtrafo, self.bgchar)
571 if self.fgchar:
572 content += "q BT /%s %f Tf %s (%s) Tj ET Q\n" % (self.font.name, self.fontsize, self.fgtrafo, self.fgchar)
573 if writer.compress:
574 import zlib
575 content = zlib.compress(content)
577 file.write("<<\n")
578 file.write("/Type /XObject\n")
579 file.write("/Subtype /Form\n")
580 file.write("/BBox [%f %f %f %f]\n" % self.bb)
581 #ile.write("/Matrix [0.98 0.17 -0.17 0.98 0 0]\n")
582 file.write("/Resources <</Font << /%s %d 0 R >> /ProcSet [/PDF /Text] >>\n" %
583 (self.font.name, registry.getrefno(self.font)))
584 file.write("/Length %i\n" % len(content))
585 if writer.compress:
586 file.write("/Filter /FlateDecode\n")
587 file.write(">>\n"
588 "stream\n")
589 file.write(content)
590 file.write("endstream\n")
593 ## Zapf Dingbats symbols for further buttonstates:
594 # "3" = thin checkmark
595 # "4" = thick checkmark
596 # "5" = thin large cross
597 # "6" = thick large cross
598 # "7" = thin small cross
599 # "8" = thick small cross
600 # "l" = filled circle
601 # "m" = empty circle
602 # "n" = filled rectangle
603 # "o" = empty rectangle (shadow bottom right)
604 # "p" = empty rectangle (shadow top right)
605 # "q" = empty box (to bottom right)
606 # "r" = empty box (to top right)
607 # >>>
610 class choicefield(formfield): # <<<
611 """An interactive pdf form field for text input.
613 The name is used for the graphical user interface and for exporing the input data.
614 Note that the behaviour under rotations is undefined."""
616 defaultflags = dict(invisible=0, hidden=0, printable=1, nozoom=0,
617 norotate=0, noview=0, readonly=0, required=0, noexport=0, combo=1,
618 edit=0, sort=0, multiselect=0, donotspellcheck=1)
620 def __init__(self, x, y, width, height, name, values, defaultvalue=None, fontsize=10, font=None,
621 borderwidth=0, align="l", **flags):
623 self.llx_pt, self.lly_pt = _topt(x), _topt(y)
624 self.urx_pt, self.ury_pt = _topt(x+width), _topt(y+height)
625 self.name = name
626 self.values = values
627 self.defaultvalue = defaultvalue
628 self.fontsize_pt = _topt(fontsize, "x", "pt")
629 self.font = font # TODO: add the generic fonts
630 self.borderwidth_pt = _topt(borderwidth, "x", "pt")
631 self.flags = self.selectflags(flags)
632 self.align = align
634 def processPDF(self, file, writer, context, registry, bbox):
635 # the bounding box is transformed by the canvas
636 bbox += self.bbox()
638 # the annotation rectangle must be transformed separately:
639 llx_pt, lly_pt = context.trafo.apply_pt(self.llx_pt, self.lly_pt)
640 urx_pt, ury_pt = context.trafo.apply_pt(self.urx_pt, self.ury_pt)
641 fontsize_pt = _sizetrafo(self.fontsize_pt, context.trafo)
642 borderwidth_pt = _sizetrafo(self.borderwidth_pt, context.trafo)
644 # we create numbers from the flags given
645 annotflag, formflag = _pdfflags(self.flags)
646 alignflag = _pdfalignment(self.align)
648 registry.add(PDFchoicefield((llx_pt, lly_pt, urx_pt, ury_pt),
649 self.name, self.values, self.defaultvalue, fontsize_pt, self.font,
650 borderwidth_pt, alignflag, annotflag, formflag, writer, registry))
651 # >>>
652 class PDFchoicefield(pdfwriter.PDFobject): # <<<
654 def __init__(self, bb_pt, name, values, defaultvalue, fontsize, font,
655 borderwidth_pt, alignflag, annotflag, formflag, writer, registry):
657 pdfwriter.PDFobject.__init__(self, "formfield_choice")
659 # append this formfield to the global document form
660 # and to the annotation list of the page:
661 self.PDFform = None
662 for object in registry.objects:
663 if object.type == "form":
664 object.append(self)
665 self.PDFform = object
666 elif object.type == "annotations":
667 object.append(self)
669 self.name = name
670 self.bb_pt = bb_pt
671 self.values = values
672 self.defaultvalue = defaultvalue
673 self.fontsize = fontsize
674 self.font = font
675 if self.font is None:
676 self.font = PDFHelvetica
677 registry.addresource("Font", self.font.name, self.font, procset="Text")
678 registry.add(self.font)
679 self.borderwidth_pt = borderwidth_pt
680 self.alignflag = alignflag
681 self.formflag = formflag
682 self.annotflag = annotflag
684 def write(self, file, writer, registry):
685 ### the dictionary entries for the annotation
686 file.write("<</Type /Annot\n")
687 file.write("/P %d 0 R\n" % registry.getrefno(self.PDFform)) # reference to the page objects
688 file.write("/Rect [%f %f %f %f]\n" % self.bb_pt) # the annotation rectangle
689 #ile.write("/BS << ... >>\n" # border style dictionary
690 file.write("/Border [0 0 %f]\n" % self.borderwidth_pt) # border style
691 file.write("/F %d\n" % self.annotflag)
692 ### the dictionary entries for the widget annotations
693 file.write("/Subtype /Widget\n")
694 file.write("/H /N\n") # highlight behaviour
695 #ile.write("/AP <</N >>\n") # appearance dictionary TODO
696 ### the dictionary entries for the form field
697 file.write("/FT /Ch\n") # type of the form field
698 file.write("/T (%s)\n" % self.name) # partial field name
699 file.write("/TU (%s)\n" % self.name) # field name for the user-interface
700 file.write("/TM (%s)\n" % self.name) # field name for exporting the data
701 if self.defaultvalue in self.values:
702 file.write("/V (%s)\n" % self.defaultvalue) # starting value
703 file.write("/Ff %d\n" % self.formflag) # flags for various purposes
704 ### the dictionary entries for the text field
705 file.write("/DR <</Font <</%s %d 0 R >> >>\n" % (self.font.name, registry.getrefno(self.font))) # default resources for appearance
706 file.write("/DA (/%s %f Tf)\n" % (self.font.name, self.fontsize)) # default appearance string
707 file.write("/Q %d\n" % self.alignflag)
708 file.write("/Opt [")
709 for value in self.values:
710 file.write(" (%s)" % value)
711 file.write(" ]\n")
712 file.write(">>\n")
713 # >>>
716 # vim:foldmethod=marker:foldmarker=<<<,>>>