text: __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
  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 = {}
  
      @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.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)))
          r = PDFDictionary(d).format(doc)
          return r
  
      def colorTuple(self,c):
          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 fillColor:
              streamFill = self.streamFillColor(fillColor)
              stream('%(streamFill)s\n0 0 %(width)s %(height)s re\nf')
          if borderWidth!=None:
              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 = 0
              bww = width
              bwh = height
          undash = ''
          if borderColor:
              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<nopts:
                          i0 = 0
                      else:
                          i = len(labels) - nopts
                          if i0>=i:
                              i0 = i
              else:   #|I|>1
                  if I[1]<nopts:
                      i0 = 0
                  else:
                      i0 = I[0]
              y = len(labels)
              i = i0 + nopts
              if i>y: i0 = i - y
              ilim = min(y,i0+nopts)
              if I:
                  i = i0
                  y = height - _2bw - leading
                  stream(sel_bg)
                  while i<ilim:
                      if i in I:
                          #draw selected bg
                          stream('%%(_2bw)s %s %%(w4bw)s %%(leading)s re\nf' % fp_str(y))
                      y -= leading
                      i += 1
              i = i0
              y = height - _2bw - fontSize
              stream('0 g\n0 G\n%(undash)s')
              while i<ilim:
                  stream('BT')
                  if i==i0:
                      stream('/%(iFontName)s %(fontSize)s Tf')
                  stream(sel_fg if i in I else '%(textFill)s')
                  stream('%%(_4bw)s %s Td\n(%s) Tj' % (fp_str(y),escPDF(labels[i])))
                  y -= leading
                  i += 1
                  stream('ET')
          else:
              stream('0 g\n0 G\n%(undash)s')
              y = height - fontSize - _2bw
              stream('BT\n/%(iFontName)s %(fontSize)s Tf\n%(textFill)s')
              for line in value.split('\n'):
                  stream('%%(_4bw)s %s Td\n(%s) Tj' % (y,escPDF(line)))
                  y -= leading
              stream('ET')
          leading = fp_str(leading)
          stream('Q\nEMC\n')
          stream = ('\n'.join(stream.__self__) % vars()).replace('  ',' ').replace('\n\n','\n')
          return self.makeStream(
                  width, height, stream,
                  Resources = PDFFromString('<< /ProcSet [/PDF /Text] /Font %(rFontName)s >>' % 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 = '<</%s %s>>' % (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 0 0 1 rg'%(iFontName,fontSize)),
                  )
          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(
                  BC=PDFArray(self.colorTuple(borderColor)),
                  BG=PDFArray(self.colorTuple(fillColor)),
                  )
          TF['MK'] = PDFDictionary(MK)
          if borderWidth: TF['BS'] = bsPDF(borderWidth,borderStyle,dashLen)
          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('''<</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]>>''')
          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 xrange(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()
  