"Fossies" - the Fresh Open Source Software Archive

Member "xhtml2pdf-0.2.5/xhtml2pdf/xhtml2pdf_reportlab.py" (25 Sep 2020, 32751 Bytes) of package /linux/www/xhtml2pdf-0.2.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "xhtml2pdf_reportlab.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.2.4_vs_0.2.5.

    1 # -*- coding: utf-8 -*-
    2 
    3 # Copyright 2010 Dirk Holtwick, holtwick.it
    4 #
    5 # Licensed under the Apache License, Version 2.0 (the "License");
    6 # you may not use this file except in compliance with the License.
    7 # You may obtain a copy of the License at
    8 #
    9 #     http://www.apache.org/licenses/LICENSE-2.0
   10 #
   11 # Unless required by applicable law or agreed to in writing, software
   12 # distributed under the License is distributed on an "AS IS" BASIS,
   13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14 # See the License for the specific language governing permissions and
   15 # limitations under the License.
   16 
   17 from hashlib import md5
   18 from reportlab.lib.enums import TA_RIGHT
   19 from reportlab.lib.styles import ParagraphStyle
   20 from reportlab.lib.utils import flatten, open_for_read, getStringIO, \
   21     LazyImageReader, haveImages
   22 from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate, IndexingFlowable
   23 from reportlab.platypus.flowables import Flowable, CondPageBreak, \
   24     KeepInFrame, ParagraphAndImage
   25 from reportlab.platypus.tableofcontents import TableOfContents
   26 from reportlab.platypus.tables import Table, TableStyle
   27 from xhtml2pdf.reportlab_paragraph import Paragraph
   28 from xhtml2pdf.util import getUID, getBorderStyle
   29 
   30 
   31 import six
   32 import sys
   33 
   34 import cgi
   35 import copy
   36 import logging
   37 import reportlab.pdfbase.pdfform as pdfform
   38 
   39 try:
   40     import PIL.Image as PILImage
   41 except:
   42     try:
   43         import Image as PILImage
   44     except:
   45         PILImage = None
   46 
   47 if not six.PY2:
   48     from html import escape as html_escape
   49 else:
   50     from cgi import escape as html_escape
   51 
   52 log = logging.getLogger("xhtml2pdf")
   53 
   54 MAX_IMAGE_RATIO = 0.95
   55 PRODUCER = "xhtml2pdf <https://github.com/xhtml2pdf/xhtml2pdf/>"
   56 
   57 
   58 class PTCycle(list):
   59     def __init__(self):
   60         self._restart = 0
   61         self._idx = 0
   62         list.__init__(self)
   63 
   64     def cyclicIterator(self):
   65         while 1:
   66             yield self[self._idx]
   67             self._idx += 1
   68             if self._idx >= len(self):
   69                 self._idx = self._restart
   70 
   71 
   72 class PmlMaxHeightMixIn:
   73     def setMaxHeight(self, availHeight):
   74         self.availHeightValue = availHeight
   75         if availHeight < 70000:
   76             if hasattr(self, "canv"):
   77                 if not hasattr(self.canv, "maxAvailHeightValue"):
   78                     self.canv.maxAvailHeightValue = 0
   79                 self.availHeightValue = self.canv.maxAvailHeightValue = max(
   80                     availHeight,
   81                     self.canv.maxAvailHeightValue)
   82         # TODO: Useless condition
   83         else:
   84             self.availHeightValue = availHeight
   85         # TODO: availHeightValue is set above
   86         if not hasattr(self, "availHeightValue"):
   87             self.availHeightValue = 0
   88         return self.availHeightValue
   89 
   90     def getMaxHeight(self):
   91         if not hasattr(self, "availHeightValue"):
   92             return 0
   93         return self.availHeightValue
   94 
   95 
   96 class PmlBaseDoc(BaseDocTemplate):
   97     """
   98     We use our own document template to get access to the canvas
   99     and set some informations once.
  100     """
  101 
  102     def beforePage(self):
  103         self.canv._doc.info.producer = PRODUCER
  104 
  105         '''
  106         # Convert to ASCII because there is a Bug in Reportlab not
  107         # supporting other than ASCII. Send to list on 23.1.2007
  108         author = toString(self.pml_data.get("author", "")).encode("ascii","ignore")
  109         subject = toString(self.pml_data.get("subject", "")).encode("ascii","ignore")
  110         title = toString(self.pml_data.get("title", "")).encode("ascii","ignore")
  111         # print repr((author,title,subject))
  112         self.canv.setAuthor(author)
  113         self.canv.setSubject(subject)
  114         self.canv.setTitle(title)
  115         if self.pml_data.get("fullscreen", 0):
  116             self.canv.showFullScreen0()
  117         if self.pml_data.get("showoutline", 0):
  118             self.canv.showOutline()
  119         if self.pml_data.get("duration", None) is not None:
  120             self.canv.setPageDuration(self.pml_data["duration"])
  121         '''
  122 
  123     def afterFlowable(self, flowable):
  124         # Does the flowable contain fragments?
  125         if getattr(flowable, "outline", False):
  126             self.notify('TOCEntry', (
  127                 flowable.outlineLevel,
  128                 html_escape(copy.deepcopy(flowable.text), 1),
  129                 self.page))
  130 
  131     def handle_nextPageTemplate(self, pt):
  132         '''
  133         if pt has also templates for even and odd page convert it to list
  134         '''
  135         has_left_template = self._has_template_for_name(pt + '_left')
  136         has_right_template = self._has_template_for_name(pt + '_right')
  137 
  138         if has_left_template and has_right_template:
  139             pt = [pt + '_left', pt + '_right']
  140 
  141         '''On endPage change to the page template with name or index pt'''
  142         if isinstance(pt, str):
  143             if hasattr(self, '_nextPageTemplateCycle'):
  144                 del self._nextPageTemplateCycle
  145             for t in self.pageTemplates:
  146                 if t.id == pt:
  147                     self._nextPageTemplateIndex = self.pageTemplates.index(t)
  148                     return
  149             raise ValueError("can't find template('%s')" % pt)
  150         elif isinstance(pt, int):
  151             if hasattr(self, '_nextPageTemplateCycle'):
  152                 del self._nextPageTemplateCycle
  153             self._nextPageTemplateIndex = pt
  154         elif isinstance(pt, (list, tuple)):
  155             #used for alternating left/right pages
  156             #collect the refs to the template objects, complain if any are bad
  157             c = PTCycle()
  158             for ptn in pt:
  159                 #special case name used to short circuit the iteration
  160                 if ptn == '*':
  161                     c._restart = len(c)
  162                     continue
  163                 for t in self.pageTemplates:
  164                     if t.id == ptn.strip():
  165                         c.append(t)
  166                         break
  167             if not c:
  168                 raise ValueError("No valid page templates in cycle")
  169             elif c._restart > len(c):
  170                 raise ValueError("Invalid cycle restart position")
  171 
  172             #ensure we start on the first one$
  173             self._nextPageTemplateCycle = c.cyclicIterator()
  174         else:
  175             raise TypeError("Argument pt should be string or integer or list")
  176 
  177     def _has_template_for_name(self, name):
  178         for template in self.pageTemplates:
  179             if template.id == name.strip():
  180                 return True
  181         return False
  182 
  183 
  184 class PmlPageTemplate(PageTemplate):
  185     PORTRAIT = 'portrait'
  186     LANDSCAPE = 'landscape'
  187     # by default portrait
  188     pageorientation = PORTRAIT
  189 
  190     def __init__(self, **kw):
  191         self.pisaStaticList = []
  192         self.pisaBackgroundList = []
  193         self.pisaBackground = None
  194         PageTemplate.__init__(self, **kw)
  195         self._page_count = 0
  196         self._first_flow = True
  197 
  198         ### Background Image ###
  199         self.img = None
  200         self.ph = 0
  201         self.h = 0
  202         self.w = 0
  203 
  204     def isFirstFlow(self, canvas):
  205         if self._first_flow:
  206             if canvas.getPageNumber() <= self._page_count:
  207                 self._first_flow = False
  208             else:
  209                 self._page_count = canvas.getPageNumber()
  210                 canvas._doctemplate._page_count = canvas.getPageNumber()
  211         return self._first_flow
  212 
  213     def isPortrait(self):
  214         return self.pageorientation == self.PORTRAIT
  215 
  216     def isLandscape(self):
  217         return self.pageorientation == self.LANDSCAPE
  218 
  219     def beforeDrawPage(self, canvas, doc):
  220         canvas.saveState()
  221         try:
  222 
  223             # Background
  224             pisaBackground = None
  225             if (self.isFirstFlow(canvas)
  226                 and hasattr(self, "pisaBackground")
  227                 and self.pisaBackground
  228                 and (not self.pisaBackground.notFound())):
  229 
  230                 # Is image not PDF
  231                 if self.pisaBackground.mimetype.startswith("image/"):
  232 
  233                     try:
  234                         self.img = PmlImageReader(six.BytesIO(self.pisaBackground.getData()))
  235                         iw, ih = self.img.getSize()
  236                         pw, self.ph = canvas._pagesize
  237 
  238                         width = pw  # min(iw, pw) # max
  239                         wfactor = float(width) / iw
  240                         height = self.ph  # min(ih, ph) # max
  241                         hfactor = float(height) / ih
  242                         factor_min = min(wfactor, hfactor)
  243 
  244                         if self.isPortrait():
  245                             self.w = iw * factor_min
  246                             self.h = ih * factor_min
  247                             canvas.drawImage(self.img, 0, self.ph - self.h, self.w, self.h)
  248                         elif self.isLandscape():
  249                             factor_max = max(wfactor, hfactor)
  250                             self.h = ih * factor_max
  251                             self.w = iw * factor_min
  252                             canvas.drawImage(self.img, 0, 0, self.w, self.h)
  253                     except:
  254                         log.exception("Draw background")
  255 
  256                 # PDF!
  257                 else:
  258                     pisaBackground = self.pisaBackground
  259 
  260             self.pisaBackgroundList.append(pisaBackground)
  261 
  262             def pageNumbering(objList):
  263                 for obj in flatten(objList):
  264                     if isinstance(obj, PmlParagraph):
  265                         for frag in obj.frags:
  266                             if frag.pageNumber:
  267                                 frag.text = str(pagenumber)
  268                             elif frag.pageCount:
  269                                 frag.text = str(canvas._doctemplate._page_count)
  270 
  271                     elif isinstance(obj, PmlTable):
  272                         # Flatten the cells ([[1,2], [3,4]] becomes [1,2,3,4])
  273                         flat_cells = [item for sublist in obj._cellvalues for item in sublist]
  274                         pageNumbering(flat_cells)
  275 
  276             try:
  277 
  278                 # Paint static frames
  279                 pagenumber = canvas.getPageNumber()
  280                 for frame in self.pisaStaticList:
  281                     frame = copy.deepcopy(frame)
  282                     story = frame.pisaStaticStory
  283                     pageNumbering(story)
  284 
  285                     frame.addFromList(story, canvas)
  286 
  287             except Exception:  # TODO: Kill this!
  288                 log.debug("PmlPageTemplate", exc_info=1)
  289         finally:
  290             canvas.restoreState()
  291 
  292 
  293 _ctr = 1
  294 
  295 
  296 class PmlImageReader(object):  # TODO We need a factory here, returning either a class for java or a class for PIL
  297     """
  298     Wraps up either PIL or Java to get data from bitmaps
  299     """
  300     _cache = {}
  301 
  302     def __init__(self, fileName):
  303         if isinstance(fileName, PmlImageReader):
  304             self.__dict__ = fileName.__dict__   # borgize
  305             return
  306             #start wih lots of null private fields, to be populated by
  307         #the relevant engine.
  308         self.fileName = fileName
  309         self._image = None
  310         self._width = None
  311         self._height = None
  312         self._transparent = None
  313         self._data = None
  314         imageReaderFlags = 0
  315         if PILImage and isinstance(fileName, PILImage.Image):
  316             self._image = fileName
  317             self.fp = getattr(fileName, 'fp', None)
  318             try:
  319                 self.fileName = self._image.fileName
  320             except AttributeError:
  321                 self.fileName = 'PILIMAGE_%d' % id(self)
  322         else:
  323             try:
  324                 self.fp = open_for_read(fileName, 'b')
  325                 if isinstance(self.fp, six.BytesIO().__class__):
  326                     # avoid messing with already internal files
  327                     imageReaderFlags = 0
  328                 if imageReaderFlags > 0:  # interning
  329                     data = self.fp.read()
  330                     if imageReaderFlags & 2:  # autoclose
  331                         try:
  332                             self.fp.close()
  333                         except:
  334                             pass
  335                     if imageReaderFlags & 4:  # cache the data
  336                         if not self._cache:
  337                             from rl_config import register_reset
  338                             register_reset(self._cache.clear)
  339 
  340                         data = self._cache.setdefault(md5(data).digest(), data)
  341                     self.fp = getStringIO(data)
  342                 elif imageReaderFlags == - 1 and isinstance(fileName, six.text_type):
  343                     #try Ralf Schmitt's re-opening technique of avoiding too many open files
  344                     self.fp.close()
  345                     del self.fp  # will become a property in the next statement
  346                     self.__class__ = LazyImageReader
  347                 if haveImages:
  348                     #detect which library we are using and open the image
  349                     if not self._image:
  350                         self._image = self._read_image(self.fp)
  351                     if getattr(self._image, 'format', None) == 'JPEG':
  352                         self.jpeg_fh = self._jpeg_fh
  353                 else:
  354                     from reportlab.pdfbase.pdfutils import readJPEGInfo
  355 
  356                     try:
  357                         self._width, self._height, c = readJPEGInfo(self.fp)
  358                     except:
  359                         raise RuntimeError('Imaging Library not available, unable to import bitmaps only jpegs')
  360                     self.jpeg_fh = self._jpeg_fh
  361                     self._data = self.fp.read()
  362                     self._dataA = None
  363                     self.fp.seek(0)
  364             except:  # TODO: Kill the catch-all
  365                 et, ev, tb = sys.exc_info()
  366                 if hasattr(ev, 'args'):
  367                     a = str(ev.args[- 1]) + (' fileName=%r' % fileName)
  368                     ev.args = ev.args[: - 1] + (a,)
  369                     raise RuntimeError("{0} {1} {2}".format(et, ev, tb))
  370                 else:
  371                     raise
  372 
  373     def _read_image(self, fp):
  374         if sys.platform[0:4] == 'java':
  375             from javax.imageio import ImageIO
  376             from java.io import ByteArrayInputStream
  377             input_stream = ByteArrayInputStream(fp.read())
  378             return ImageIO.read(input_stream)
  379         elif PILImage:
  380             return PILImage.open(fp)
  381 
  382     def _jpeg_fh(self):
  383         fp = self.fp
  384         fp.seek(0)
  385         return fp
  386 
  387     def jpeg_fh(self):
  388         return None
  389 
  390     def getSize(self):
  391         if self._width is None or self._height is None:
  392             if sys.platform[0:4] == 'java':
  393                 self._width = self._image.getWidth()
  394                 self._height = self._image.getHeight()
  395             else:
  396                 self._width, self._height = self._image.size
  397         return self._width, self._height
  398 
  399     def getRGBData(self):
  400         "Return byte array of RGB data as string"
  401         if self._data is None:
  402             self._dataA = None
  403             if sys.platform[0:4] == 'java':
  404                 import jarray  # TODO: Move to top.
  405                 from java.awt.image import PixelGrabber
  406 
  407                 width, height = self.getSize()
  408                 buffer = jarray.zeros(width * height, 'i')
  409                 pg = PixelGrabber(self._image, 0, 0, width, height, buffer, 0, width)
  410                 pg.grabPixels()
  411                 # there must be a way to do this with a cast not a byte-level loop,
  412                 # I just haven't found it yet...
  413                 pixels = []
  414                 a = pixels.append
  415                 for rgb in buffer:
  416                     a(chr((rgb >> 16) & 0xff))
  417                     a(chr((rgb >> 8) & 0xff))
  418                     a(chr(rgb & 0xff))
  419                 self._data = ''.join(pixels)
  420                 self.mode = 'RGB'
  421             else:
  422                 im = self._image
  423                 mode = self.mode = im.mode
  424                 if mode == 'RGBA':
  425                     im.load()
  426                     self._dataA = PmlImageReader(im.split()[3])
  427                     im = im.convert('RGB')
  428                     self.mode = 'RGB'
  429                 elif mode not in ('L', 'RGB', 'CMYK'):
  430                     im = im.convert('RGB')
  431                     self.mode = 'RGB'
  432                 if hasattr(im, 'tobytes'):
  433                     self._data = im.tobytes()
  434                 else:
  435                     # PIL compatibility
  436                     self._data = im.tostring()
  437         return self._data
  438 
  439     def getImageData(self):
  440         width, height = self.getSize()
  441         return width, height, self.getRGBData()
  442 
  443     def getTransparent(self):
  444         if sys.platform[0:4] == 'java':
  445             return None
  446         elif "transparency" in self._image.info:
  447             transparency = self._image.info["transparency"] * 3
  448             palette = self._image.palette
  449             if hasattr(palette, 'palette'):
  450                 palette = palette.palette
  451             elif hasattr(palette, 'data'):
  452                 palette = palette.data
  453             else:
  454                 return None
  455 
  456             # 8-bit PNGs could give an empty string as transparency value, so
  457             # we have to be careful here.
  458             try:
  459                 return list(six.iterbytes(palette[transparency:transparency + 3]))
  460             except:
  461                 return None
  462         else:
  463             return None
  464 
  465     def __str__(self):
  466         try:
  467             fn = self.fileName.read()
  468             if not fn:
  469                 fn = id(self)
  470             return "PmlImageObject_%s" % hash(fn)
  471         except:
  472             fn = self.fileName
  473             if not fn:
  474                 fn = id(self)
  475             return fn
  476 
  477 
  478 class PmlImage(Flowable, PmlMaxHeightMixIn):
  479 
  480     def __init__(self, data, width=None, height=None, mask="auto", mimetype=None, **kw):
  481         self.kw = kw
  482         self.hAlign = 'CENTER'
  483         self._mask = mask
  484         self._imgdata = data
  485         # print "###", repr(data)
  486         self.mimetype = mimetype
  487         img = self.getImage()
  488         if img:
  489             self.imageWidth, self.imageHeight = img.getSize()
  490         self.drawWidth = width or self.imageWidth
  491         self.drawHeight = height or self.imageHeight
  492 
  493     def wrap(self, availWidth, availHeight):
  494         " This can be called more than once! Do not overwrite important data like drawWidth "
  495         availHeight = self.setMaxHeight(availHeight)
  496         # print "image wrap", id(self), availWidth, availHeight, self.drawWidth, self.drawHeight
  497         width = min(self.drawWidth, availWidth)
  498         wfactor = float(width) / self.drawWidth
  499         height = min(self.drawHeight, availHeight * MAX_IMAGE_RATIO)
  500         hfactor = float(height) / self.drawHeight
  501         factor = min(wfactor, hfactor)
  502         self.dWidth = self.drawWidth * factor
  503         self.dHeight = self.drawHeight * factor
  504         # print "imgage result", factor, self.dWidth, self.dHeight
  505         return self.dWidth, self.dHeight
  506 
  507     def getImage(self):
  508         img = PmlImageReader(six.BytesIO(self._imgdata))
  509         return img
  510 
  511     def draw(self):
  512         img = self.getImage()
  513         self.canv.drawImage(
  514             img,
  515             0, 0,
  516             self.dWidth,
  517             self.dHeight,
  518             mask=self._mask)
  519 
  520     def identity(self, maxLen=None):
  521         r = Flowable.identity(self, maxLen)
  522         return r
  523 
  524 
  525 class PmlParagraphAndImage(ParagraphAndImage, PmlMaxHeightMixIn):
  526     def wrap(self, availWidth, availHeight):
  527         self.I.canv = self.canv
  528         result = ParagraphAndImage.wrap(self, availWidth, availHeight)
  529         del self.I.canv
  530         return result
  531 
  532     def split(self, availWidth, availHeight):
  533         # print "# split", id(self)
  534         if not hasattr(self, "wI"):
  535             self.wI, self.hI = self.I.wrap(availWidth, availHeight)  # drawWidth, self.I.drawHeight
  536         return ParagraphAndImage.split(self, availWidth, availHeight)
  537 
  538 
  539 class PmlParagraph(Paragraph, PmlMaxHeightMixIn):
  540     def _calcImageMaxSizes(self, availWidth, availHeight):
  541         self.hasImages = False
  542         for frag in self.frags:
  543             if hasattr(frag, "cbDefn") and frag.cbDefn.kind == "img":
  544                 img = frag.cbDefn
  545                 if img.width > 0 and img.height > 0:
  546                     self.hasImages = True
  547                     width = min(img.width, availWidth)
  548                     wfactor = float(width) / img.width
  549                     height = min(img.height, availHeight * MAX_IMAGE_RATIO)  # XXX 99% because 100% do not work...
  550                     hfactor = float(height) / img.height
  551                     factor = min(wfactor, hfactor)
  552                     img.height *= factor
  553                     img.width *= factor
  554 
  555     def wrap(self, availWidth, availHeight):
  556 
  557         availHeight = self.setMaxHeight(availHeight)
  558 
  559         style = self.style
  560 
  561         self.deltaWidth = style.paddingLeft + style.paddingRight + style.borderLeftWidth + style.borderRightWidth
  562         self.deltaHeight = style.paddingTop + style.paddingBottom + style.borderTopWidth + style.borderBottomWidth
  563 
  564         # reduce the available width & height by the padding so the wrapping
  565         # will use the correct size
  566         availWidth -= self.deltaWidth
  567         availHeight -= self.deltaHeight
  568 
  569         # Modify maxium image sizes
  570         self._calcImageMaxSizes(availWidth, availHeight)
  571 
  572         # call the base class to do wrapping and calculate the size
  573         Paragraph.wrap(self, availWidth, availHeight)
  574 
  575         #self.height = max(1, self.height)
  576         #self.width = max(1, self.width)
  577 
  578         # increase the calculated size by the padding
  579         self.width = self.width + self.deltaWidth
  580         self.height = self.height + self.deltaHeight
  581 
  582         return self.width, self.height
  583 
  584     def split(self, availWidth, availHeight):
  585 
  586         if len(self.frags) <= 0:
  587             return []
  588 
  589         #the split information is all inside self.blPara
  590         if not hasattr(self, 'deltaWidth'):
  591             self.wrap(availWidth, availHeight)
  592 
  593         availWidth -= self.deltaWidth
  594         availHeight -= self.deltaHeight
  595 
  596         return Paragraph.split(self, availWidth, availHeight)
  597 
  598     def draw(self):
  599 
  600         # Create outline
  601         if getattr(self, "outline", False):
  602 
  603             # Check level and add all levels
  604             last = getattr(self.canv, "outlineLast", - 1) + 1
  605             while last < self.outlineLevel:
  606                 # print "(OUTLINE",  last, self.text
  607                 key = getUID()
  608                 self.canv.bookmarkPage(key)
  609                 self.canv.addOutlineEntry(
  610                     self.text,
  611                     key,
  612                     last,
  613                     not self.outlineOpen)
  614                 last += 1
  615             self.canv.outlineLast = self.outlineLevel
  616 
  617             key = getUID()
  618 
  619             self.canv.bookmarkPage(key)
  620             self.canv.addOutlineEntry(
  621                 self.text,
  622                 key,
  623                 self.outlineLevel,
  624                 not self.outlineOpen)
  625             last += 1
  626 
  627         # Draw the background and borders here before passing control on to
  628         # ReportLab. This is because ReportLab can't handle the individual
  629         # components of the border independently. This will also let us
  630         # support more border styles eventually.
  631         canvas = self.canv
  632         style = self.style
  633         bg = style.backColor
  634         leftIndent = style.leftIndent
  635         bp = 0  # style.borderPadding
  636 
  637         x = leftIndent - bp
  638         y = - bp
  639         w = self.width - (leftIndent + style.rightIndent) + 2 * bp
  640         h = self.height + 2 * bp
  641 
  642         if bg:
  643             # draw a filled rectangle (with no stroke) using bg color
  644             canvas.saveState()
  645             canvas.setFillColor(bg)
  646             canvas.rect(x, y, w, h, fill=1, stroke=0)
  647             canvas.restoreState()
  648 
  649         # we need to hide the bg color (if any) so Paragraph won't try to draw it again
  650         style.backColor = None
  651 
  652         # offset the origin to compensate for the padding
  653         canvas.saveState()
  654         canvas.translate(
  655             (style.paddingLeft + style.borderLeftWidth),
  656             -1 * (style.paddingTop + style.borderTopWidth))  # + (style.leading / 4)))
  657 
  658         # Call the base class draw method to finish up
  659         Paragraph.draw(self)
  660         canvas.restoreState()
  661 
  662         # Reset color because we need it again if we run 2-PASS like we
  663         # do when using TOC
  664         style.backColor = bg
  665 
  666         canvas.saveState()
  667 
  668         def _drawBorderLine(bstyle, width, color, x1, y1, x2, y2):
  669             # We need width and border style to be able to draw a border
  670             if width and getBorderStyle(bstyle):
  671                 # If no color for border is given, the text color is used (like defined by W3C)
  672                 if color is None:
  673                     color = style.textColor
  674                     # print "Border", bstyle, width, color
  675                 if color is not None:
  676                     canvas.setStrokeColor(color)
  677                     canvas.setLineWidth(width)
  678                     canvas.line(x1, y1, x2, y2)
  679 
  680         _drawBorderLine(style.borderLeftStyle,
  681                         style.borderLeftWidth,
  682                         style.borderLeftColor,
  683                         x, y, x, y + h)
  684         _drawBorderLine(style.borderRightStyle,
  685                         style.borderRightWidth,
  686                         style.borderRightColor,
  687                         x + w, y, x + w, y + h)
  688         _drawBorderLine(style.borderTopStyle,
  689                         style.borderTopWidth,
  690                         style.borderTopColor,
  691                         x, y + h, x + w, y + h)
  692         _drawBorderLine(style.borderBottomStyle,
  693                         style.borderBottomWidth,
  694                         style.borderBottomColor,
  695                         x, y, x + w, y)
  696 
  697         canvas.restoreState()
  698 
  699 
  700 class PmlKeepInFrame(KeepInFrame, PmlMaxHeightMixIn):
  701     def wrap(self, availWidth, availHeight):
  702         availWidth = max(availWidth, 1.0)
  703         availHeight = max(availHeight, 1.0)
  704         self.maxWidth = availWidth
  705         self.maxHeight = self.setMaxHeight(availHeight)
  706         return KeepInFrame.wrap(self, availWidth, availHeight)
  707 
  708 
  709 class PmlTable(Table, PmlMaxHeightMixIn):
  710     def _normWidth(self, w, maxw):
  711         """
  712         Helper for calculating percentages
  713         """
  714         if type(w) == type(""):
  715             w = ((maxw / 100.0) * float(w[: - 1]))
  716         elif (w is None) or (w == "*"):
  717             w = maxw
  718         return min(w, maxw)
  719 
  720     def _listCellGeom(self, V, w, s, W=None, H=None, aH=72000):
  721         # print "#", self.availHeightValue
  722         if aH == 72000:
  723             aH = self.getMaxHeight() or aH
  724         return Table._listCellGeom(self, V, w, s, W=W, H=H, aH=aH)
  725 
  726     def wrap(self, availWidth, availHeight):
  727 
  728         self.setMaxHeight(availHeight)
  729 
  730         # Strange bug, sometime the totalWidth is not set !?
  731         try:
  732             self.totalWidth
  733         except:
  734             self.totalWidth = availWidth
  735 
  736         # Prepare values
  737         totalWidth = self._normWidth(self.totalWidth, availWidth)
  738         remainingWidth = totalWidth
  739         remainingCols = 0
  740         newColWidths = self._colWidths
  741 
  742         # Calculate widths that are fix
  743         # IMPORTANT!!! We can not substitute the private value
  744         # self._colWidths therefore we have to modify list in place
  745         for i, colWidth in enumerate(newColWidths):
  746             if (colWidth is not None) or (colWidth == '*'):
  747                 colWidth = self._normWidth(colWidth, totalWidth)
  748                 remainingWidth -= colWidth
  749             else:
  750                 remainingCols += 1
  751                 colWidth = None
  752             newColWidths[i] = colWidth
  753 
  754         # Distribute remaining space
  755         minCellWidth = totalWidth * 0.01
  756         if remainingCols > 0:
  757             for i, colWidth in enumerate(newColWidths):
  758                 if colWidth is None:
  759                     newColWidths[i] = max(minCellWidth, remainingWidth / remainingCols)  # - 0.1
  760 
  761         # Bigger than totalWidth? Lets reduce the fix entries propotionally
  762 
  763         if sum(newColWidths) > totalWidth:
  764             quotient = totalWidth / sum(newColWidths)
  765             for i in range(len(newColWidths)):
  766                 newColWidths[i] = newColWidths[i] * quotient
  767 
  768         # To avoid rounding errors adjust one col with the difference
  769         diff = sum(newColWidths) - totalWidth
  770         if diff > 0:
  771             newColWidths[0] -= diff
  772 
  773         return Table.wrap(self, availWidth, availHeight)
  774 
  775 
  776 class PmlPageCount(IndexingFlowable):
  777     def __init__(self):
  778         IndexingFlowable.__init__(self)
  779         self.second_round = False
  780 
  781     def isSatisfied(self):
  782         s = self.second_round
  783         self.second_round = True
  784         return s
  785 
  786     def drawOn(self, canvas, x, y, _sW=0):
  787         pass
  788 
  789 
  790 class PmlTableOfContents(TableOfContents):
  791     def wrap(self, availWidth, availHeight):
  792         """
  793         All table properties should be known by now.
  794         """
  795 
  796         widths = (availWidth - self.rightColumnWidth,
  797                   self.rightColumnWidth)
  798 
  799         # makes an internal table which does all the work.
  800         # we draw the LAST RUN's entries!  If there are
  801         # none, we make some dummy data to keep the table
  802         # from complaining
  803         if len(self._lastEntries) == 0:
  804             _tempEntries = [(0, 'Placeholder for table of contents', 0)]
  805         else:
  806             _tempEntries = self._lastEntries
  807 
  808         lastMargin = 0
  809         tableData = []
  810         tableStyle = [
  811             ('VALIGN', (0, 0), (- 1, - 1), 'TOP'),
  812             ('LEFTPADDING', (0, 0), (- 1, - 1), 0),
  813             ('RIGHTPADDING', (0, 0), (- 1, - 1), 0),
  814             ('TOPPADDING', (0, 0), (- 1, - 1), 0),
  815             ('BOTTOMPADDING', (0, 0), (- 1, - 1), 0),
  816         ]
  817         for i, entry in enumerate(_tempEntries):
  818             level, text, pageNum = entry[:3]
  819             leftColStyle = self.levelStyles[level]
  820             if i:  # Not for first element
  821                 tableStyle.append((
  822                     'TOPPADDING',
  823                     (0, i), (- 1, i),
  824                     max(lastMargin, leftColStyle.spaceBefore)))
  825                 # print leftColStyle.leftIndent
  826             lastMargin = leftColStyle.spaceAfter
  827             #right col style is right aligned
  828             rightColStyle = ParagraphStyle(name='leftColLevel%d' % level,
  829                                            parent=leftColStyle,
  830                                            leftIndent=0,
  831                                            alignment=TA_RIGHT)
  832             leftPara = Paragraph(text, leftColStyle)
  833             rightPara = Paragraph(str(pageNum), rightColStyle)
  834             tableData.append([leftPara, rightPara])
  835 
  836         self._table = Table(
  837             tableData,
  838             colWidths=widths,
  839             style=TableStyle(tableStyle))
  840 
  841         self.width, self.height = self._table.wrapOn(self.canv, availWidth, availHeight)
  842         return self.width, self.height
  843 
  844 
  845 class PmlRightPageBreak(CondPageBreak):
  846     def __init__(self):
  847         pass
  848 
  849     def wrap(self, availWidth, availHeight):
  850         if not self.canv.getPageNumber() % 2:
  851             self.width = availWidth
  852             self.height = availHeight
  853             return availWidth, availHeight
  854         self.width = self.height = 0
  855         return 0, 0
  856 
  857 
  858 class PmlLeftPageBreak(CondPageBreak):
  859     def __init__(self):
  860         pass
  861 
  862     def wrap(self, availWidth, availHeight):
  863         if self.canv.getPageNumber() % 2:
  864             self.width = availWidth
  865             self.height = availHeight
  866             return availWidth, availHeight
  867         self.width = self.height = 0
  868         return 0, 0
  869 
  870 # --- Pdf Form
  871 
  872 
  873 class PmlInput(Flowable):
  874     def __init__(self, name, type="text", width=10, height=10, default="",
  875                  options=None):
  876         self.width = width
  877         self.height = height
  878         self.type = type
  879         self.name = name
  880         self.default = default
  881         self.options = options if options is not None else []
  882 
  883     def wrap(self, *args):
  884         return self.width, self.height
  885 
  886     def draw(self):
  887         c = self.canv
  888 
  889         c.saveState()
  890         c.setFont("Helvetica", 10)
  891         if self.type == "text":
  892             pdfform.textFieldRelative(c, self.name, 0, 0, self.width, self.height)
  893             c.rect(0, 0, self.width, self.height)
  894         elif self.type == "radio":
  895             c.rect(0, 0, self.width, self.height)
  896         elif self.type == "checkbox":
  897             if self.default:
  898                 pdfform.buttonFieldRelative(c, self.name, "Yes", 0, 0)
  899             else:
  900                 pdfform.buttonFieldRelative(c, self.name, "Off", 0, 0)
  901             c.rect(0, 0, self.width, self.height)
  902         elif self.type == "select":
  903             pdfform.selectFieldRelative(c, self.name, self.default, self.options, 0, 0, self.width, self.height)
  904             c.rect(0, 0, self.width, self.height)
  905 
  906         c.restoreState()