"Fossies" - the Fresh Open Source Software Archive

Member "xhtml2pdf-0.2.5/xhtml2pdf/context.py" (8 Oct 2020, 33760 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 "context.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 import copy
    3 import logging
    4 import os
    5 import re
    6 
    7 import six
    8 
    9 import reportlab
   10 import xhtml2pdf.default
   11 import xhtml2pdf.parser
   12 from reportlab.lib.enums import TA_LEFT
   13 from reportlab.lib.fonts import addMapping
   14 from reportlab.lib.pagesizes import A4
   15 from reportlab.lib.styles import ParagraphStyle
   16 from reportlab.pdfbase import pdfmetrics
   17 from reportlab.pdfbase.ttfonts import TTFont
   18 from reportlab.platypus.frames import Frame, ShowBoundaryValue
   19 from reportlab.platypus.paraparser import ParaFrag, ps2tt, tt2ps
   20 from xhtml2pdf.util import (copy_attrs, getColor, getCoords, getFile,
   21                             getFrameDimensions, getSize, pisaFileObject,
   22                             set_value, set_asian_fonts, arabic_format, frag_text_language_check)
   23 
   24 from xhtml2pdf.w3c import css
   25 from xhtml2pdf.xhtml2pdf_reportlab import (PmlPageCount, PmlPageTemplate,
   26                                            PmlParagraph, PmlParagraphAndImage,
   27                                            PmlTableOfContents)
   28 
   29 TupleType = tuple
   30 ListType = list
   31 basestring = six.text_type
   32 try:
   33     import urlparse
   34 except ImportError:
   35     import urllib.parse as urlparse
   36 
   37 # Copyright 2010 Dirk Holtwick, holtwick.it
   38 #
   39 # Licensed under the Apache License, Version 2.0 (the "License");
   40 # you may not use this file except in compliance with the License.
   41 # You may obtain a copy of the License at
   42 #
   43 #     http://www.apache.org/licenses/LICENSE-2.0
   44 #
   45 # Unless required by applicable law or agreed to in writing, software
   46 # distributed under the License is distributed on an "AS IS" BASIS,
   47 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   48 # See the License for the specific language governing permissions and
   49 # limitations under the License.
   50 
   51 reportlab.rl_config.warnOnMissingFontGlyphs = 0
   52 
   53 log = logging.getLogger("xhtml2pdf")
   54 
   55 sizeDelta = 2       # amount to reduce font size by for super and sub script
   56 subFraction = 0.4   # fraction of font size that a sub script should be lowered
   57 superFraction = 0.4
   58 
   59 NBSP = u"\u00a0"
   60 
   61 
   62 def clone(self, **kwargs):
   63     n = ParaFrag(**self.__dict__)
   64     if kwargs:
   65         d = n.__dict__
   66         d.update(kwargs)
   67         # This else could cause trouble in Paragraphs with images etc.
   68         if "cbDefn" in d:
   69             del d["cbDefn"]
   70     n.bulletText = None
   71     return n
   72 
   73 
   74 ParaFrag.clone = clone
   75 
   76 
   77 def getParaFrag(style):
   78     frag = ParaFrag()
   79 
   80     set_value(frag,
   81               ('sub', 'super', 'rise', 'underline', 'strike', 'greek',
   82                'leading', 'leadingSpace', 'spaceBefore',
   83                'spaceAfter', 'leftIndent', 'rightIndent', 'firstLineIndent',
   84                'borderPadding', 'paddingLeft', 'paddingRight',
   85                'paddingTop', 'paddingBottom', 'bulletIndent',
   86                'insideStaticFrame', 'outlineLevel'
   87                ),
   88               0)
   89 
   90     set_value(frag,
   91               ('backColor', 'vAlign', 'link', 'borderStyle',
   92                'borderColor', 'listStyleType', 'listStyleImage',
   93                'wordWrap', 'height', 'width', 'bulletText'
   94                ),
   95               None
   96               )
   97     set_value(frag,
   98               ('pageNumber', 'pageCount', 'outline',
   99                'outlineOpen', 'keepWithNext'),
  100               False)
  101 
  102     frag.text = ""
  103     frag.fontName = "Times-Roman"
  104     frag.fontName, frag.bold, frag.italic = ps2tt(style.fontName)
  105     frag.fontSize = style.fontSize
  106     frag.textColor = style.textColor
  107 
  108     # Extras
  109     frag.letterSpacing = "normal"
  110     frag.leadingSource = "150%"
  111     frag.alignment = TA_LEFT
  112     frag.borderWidth = 1
  113 
  114     frag.borderLeftWidth = frag.borderWidth
  115     frag.borderLeftColor = frag.borderColor
  116     frag.borderLeftStyle = frag.borderStyle
  117     frag.borderRightWidth = frag.borderWidth
  118     frag.borderRightColor = frag.borderColor
  119     frag.borderRightStyle = frag.borderStyle
  120     frag.borderTopWidth = frag.borderWidth
  121     frag.borderTopColor = frag.borderColor
  122     frag.borderTopStyle = frag.borderStyle
  123     frag.borderBottomWidth = frag.borderWidth
  124     frag.borderBottomColor = frag.borderColor
  125     frag.borderBottomStyle = frag.borderStyle
  126 
  127     frag.whiteSpace = "normal"
  128     frag.bulletFontName = "Helvetica"
  129     frag.zoom = 1.0
  130 
  131     return frag
  132 
  133 
  134 def getDirName(path):
  135     parts = urlparse.urlparse(path)
  136     if parts.scheme:
  137         return path
  138     else:
  139         return os.path.dirname(os.path.abspath(path))
  140 
  141 
  142 class pisaCSSBuilder(css.CSSBuilder):
  143 
  144     def atFontFace(self, declarations):
  145         """
  146         Embed fonts
  147         """
  148         result = self.ruleset([self.selector('*')], declarations)
  149         data = list(result[0].values())[0]
  150         if "src" not in data:
  151             # invalid - source is required, ignore this specification
  152             return {}, {}
  153         names = data["font-family"]
  154 
  155         # Font weight
  156         fweight = str(data.get("font-weight", "normal")).lower()
  157         bold = fweight in ("bold", "bolder", "500", "600", "700", "800", "900")
  158         if not bold and fweight != "normal":
  159             log.warning(
  160                 self.c.warning("@fontface, unknown value font-weight '%s'", fweight))
  161 
  162         # Font style
  163         italic = str(
  164             data.get("font-style", "")).lower() in ("italic", "oblique")
  165 
  166         # The "src" attribute can be a CSS group but in that case
  167         # ignore everything except the font URI
  168         uri = data['src']
  169         fonts = []
  170 
  171         if isinstance(data['src'], list):
  172             for part in uri:
  173                 if isinstance(part, basestring):
  174                     fonts.append(part)
  175         else:
  176             fonts.append(uri)
  177 
  178         for font in fonts:
  179             src = self.c.getFile(font, relative=self.c.cssParser.rootPath)
  180             self.c.loadFont(
  181                 names,
  182                 src,
  183                 bold=bold,
  184                 italic=italic)
  185         return {}, {}
  186 
  187     def _pisaAddFrame(self, name, data, first=False, border=None, size=(0, 0)):
  188         c = self.c
  189         if not name:
  190             name = "-pdf-frame-%d" % c.UID()
  191         if data.get('is_landscape', False):
  192             size = (size[1], size[0])
  193         x, y, w, h = getFrameDimensions(data, size[0], size[1])
  194         # print name, x, y, w, h
  195         # if not (w and h):
  196         #    return None
  197         if first:
  198             return name, None, data.get("-pdf-frame-border", border), x, y, w, h, data
  199 
  200         return (name, data.get("-pdf-frame-content", None),
  201                 data.get("-pdf-frame-border", border), x, y, w, h, data)
  202 
  203     def _getFromData(self, data, attr, default=None, func=None):
  204         if not func:
  205             def func(x): return x
  206 
  207         if type(attr) in (list, tuple):
  208             for a in attr:
  209                 if a in data:
  210                     return func(data[a])
  211                 return default
  212         else:
  213             if attr in data:
  214                 return func(data[attr])
  215             return default
  216 
  217     def atPage(self, name, pseudopage, data, isLandscape, pageBorder):
  218         c = self.c
  219         name = name or "body"
  220 
  221         if name in c.templateList:
  222             log.warning(
  223                 self.c.warning("template '%s' has already been defined", name))
  224 
  225         padding_top = self._getFromData(data, 'padding-top', 0, getSize)
  226         padding_left = self._getFromData(data, 'padding-left', 0, getSize)
  227         padding_right = self._getFromData(data, 'padding-right', 0, getSize)
  228         padding_bottom = self._getFromData(data, 'padding-bottom', 0, getSize)
  229         border_color = self._getFromData(data, ('border-top-color', 'border-bottom-color',
  230                                                 'border-left-color', 'border-right-color'), None, getColor)
  231         border_width = self._getFromData(data, ('border-top-width', 'border-bottom-width',
  232                                                 'border-left-width', 'border-right-width'), 0, getSize)
  233 
  234         for prop in ("margin-top", "margin-left", "margin-right", "margin-bottom",
  235                      "top", "left", "right", "bottom", "width", "height"):
  236             if prop in data:
  237                 c.frameList.append(
  238                     self._pisaAddFrame(name, data, first=True, border=pageBorder, size=c.pageSize))
  239                 break
  240 
  241         # Frames have to be calculated after we know the pagesize
  242         frameList = []
  243         staticList = []
  244         for fname, static, border, x, y, w, h, fdata in c.frameList:
  245             fpadding_top = self._getFromData(
  246                 fdata, 'padding-top', padding_top, getSize)
  247             fpadding_left = self._getFromData(
  248                 fdata, 'padding-left', padding_left, getSize)
  249             fpadding_right = self._getFromData(
  250                 fdata, 'padding-right', padding_right, getSize)
  251             fpadding_bottom = self._getFromData(
  252                 fdata, 'padding-bottom', padding_bottom, getSize)
  253             fborder_color = self._getFromData(fdata, ('border-top-color', 'border-bottom-color',
  254                                                       'border-left-color', 'border-right-color'), border_color, getColor)
  255             fborder_width = self._getFromData(fdata, ('border-top-width', 'border-bottom-width',
  256                                                       'border-left-width', 'border-right-width'), border_width, getSize)
  257 
  258             if border or pageBorder:
  259 
  260                 frame_border = ShowBoundaryValue(width=int(border))   #frame_border = ShowBoundaryValue() to
  261                                                                       #frame_border = ShowBoundaryValue(width=int(border))
  262             else:
  263                 frame_border = ShowBoundaryValue(
  264                     color=fborder_color, width=fborder_width)
  265 
  266             # fix frame sizing problem.
  267             if static:
  268                 x, y, w, h = getFrameDimensions(
  269                     fdata, c.pageSize[0], c.pageSize[1])
  270             x, y, w, h = getCoords(x, y, w, h, c.pageSize)
  271             if w <= 0 or h <= 0:
  272                 log.warning(
  273                     self.c.warning("Negative width or height of frame. Check @frame definitions."))
  274 
  275             frame = Frame(
  276                 x, y, w, h,
  277                 id=fname,
  278                 leftPadding=fpadding_left,
  279                 rightPadding=fpadding_right,
  280                 bottomPadding=fpadding_bottom,
  281                 topPadding=fpadding_top,
  282                 showBoundary=frame_border)
  283 
  284             if static:
  285                 frame.pisaStaticStory = []
  286                 c.frameStatic[static] = [frame] + c.frameStatic.get(static, [])
  287                 staticList.append(frame)
  288             else:
  289                 frameList.append(frame)
  290 
  291         background = data.get("background-image", None)
  292         if background:
  293             # should be relative to the css file
  294             background = self.c.getFile(
  295                 background, relative=self.c.cssParser.rootPath)
  296 
  297         if not frameList:
  298             log.warning(
  299                 c.warning("missing explicit frame definition for content or just static frames"))
  300             fname, static, border, x, y, w, h, data = self._pisaAddFrame(name, data, first=True, border=pageBorder,
  301                                                                          size=c.pageSize)
  302             x, y, w, h = getCoords(x, y, w, h, c.pageSize)
  303             if w <= 0 or h <= 0:
  304                 log.warning(
  305                     c.warning("Negative width or height of frame. Check @page definitions."))
  306 
  307             if border or pageBorder:
  308                 frame_border = ShowBoundaryValue()
  309             else:
  310                 frame_border = ShowBoundaryValue(
  311                     color=border_color, width=border_width)
  312 
  313             frameList.append(Frame(
  314                 x, y, w, h,
  315                 id=fname,
  316                 leftPadding=padding_left,
  317                 rightPadding=padding_right,
  318                 bottomPadding=padding_bottom,
  319                 topPadding=padding_top,
  320                 showBoundary=frame_border))
  321 
  322         pt = PmlPageTemplate(
  323             id=name,
  324             frames=frameList,
  325             pagesize=c.pageSize,
  326         )
  327         pt.pisaStaticList = staticList
  328         pt.pisaBackground = background
  329         pt.pisaBackgroundList = c.pisaBackgroundList
  330 
  331         if isLandscape:
  332             pt.pageorientation = pt.LANDSCAPE
  333 
  334         c.templateList[name] = pt
  335         c.template = None
  336         c.frameList = []
  337         c.frameStaticList = []
  338 
  339         return {}, {}
  340 
  341     def atFrame(self, name, declarations):
  342         if declarations:
  343             result = self.ruleset([self.selector('*')], declarations)
  344             # print "@BOX", name, declarations, result
  345 
  346             data = result[0]
  347             if data:
  348                 try:
  349                     data = data.values()[0]
  350                 except Exception:
  351                     data = data.popitem()[1]
  352                 self.c.frameList.append(
  353                     self._pisaAddFrame(name, data, size=self.c.pageSize))
  354 
  355         return {}, {}  # TODO: It always returns empty dicts?
  356 
  357 
  358 class pisaCSSParser(css.CSSParser):
  359 
  360     def parseExternal(self, cssResourceName):
  361         result = None
  362         oldRootPath = self.rootPath
  363         cssFile = self.c.getFile(cssResourceName, relative=self.rootPath)
  364         if not cssFile:
  365             return None
  366         if self.rootPath and urlparse.urlparse(self.rootPath).scheme:
  367             self.rootPath = urlparse.urljoin(self.rootPath, cssResourceName)
  368         else:
  369             self.rootPath = getDirName(cssFile.uri)
  370         try:
  371             result = self.parse(cssFile.getData())
  372             self.rootPath = oldRootPath
  373         except Exception as e:
  374             print(e)
  375         return result
  376 
  377 
  378 class pisaContext(object):
  379     """
  380     Helper class for creation of reportlab story and container for
  381     various data.
  382     """
  383 
  384     def __init__(self, path, debug=0, capacity=-1):
  385         self.fontList = copy.copy(xhtml2pdf.default.DEFAULT_FONT)
  386         self.asianFontList = copy.copy(xhtml2pdf.default.DEFAULT_ASIAN_FONT)
  387         set_value(self,
  388                   ('path', 'story', 'text', 'log', 'frameStaticList',
  389                    'pisaBackgroundList', 'frameList', 'anchorFrag',
  390                    'anchorName', 'fragList', 'fragAnchor', 'fragStack'
  391                    ), [],  _copy=True)
  392 
  393         set_value(self, ('node', 'indexing_story',
  394                          'template', 'keepInFrameIndex',
  395                          'tableData', 'image'),
  396                   None)
  397         set_value(self, ('err', 'warn', 'uidctr', 'listCounter'), 0)
  398         set_value(self, ('text', 'cssText', 'cssDefaultText'), "")
  399         set_value(self, ('templateList', 'frameStatic', 'imageData'),
  400                   {}, _copy=True)
  401         self.capacity = capacity
  402         self.toc = PmlTableOfContents()
  403         self.multiBuild = False
  404         self.pageSize = A4
  405         self.baseFontSize = getSize("12pt")
  406         self.frag = self.fragBlock = getParaFrag(
  407             ParagraphStyle('default%d' % self.UID()))
  408         self.fragStrip = True
  409         self.force = False
  410 
  411         # External callback function for path calculations
  412         self.pathCallback = None
  413 
  414         # Store path to document
  415         self.pathDocument = path or "__dummy__"
  416         parts = urlparse.urlparse(self.pathDocument)
  417         if not parts.scheme:
  418             self.pathDocument = os.path.abspath(self.pathDocument)
  419         self.pathDirectory = getDirName(self.pathDocument)
  420 
  421         self.meta = dict(
  422             author="",
  423             title="",
  424             subject="",
  425             keywords="",
  426             pagesize=A4,
  427         )
  428 
  429     def UID(self):
  430         self.uidctr += 1
  431         return self.uidctr
  432 
  433     # METHODS FOR CSS
  434     def addCSS(self, value):
  435         value = value.strip()
  436         if value.startswith("<![CDATA["):
  437             value = value[9: - 3]
  438         if value.startswith("<!--"):
  439             value = value[4: - 3]
  440         self.cssText += value.strip() + "\n"
  441 
  442     # METHODS FOR CSS
  443     def addDefaultCSS(self, value):
  444         value = value.strip()
  445         if value.startswith("<![CDATA["):
  446             value = value[9: - 3]
  447         if value.startswith("<!--"):
  448             value = value[4: - 3]
  449         self.cssDefaultText += value.strip() + "\n"
  450 
  451     def parseCSS(self):
  452         # This self-reference really should be refactored. But for now
  453         # we'll settle for using weak references. This avoids memory
  454         # leaks because the garbage collector (at least on cPython
  455         # 2.7.3) isn't aggressive enough.
  456         import weakref
  457 
  458         self.cssBuilder = pisaCSSBuilder(mediumSet=["all", "print", "pdf"])
  459         #self.cssBuilder.c = self
  460         self.cssBuilder._c = weakref.ref(self)
  461         pisaCSSBuilder.c = property(lambda self: self._c())
  462 
  463         self.cssParser = pisaCSSParser(self.cssBuilder)
  464         self.cssParser.rootPath = self.pathDirectory
  465         #self.cssParser.c = self
  466         self.cssParser._c = weakref.ref(self)
  467         pisaCSSParser.c = property(lambda self: self._c())
  468 
  469         self.css = self.cssParser.parse(self.cssText)
  470         self.cssDefault = self.cssParser.parse(self.cssDefaultText)
  471         self.cssCascade = css.CSSCascadeStrategy(
  472             userAgent=self.cssDefault, user=self.css)
  473         self.cssCascade.parser = self.cssParser
  474 
  475     # METHODS FOR STORY
  476     def addStory(self, data):
  477         self.story.append(data)
  478 
  479     def swapStory(self, story=None):
  480         story = story if story is not None else []
  481         self.story, story = copy.copy(story), copy.copy(self.story)
  482         return story
  483 
  484     def toParagraphStyle(self, first):
  485         style = ParagraphStyle(
  486             'default%d' % self.UID(), keepWithNext=first.keepWithNext)
  487 
  488         copy_attrs(style, first,
  489                    ('fontName', 'fontSize', 'letterSpacing', 'backColor',
  490                     'spaceBefore', 'spaceAfter', 'leftIndent', 'rightIndent',
  491                        'firstLineIndent', 'textColor', 'alignment',
  492                        'bulletIndent', 'wordWrap', 'borderTopStyle',
  493                        'borderTopWidth', 'borderTopColor',
  494                        'borderBottomStyle', 'borderBottomWidth',
  495                        'borderBottomColor', 'borderLeftStyle',
  496                        'borderLeftWidth', 'borderLeftColor',
  497                        'borderRightStyle', 'borderRightWidth',
  498                        'borderRightColor', 'paddingTop', 'paddingBottom',
  499                        'paddingLeft', 'paddingRight', 'borderPadding'
  500                     )
  501                    )
  502 
  503         style.leading = max(
  504             first.leading + first.leadingSpace, first.fontSize * 1.25)
  505         style.bulletFontName = first.bulletFontName or first.fontName
  506         style.bulletFontSize = first.fontSize
  507 
  508         # Border handling for Paragraph
  509 
  510         # Transfer the styles for each side of the border, *not* the whole
  511         # border values that reportlab supports. We'll draw them ourselves in
  512         # PmlParagraph.
  513 
  514         # If no border color is given, the text color is used (XXX Tables!)
  515         if (style.borderTopColor is None) and style.borderTopWidth:
  516             style.borderTopColor = first.textColor
  517         if (style.borderBottomColor is None) and style.borderBottomWidth:
  518             style.borderBottomColor = first.textColor
  519         if (style.borderLeftColor is None) and style.borderLeftWidth:
  520             style.borderLeftColor = first.textColor
  521         if (style.borderRightColor is None) and style.borderRightWidth:
  522             style.borderRightColor = first.textColor
  523 
  524         style.fontName = tt2ps(first.fontName, first.bold, first.italic)
  525 
  526         return style
  527 
  528     def addTOC(self):
  529         styles = []
  530         for i in six.moves.range(20):
  531             self.node.attributes["class"] = "pdftoclevel%d" % i
  532             self.cssAttr = xhtml2pdf.parser.CSSCollect(self.node, self)
  533             xhtml2pdf.parser.CSS2Frag(self, {
  534                 "margin-top": 0,
  535                 "margin-bottom": 0,
  536                 "margin-left": 0,
  537                 "margin-right": 0,
  538             }, True)
  539             pstyle = self.toParagraphStyle(self.frag)
  540             styles.append(pstyle)
  541 
  542         self.toc.levelStyles = styles
  543         self.addStory(self.toc)
  544         self.indexing_story = None
  545 
  546     def addPageCount(self):
  547         if not self.multiBuild:
  548             self.indexing_story = PmlPageCount()
  549             self.multiBuild = True
  550 
  551     def dumpPara(self, frags, style):
  552         return
  553 
  554     def addPara(self, force=False):
  555 
  556         force = (force or self.force)
  557         self.force = False
  558 
  559         # Cleanup the trail
  560         rfragList = reversed(self.fragList)
  561 
  562         # Find maximum lead
  563         maxLeading = 0
  564         #fontSize = 0
  565         for frag in self.fragList:
  566             leading = getSize(
  567                 frag.leadingSource, frag.fontSize) + frag.leadingSpace
  568             maxLeading = max(
  569                 leading, frag.fontSize + frag.leadingSpace, maxLeading)
  570             frag.leading = leading
  571 
  572         if force or (self.text.strip() and self.fragList):
  573 
  574             # Update paragraph style by style of first fragment
  575             first = self.fragBlock
  576             style = self.toParagraphStyle(first)
  577             # style.leading = first.leading + first.leadingSpace
  578             if first.leadingSpace:
  579                 style.leading = maxLeading
  580             else:
  581                 style.leading = getSize(
  582                     first.leadingSource, first.fontSize) + first.leadingSpace
  583 
  584             bulletText = copy.copy(first.bulletText)
  585             first.bulletText = None
  586 
  587             # Add paragraph to story
  588             if force or len(self.fragAnchor + self.fragList) > 0:
  589 
  590                 # We need this empty fragment to work around problems in
  591                 # Reportlab paragraphs regarding backGround etc.
  592                 if self.fragList:
  593                     self.fragList.append(self.fragList[- 1].clone(text=''))
  594                 else:
  595                     blank = self.frag.clone()
  596                     blank.fontName = "Helvetica"
  597                     blank.text = ''
  598                     self.fragList.append(blank)
  599 
  600                 self.dumpPara(self.fragAnchor + self.fragList, style)
  601                 if hasattr(self, 'language'):
  602                     language = self.__getattribute__('language')
  603                     detect_language_result = arabic_format(self.text, language)
  604                     if detect_language_result != None:
  605                         self.text = detect_language_result
  606                 para = PmlParagraph(
  607                     self.text,
  608                     style,
  609                     frags=self.fragAnchor + self.fragList,
  610                     bulletText=bulletText)
  611 
  612                 para.outline = first.outline
  613                 para.outlineLevel = first.outlineLevel
  614                 para.outlineOpen = first.outlineOpen
  615                 para.keepWithNext = first.keepWithNext
  616                 para.autoLeading = "max"
  617 
  618                 if self.image:
  619                     para = PmlParagraphAndImage(
  620                         para,
  621                         self.image,
  622                         side=self.imageData.get("align", "left"))
  623 
  624                 self.addStory(para)
  625 
  626             self.fragAnchor = []
  627             first.bulletText = None
  628 
  629         # Reset data
  630 
  631         self.image = None
  632         self.imageData = {}
  633 
  634         self.clearFrag()
  635 
  636     # METHODS FOR FRAG
  637     def clearFrag(self):
  638         self.fragList = []
  639         self.fragStrip = True
  640         self.text = u""
  641 
  642     def copyFrag(self, **kw):
  643         return self.frag.clone(**kw)
  644 
  645     def newFrag(self, **kw):
  646         self.frag = self.frag.clone(**kw)
  647         return self.frag
  648 
  649     def _appendFrag(self, frag):
  650         if frag.link and frag.link.startswith("#"):
  651             self.anchorFrag.append((frag, frag.link[1:]))
  652         self.fragList.append(frag)
  653 
  654     # XXX Argument frag is useless!
  655     def addFrag(self, text="", frag=None):
  656 
  657         frag = baseFrag = self.frag.clone()
  658 
  659         # if sub and super are both on they will cancel each other out
  660         if frag.sub == 1 and frag.super == 1:
  661             frag.sub = 0
  662             frag.super = 0
  663 
  664         # XXX Has to be replaced by CSS styles like vertical-align and
  665         # font-size
  666         if frag.sub:
  667             frag.rise = - frag.fontSize * subFraction
  668             frag.fontSize = max(frag.fontSize - sizeDelta, 3)
  669         elif frag.super:
  670             frag.rise = frag.fontSize * superFraction
  671             frag.fontSize = max(frag.fontSize - sizeDelta, 3)
  672 
  673        # bold, italic, and underline
  674         frag.fontName = frag.bulletFontName = tt2ps(
  675             frag.fontName, frag.bold, frag.italic)
  676 
  677         # Replace &shy; with empty and normalize NBSP
  678         text = (text
  679                 .replace(u"\xad", u"")
  680                 .replace(u"\xc2\xa0", NBSP)
  681                 .replace(u"\xa0", NBSP))
  682 
  683         if frag.whiteSpace == "pre":
  684 
  685             # Handle by lines
  686             for text in re.split(r'(\r\n|\n|\r)', text):
  687                 # This is an exceptionally expensive piece of code
  688                 self.text += text
  689                 if ("\n" in text) or ("\r" in text):
  690                     # If EOL insert a linebreak
  691                     frag = baseFrag.clone()
  692                     frag.text = ""
  693                     frag.lineBreak = 1
  694                     self._appendFrag(frag)
  695                 else:
  696                     # Handle tabs in a simple way
  697                     text = text.replace(u"\t", 8 * u" ")
  698                     # Somehow for Reportlab NBSP have to be inserted
  699                     # as single character fragments
  700                     for text in re.split(r'(\ )', text):
  701                         frag = baseFrag.clone()
  702                         if text == " ":
  703                             text = NBSP
  704                         frag.text = text
  705                         self._appendFrag(frag)
  706         else:
  707             for text in re.split(u'(' + NBSP + u')', text):
  708                 frag = baseFrag.clone()
  709                 if text == NBSP:
  710                     self.force = True
  711                     frag.text = NBSP
  712                     self.text += text
  713                     self._appendFrag(frag)
  714                 else:
  715                     frag.text = " ".join(("x" + text + "x").split())[1: - 1]
  716                     language_check = frag_text_language_check(self, frag.text)
  717                     if language_check:
  718                         frag.text = language_check
  719                     if self.fragStrip:
  720                         frag.text = frag.text.lstrip()
  721                         if frag.text:
  722                             self.fragStrip = False
  723                     self.text += frag.text
  724                     self._appendFrag(frag)
  725 
  726     def pushFrag(self):
  727         self.fragStack.append(self.frag)
  728         self.newFrag()
  729 
  730     def pullFrag(self):
  731         self.frag = self.fragStack.pop()
  732 
  733     # XXX
  734     def _getFragment(self, l=20):
  735         try:
  736             return repr(" ".join(self.node.toxml().split()[:l]))
  737         except:
  738             return ""
  739 
  740     def _getLineNumber(self):
  741         return 0
  742 
  743     def context(self, msg):
  744         return "%s\n%s" % (
  745             str(msg),
  746             self._getFragment(50))
  747 
  748     def warning(self, msg, *args):
  749         self.warn += 1
  750         self.log.append((xhtml2pdf.default.PML_WARNING, self._getLineNumber(), str(
  751             msg), self._getFragment(50)))
  752         try:
  753             return self.context(msg % args)
  754         except:
  755             return self.context(msg)
  756 
  757     def error(self, msg, *args):
  758         self.err += 1
  759         self.log.append((xhtml2pdf.default.PML_ERROR, self._getLineNumber(), str(
  760             msg), self._getFragment(50)))
  761         try:
  762             return self.context(msg % args)
  763         except:
  764             return self.context(msg)
  765 
  766     # UTILS
  767     def _getFileDeprecated(self, name, relative):
  768         try:
  769             path = relative or self.pathDirectory
  770             if name.startswith("data:"):
  771                 return name
  772             if self.pathCallback is not None:
  773                 nv = self.pathCallback(name, relative)
  774             else:
  775                 if path is None:
  776                     log.warning(
  777                         "Could not find main directory for getting filename. Use CWD")
  778                     path = os.getcwd()
  779                 nv = os.path.normpath(os.path.join(path, name))
  780                 if not (nv and os.path.isfile(nv)):
  781                     nv = None
  782             if nv is None:
  783                 log.warning(self.warning("File '%s' does not exist", name))
  784             return nv
  785         except:
  786             log.warning(
  787                 self.warning("getFile %r %r %r", name, relative, path), exc_info=1)
  788 
  789     def getFile(self, name, relative=None):
  790         """
  791         Returns a file name or None
  792         """
  793         if self.pathCallback is not None:
  794             return getFile(self._getFileDeprecated(name, relative))
  795         return getFile(name, relative or self.pathDirectory)
  796 
  797     def getFontName(self, names, default="helvetica"):
  798         """
  799         Name of a font
  800         """
  801         # print names, self.fontList
  802         if type(names) is not ListType:
  803             if type(names) not in six.string_types:
  804                 names = str(names)
  805             names = names.strip().split(",")
  806         for name in names:
  807             if type(name) not in six.string_types:
  808                 name = str(name)
  809             font = name.strip().lower()
  810             if font in self.asianFontList:
  811                 font = self.asianFontList.get(font, None)
  812                 set_asian_fonts(font)
  813             else:
  814                 font = self.fontList.get(font,None)
  815             if font is not None:
  816                 return font
  817         return self.fontList.get(default, None)
  818 
  819     def registerFont(self, fontname, alias=None):
  820         alias = alias if alias is not None else []
  821         self.fontList[str(fontname).lower()] = str(fontname)
  822         for a in alias:
  823             if type(fontname) not in six.string_types:
  824                 fontname = str(fontname)
  825             self.fontList[str(a)] = fontname
  826 
  827     def loadFont(self, names, src, encoding="WinAnsiEncoding", bold=0, italic=0):
  828 
  829         # XXX Just works for local filenames!
  830         if names and src:
  831 
  832             file = src
  833             src = file.uri
  834 
  835             log.debug("Load font %r", src)
  836             if names.startswith("#"):
  837                 names = names.strip('#')
  838             if type(names) is ListType:
  839                 fontAlias = names
  840             else:
  841                 fontAlias = (x.lower().strip() for x in names.split(",") if x)
  842 
  843             # XXX Problems with unicode here
  844             fontAlias = [str(x) for x in fontAlias]
  845 
  846             ffname = names
  847             fontName = fontAlias[0]
  848             parts = src.split(".")
  849             baseName, suffix = ".".join(parts[: - 1]), parts[- 1]
  850             suffix = suffix.lower()
  851 
  852             if suffix in ["ttc", "ttf"]:
  853 
  854                 # determine full font name according to weight and style
  855                 fullFontName = "%s_%d%d" % (fontName, bold, italic)
  856 
  857                 # check if font has already been registered
  858                 if fullFontName in self.fontList:
  859                     log.warning(
  860                         self.warning("Repeated font embed for %s, skip new embed ", fullFontName))
  861                 else:
  862 
  863                     # Register TTF font and special name
  864                     filename = file.getNamedFile()
  865                     file = TTFont(fullFontName, filename)
  866                     pdfmetrics.registerFont(file)
  867 
  868                     # Add or replace missing styles
  869                     for bold in (0, 1):
  870                         for italic in (0, 1):
  871                             if ("%s_%d%d" % (fontName, bold, italic)) not in self.fontList:
  872                                 addMapping(
  873                                     fontName, bold, italic, fullFontName)
  874 
  875                     # Register "normal" name and the place holder for style
  876                     self.registerFont(fontName, fontAlias + [fullFontName])
  877 
  878             elif suffix in ("afm", "pfb"):
  879 
  880                 if suffix == "afm":
  881                     afm = file.getNamedFile()
  882                     tfile = pisaFileObject(baseName + ".pfb")
  883                     pfb = tfile.getNamedFile()
  884                 else:
  885                     pfb = file.getNamedFile()
  886                     tfile = pisaFileObject(baseName + ".afm")
  887                     afm = tfile.getNamedFile()
  888 
  889                 # determine full font name according to weight and style
  890                 fullFontName = "%s_%d%d" % (fontName, bold, italic)
  891 
  892                 # check if font has already been registered
  893                 if fullFontName in self.fontList:
  894                     log.warning(
  895                         self.warning("Repeated font embed for %s, skip new embed", fontName))
  896                 else:
  897 
  898                     # Include font
  899                     face = pdfmetrics.EmbeddedType1Face(afm, pfb)
  900                     fontNameOriginal = face.name
  901                     pdfmetrics.registerTypeFace(face)
  902                     # print fontName, fontNameOriginal, fullFontName
  903                     justFont = pdfmetrics.Font(
  904                         fullFontName, fontNameOriginal, encoding)
  905                     pdfmetrics.registerFont(justFont)
  906 
  907                     # Add or replace missing styles
  908                     for bold in (0, 1):
  909                         for italic in (0, 1):
  910                             if ("%s_%d%d" % (fontName, bold, italic)) not in self.fontList:
  911                                 addMapping(
  912                                     fontName, bold, italic, fontNameOriginal)
  913 
  914                     # Register "normal" name and the place holder for style
  915                     self.registerFont(
  916                         fontName, fontAlias + [fullFontName, fontNameOriginal])
  917             else:
  918                 log.warning(self.warning("wrong attributes for <pdf:font>"))