__all__=('AcroForm',) from reportlab.pdfbase.pdfdoc import (PDFObject, PDFArray, PDFDictionary, PDFString, pdfdocEnc, PDFName, PDFStream, PDFStreamFilterZCompress, escapePDF) from reportlab.pdfgen.canvas import Canvas from reportlab.pdfbase.pdfmetrics import stringWidth from reportlab.lib.colors import Color, CMYKColor, Whiter, Blacker, opaqueColor from reportlab.lib.rl_accel import fp_str from reportlab.lib.utils import isStr, asNative import weakref visibilities = dict( visible=0, hidden=0, visibleNonPrinting=0, hiddenPrintable=0, ) orientations = { 0: [], 90: [], 180: [], 270: [], } #adobe counts bits 1 - 32 fieldFlagValues = dict( readOnly = 1<<0, required = 1<<1, noExport = 1<<2, noToggleToOff = 1<<14, radio = 1<<15, pushButton = 1<<16, radiosInUnison = 1<<25, #text fields multiline = 1<<12, password = 1<<13, fileSelect = 1<<20, #1.4 doNotSpellCheck = 1<<22, #1.4 doNotScroll = 1<<23, #1.4 comb = 1<<24, #1.5 richText = 1<<25, #1.5 #choice fields combo = 1<<17, edit = 1<<18, sort = 1<<19, multiSelect = 1<<21, #1.4 commitOnSelChange = 1<<26, #1.5 ) annotationFlagValues = dict( invisible=1<<0, hidden=1<<1, nozoom=1<<3, norotate=1<<4, noview=1<<5, readonly=1<<6, locked=1<<7, #1.4 togglenoview=1<<8, #1.9 lockedcontents=1<<9, #1.7 ) annotationFlagValues['print']=1<<2 _bsStyles = dict( solid='S', dashed='D', bevelled='B', inset='I', underlined='U', ) def bsPDF(borderWidth,borderStyle,dashLen): d = dict(W=borderWidth,S=PDFName(_bsStyles[borderStyle])) if borderStyle=='dashed': if not dashLen: dashLen = [3] elif not isinstance(dashLen,(list,tuple)): dashLen = [dashLen] d['D'] = PDFArray(dashLen) return PDFDictionary(d) def escPDF(s): return escapePDF(s).replace('%','\\045') def makeFlags(s,d=annotationFlagValues): if not isinstance(s,int): v = s s = 0 for x in v.split(): s |= d[x] return s class PDFFromString(PDFObject): def __init__(self,s): if not isStr(s): raise ValueError('need a unicode/bytes argument not %r' % s) self._s = s def format(self,document): return pdfdocEnc(self._s) class RadioGroup(PDFObject): def __init__(self,name,tooltip='',fieldFlags='noToggleToOff required radio'): if not name: raise ValueError('RadioGroup created with no name') self.TU = tooltip self.Ff = makeFlags(fieldFlags,fieldFlagValues) self.kids = [] self.T = name self.V = None def format(self,doc): kids = self.kids d = len(kids) if d<2: raise ValueError('RadioGroup:%s has %d < 2 RadioBoxes' % (self.T,d)) d = dict( Ff=self.Ff, Kids = PDFArray([k for k in self.kids]), FT = PDFName('Btn'), T = PDFString(self.T), #DA = PDFString('0 g'), ) if self.V: d['V'] = PDFName(self.V) if self.TU: d['TU'] =PDFString(self.TU) r = PDFDictionary(d).format(doc) return r def _pdfObjToStr(obj): if isinstance(obj,PDFArray): return '[%s]' % ''.join((_pdfObjToStr(e) for e in obj.sequence)) if isinstance(obj,PDFFromString): return obj._s return str(obj) class AcroForm(PDFObject): 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", } def __init__(self,canv,**kwds): self.referenceMap = {} self._canv = weakref.ref(canv) self.fonts = {} self.fields = [] self._radios = {} self._refMap = {} self._pdfdocenc = {} self.sigFlags = None self.extras = {} @property def canv(self): _canv = self._canv() if _canv is None: raise ValueError('%s.canv is no longer available' % self.__class__.__name__) return _canv def fontRef(self,f): return '/Font << /%s %s >>' % (f,self.fonts[f]) def format(self,doc): d = dict( Fields = PDFArray([self.getRef(f) for f in self.fields]), ) if self.sigFlags: d['SigFlags'] = self.sigFlags if self.fonts: FK = list(sorted(self.fonts.keys())) F = [self.fontRef(f) for f in FK] d['DA'] = PDFString('/%s 0 Tf 0 g' % FK[0]) d['DR'] = PDFFromString('<< /Encoding\n<<\n/RLAFencoding\n%s\n>>\n%s\n>>' % (self.encRefStr,'\n'.join(F))) d.update(self.extras) r = PDFDictionary(d).format(doc) return r def colorTuple(self,c): # ISO-32000-1, Table 189: An array of numbers that shall be in ther # range 0.0 to 1.0 specifying the colour [..]. The number of array # elements determines the colour space in which the colour shall # be defined: # 0 No colour; transparent 1 DeviceGray 3 DeviceRGB 4 DeviceCMYK if c is None or c.alpha == 0: return () return c.cmyk() if isinstance(c,CMYKColor) else c.rgb() def streamFillColor(self,c): t = self.colorTuple(c) return fp_str(*t)+(' k' if len(t)==4 else ' rg') def streamStrokeColor(self,c): t = self.colorTuple(c) return fp_str(*t)+(' K' if len(t)==4 else ' RG') def checkboxAP(self, key, #N/D/R value, #Yes/Off buttonStyle='circle', shape='square', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', size=20, dashLen=3, ): stream = [].append ds = size if shape=='square': stream('q') streamFill = self.streamFillColor(fillColor) stream('1 g 1 G %(streamFill)s 0 0 %(size)s %(size)s re f') if borderWidth!=None: streamStroke = self.streamStrokeColor(borderColor) hbw = borderWidth*0.5 smbw = size - borderWidth ds = smbw if borderStyle=='underlined': stream('%(streamStroke)s %(borderWidth)s w 0 %(hbw)s m %(size)s %(hbw)s l s') elif borderStyle in ('dashed','inset','bevelled','solid'): if borderStyle=='dashed': dash = ' [%s ] 0 d' % fp_str(dashLen) else: dash = '' stream('%(streamStroke)s%(dash)s %(borderWidth)s w %(hbw)s %(hbw)s %(smbw)s %(smbw)s re s') if borderStyle in ('bevelled','inset'): _2bw = 2*borderWidth sm2bw = size - _2bw ds = sm2bw bbs0 = Blacker(fillColor,0.5) bbs1 = fillColor if key!='D': bbs0, bbs1 = bbs1, bbs0 bbs0 = self.streamFillColor(bbs0) bbs1 = self.streamFillColor(bbs1) stream('%(bbs0)s %(borderWidth)s %(borderWidth)s m %(borderWidth)s %(smbw)s l %(smbw)s %(smbw)s l %(sm2bw)s %(sm2bw)s l %(_2bw)s %(sm2bw)s l %(_2bw)s %(_2bw)s l f %(bbs1)s %(smbw)s %(smbw)s m %(smbw)s %(borderWidth)s l %(borderWidth)s %(borderWidth)s l %(_2bw)s %(_2bw)s l %(sm2bw)s %(_2bw)s l %(sm2bw)s %(sm2bw)s l f') stream('Q') elif shape=='circle': cas = lambda _r,**_casKwds: self.circleArcStream(size,_r,**_casKwds) r = size*0.5 streamFill = self.streamFillColor(fillColor) stream('q 1 g 1 G %(streamFill)s') stream(cas(r)) stream('f') stream('Q') if borderWidth!=None: stream('q') streamStroke = self.streamStrokeColor(borderColor) hbw = borderWidth*0.5 ds = size - borderWidth if borderStyle=='underlined': stream('q %(streamStroke)s %(borderWidth)s w 0 %(hbw)s m %(size)s %(hbw)s l s Q') elif borderStyle in ('dashed','inset','bevelled','solid'): if borderStyle=='dashed': dash = ' [3 ] 0 d' else: dash = '' stream('%(streamStroke)s%(dash)s %(borderWidth)s w') stream(cas(r-hbw)) stream('s') stream('Q') if borderStyle in ('bevelled','inset'): _3bwh = 3*hbw ds = size - _3bwh bbs0 = Blacker(fillColor,0.5) bbs1 = Whiter(fillColor,0.5) a0 = (0,1) a1 = (2,3) if borderStyle=='inset': bbs0, bbs1 = bbs1, bbs0 if key!='D': bbs0, bbs1 = bbs1, bbs0 bbs0 = self.streamStrokeColor(bbs0) bbs1 = self.streamStrokeColor(bbs1) stream('q %(bbs0)s %(borderWidth)s w') stream(cas(r-_3bwh,rotated=True,arcs=a0)) stream('S Q %(bbs1)s q') stream(cas(r-_3bwh,rotated=True,arcs=a1)) stream('S Q') if value=='Yes': textFillColor = self.streamFillColor(textColor) textStrokeColor = self.streamStrokeColor(textColor) stream('q %(textFillColor)s %(textStrokeColor)s') cbm = cbmarks[buttonStyle] if shape=='circle' and buttonStyle=='circle': stream(cas((max(r-(size-ds),1))*0.5)) stream('f') else: stream(cbm.scaledRender(size,size-ds)) stream('Q') stream = ('\n'.join(stream.__self__) % vars()).replace(' ',' ').replace('\n\n','\n') return self.makeStream( size, size, stream, Resources = PDFFromString('<< /ProcSet [/PDF] >>'), ) @staticmethod def circleArcStream(size, r, arcs=(0,1,2,3), rotated=False): R = [].append rlen = R.__self__.__len__ hsize = size * 0.5 f = size / 20.0 size *= f hsize *= f r *= f cp = fp_str(0.55231 * r) r = fp_str(r) hsize = fp_str(hsize) mx = '0.7071 0.7071 -0.7071 0.7071' if rotated else '1 0 0 1' R('%(mx)s %(hsize)s %(hsize)s cm') if 0 in arcs: if rlen()==1: R('%(r)s 0 m') R('%(r)s %(cp)s %(cp)s %(r)s 0 %(r)s c') if 1 in arcs: if rlen()==1: R('0 %(r)s m') R('-%(cp)s %(r)s -%(r)s %(cp)s -%(r)s 0 c') if 2 in arcs: if rlen()==1: R('-%(r)s 0 m') R('-%(r)s -%(cp)s -%(cp)s -%(r)s 0 -%(r)s c') if 3 in arcs: if rlen()==1: R('0 -%(r)s m') R('%(cp)s -%(r)s %(r)s -%(cp)s %(r)s 0 c') return '\n'.join(R.__self__) % vars() def zdMark(self,c,size,ds,iFontName): c = ZDSyms[c] W = H = size-ds fs = H/1.2 w = float(stringWidth(c,'ZapfDingbats',fs)) if w>W: fs *= W/w dx = ds + 0.5*(W-w) dy = 0 return 'BT %(iFontName)s %(fs)s Tf %(dx)s %(dy)s Td %(fs)s TL (%(c)s) Tj ET' % vars() def getRef(self,obj): return self.canv._doc.Reference(obj) def getRefStr(self,obj): return asNative(self.getRef(obj).format(self.canv._doc)) @staticmethod def stdColors(t,b,f): if isinstance(f,CMYKColor) or isinstance(t,CMYKColor) or isinstance(b,CMYKColor): return (t or CMYKColor(0,0,0,0.9), b or CMYKColor(0,0,0,0.9), f or CMYKColor(0.12,0.157,0,0)) else: return (t or Color(0.1,0.1,0.1), b or Color(0.1,0.1,0.1), f or Color(0.8,0.843,1)) @staticmethod def varyColors(key,t,b,f): if key!='N': func = Whiter if key=='R' else Blacker t,b,f = [func(c,0.9) for c in (t,b,f)] return t,b,f def checkForceBorder(self,x,y,width,height,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor): if forceBorder: canv = self.canv canv.saveState() canv.resetTransforms() if borderWidth!=None: hbw = 0.5*borderWidth canv.setLineWidth(borderWidth) canv.setStrokeColor(borderColor) s = 1 else: s = hbw = 0 width -= 2*hbw height -= 2*hbw x += hbw y += hbw canv.setFillColor(fillColor) if shape=='square': canv.rect(x,y,width,height,stroke=s,fill=1) else: r = min(width,height) * 0.5 canv.circle(x+r,y+r,r,stroke=s,fill=1) canv.restoreState() def checkbox(self, checked=False, buttonStyle='check', shape='square', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', size=20, x=0, y=0, tooltip=None, name=None, annotationFlags='print', fieldFlags='required', forceBorder=False, relative=False, dashLen = 3, ): initialValue = 'Yes' if checked else 'Off' textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor) canv = self.canv if relative: x, y = self.canv.absolutePosition(x,y) doc = canv._doc AP = {} for key in 'NDR': APV = {} tC,bC,fC = self.varyColors(key,textColor,borderColor,fillColor) for value in ('Yes','Off'): ap = self.checkboxAP( key, value, buttonStyle=buttonStyle, shape=shape, fillColor=fC, borderColor=bC, textColor=tC, borderWidth=borderWidth, borderStyle=borderStyle, size=size, dashLen=dashLen, ) if ap._af_refstr in self._refMap: ref = self._refMap[ap._af_refstr] else: ref = self.getRef(ap) self._refMap[ap._af_refstr] = ref APV[value]=ref AP[key] = PDFDictionary(APV) del APV CB = dict( FT = PDFName('Btn'), P = doc.thisPageRef(), V = PDFName(initialValue), AS = PDFName(initialValue), #DV = PDFName(initialValue), Rect = PDFArray((x,y,x+size,y+size)), AP = PDFDictionary(AP), Subtype = PDFName('Widget'), Type = PDFName('Annot'), F = makeFlags(annotationFlags,annotationFlagValues), Ff = makeFlags(fieldFlags,fieldFlagValues), H=PDFName('N'), ) if tooltip: CB['TU'] = PDFString(tooltip) if not name: name = 'AFF%03d' % len(self.fields) if borderWidth: CB['BS'] = bsPDF(borderWidth,borderStyle,dashLen) CB['T'] = PDFString(name) MK = dict( CA='(%s)' % ZDSyms[buttonStyle], BC=PDFArray(self.colorTuple(borderColor)), BG=PDFArray(self.colorTuple(fillColor)), ) CB['MK'] = PDFDictionary(MK) CB = PDFDictionary(CB) self.canv._addAnnotation(CB) self.fields.append(self.getRef(CB)) self.checkForceBorder(x,y,size,size,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor) def radio(self, value=None, selected=False, buttonStyle='circle', shape='circle', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', size=20, x=0, y=0, tooltip=None, name=None, annotationFlags='print', fieldFlags='noToggleToOff required radio', forceBorder=False, relative=False, dashLen=3, ): if name not in self._radios: group = RadioGroup(name,tooltip=tooltip,fieldFlags=fieldFlags) group._ref = self.getRef(group) self._radios[name] = group self.fields.append(group._ref) else: group = self._radios[name] fieldFlags = makeFlags(fieldFlags,fieldFlagValues) if fieldFlags!=group.Ff: raise ValueError('radio.%s.%s created with different flags' % (name,value)) if not value: raise ValueError('bad value %r for radio.%s' % (value,name)) initialValue = value if selected else 'Off' textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor) if initialValue==value: if group.V is not None: if group.V!=value: raise ValueError('radio.%s.%s sets initial value conflicting with %s'%(name,value,group.V)) else: group.V = value canv = self.canv if relative: x, y = self.canv.absolutePosition(x,y) doc = canv._doc AP = {} for key in 'NDR': APV = {} tC,bC,fC = self.varyColors(key,textColor,borderColor,fillColor) for v in (value,'Off'): ap = self.checkboxAP( key, 'Yes' if v==value else 'Off', buttonStyle=buttonStyle, shape=shape, fillColor=fC, borderColor=bC, textColor=tC, borderWidth=borderWidth, borderStyle=borderStyle, size=size, dashLen=dashLen, ) if ap._af_refstr in self._refMap: ref = self._refMap[ap._af_refstr] else: ref = self.getRef(ap) self._refMap[ap._af_refstr] = ref APV[v]=ref AP[key] = PDFDictionary(APV) del APV RB = dict( FT = PDFName('Btn'), P = doc.thisPageRef(), AS = PDFName(initialValue), #DV = PDFName(initialValue), Rect = PDFArray((x,y,x+size,y+size)), AP = PDFDictionary(AP), Subtype = PDFName('Widget'), Type = PDFName('Annot'), F = makeFlags(annotationFlags,annotationFlagValues), Parent = group._ref, #DA = PDFString('1 g '+(self.streamFillColor(fillColor) if fillColor else '-0.25 0.75 -0.25 rg')) H=PDFName('N'), ) #RB['T'] = PDFString(name) MK = dict( CA='(%s)' % ZDSyms[buttonStyle], BC=PDFArray(self.colorTuple(borderColor)), BG=PDFArray(self.colorTuple(fillColor)), ) if borderWidth: RB['BS'] = bsPDF(borderWidth,borderStyle,dashLen) RB['MK'] = PDFDictionary(MK) RB = PDFDictionary(RB) self.canv._addAnnotation(RB) group.kids.append(self.getRef(RB)) self.checkForceBorder(x,y,size,size,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor) def makeStream(self, width, height, stream, **D ): D['Matrix'] = PDFArray([1.0,0.0,0.0,1.0,0.0,0.0]) D['BBox'] = PDFArray([0,0,width,height]) D['Subtype'] = PDFName('Form') D['Type'] = PDFName('XObject') D['FormType'] = 1 s = PDFStream( PDFDictionary(D), stream, filters = [PDFStreamFilterZCompress()] if self.canv._doc.compression else None, ) #compute a lookup string s._af_refstr = stream+'\n'.join(('%s=%r' % (k,_pdfObjToStr(v)) for k,v in sorted(D.items()))) return s def txAP(self, key, #N/D/R value, iFontName, rFontName, fontSize, shape='square', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', width=120, height=36, dashLen=3, wkind='textfield', labels=[], I=[], sel_bg='0.600006 0.756866 0.854904 rg', sel_fg='0 g', ): stream = [].append if opaqueColor(fillColor): streamFill = self.streamFillColor(fillColor) stream('%(streamFill)s\n0 0 %(width)s %(height)s re\nf') if borderWidth!=None and borderWidth>0 and opaqueColor(borderColor): hbw = borderWidth*0.5 bww = width - borderWidth bwh = height - borderWidth _2bw = 2*borderWidth if borderStyle in ('bevelled','inset'): bw2w = width - _2bw bw2h = height - _2bw if borderStyle == 'bevelled': bbs0 = '1 g' if fillColor or borderColor: bbs1 = '-0.250977 0.749023 -0.250977 rg' else: bbs1 = '.75293 g' else: bbs0 = '.501953 g' bbs1 = '.75293 g' stream('%(bbs0)s\n%(borderWidth)s %(borderWidth)s m\n%(borderWidth)s %(bwh)s l\n%(bww)s %(bwh)s l\n%(bw2w)s %(bw2h)s l\n%(_2bw)s %(bw2h)s l\n%(_2bw)s %(_2bw)s l\nf\n%(bbs1)s\n%(bww)s %(bwh)s m\n%(bww)s %(borderWidth)s l\n%(borderWidth)s %(borderWidth)s l\n%(_2bw)s %(_2bw)s l\n%(bw2w)s %(_2bw)s l\n%(bw2w)s %(bw2h)s l\nf') else: hbw = _2bw = borderWidth = 0 bww = width bwh = height undash = '' if opaqueColor(borderColor) and borderWidth: streamStroke = self.streamStrokeColor(borderColor) if borderStyle=='underlined': stream('%(streamStroke)s %(borderWidth)s w 0 %(hbw)s m %(width)s %(hbw)s l s') elif borderStyle in ('dashed','inset','bevelled','solid'): if borderStyle=='dashed': dash = '\n[%s ] 0 d\n' % fp_str(dashLen) undash = '[] 0 d' else: dash = '\n%s w' % borderWidth stream('%(streamStroke)s\n%(dash)s\n%(hbw)s %(hbw)s %(bww)s %(bwh)s re\ns') _4bw = 4*borderWidth w4bw = width - _4bw h4bw = height - _4bw textFill = self.streamFillColor(textColor) stream('/Tx BMC \nq\n%(_2bw)s %(_2bw)s %(w4bw)s %(h4bw)s re\nW\nn') leading = 1.2 * fontSize if wkind=='listbox': nopts = int(h4bw/leading) leading = h4bw/float(nopts) if nopts>len(labels): i0 = 0 nopts = len(labels) elif len(I)<=1: i0 = I[0] if I else 0 if i0: if i0=i: i0 = i else: #|I|>1 if I[1]y: i0 = i - y ilim = min(y,i0+nopts) if I: i = i0 y = height - _2bw - leading stream(sel_bg) while i>' % vars()), ) def makeFont(self,fontName): if fontName is None: fontName = 'Helvetica' if fontName not in self.formFontNames: raise ValueError('form font name, %r, is not one of the standard 14 fonts' % fontName) fn = self.formFontNames[fontName] ref = self.getRefStr(PDFFromString('<< /BaseFont /%s /Subtype /Type1 /Name /%s /Type /Font /Encoding %s >>' % ( fontName,fn,self.encRefStr))) if fn not in self.fonts: self.fonts[fn] = ref return ref, fn def _textfield(self, value='', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', width=120, height=36, x=0, y=0, tooltip=None, name=None, annotationFlags='print', fieldFlags='', forceBorder=False, relative=False, maxlen=100, fontName=None, fontSize=None, wkind=None, options=None, dashLen=3, ): rFontName, iFontName = self.makeFont(fontName) if fontSize is None: fontSize = 12 textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor) canv = self.canv if relative: x, y = self.canv.absolutePosition(x,y) doc = canv._doc rFontName = '<>' % (iFontName,rFontName) Ff = makeFlags(fieldFlags,fieldFlagValues) if wkind!='textfield': #options must be a list of pairs (label value) #value must be a list of the values FT='Ch' if wkind=='choice': Ff |= fieldFlagValues['combo'] #just in case V = [] Opt = [] AP = [] I = [] TF = [] if not isinstance(options,(list,tuple)): raise TypeError('%s options=%r is wrong type' % (wkind,options)) for v in options: if isStr(v): Opt.append(PDFString(v)) l = v elif isinstance(v,(list,tuple)): if len(v)==1: v=l=v[0] else: l,v = v Opt.append(PDFArray([PDFString(v),PDFString(l)])) else: raise TypeError('%s option %r is wrong type' % (wkind,v)) AP.append(v) TF.append(l) Opt = PDFArray(Opt) if value: if not isinstance(value,(list,tuple)): value = [value] for v in value: if v not in AP: if v not in TF: raise ValueError('%s value %r is not in option\nvalues %r\nor labels %r' % (wkind,v,AP,TF)) else: v = AP[TF.index(v)] I.append(AP.index(v)) V.append(PDFString(v)) I.sort() if not (Ff & fieldFlagValues['multiSelect']) or len(value)==1: if wkind=='choice': value = TF[I[0]] else: value = value[:1] V = V[:1] V = V[0] if len(V)==1 else PDFArray(V) lbextras = dict(labels=TF,I=I,wkind=wkind) else: V = PDFString(value) else: I = Opt = [] lbextras = {} FT='Tx' if not isStr(value): raise TypeError('textfield value=%r is wrong type' % value) V = PDFString(value) AP = {} for key in 'N': tC,bC,fC = self.varyColors(key,textColor,borderColor,fillColor) ap = self.txAP( key, value, iFontName, rFontName, fontSize, fillColor=fC, borderColor=bC, textColor=tC, borderWidth=borderWidth, borderStyle=borderStyle, width=width, height=height, dashLen = dashLen, **lbextras ) if ap._af_refstr in self._refMap: ref = self._refMap[ap._af_refstr] else: ref = self.getRef(ap) self._refMap[ap._af_refstr] = ref AP[key] = ref TF = dict( FT = PDFName(FT), P = doc.thisPageRef(), V = V, #AS = PDFName(value), DV = V, Rect = PDFArray((x,y,x+width,y+height)), AP = PDFDictionary(AP), Subtype = PDFName('Widget'), Type = PDFName('Annot'), F = makeFlags(annotationFlags,annotationFlagValues), Ff = Ff, #H=PDFName('N'), DA=PDFString('/%s %d Tf %s' % (iFontName,fontSize, self.streamFillColor(textColor))), ) if Opt: TF['Opt'] = Opt if I: TF['I'] = PDFArray(I) if maxlen: TF['MaxLen'] = maxlen if tooltip: TF['TU'] = PDFString(tooltip) if not name: name = 'AFF%03d' % len(self.fields) TF['T'] = PDFString(name) MK = dict( BG=PDFArray(self.colorTuple(fillColor)), ) # Acrobat seems to draw a thin border when BS is defined, so only # include this if there actually is a border to draw if borderWidth: TF['BS'] = bsPDF(borderWidth,borderStyle,dashLen) MK['BC'] = PDFArray(self.colorTuple(borderColor)) TF['MK'] = PDFDictionary(MK) TF = PDFDictionary(TF) self.canv._addAnnotation(TF) self.fields.append(self.getRef(TF)) self.checkForceBorder(x,y,width,height,forceBorder,'square',borderStyle,borderWidth,borderColor,fillColor) def textfield(self, value='', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', width=120, height=36, x=0, y=0, tooltip=None, name=None, annotationFlags='print', fieldFlags='', forceBorder=False, relative=False, maxlen=100, fontName=None, fontSize=None, dashLen=3, ): return self._textfield( value=value, fillColor=fillColor, borderColor=borderColor, textColor=textColor, borderWidth=borderWidth, borderStyle=borderStyle, width=width, height=height, x=x, y=y, tooltip=tooltip, name=name, annotationFlags=annotationFlags, fieldFlags=fieldFlags, forceBorder=forceBorder, relative=relative, maxlen=maxlen, fontName=fontName, fontSize=fontSize, dashLen=dashLen, wkind='textfield', ) def listbox(self, value='', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', width=120, height=36, x=0, y=0, tooltip=None, name=None, annotationFlags='print', fieldFlags='', forceBorder=False, relative=False, fontName=None, fontSize=None, dashLen=3, maxlen=None, options=[], ): return self._textfield( value=value, fillColor=fillColor, borderColor=borderColor, textColor=textColor, borderWidth=borderWidth, borderStyle=borderStyle, width=width, height=height, x=x, y=y, tooltip=tooltip, name=name, annotationFlags=annotationFlags, fieldFlags=fieldFlags, forceBorder=forceBorder, relative=relative, maxlen=maxlen, fontName=fontName, fontSize=fontSize, dashLen=dashLen, wkind='listbox', options = options, ) def choice(self, value='', fillColor=None, borderColor=None, textColor=None, borderWidth=1, borderStyle='solid', width=120, height=36, x=0, y=0, tooltip=None, name=None, annotationFlags='print', fieldFlags='combo', forceBorder=False, relative=False, fontName=None, fontSize=None, dashLen=3, maxlen=None, options=[], ): return self._textfield( value=value, fillColor=fillColor, borderColor=borderColor, textColor=textColor, borderWidth=borderWidth, borderStyle=borderStyle, width=width, height=height, x=x, y=y, tooltip=tooltip, name=name, annotationFlags=annotationFlags, fieldFlags=fieldFlags, forceBorder=forceBorder, relative=relative, maxlen=maxlen, fontName=fontName, fontSize=fontSize, dashLen=dashLen, wkind='choice', options = options, ) def checkboxRelative(self, **kwds): "same as checkbox except the x and y are relative to the canvas coordinate transform" kwds['relative']=True self.checkbox(**kwds) def radioRelative(self, **kwds): "same as radio except the x and y are relative to the canvas coordinate transform" kwds['relative']=True self.radio(**kwds) def textfieldRelative(self, **kwds): "same as textfield except the x and y are relative to the canvas coordinate transform" kwds['relative']=True self.textfield(**kwds) def listboxRelative(self, **kwds): "same as textfield except the x and y are relative to the canvas coordinate transform" kwds['relative']=True self.textfield(**kwds) def choiceRelative(self, **kwds): "same as textfield except the x and y are relative to the canvas coordinate transform" kwds['relative']=True self.textfield(**kwds) @property def encRefStr(self): if not self._pdfdocenc: self._pdfdocenc = PDFFromString('''<>''') return self.getRefStr(self._pdfdocenc) class CBMark: opNames = 'm l c h'.split() opCount = 1,1,3,0 def __init__(self,ops,points,bounds,slack=0.05): self.ops = ops self.xmin,self.ymin,self.xmax,self.ymax = bounds self.points = points self.slack = slack def scaledRender(self,size,ds=0): ''' >>> print(cbmarks['check'].scaledRender(20)) 12.97075 14.68802 m 15.00139 17.16992 l 15.9039 18.1727 17.93454 18.67409 19.2883 18.67409 c 19.46379 18.27298 l 17.13231 15.51532 l 11.91783 8.62117 l 8.307799 3.030641 l 7.430362 1.526462 l 7.305014 1.275766 7.154596 .97493 6.9039 .824513 c 6.577994 .674095 5.825905 .674095 5.47493 .674095 c 4.672702 .674095 4.497214 .674095 4.321727 .799443 c 4.071031 .97493 3.945682 1.325905 3.770195 1.67688 c 3.218663 2.830084 2.240947 5.337047 2.240947 6.590529 c 2.240947 7.016713 2.491643 7.21727 2.817549 7.442897 c 3.344011 7.818942 4.0961 8.245125 4.747911 8.245125 c 5.249304 8.245125 5.299443 7.818942 5.449861 7.417827 c 5.951253 6.239554 l 6.026462 6.038997 6.252089 5.337047 6.527855 5.337047 c 6.778552 5.337047 7.079387 5.913649 7.179666 6.089136 c 12.97075 14.68802 l h f >>> print(cbmarks['cross'].scaledRender(20)) 19.9104 17.43931 m 12.41908 10 l 19.9104 2.534682 l 18.37572 1 l 10.9104 8.491329 l 3.445087 1 l 1.910405 2.534682 l 9.427746 10 l 1.910405 17.46532 l 3.445087 19 l 10.9104 11.50867 l 18.37572 19 l 19.9104 17.43931 l h f >>> print(cbmarks['circle'].scaledRender(20)) 1.872576 9.663435 m 1.872576 14.64958 5.936288 18.61357 10.89751 18.61357 c 15.8338 18.61357 19.87258 14.59972 19.87258 9.663435 c 19.87258 4.727147 15.8338 .688366 10.89751 .688366 c 5.936288 .688366 1.872576 4.677285 1.872576 9.663435 c h f >>> print(cbmarks['star'].scaledRender(20)) 10.85542 18.3253 m 12.90361 11.84337 l 19.84337 11.84337 l 14.25301 7.650602 l 16.42169 1 l 10.85542 5.096386 l 5.289157 1 l 7.481928 7.650602 l 1.843373 11.84337 l 8.759036 11.84337 l 10.85542 18.3253 l h f >>> print(cbmarks['diamond'].scaledRender(20)) 17.43533 9.662031 m 15.63282 7.484006 l 10.85118 .649513 l 8.422809 4.329624 l 5.919332 7.659249 l 4.267038 9.662031 l 6.16968 12.0153 l 10.85118 18.64951 l 12.75382 15.4701 15.00695 12.49096 17.43533 9.662031 c h f ''' #work out the scale and translation W = H = size - 2*ds xmin = self.xmin ymin = self.ymin w = self.xmax-xmin h = self.ymax-ymin slack = self.slack*min(W,H) sx = (W - 2*slack)/float(w) sy = (H - 2*slack)/float(h) sx = sy = min(sx,sy) w *= sx h *= sy dx = ds+(W - w)*0.5 dy = ds+(H - h)*0.5 xsc = lambda v: fp_str((v-xmin)*sx+dx) ysc = lambda v: fp_str((v-ymin)*sy+dy) opNames = self.opNames opCount = self.opCount C = [].append i = 0 points = self.points for op in self.ops: c = opCount[op] for _ in range(c): C(xsc(points[i])) C(ysc(points[i+1])) i += 2 C(opNames[op]) C('f') return ' '.join(C.__self__) cbmarks = dict( check=CBMark( [0, 1, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 1, 3], [462, 546, 543, 645, 579, 685, 660, 705, 714, 705, 721, 689, 628, 579, 420, 304, 276, 81, 241, 21, 236, 11, 230, -1, 220, -7, 207, -13, 177, -13, 163, -13, 131, -13, 124, -13, 117, -8, 107, -1, 102, 13, 95, 27, 73, 73, 34, 173, 34, 223, 34, 240, 44, 248, 57, 257, 78, 272, 108, 289, 134, 289, 154, 289, 156, 272, 162, 256, 182, 209, 185, 201, 194, 173, 205, 173, 215, 173, 227, 196, 231, 203, 462, 546], (34,-12,721,706), ), cross = CBMark( [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [727, 632, 439, 346, 727, 59, 668, 0, 381, 288, 94, 0, 35, 59, 324, 346, 35, 633, 94, 692, 381, 404, 668, 692, 727, 632], (35,0,727,692), ), circle = CBMark( [0, 2, 2, 2, 2, 3], [35, 346, 35, 546, 198, 705, 397, 705, 595, 705, 757, 544, 757, 346, 757, 148, 595, -14, 397, -14, 198, -14, 35, 146, 35, 346], (35,-14,757,705), ), star = CBMark( [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], [409, 705, 494, 436, 782, 436, 550, 262, 640, -14, 409, 156, 178, -14, 269, 262, 35, 436, 322, 436, 409, 705], (35,-14,782,705), ), diamond = CBMark( [0, 1, 1, 1, 1, 1, 1, 1, 2, 3], [560, 346, 488, 259, 297, -14, 200, 133, 100, 266, 34, 346, 110, 440, 297, 705, 373, 578, 463, 459, 560, 346], (34,-14,560,705), ), ) ZDSyms=dict(check='4',cross='5',circle='l',star='N',diamond='u') if __name__ == "__main__": import doctest doctest.testmod()