acroform.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. __all__=('AcroForm',)
  2. from reportlab.pdfbase.pdfdoc import (PDFObject, PDFArray, PDFDictionary, PDFString, pdfdocEnc,
  3. PDFName, PDFStream, PDFStreamFilterZCompress, escapePDF)
  4. from reportlab.pdfgen.canvas import Canvas
  5. from reportlab.pdfbase.pdfmetrics import stringWidth
  6. from reportlab.lib.colors import Color, CMYKColor, Whiter, Blacker, opaqueColor
  7. from reportlab.lib.rl_accel import fp_str
  8. from reportlab.lib.utils import isStr, asNative
  9. import weakref
  10. visibilities = dict(
  11. visible=0,
  12. hidden=0,
  13. visibleNonPrinting=0,
  14. hiddenPrintable=0,
  15. )
  16. orientations = {
  17. 0: [],
  18. 90: [],
  19. 180: [],
  20. 270: [],
  21. }
  22. #adobe counts bits 1 - 32
  23. fieldFlagValues = dict(
  24. readOnly = 1<<0,
  25. required = 1<<1,
  26. noExport = 1<<2,
  27. noToggleToOff = 1<<14,
  28. radio = 1<<15,
  29. pushButton = 1<<16,
  30. radiosInUnison = 1<<25,
  31. #text fields
  32. multiline = 1<<12,
  33. password = 1<<13,
  34. fileSelect = 1<<20, #1.4
  35. doNotSpellCheck = 1<<22, #1.4
  36. doNotScroll = 1<<23, #1.4
  37. comb = 1<<24, #1.5
  38. richText = 1<<25, #1.5
  39. #choice fields
  40. combo = 1<<17,
  41. edit = 1<<18,
  42. sort = 1<<19,
  43. multiSelect = 1<<21, #1.4
  44. commitOnSelChange = 1<<26, #1.5
  45. )
  46. annotationFlagValues = dict(
  47. invisible=1<<0,
  48. hidden=1<<1,
  49. nozoom=1<<3,
  50. norotate=1<<4,
  51. noview=1<<5,
  52. readonly=1<<6,
  53. locked=1<<7, #1.4
  54. togglenoview=1<<8, #1.9
  55. lockedcontents=1<<9, #1.7
  56. )
  57. annotationFlagValues['print']=1<<2
  58. _bsStyles = dict(
  59. solid='S',
  60. dashed='D',
  61. bevelled='B',
  62. inset='I',
  63. underlined='U',
  64. )
  65. def bsPDF(borderWidth,borderStyle,dashLen):
  66. d = dict(W=borderWidth,S=PDFName(_bsStyles[borderStyle]))
  67. if borderStyle=='dashed':
  68. if not dashLen:
  69. dashLen = [3]
  70. elif not isinstance(dashLen,(list,tuple)):
  71. dashLen = [dashLen]
  72. d['D'] = PDFArray(dashLen)
  73. return PDFDictionary(d)
  74. def escPDF(s):
  75. return escapePDF(s).replace('%','\\045')
  76. def makeFlags(s,d=annotationFlagValues):
  77. if not isinstance(s,int):
  78. v = s
  79. s = 0
  80. for x in v.split():
  81. s |= d[x]
  82. return s
  83. class PDFFromString(PDFObject):
  84. def __init__(self,s):
  85. if not isStr(s):
  86. raise ValueError('need a unicode/bytes argument not %r' % s)
  87. self._s = s
  88. def format(self,document):
  89. return pdfdocEnc(self._s)
  90. class RadioGroup(PDFObject):
  91. def __init__(self,name,tooltip='',fieldFlags='noToggleToOff required radio'):
  92. if not name:
  93. raise ValueError('RadioGroup created with no name')
  94. self.TU = tooltip
  95. self.Ff = makeFlags(fieldFlags,fieldFlagValues)
  96. self.kids = []
  97. self.T = name
  98. self.V = None
  99. def format(self,doc):
  100. kids = self.kids
  101. d = len(kids)
  102. if d<2: raise ValueError('RadioGroup:%s has %d < 2 RadioBoxes' % (self.T,d))
  103. d = dict(
  104. Ff=self.Ff,
  105. Kids = PDFArray([k for k in self.kids]),
  106. FT = PDFName('Btn'),
  107. T = PDFString(self.T),
  108. #DA = PDFString('0 g'),
  109. )
  110. if self.V: d['V'] = PDFName(self.V)
  111. if self.TU: d['TU'] =PDFString(self.TU)
  112. r = PDFDictionary(d).format(doc)
  113. return r
  114. def _pdfObjToStr(obj):
  115. if isinstance(obj,PDFArray):
  116. return '[%s]' % ''.join((_pdfObjToStr(e) for e in obj.sequence))
  117. if isinstance(obj,PDFFromString):
  118. return obj._s
  119. return str(obj)
  120. class AcroForm(PDFObject):
  121. formFontNames = {
  122. "Helvetica": "Helv",
  123. "Helvetica-Bold": "HeBo",
  124. 'Courier': "Cour",
  125. 'Courier-Bold': "CoBo",
  126. 'Courier-Oblique': "CoOb",
  127. 'Courier-BoldOblique': "CoBO",
  128. 'Helvetica-Oblique': "HeOb",
  129. 'Helvetica-BoldOblique': "HeBO",
  130. 'Times-Roman': "Time",
  131. 'Times-Bold': "TiBo",
  132. 'Times-Italic': "TiIt",
  133. 'Times-BoldItalic': "TiBI",
  134. }
  135. def __init__(self,canv,**kwds):
  136. self.referenceMap = {}
  137. self._canv = weakref.ref(canv)
  138. self.fonts = {}
  139. self.fields = []
  140. self._radios = {}
  141. self._refMap = {}
  142. self._pdfdocenc = {}
  143. self.sigFlags = None
  144. self.extras = {}
  145. @property
  146. def canv(self):
  147. _canv = self._canv()
  148. if _canv is None:
  149. raise ValueError('%s.canv is no longer available' % self.__class__.__name__)
  150. return _canv
  151. def fontRef(self,f):
  152. return '/Font << /%s %s >>' % (f,self.fonts[f])
  153. def format(self,doc):
  154. d = dict(
  155. Fields = PDFArray([self.getRef(f) for f in self.fields]),
  156. )
  157. if self.sigFlags: d['SigFlags'] = self.sigFlags
  158. if self.fonts:
  159. FK = list(sorted(self.fonts.keys()))
  160. F = [self.fontRef(f) for f in FK]
  161. d['DA'] = PDFString('/%s 0 Tf 0 g' % FK[0])
  162. d['DR'] = PDFFromString('<< /Encoding\n<<\n/RLAFencoding\n%s\n>>\n%s\n>>' % (self.encRefStr,'\n'.join(F)))
  163. d.update(self.extras)
  164. r = PDFDictionary(d).format(doc)
  165. return r
  166. def colorTuple(self,c):
  167. # ISO-32000-1, Table 189: An array of numbers that shall be in ther
  168. # range 0.0 to 1.0 specifying the colour [..]. The number of array
  169. # elements determines the colour space in which the colour shall
  170. # be defined:
  171. # 0 No colour; transparent 1 DeviceGray 3 DeviceRGB 4 DeviceCMYK
  172. if c is None or c.alpha == 0:
  173. return ()
  174. return c.cmyk() if isinstance(c,CMYKColor) else c.rgb()
  175. def streamFillColor(self,c):
  176. t = self.colorTuple(c)
  177. return fp_str(*t)+(' k' if len(t)==4 else ' rg')
  178. def streamStrokeColor(self,c):
  179. t = self.colorTuple(c)
  180. return fp_str(*t)+(' K' if len(t)==4 else ' RG')
  181. def checkboxAP(self,
  182. key, #N/D/R
  183. value, #Yes/Off
  184. buttonStyle='circle',
  185. shape='square',
  186. fillColor=None,
  187. borderColor=None,
  188. textColor=None,
  189. borderWidth=1,
  190. borderStyle='solid',
  191. size=20,
  192. dashLen=3,
  193. ):
  194. stream = [].append
  195. ds = size
  196. if shape=='square':
  197. stream('q')
  198. streamFill = self.streamFillColor(fillColor)
  199. stream('1 g 1 G %(streamFill)s 0 0 %(size)s %(size)s re f')
  200. if borderWidth!=None:
  201. streamStroke = self.streamStrokeColor(borderColor)
  202. hbw = borderWidth*0.5
  203. smbw = size - borderWidth
  204. ds = smbw
  205. if borderStyle=='underlined':
  206. stream('%(streamStroke)s %(borderWidth)s w 0 %(hbw)s m %(size)s %(hbw)s l s')
  207. elif borderStyle in ('dashed','inset','bevelled','solid'):
  208. if borderStyle=='dashed':
  209. dash = ' [%s ] 0 d' % fp_str(dashLen)
  210. else:
  211. dash = ''
  212. stream('%(streamStroke)s%(dash)s %(borderWidth)s w %(hbw)s %(hbw)s %(smbw)s %(smbw)s re s')
  213. if borderStyle in ('bevelled','inset'):
  214. _2bw = 2*borderWidth
  215. sm2bw = size - _2bw
  216. ds = sm2bw
  217. bbs0 = Blacker(fillColor,0.5)
  218. bbs1 = fillColor
  219. if key!='D':
  220. bbs0, bbs1 = bbs1, bbs0
  221. bbs0 = self.streamFillColor(bbs0)
  222. bbs1 = self.streamFillColor(bbs1)
  223. 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')
  224. stream('Q')
  225. elif shape=='circle':
  226. cas = lambda _r,**_casKwds: self.circleArcStream(size,_r,**_casKwds)
  227. r = size*0.5
  228. streamFill = self.streamFillColor(fillColor)
  229. stream('q 1 g 1 G %(streamFill)s')
  230. stream(cas(r))
  231. stream('f')
  232. stream('Q')
  233. if borderWidth!=None:
  234. stream('q')
  235. streamStroke = self.streamStrokeColor(borderColor)
  236. hbw = borderWidth*0.5
  237. ds = size - borderWidth
  238. if borderStyle=='underlined':
  239. stream('q %(streamStroke)s %(borderWidth)s w 0 %(hbw)s m %(size)s %(hbw)s l s Q')
  240. elif borderStyle in ('dashed','inset','bevelled','solid'):
  241. if borderStyle=='dashed':
  242. dash = ' [3 ] 0 d'
  243. else:
  244. dash = ''
  245. stream('%(streamStroke)s%(dash)s %(borderWidth)s w')
  246. stream(cas(r-hbw))
  247. stream('s')
  248. stream('Q')
  249. if borderStyle in ('bevelled','inset'):
  250. _3bwh = 3*hbw
  251. ds = size - _3bwh
  252. bbs0 = Blacker(fillColor,0.5)
  253. bbs1 = Whiter(fillColor,0.5)
  254. a0 = (0,1)
  255. a1 = (2,3)
  256. if borderStyle=='inset':
  257. bbs0, bbs1 = bbs1, bbs0
  258. if key!='D':
  259. bbs0, bbs1 = bbs1, bbs0
  260. bbs0 = self.streamStrokeColor(bbs0)
  261. bbs1 = self.streamStrokeColor(bbs1)
  262. stream('q %(bbs0)s %(borderWidth)s w')
  263. stream(cas(r-_3bwh,rotated=True,arcs=a0))
  264. stream('S Q %(bbs1)s q')
  265. stream(cas(r-_3bwh,rotated=True,arcs=a1))
  266. stream('S Q')
  267. if value=='Yes':
  268. textFillColor = self.streamFillColor(textColor)
  269. textStrokeColor = self.streamStrokeColor(textColor)
  270. stream('q %(textFillColor)s %(textStrokeColor)s')
  271. cbm = cbmarks[buttonStyle]
  272. if shape=='circle' and buttonStyle=='circle':
  273. stream(cas((max(r-(size-ds),1))*0.5))
  274. stream('f')
  275. else:
  276. stream(cbm.scaledRender(size,size-ds))
  277. stream('Q')
  278. stream = ('\n'.join(stream.__self__) % vars()).replace(' ',' ').replace('\n\n','\n')
  279. return self.makeStream(
  280. size, size, stream,
  281. Resources = PDFFromString('<< /ProcSet [/PDF] >>'),
  282. )
  283. @staticmethod
  284. def circleArcStream(size, r, arcs=(0,1,2,3), rotated=False):
  285. R = [].append
  286. rlen = R.__self__.__len__
  287. hsize = size * 0.5
  288. f = size / 20.0
  289. size *= f
  290. hsize *= f
  291. r *= f
  292. cp = fp_str(0.55231 * r)
  293. r = fp_str(r)
  294. hsize = fp_str(hsize)
  295. mx = '0.7071 0.7071 -0.7071 0.7071' if rotated else '1 0 0 1'
  296. R('%(mx)s %(hsize)s %(hsize)s cm')
  297. if 0 in arcs:
  298. if rlen()==1: R('%(r)s 0 m')
  299. R('%(r)s %(cp)s %(cp)s %(r)s 0 %(r)s c')
  300. if 1 in arcs:
  301. if rlen()==1: R('0 %(r)s m')
  302. R('-%(cp)s %(r)s -%(r)s %(cp)s -%(r)s 0 c')
  303. if 2 in arcs:
  304. if rlen()==1: R('-%(r)s 0 m')
  305. R('-%(r)s -%(cp)s -%(cp)s -%(r)s 0 -%(r)s c')
  306. if 3 in arcs:
  307. if rlen()==1: R('0 -%(r)s m')
  308. R('%(cp)s -%(r)s %(r)s -%(cp)s %(r)s 0 c')
  309. return '\n'.join(R.__self__) % vars()
  310. def zdMark(self,c,size,ds,iFontName):
  311. c = ZDSyms[c]
  312. W = H = size-ds
  313. fs = H/1.2
  314. w = float(stringWidth(c,'ZapfDingbats',fs))
  315. if w>W:
  316. fs *= W/w
  317. dx = ds + 0.5*(W-w)
  318. dy = 0
  319. return 'BT %(iFontName)s %(fs)s Tf %(dx)s %(dy)s Td %(fs)s TL (%(c)s) Tj ET' % vars()
  320. def getRef(self,obj):
  321. return self.canv._doc.Reference(obj)
  322. def getRefStr(self,obj):
  323. return asNative(self.getRef(obj).format(self.canv._doc))
  324. @staticmethod
  325. def stdColors(t,b,f):
  326. if isinstance(f,CMYKColor) or isinstance(t,CMYKColor) or isinstance(b,CMYKColor):
  327. 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))
  328. else:
  329. 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))
  330. @staticmethod
  331. def varyColors(key,t,b,f):
  332. if key!='N':
  333. func = Whiter if key=='R' else Blacker
  334. t,b,f = [func(c,0.9) for c in (t,b,f)]
  335. return t,b,f
  336. def checkForceBorder(self,x,y,width,height,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor):
  337. if forceBorder:
  338. canv = self.canv
  339. canv.saveState()
  340. canv.resetTransforms()
  341. if borderWidth!=None:
  342. hbw = 0.5*borderWidth
  343. canv.setLineWidth(borderWidth)
  344. canv.setStrokeColor(borderColor)
  345. s = 1
  346. else:
  347. s = hbw = 0
  348. width -= 2*hbw
  349. height -= 2*hbw
  350. x += hbw
  351. y += hbw
  352. canv.setFillColor(fillColor)
  353. if shape=='square':
  354. canv.rect(x,y,width,height,stroke=s,fill=1)
  355. else:
  356. r = min(width,height) * 0.5
  357. canv.circle(x+r,y+r,r,stroke=s,fill=1)
  358. canv.restoreState()
  359. def checkbox(self,
  360. checked=False,
  361. buttonStyle='check',
  362. shape='square',
  363. fillColor=None,
  364. borderColor=None,
  365. textColor=None,
  366. borderWidth=1,
  367. borderStyle='solid',
  368. size=20,
  369. x=0,
  370. y=0,
  371. tooltip=None,
  372. name=None,
  373. annotationFlags='print',
  374. fieldFlags='required',
  375. forceBorder=False,
  376. relative=False,
  377. dashLen = 3,
  378. ):
  379. initialValue = 'Yes' if checked else 'Off'
  380. textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor)
  381. canv = self.canv
  382. if relative:
  383. x, y = self.canv.absolutePosition(x,y)
  384. doc = canv._doc
  385. AP = {}
  386. for key in 'NDR':
  387. APV = {}
  388. tC,bC,fC = self.varyColors(key,textColor,borderColor,fillColor)
  389. for value in ('Yes','Off'):
  390. ap = self.checkboxAP(
  391. key,
  392. value,
  393. buttonStyle=buttonStyle,
  394. shape=shape,
  395. fillColor=fC,
  396. borderColor=bC,
  397. textColor=tC,
  398. borderWidth=borderWidth,
  399. borderStyle=borderStyle,
  400. size=size,
  401. dashLen=dashLen,
  402. )
  403. if ap._af_refstr in self._refMap:
  404. ref = self._refMap[ap._af_refstr]
  405. else:
  406. ref = self.getRef(ap)
  407. self._refMap[ap._af_refstr] = ref
  408. APV[value]=ref
  409. AP[key] = PDFDictionary(APV)
  410. del APV
  411. CB = dict(
  412. FT = PDFName('Btn'),
  413. P = doc.thisPageRef(),
  414. V = PDFName(initialValue),
  415. AS = PDFName(initialValue),
  416. #DV = PDFName(initialValue),
  417. Rect = PDFArray((x,y,x+size,y+size)),
  418. AP = PDFDictionary(AP),
  419. Subtype = PDFName('Widget'),
  420. Type = PDFName('Annot'),
  421. F = makeFlags(annotationFlags,annotationFlagValues),
  422. Ff = makeFlags(fieldFlags,fieldFlagValues),
  423. H=PDFName('N'),
  424. )
  425. if tooltip:
  426. CB['TU'] = PDFString(tooltip)
  427. if not name:
  428. name = 'AFF%03d' % len(self.fields)
  429. if borderWidth: CB['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
  430. CB['T'] = PDFString(name)
  431. MK = dict(
  432. CA='(%s)' % ZDSyms[buttonStyle],
  433. BC=PDFArray(self.colorTuple(borderColor)),
  434. BG=PDFArray(self.colorTuple(fillColor)),
  435. )
  436. CB['MK'] = PDFDictionary(MK)
  437. CB = PDFDictionary(CB)
  438. self.canv._addAnnotation(CB)
  439. self.fields.append(self.getRef(CB))
  440. self.checkForceBorder(x,y,size,size,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor)
  441. def radio(self,
  442. value=None,
  443. selected=False,
  444. buttonStyle='circle',
  445. shape='circle',
  446. fillColor=None,
  447. borderColor=None,
  448. textColor=None,
  449. borderWidth=1,
  450. borderStyle='solid',
  451. size=20,
  452. x=0,
  453. y=0,
  454. tooltip=None,
  455. name=None,
  456. annotationFlags='print',
  457. fieldFlags='noToggleToOff required radio',
  458. forceBorder=False,
  459. relative=False,
  460. dashLen=3,
  461. ):
  462. if name not in self._radios:
  463. group = RadioGroup(name,tooltip=tooltip,fieldFlags=fieldFlags)
  464. group._ref = self.getRef(group)
  465. self._radios[name] = group
  466. self.fields.append(group._ref)
  467. else:
  468. group = self._radios[name]
  469. fieldFlags = makeFlags(fieldFlags,fieldFlagValues)
  470. if fieldFlags!=group.Ff:
  471. raise ValueError('radio.%s.%s created with different flags' % (name,value))
  472. if not value:
  473. raise ValueError('bad value %r for radio.%s' % (value,name))
  474. initialValue = value if selected else 'Off'
  475. textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor)
  476. if initialValue==value:
  477. if group.V is not None:
  478. if group.V!=value:
  479. raise ValueError('radio.%s.%s sets initial value conflicting with %s'%(name,value,group.V))
  480. else:
  481. group.V = value
  482. canv = self.canv
  483. if relative:
  484. x, y = self.canv.absolutePosition(x,y)
  485. doc = canv._doc
  486. AP = {}
  487. for key in 'NDR':
  488. APV = {}
  489. tC,bC,fC = self.varyColors(key,textColor,borderColor,fillColor)
  490. for v in (value,'Off'):
  491. ap = self.checkboxAP(
  492. key,
  493. 'Yes' if v==value else 'Off',
  494. buttonStyle=buttonStyle,
  495. shape=shape,
  496. fillColor=fC,
  497. borderColor=bC,
  498. textColor=tC,
  499. borderWidth=borderWidth,
  500. borderStyle=borderStyle,
  501. size=size,
  502. dashLen=dashLen,
  503. )
  504. if ap._af_refstr in self._refMap:
  505. ref = self._refMap[ap._af_refstr]
  506. else:
  507. ref = self.getRef(ap)
  508. self._refMap[ap._af_refstr] = ref
  509. APV[v]=ref
  510. AP[key] = PDFDictionary(APV)
  511. del APV
  512. RB = dict(
  513. FT = PDFName('Btn'),
  514. P = doc.thisPageRef(),
  515. AS = PDFName(initialValue),
  516. #DV = PDFName(initialValue),
  517. Rect = PDFArray((x,y,x+size,y+size)),
  518. AP = PDFDictionary(AP),
  519. Subtype = PDFName('Widget'),
  520. Type = PDFName('Annot'),
  521. F = makeFlags(annotationFlags,annotationFlagValues),
  522. Parent = group._ref,
  523. #DA = PDFString('1 g '+(self.streamFillColor(fillColor) if fillColor else '-0.25 0.75 -0.25 rg'))
  524. H=PDFName('N'),
  525. )
  526. #RB['T'] = PDFString(name)
  527. MK = dict(
  528. CA='(%s)' % ZDSyms[buttonStyle],
  529. BC=PDFArray(self.colorTuple(borderColor)),
  530. BG=PDFArray(self.colorTuple(fillColor)),
  531. )
  532. if borderWidth: RB['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
  533. RB['MK'] = PDFDictionary(MK)
  534. RB = PDFDictionary(RB)
  535. self.canv._addAnnotation(RB)
  536. group.kids.append(self.getRef(RB))
  537. self.checkForceBorder(x,y,size,size,forceBorder,shape,borderStyle,borderWidth,borderColor,fillColor)
  538. def makeStream(self,
  539. width,
  540. height,
  541. stream,
  542. **D
  543. ):
  544. D['Matrix'] = PDFArray([1.0,0.0,0.0,1.0,0.0,0.0])
  545. D['BBox'] = PDFArray([0,0,width,height])
  546. D['Subtype'] = PDFName('Form')
  547. D['Type'] = PDFName('XObject')
  548. D['FormType'] = 1
  549. s = PDFStream(
  550. PDFDictionary(D),
  551. stream,
  552. filters = [PDFStreamFilterZCompress()] if self.canv._doc.compression else None,
  553. )
  554. #compute a lookup string
  555. s._af_refstr = stream+'\n'.join(('%s=%r' % (k,_pdfObjToStr(v)) for k,v in sorted(D.items())))
  556. return s
  557. def txAP(self,
  558. key, #N/D/R
  559. value,
  560. iFontName,
  561. rFontName,
  562. fontSize,
  563. shape='square',
  564. fillColor=None,
  565. borderColor=None,
  566. textColor=None,
  567. borderWidth=1,
  568. borderStyle='solid',
  569. width=120,
  570. height=36,
  571. dashLen=3,
  572. wkind='textfield',
  573. labels=[],
  574. I=[],
  575. sel_bg='0.600006 0.756866 0.854904 rg',
  576. sel_fg='0 g',
  577. ):
  578. stream = [].append
  579. if opaqueColor(fillColor):
  580. streamFill = self.streamFillColor(fillColor)
  581. stream('%(streamFill)s\n0 0 %(width)s %(height)s re\nf')
  582. if borderWidth!=None and borderWidth>0 and opaqueColor(borderColor):
  583. hbw = borderWidth*0.5
  584. bww = width - borderWidth
  585. bwh = height - borderWidth
  586. _2bw = 2*borderWidth
  587. if borderStyle in ('bevelled','inset'):
  588. bw2w = width - _2bw
  589. bw2h = height - _2bw
  590. if borderStyle == 'bevelled':
  591. bbs0 = '1 g'
  592. if fillColor or borderColor:
  593. bbs1 = '-0.250977 0.749023 -0.250977 rg'
  594. else:
  595. bbs1 = '.75293 g'
  596. else:
  597. bbs0 = '.501953 g'
  598. bbs1 = '.75293 g'
  599. 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')
  600. else:
  601. hbw = _2bw = borderWidth = 0
  602. bww = width
  603. bwh = height
  604. undash = ''
  605. if opaqueColor(borderColor) and borderWidth:
  606. streamStroke = self.streamStrokeColor(borderColor)
  607. if borderStyle=='underlined':
  608. stream('%(streamStroke)s %(borderWidth)s w 0 %(hbw)s m %(width)s %(hbw)s l s')
  609. elif borderStyle in ('dashed','inset','bevelled','solid'):
  610. if borderStyle=='dashed':
  611. dash = '\n[%s ] 0 d\n' % fp_str(dashLen)
  612. undash = '[] 0 d'
  613. else:
  614. dash = '\n%s w' % borderWidth
  615. stream('%(streamStroke)s\n%(dash)s\n%(hbw)s %(hbw)s %(bww)s %(bwh)s re\ns')
  616. _4bw = 4*borderWidth
  617. w4bw = width - _4bw
  618. h4bw = height - _4bw
  619. textFill = self.streamFillColor(textColor)
  620. stream('/Tx BMC \nq\n%(_2bw)s %(_2bw)s %(w4bw)s %(h4bw)s re\nW\nn')
  621. leading = 1.2 * fontSize
  622. if wkind=='listbox':
  623. nopts = int(h4bw/leading)
  624. leading = h4bw/float(nopts)
  625. if nopts>len(labels):
  626. i0 = 0
  627. nopts = len(labels)
  628. elif len(I)<=1:
  629. i0 = I[0] if I else 0
  630. if i0:
  631. if i0<nopts:
  632. i0 = 0
  633. else:
  634. i = len(labels) - nopts
  635. if i0>=i:
  636. i0 = i
  637. else: #|I|>1
  638. if I[1]<nopts:
  639. i0 = 0
  640. else:
  641. i0 = I[0]
  642. y = len(labels)
  643. i = i0 + nopts
  644. if i>y: i0 = i - y
  645. ilim = min(y,i0+nopts)
  646. if I:
  647. i = i0
  648. y = height - _2bw - leading
  649. stream(sel_bg)
  650. while i<ilim:
  651. if i in I:
  652. #draw selected bg
  653. stream('%%(_2bw)s %s %%(w4bw)s %%(leading)s re\nf' % fp_str(y))
  654. y -= leading
  655. i += 1
  656. i = i0
  657. y = height - _2bw - fontSize
  658. stream('0 g\n0 G\n%(undash)s')
  659. while i<ilim:
  660. stream('BT')
  661. if i==i0:
  662. stream('/%(iFontName)s %(fontSize)s Tf')
  663. stream(sel_fg if i in I else '%(textFill)s')
  664. stream('%%(_4bw)s %s Td\n(%s) Tj' % (fp_str(y),escPDF(labels[i])))
  665. y -= leading
  666. i += 1
  667. stream('ET')
  668. else:
  669. stream('0 g\n0 G\n%(undash)s')
  670. y = height - fontSize - _2bw
  671. stream('BT\n/%(iFontName)s %(fontSize)s Tf\n%(textFill)s')
  672. for line in value.split('\n'):
  673. stream('%%(_4bw)s %s Td\n(%s) Tj' % (y,escPDF(line)))
  674. y -= leading
  675. stream('ET')
  676. leading = fp_str(leading)
  677. stream('Q\nEMC\n')
  678. stream = ('\n'.join(stream.__self__) % vars()).replace(' ',' ').replace('\n\n','\n')
  679. return self.makeStream(
  680. width, height, stream,
  681. Resources = PDFFromString('<< /ProcSet [/PDF /Text] /Font %(rFontName)s >>' % vars()),
  682. )
  683. def makeFont(self,fontName):
  684. if fontName is None:
  685. fontName = 'Helvetica'
  686. if fontName not in self.formFontNames:
  687. raise ValueError('form font name, %r, is not one of the standard 14 fonts' % fontName)
  688. fn = self.formFontNames[fontName]
  689. ref = self.getRefStr(PDFFromString('<< /BaseFont /%s /Subtype /Type1 /Name /%s /Type /Font /Encoding %s >>' % (
  690. fontName,fn,self.encRefStr)))
  691. if fn not in self.fonts:
  692. self.fonts[fn] = ref
  693. return ref, fn
  694. def _textfield(self,
  695. value='',
  696. fillColor=None,
  697. borderColor=None,
  698. textColor=None,
  699. borderWidth=1,
  700. borderStyle='solid',
  701. width=120,
  702. height=36,
  703. x=0,
  704. y=0,
  705. tooltip=None,
  706. name=None,
  707. annotationFlags='print',
  708. fieldFlags='',
  709. forceBorder=False,
  710. relative=False,
  711. maxlen=100,
  712. fontName=None,
  713. fontSize=None,
  714. wkind=None,
  715. options=None,
  716. dashLen=3,
  717. ):
  718. rFontName, iFontName = self.makeFont(fontName)
  719. if fontSize is None:
  720. fontSize = 12
  721. textColor,borderColor,fillColor=self.stdColors(textColor,borderColor,fillColor)
  722. canv = self.canv
  723. if relative:
  724. x, y = self.canv.absolutePosition(x,y)
  725. doc = canv._doc
  726. rFontName = '<</%s %s>>' % (iFontName,rFontName)
  727. Ff = makeFlags(fieldFlags,fieldFlagValues)
  728. if wkind!='textfield':
  729. #options must be a list of pairs (label value)
  730. #value must be a list of the values
  731. FT='Ch'
  732. if wkind=='choice':
  733. Ff |= fieldFlagValues['combo'] #just in case
  734. V = []
  735. Opt = []
  736. AP = []
  737. I = []
  738. TF = []
  739. if not isinstance(options,(list,tuple)):
  740. raise TypeError('%s options=%r is wrong type' % (wkind,options))
  741. for v in options:
  742. if isStr(v):
  743. Opt.append(PDFString(v))
  744. l = v
  745. elif isinstance(v,(list,tuple)):
  746. if len(v)==1:
  747. v=l=v[0]
  748. else:
  749. l,v = v
  750. Opt.append(PDFArray([PDFString(v),PDFString(l)]))
  751. else:
  752. raise TypeError('%s option %r is wrong type' % (wkind,v))
  753. AP.append(v)
  754. TF.append(l)
  755. Opt = PDFArray(Opt)
  756. if value:
  757. if not isinstance(value,(list,tuple)):
  758. value = [value]
  759. for v in value:
  760. if v not in AP:
  761. if v not in TF:
  762. raise ValueError('%s value %r is not in option\nvalues %r\nor labels %r' % (wkind,v,AP,TF))
  763. else:
  764. v = AP[TF.index(v)]
  765. I.append(AP.index(v))
  766. V.append(PDFString(v))
  767. I.sort()
  768. if not (Ff & fieldFlagValues['multiSelect']) or len(value)==1:
  769. if wkind=='choice':
  770. value = TF[I[0]]
  771. else:
  772. value = value[:1]
  773. V = V[:1]
  774. V = V[0] if len(V)==1 else PDFArray(V)
  775. lbextras = dict(labels=TF,I=I,wkind=wkind)
  776. else:
  777. V = PDFString(value)
  778. else:
  779. I = Opt = []
  780. lbextras = {}
  781. FT='Tx'
  782. if not isStr(value):
  783. raise TypeError('textfield value=%r is wrong type' % value)
  784. V = PDFString(value)
  785. AP = {}
  786. for key in 'N':
  787. tC,bC,fC = self.varyColors(key,textColor,borderColor,fillColor)
  788. ap = self.txAP(
  789. key,
  790. value,
  791. iFontName,
  792. rFontName,
  793. fontSize,
  794. fillColor=fC,
  795. borderColor=bC,
  796. textColor=tC,
  797. borderWidth=borderWidth,
  798. borderStyle=borderStyle,
  799. width=width,
  800. height=height,
  801. dashLen = dashLen,
  802. **lbextras
  803. )
  804. if ap._af_refstr in self._refMap:
  805. ref = self._refMap[ap._af_refstr]
  806. else:
  807. ref = self.getRef(ap)
  808. self._refMap[ap._af_refstr] = ref
  809. AP[key] = ref
  810. TF = dict(
  811. FT = PDFName(FT),
  812. P = doc.thisPageRef(),
  813. V = V,
  814. #AS = PDFName(value),
  815. DV = V,
  816. Rect = PDFArray((x,y,x+width,y+height)),
  817. AP = PDFDictionary(AP),
  818. Subtype = PDFName('Widget'),
  819. Type = PDFName('Annot'),
  820. F = makeFlags(annotationFlags,annotationFlagValues),
  821. Ff = Ff,
  822. #H=PDFName('N'),
  823. DA=PDFString('/%s %d Tf %s' % (iFontName,fontSize, self.streamFillColor(textColor))),
  824. )
  825. if Opt: TF['Opt'] = Opt
  826. if I: TF['I'] = PDFArray(I)
  827. if maxlen:
  828. TF['MaxLen'] = maxlen
  829. if tooltip:
  830. TF['TU'] = PDFString(tooltip)
  831. if not name:
  832. name = 'AFF%03d' % len(self.fields)
  833. TF['T'] = PDFString(name)
  834. MK = dict(
  835. BG=PDFArray(self.colorTuple(fillColor)),
  836. )
  837. # Acrobat seems to draw a thin border when BS is defined, so only
  838. # include this if there actually is a border to draw
  839. if borderWidth:
  840. TF['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
  841. MK['BC'] = PDFArray(self.colorTuple(borderColor))
  842. TF['MK'] = PDFDictionary(MK)
  843. TF = PDFDictionary(TF)
  844. self.canv._addAnnotation(TF)
  845. self.fields.append(self.getRef(TF))
  846. self.checkForceBorder(x,y,width,height,forceBorder,'square',borderStyle,borderWidth,borderColor,fillColor)
  847. def textfield(self,
  848. value='',
  849. fillColor=None,
  850. borderColor=None,
  851. textColor=None,
  852. borderWidth=1,
  853. borderStyle='solid',
  854. width=120,
  855. height=36,
  856. x=0,
  857. y=0,
  858. tooltip=None,
  859. name=None,
  860. annotationFlags='print',
  861. fieldFlags='',
  862. forceBorder=False,
  863. relative=False,
  864. maxlen=100,
  865. fontName=None,
  866. fontSize=None,
  867. dashLen=3,
  868. ):
  869. return self._textfield(
  870. value=value,
  871. fillColor=fillColor,
  872. borderColor=borderColor,
  873. textColor=textColor,
  874. borderWidth=borderWidth,
  875. borderStyle=borderStyle,
  876. width=width,
  877. height=height,
  878. x=x,
  879. y=y,
  880. tooltip=tooltip,
  881. name=name,
  882. annotationFlags=annotationFlags,
  883. fieldFlags=fieldFlags,
  884. forceBorder=forceBorder,
  885. relative=relative,
  886. maxlen=maxlen,
  887. fontName=fontName,
  888. fontSize=fontSize,
  889. dashLen=dashLen,
  890. wkind='textfield',
  891. )
  892. def listbox(self,
  893. value='',
  894. fillColor=None,
  895. borderColor=None,
  896. textColor=None,
  897. borderWidth=1,
  898. borderStyle='solid',
  899. width=120,
  900. height=36,
  901. x=0,
  902. y=0,
  903. tooltip=None,
  904. name=None,
  905. annotationFlags='print',
  906. fieldFlags='',
  907. forceBorder=False,
  908. relative=False,
  909. fontName=None,
  910. fontSize=None,
  911. dashLen=3,
  912. maxlen=None,
  913. options=[],
  914. ):
  915. return self._textfield(
  916. value=value,
  917. fillColor=fillColor,
  918. borderColor=borderColor,
  919. textColor=textColor,
  920. borderWidth=borderWidth,
  921. borderStyle=borderStyle,
  922. width=width,
  923. height=height,
  924. x=x,
  925. y=y,
  926. tooltip=tooltip,
  927. name=name,
  928. annotationFlags=annotationFlags,
  929. fieldFlags=fieldFlags,
  930. forceBorder=forceBorder,
  931. relative=relative,
  932. maxlen=maxlen,
  933. fontName=fontName,
  934. fontSize=fontSize,
  935. dashLen=dashLen,
  936. wkind='listbox',
  937. options = options,
  938. )
  939. def choice(self,
  940. value='',
  941. fillColor=None,
  942. borderColor=None,
  943. textColor=None,
  944. borderWidth=1,
  945. borderStyle='solid',
  946. width=120,
  947. height=36,
  948. x=0,
  949. y=0,
  950. tooltip=None,
  951. name=None,
  952. annotationFlags='print',
  953. fieldFlags='combo',
  954. forceBorder=False,
  955. relative=False,
  956. fontName=None,
  957. fontSize=None,
  958. dashLen=3,
  959. maxlen=None,
  960. options=[],
  961. ):
  962. return self._textfield(
  963. value=value,
  964. fillColor=fillColor,
  965. borderColor=borderColor,
  966. textColor=textColor,
  967. borderWidth=borderWidth,
  968. borderStyle=borderStyle,
  969. width=width,
  970. height=height,
  971. x=x,
  972. y=y,
  973. tooltip=tooltip,
  974. name=name,
  975. annotationFlags=annotationFlags,
  976. fieldFlags=fieldFlags,
  977. forceBorder=forceBorder,
  978. relative=relative,
  979. maxlen=maxlen,
  980. fontName=fontName,
  981. fontSize=fontSize,
  982. dashLen=dashLen,
  983. wkind='choice',
  984. options = options,
  985. )
  986. def checkboxRelative(self, **kwds):
  987. "same as checkbox except the x and y are relative to the canvas coordinate transform"
  988. kwds['relative']=True
  989. self.checkbox(**kwds)
  990. def radioRelative(self, **kwds):
  991. "same as radio except the x and y are relative to the canvas coordinate transform"
  992. kwds['relative']=True
  993. self.radio(**kwds)
  994. def textfieldRelative(self, **kwds):
  995. "same as textfield except the x and y are relative to the canvas coordinate transform"
  996. kwds['relative']=True
  997. self.textfield(**kwds)
  998. def listboxRelative(self, **kwds):
  999. "same as textfield except the x and y are relative to the canvas coordinate transform"
  1000. kwds['relative']=True
  1001. self.textfield(**kwds)
  1002. def choiceRelative(self, **kwds):
  1003. "same as textfield except the x and y are relative to the canvas coordinate transform"
  1004. kwds['relative']=True
  1005. self.textfield(**kwds)
  1006. @property
  1007. def encRefStr(self):
  1008. if not self._pdfdocenc:
  1009. self._pdfdocenc = PDFFromString('''<</Type /Encoding /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]>>''')
  1010. return self.getRefStr(self._pdfdocenc)
  1011. class CBMark:
  1012. opNames = 'm l c h'.split()
  1013. opCount = 1,1,3,0
  1014. def __init__(self,ops,points,bounds,slack=0.05):
  1015. self.ops = ops
  1016. self.xmin,self.ymin,self.xmax,self.ymax = bounds
  1017. self.points = points
  1018. self.slack = slack
  1019. def scaledRender(self,size,ds=0):
  1020. '''
  1021. >>> print(cbmarks['check'].scaledRender(20))
  1022. 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
  1023. >>> print(cbmarks['cross'].scaledRender(20))
  1024. 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
  1025. >>> print(cbmarks['circle'].scaledRender(20))
  1026. 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
  1027. >>> print(cbmarks['star'].scaledRender(20))
  1028. 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
  1029. >>> print(cbmarks['diamond'].scaledRender(20))
  1030. 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
  1031. '''
  1032. #work out the scale and translation
  1033. W = H = size - 2*ds
  1034. xmin = self.xmin
  1035. ymin = self.ymin
  1036. w = self.xmax-xmin
  1037. h = self.ymax-ymin
  1038. slack = self.slack*min(W,H)
  1039. sx = (W - 2*slack)/float(w)
  1040. sy = (H - 2*slack)/float(h)
  1041. sx = sy = min(sx,sy)
  1042. w *= sx
  1043. h *= sy
  1044. dx = ds+(W - w)*0.5
  1045. dy = ds+(H - h)*0.5
  1046. xsc = lambda v: fp_str((v-xmin)*sx+dx)
  1047. ysc = lambda v: fp_str((v-ymin)*sy+dy)
  1048. opNames = self.opNames
  1049. opCount = self.opCount
  1050. C = [].append
  1051. i = 0
  1052. points = self.points
  1053. for op in self.ops:
  1054. c = opCount[op]
  1055. for _ in range(c):
  1056. C(xsc(points[i]))
  1057. C(ysc(points[i+1]))
  1058. i += 2
  1059. C(opNames[op])
  1060. C('f')
  1061. return ' '.join(C.__self__)
  1062. cbmarks = dict(
  1063. check=CBMark(
  1064. [0, 1, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 1, 3],
  1065. [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],
  1066. (34,-12,721,706),
  1067. ),
  1068. cross = CBMark(
  1069. [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3],
  1070. [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],
  1071. (35,0,727,692),
  1072. ),
  1073. circle = CBMark(
  1074. [0, 2, 2, 2, 2, 3],
  1075. [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],
  1076. (35,-14,757,705),
  1077. ),
  1078. star = CBMark(
  1079. [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3],
  1080. [409, 705, 494, 436, 782, 436, 550, 262, 640, -14, 409, 156, 178, -14, 269, 262, 35, 436, 322, 436, 409, 705],
  1081. (35,-14,782,705),
  1082. ),
  1083. diamond = CBMark(
  1084. [0, 1, 1, 1, 1, 1, 1, 1, 2, 3],
  1085. [560, 346, 488, 259, 297, -14, 200, 133, 100, 266, 34, 346, 110, 440, 297, 705, 373, 578, 463, 459, 560, 346],
  1086. (34,-14,560,705),
  1087. ),
  1088. )
  1089. ZDSyms=dict(check='4',cross='5',circle='l',star='N',diamond='u')
  1090. if __name__ == "__main__":
  1091. import doctest
  1092. doctest.testmod()