"Fossies" - the Fresh Open Source Software Archive

Member "xhtml2pdf-0.2.5/xhtml2pdf/util.py" (8 Oct 2020, 32978 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 "util.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 base64
    3 from copy import copy
    4 import logging
    5 import mimetypes
    6 import os.path
    7 import re
    8 import shutil
    9 import string
   10 import sys
   11 import tempfile
   12 import xhtml2pdf.default
   13 import reportlab
   14 from bidi.algorithm import get_display
   15 from reportlab.lib.colors import Color, toColor
   16 from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
   17 from reportlab.lib.units import inch, cm
   18 import six
   19 import reportlab.pdfbase._cidfontdata
   20 from reportlab.pdfbase import pdfmetrics
   21 from reportlab.pdfbase.cidfonts import UnicodeCIDFont
   22 import arabic_reshaper
   23 
   24 
   25 
   26 
   27 try:
   28     import httplib
   29 except ImportError:
   30     import http.client as httplib
   31 
   32 try:
   33     import urllib.request as urllib2
   34 except ImportError:
   35     import urllib2
   36 
   37 try:
   38     import urllib.parse as urlparse
   39 except ImportError:
   40     import urlparse
   41 
   42 try:
   43     from urllib.parse import unquote as urllib_unquote
   44 except ImportError:
   45     from urllib import unquote as urllib_unquote
   46 
   47 # Copyright 2010 Dirk Holtwick, holtwick.it
   48 #
   49 # Licensed under the Apache License, Version 2.0 (the "License");
   50 # you may not use this file except in compliance with the License.
   51 # You may obtain a copy of the License at
   52 #
   53 #     http://www.apache.org/licenses/LICENSE-2.0
   54 #
   55 # Unless required by applicable law or agreed to in writing, software
   56 # distributed under the License is distributed on an "AS IS" BASIS,
   57 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   58 # See the License for the specific language governing permissions and
   59 # limitations under the License.
   60 
   61 rgb_re = re.compile(
   62     "^.*?rgb[a]?[(]([0-9]+).*?([0-9]+).*?([0-9]+)(?:.*?(?:[01]\.(?:[0-9]+)))?[)].*?[ ]*$")
   63 
   64 _reportlab_version = tuple(map(int, reportlab.Version.split('.')))
   65 if _reportlab_version < (2, 1):
   66     raise ImportError("Reportlab Version 2.1+ is needed!")
   67 
   68 log = logging.getLogger("xhtml2pdf")
   69 
   70 try:
   71     import PyPDF2
   72 except ImportError:
   73     PyPDF2 = None
   74 
   75 try:
   76     from reportlab.graphics import renderPM
   77 except ImportError:
   78     renderPM = None
   79 
   80 try:
   81     from reportlab.graphics import renderSVG
   82 except ImportError:
   83     renderSVG = None
   84 
   85 from xhtml2pdf.config.httpconfig import httpConfig
   86 #=========================================================================
   87 # Memoize decorator
   88 #=========================================================================
   89 
   90 
   91 class memoized(object):
   92 
   93     """
   94     A kwargs-aware memoizer, better than the one in python :)
   95 
   96     Don't pass in too large kwargs, since this turns them into a tuple of
   97     tuples. Also, avoid mutable types (as usual for memoizers)
   98 
   99     What this does is to create a dictionnary of {(*parameters):return value},
  100     and uses it as a cache for subsequent calls to the same method.
  101     It is especially useful for functions that don't rely on external variables
  102     and that are called often. It's a perfect match for our getSize etc...
  103     """
  104 
  105     def __init__(self, func):
  106         self.cache = {}
  107         self.func = func
  108         self.__doc__ = self.func.__doc__  # To avoid great confusion
  109         self.__name__ = self.func.__name__  # This also avoids great confusion
  110 
  111     def __call__(self, *args, **kwargs):
  112         # Make sure the following line is not actually slower than what you're
  113         # trying to memoize
  114         args_plus = tuple(six.iteritems(kwargs))
  115         key = (args, args_plus)
  116         try:
  117             if key not in self.cache:
  118                 res = self.func(*args, **kwargs)
  119                 self.cache[key] = res
  120             return self.cache[key]
  121         except TypeError:
  122             # happens if any of the parameters is a list
  123             return self.func(*args, **kwargs)
  124 
  125 
  126 def ErrorMsg():
  127     """
  128     Helper to get a nice traceback as string
  129     """
  130     import traceback
  131 
  132     limit = None
  133     _type, value, tb = sys.exc_info()
  134     _list = traceback.format_tb(tb, limit) + \
  135         traceback.format_exception_only(_type, value)
  136     return "Traceback (innermost last):\n" + "%-20s %s" % (
  137         " ".join(_list[:-1]),
  138         _list[-1])
  139 
  140 
  141 def toList(value):
  142     if type(value) not in (list, tuple):
  143         return [value]
  144     return list(value)
  145 
  146 
  147 def transform_attrs(obj, keys, container, func, extras=None):
  148     """
  149     Allows to apply one function to set of keys cheching if key is in container,
  150     also trasform ccs key to report lab keys.
  151 
  152     extras = Are extra params for func, it will be call like func(*[param1, param2])
  153 
  154     obj = frag
  155     keys = [(reportlab, css), ... ]
  156     container = cssAttr
  157     """
  158     cpextras = extras
  159 
  160     for reportlab, css in keys:
  161         extras = cpextras
  162         if extras is None:
  163             extras = []
  164         elif not isinstance(extras, list):
  165             extras = [extras]
  166         if css in container:
  167             extras.insert(0, container[css])
  168             setattr(obj,
  169                     reportlab,
  170                     func(*extras)
  171                     )
  172 
  173 
  174 def copy_attrs(obj1, obj2, attrs):
  175     """
  176     Allows copy a list of attributes from object2 to object1.
  177     Useful for copy ccs attributes to fragment
  178     """
  179     for attr in attrs:
  180         value = getattr(obj2, attr) if hasattr(obj2, attr) else None
  181         if value is None and isinstance(obj2, dict) and attr in obj2:
  182             value = obj2[attr]
  183         setattr(obj1, attr, value)
  184 
  185 
  186 def set_value(obj, attrs, value, _copy=False):
  187     """
  188     Allows set the same value to a list of attributes
  189     """
  190     for attr in attrs:
  191         if _copy:
  192             value = copy(value)
  193         setattr(obj, attr, value)
  194 
  195 
  196 @memoized
  197 def getColor(value, default=None):
  198     """
  199     Convert to color value.
  200     This returns a Color object instance from a text bit.
  201     """
  202 
  203     if isinstance(value, Color):
  204         return value
  205     value = str(value).strip().lower()
  206     if value == "transparent" or value == "none":
  207         return default
  208     if value in COLOR_BY_NAME:
  209         return COLOR_BY_NAME[value]
  210     if value.startswith("#") and len(value) == 4:
  211         value = "#" + value[1] + value[1] + \
  212             value[2] + value[2] + value[3] + value[3]
  213     elif rgb_re.search(value):
  214         # e.g., value = "<css function: rgb(153, 51, 153)>", go figure:
  215         r, g, b = [int(x) for x in rgb_re.search(value).groups()]
  216         value = "#%02x%02x%02x" % (r, g, b)
  217     else:
  218         # Shrug
  219         pass
  220 
  221     return toColor(value, default)  # Calling the reportlab function
  222 
  223 
  224 def getBorderStyle(value, default=None):
  225     if value and (str(value).lower() not in ("none", "hidden")):
  226         return value
  227     return default
  228 
  229 
  230 mm = cm / 10.0
  231 dpi96 = (1.0 / 96.0 * inch)
  232 
  233 _absoluteSizeTable = {
  234     "1": 50.0 / 100.0,
  235     "xx-small": 50.0 / 100.0,
  236     "x-small": 50.0 / 100.0,
  237     "2": 75.0 / 100.0,
  238     "small": 75.0 / 100.0,
  239     "3": 100.0 / 100.0,
  240     "medium": 100.0 / 100.0,
  241     "4": 125.0 / 100.0,
  242     "large": 125.0 / 100.0,
  243     "5": 150.0 / 100.0,
  244     "x-large": 150.0 / 100.0,
  245     "6": 175.0 / 100.0,
  246     "xx-large": 175.0 / 100.0,
  247     "7": 200.0 / 100.0,
  248     "xxx-large": 200.0 / 100.0,
  249 }
  250 
  251 _relativeSizeTable = {
  252     "larger": 1.25,
  253     "smaller": 0.75,
  254     "+4": 200.0 / 100.0,
  255     "+3": 175.0 / 100.0,
  256     "+2": 150.0 / 100.0,
  257     "+1": 125.0 / 100.0,
  258     "-1": 75.0 / 100.0,
  259     "-2": 50.0 / 100.0,
  260     "-3": 25.0 / 100.0,
  261 }
  262 
  263 MIN_FONT_SIZE = 1.0
  264 
  265 
  266 @memoized
  267 def getSize(value, relative=0, base=None, default=0.0):
  268     """
  269     Converts strings to standard sizes.
  270     That is the function taking a string of CSS size ('12pt', '1cm' and so on)
  271     and converts it into a float in a standard unit (in our case, points).
  272 
  273     >>> getSize('12pt')
  274     12.0
  275     >>> getSize('1cm')
  276     28.346456692913385
  277     """
  278     try:
  279         original = value
  280         if value is None:
  281             return relative
  282         elif type(value) is float:
  283             return value
  284         elif isinstance(value, int):
  285             return float(value)
  286         elif type(value) in (tuple, list):
  287             value = "".join(value)
  288         value = str(value).strip().lower().replace(",", ".")
  289         if value[-2:] == 'cm':
  290             return float(value[:-2].strip()) * cm
  291         elif value[-2:] == 'mm':
  292             return float(value[:-2].strip()) * mm  # 1mm = 0.1cm
  293         elif value[-2:] == 'in':
  294             return float(value[:-2].strip()) * inch  # 1pt == 1/72inch
  295         elif value[-2:] == 'pt':
  296             return float(value[:-2].strip())
  297         elif value[-2:] == 'pc':
  298             return float(value[:-2].strip()) * 12.0  # 1pc == 12pt
  299         elif value[-2:] == 'px':
  300             # XXX W3C says, use 96pdi
  301             # http://www.w3.org/TR/CSS21/syndata.html#length-units
  302             return float(value[:-2].strip()) * dpi96
  303         elif value in ("none", "0", '0.0', "auto"):
  304             return 0.0
  305         elif relative:
  306             if value[-3:] == 'rem':  # XXX
  307                 # 1rem = 1 * fontSize
  308                 return float(value[:-3].strip()) * relative
  309             elif value[-2:] == 'em':  # XXX
  310                 # 1em = 1 * fontSize
  311                 return float(value[:-2].strip()) * relative
  312             elif value[-2:] == 'ex':  # XXX
  313                 # 1ex = 1/2 fontSize
  314                 return float(value[:-2].strip()) * (relative / 2.0)
  315             elif value[-1:] == '%':
  316                 # 1% = (fontSize * 1) / 100
  317                 return (relative * float(value[:-1].strip())) / 100.0
  318             elif value in ("normal", "inherit"):
  319                 return relative
  320             elif value in _relativeSizeTable:
  321                 if base:
  322                     return max(MIN_FONT_SIZE, base * _relativeSizeTable[value])
  323                 return max(MIN_FONT_SIZE, relative * _relativeSizeTable[value])
  324             elif value in _absoluteSizeTable:
  325                 if base:
  326                     return max(MIN_FONT_SIZE, base * _absoluteSizeTable[value])
  327                 return max(MIN_FONT_SIZE, relative * _absoluteSizeTable[value])
  328             else:
  329                 return max(MIN_FONT_SIZE, relative * float(value))
  330         try:
  331             value = float(value)
  332         except ValueError:
  333             log.warning("getSize: Not a float %r", value)
  334             return default  # value = 0
  335         return max(0, value)
  336     except Exception:
  337         log.warning("getSize %r %r", original, relative, exc_info=1)
  338         return default
  339 
  340 
  341 @memoized
  342 def getCoords(x, y, w, h, pagesize):
  343     """
  344     As a stupid programmer I like to use the upper left
  345     corner of the document as the 0,0 coords therefore
  346     we need to do some fancy calculations
  347     """
  348     #~ print pagesize
  349     ax, ay = pagesize
  350     if x < 0:
  351         x = ax + x
  352     if y < 0:
  353         y = ay + y
  354     if w is not None and h is not None:
  355         if w <= 0:
  356             w = (ax - x + w)
  357         if h <= 0:
  358             h = (ay - y + h)
  359         return x, (ay - y - h), w, h
  360     return x, (ay - y)
  361 
  362 
  363 @memoized
  364 def getBox(box, pagesize):
  365     """
  366     Parse sizes by corners in the form:
  367     <X-Left> <Y-Upper> <Width> <Height>
  368     The last to values with negative values are interpreted as offsets form
  369     the right and lower border.
  370     """
  371     box = str(box).split()
  372     if len(box) != 4:
  373         raise Exception("box not defined right way")
  374     x, y, w, h = [getSize(pos) for pos in box]
  375     return getCoords(x, y, w, h, pagesize)
  376 
  377 
  378 def getFrameDimensions(data, page_width, page_height):
  379     """Calculate dimensions of a frame
  380 
  381     Returns left, top, width and height of the frame in points.
  382     """
  383     box = data.get("-pdf-frame-box", [])
  384     if len(box) == 4:
  385         return [getSize(x) for x in box]
  386     top = getSize(data.get("top", 0))
  387     left = getSize(data.get("left", 0))
  388     bottom = getSize(data.get("bottom", 0))
  389     right = getSize(data.get("right", 0))
  390     if "height" in data:
  391         height = getSize(data["height"])
  392         if "top" in data:
  393             top = getSize(data["top"])
  394             bottom = page_height - (top + height)
  395         elif "bottom" in data:
  396             bottom = getSize(data["bottom"])
  397             top = page_height - (bottom + height)
  398     if "width" in data:
  399         width = getSize(data["width"])
  400         if "left" in data:
  401             left = getSize(data["left"])
  402             right = page_width - (left + width)
  403         elif "right" in data:
  404             right = getSize(data["right"])
  405             left = page_width - (right + width)
  406     top += getSize(data.get("margin-top", 0))
  407     left += getSize(data.get("margin-left", 0))
  408     bottom += getSize(data.get("margin-bottom", 0))
  409     right += getSize(data.get("margin-right", 0))
  410 
  411     width = page_width - (left + right)
  412     height = page_height - (top + bottom)
  413     return left, top, width, height
  414 
  415 
  416 @memoized
  417 def getPos(position, pagesize):
  418     """
  419     Pair of coordinates
  420     """
  421     position = str(position).split()
  422     if len(position) != 2:
  423         raise Exception("position not defined right way")
  424     x, y = [getSize(pos) for pos in position]
  425     return getCoords(x, y, None, None, pagesize)
  426 
  427 
  428 def getBool(s):
  429     " Is it a boolean? "
  430     return str(s).lower() in ("y", "yes", "1", "true")
  431 
  432 
  433 _uid = 0
  434 
  435 
  436 def getUID():
  437     " Unique ID "
  438     global _uid
  439     _uid += 1
  440     return str(_uid)
  441 
  442 
  443 _alignments = {
  444     "left": TA_LEFT,
  445     "center": TA_CENTER,
  446     "middle": TA_CENTER,
  447     "right": TA_RIGHT,
  448     "justify": TA_JUSTIFY,
  449 }
  450 
  451 
  452 def getAlign(value, default=TA_LEFT):
  453     return _alignments.get(str(value).lower(), default)
  454 
  455 GAE = "google.appengine" in sys.modules
  456 
  457 if GAE:
  458     STRATEGIES = (
  459         six.BytesIO,
  460         six.BytesIO)
  461 else:
  462     STRATEGIES = (
  463         six.BytesIO,
  464         tempfile.NamedTemporaryFile)
  465 
  466 
  467 class pisaTempFile(object):
  468 
  469     """
  470     A temporary file implementation that uses memory unless
  471     either capacity is breached or fileno is requested, at which
  472     point a real temporary file will be created and the relevant
  473     details returned
  474 
  475     If capacity is -1 the second strategy will never be used.
  476 
  477     Inspired by:
  478     http://code.activestate.com/recipes/496744/
  479     """
  480 
  481     STRATEGIES = STRATEGIES
  482 
  483     CAPACITY = 10 * 1024
  484 
  485     def __init__(self, buffer="", capacity=CAPACITY):
  486         """Creates a TempFile object containing the specified buffer.
  487         If capacity is specified, we use a real temporary file once the
  488         file gets larger than that size.  Otherwise, the data is stored
  489         in memory.
  490         """
  491 
  492         self.capacity = capacity
  493         self.strategy = int(len(buffer) > self.capacity)
  494         try:
  495             self._delegate = self.STRATEGIES[self.strategy]()
  496         except IndexError:
  497             # Fallback for Google AppEnginge etc.
  498             self._delegate = self.STRATEGIES[0]()
  499         self.write(buffer)
  500         # we must set the file's position for preparing to read
  501         self.seek(0)
  502 
  503     def makeTempFile(self):
  504         """
  505         Switch to next startegy. If an error occured,
  506         stay with the first strategy
  507         """
  508 
  509         if self.strategy == 0:
  510             try:
  511                 new_delegate = self.STRATEGIES[1]()
  512                 new_delegate.write(self.getvalue())
  513                 self._delegate = new_delegate
  514                 self.strategy = 1
  515                 log.warning("Created temporary file %s", self.name)
  516             except:
  517                 self.capacity = - 1
  518 
  519     def getFileName(self):
  520         """
  521         Get a named temporary file
  522         """
  523 
  524         self.makeTempFile()
  525         return self.name
  526 
  527     def fileno(self):
  528         """
  529         Forces this buffer to use a temporary file as the underlying.
  530         object and returns the fileno associated with it.
  531         """
  532         self.makeTempFile()
  533         return self._delegate.fileno()
  534 
  535     def getvalue(self):
  536         """
  537         Get value of file. Work around for second strategy.
  538         Always returns bytes
  539         """
  540 
  541         if self.strategy == 0:
  542             return self._delegate.getvalue()
  543         self._delegate.flush()
  544         self._delegate.seek(0)
  545         value = self._delegate.read()
  546         if not isinstance(value, six.binary_type):
  547             value = value.encode('utf-8')
  548         return value
  549 
  550     def write(self, value):
  551         """
  552         If capacity != -1 and length of file > capacity it is time to switch
  553         """
  554 
  555         if self.capacity > 0 and self.strategy == 0:
  556             len_value = len(value)
  557             if len_value >= self.capacity:
  558                 needs_new_strategy = True
  559             else:
  560                 self.seek(0, 2)  # find end of file
  561                 needs_new_strategy = \
  562                     (self.tell() + len_value) >= self.capacity
  563             if needs_new_strategy:
  564                 self.makeTempFile()
  565 
  566         if not isinstance(value, six.binary_type):
  567             value = value.encode('utf-8')
  568 
  569         self._delegate.write(value)
  570 
  571     def __getattr__(self, name):
  572         try:
  573             return getattr(self._delegate, name)
  574         except AttributeError:
  575             # hide the delegation
  576             e = "object '%s' has no attribute '%s'" \
  577                 % (self.__class__.__name__, name)
  578             raise AttributeError(e)
  579 
  580 
  581 _rx_datauri = re.compile(
  582     "^data:(?P<mime>[a-z]+/[a-z]+);base64,(?P<data>.*)$", re.M | re.DOTALL)
  583 
  584 
  585 class pisaFileObject:
  586 
  587     """
  588     XXX
  589     """
  590 
  591     def __init__(self, uri, basepath=None):
  592 
  593         self.basepath = basepath
  594         self.mimetype = None
  595         self.file = None
  596         self.data = None
  597         self.uri = None
  598         self.local = None
  599         self.tmp_file = None
  600         uri = uri or str()
  601         if not isinstance(uri, str):
  602             uri = uri.decode("utf-8")
  603         log.debug("FileObject %r, Basepath: %r", uri, basepath)
  604 
  605         # Data URI
  606         if uri.startswith("data:"):
  607             m = _rx_datauri.match(uri)
  608             self.mimetype = m.group("mime")
  609 
  610             b64 = urllib_unquote(m.group("data"))
  611 
  612             # The data may be incorrectly unescaped... repairs needed
  613             b64 = b64.strip("b'").strip("'").encode()
  614             b64 = re.sub(b"\\n", b'', b64)
  615             b64 = re.sub(b'[^A-Za-z0-9\+\/]+', b'', b64)
  616 
  617             # Add padding as needed, to make length into a multiple of 4
  618             #
  619             b64 += b"=" * ((4 - len(b64) % 4) % 4)
  620 
  621             self.data = base64.b64decode(b64)
  622 
  623         else:
  624             # Check if we have an external scheme
  625             if basepath and not urlparse.urlparse(uri).scheme:
  626                 urlParts = urlparse.urlparse(basepath)
  627             else:
  628                 urlParts = urlparse.urlparse(uri)
  629 
  630             log.debug("URLParts: {}".format((urlParts, urlParts.scheme)))
  631 
  632             if urlParts.scheme == 'file':
  633                 if basepath and uri.startswith('/'):
  634                     uri = urlparse.urljoin(basepath, uri[1:])
  635                 urlResponse = urllib2.urlopen(uri)
  636                 self.mimetype = urlResponse.info().get(
  637                     "Content-Type", '').split(";")[0]
  638                 self.uri = urlResponse.geturl()
  639                 self.file = urlResponse
  640 
  641             # Drive letters have len==1 but we are looking
  642             # for things like http:
  643             elif urlParts.scheme in ('http', 'https'):
  644 
  645                 log.debug("Sending request for {} with httplib".format(uri))
  646 
  647                 # External data
  648                 if basepath:
  649                     uri = urlparse.urljoin(basepath, uri)
  650 
  651                 log.debug("Uri parsed: {}".format(uri))
  652 
  653                 #path = urlparse.urlsplit(url)[2]
  654                 #mimetype = getMimeType(path)
  655 
  656                 # Using HTTPLIB
  657                 url_splitted = urlparse.urlsplit(uri)
  658                 server = url_splitted[1]
  659                 path = url_splitted[2]
  660                 path += "?" + url_splitted[3] if url_splitted[3] else ""
  661                 if uri.startswith("https://"):
  662                     conn = httplib.HTTPSConnection(server,  **httpConfig)
  663                 else:
  664                     conn = httplib.HTTPConnection(server)
  665                 conn.request("GET", path)
  666                 r1 = conn.getresponse()
  667                 # log.debug("HTTP %r %r %r %r", server, path, uri, r1)
  668                 if (r1.status, r1.reason) == (200, "OK"):
  669                     self.mimetype = r1.getheader(
  670                         "Content-Type", '').split(";")[0]
  671                     self.uri = uri
  672                     log.debug("here")
  673                     if r1.getheader("content-encoding") == "gzip":
  674                         import gzip
  675 
  676                         self.file = gzip.GzipFile(
  677                             mode="rb", fileobj=six.BytesIO(r1.read()))
  678                     else:
  679                         self.file = pisaTempFile(r1.read())
  680                 else:
  681                     log.debug(
  682                         "Received non-200 status: {}".format((r1.status, r1.reason)))
  683                     try:
  684                         urlResponse = urllib2.urlopen(uri)
  685                     except urllib2.HTTPError as e:
  686                         log.error("Could not process uri: {}".format(e))
  687                         return
  688                     self.mimetype = urlResponse.info().get(
  689                         "Content-Type", '').split(";")[0]
  690                     self.uri = urlResponse.geturl()
  691                     self.file = urlResponse
  692                 conn.close()
  693 
  694             else:
  695 
  696                 log.debug("Unrecognized scheme, assuming local file path")
  697 
  698                 # Local data
  699                 if basepath:
  700                     if sys.platform == 'win32' and os.path.isfile(basepath):
  701                         basepath = os.path.dirname(basepath)
  702                     uri = os.path.normpath(os.path.join(basepath, uri))
  703 
  704                 if os.path.isfile(uri):
  705                     self.uri = uri
  706                     self.local = uri
  707 
  708                     self.setMimeTypeByName(uri)
  709                     if self.mimetype and self.mimetype.startswith('text'):
  710                         self.file = open(uri, "r") #removed bytes... lets hope it goes ok :/
  711                     else:
  712                         # removed bytes... lets hope it goes ok :/
  713                         self.file = open(uri, "rb")
  714 
  715     def getFile(self):
  716         if self.file is not None:
  717             return self.file
  718         if self.data is not None:
  719             return pisaTempFile(self.data)
  720         return None
  721 
  722     def getNamedFile(self):
  723         if self.notFound():
  724             return None
  725         if self.local:
  726             return str(self.local)
  727         if not self.tmp_file:
  728             self.tmp_file = tempfile.NamedTemporaryFile()
  729             if self.file:
  730                 shutil.copyfileobj(self.file, self.tmp_file)
  731             else:
  732                 self.tmp_file.write(self.getData())
  733             self.tmp_file.flush()
  734         return self.tmp_file.name
  735 
  736     def getData(self):
  737         if self.data is not None:
  738             return self.data
  739         if self.file is not None:
  740             try:
  741                 self.data = self.file.read()
  742             except:
  743                 if self.mimetype and self.mimetype.startswith('text'):
  744                     self.file = open(self.file.name, "rb") #removed bytes... lets hope it goes ok :/
  745                     self.data = self.file.read().decode('utf-8')
  746                 else:
  747                     raise
  748             return self.data
  749         return None
  750 
  751     def notFound(self):
  752         return (self.file is None) and (self.data is None)
  753 
  754     def setMimeTypeByName(self, name):
  755         " Guess the mime type "
  756         mimetype = mimetypes.guess_type(name)[0]
  757         if mimetype is not None:
  758             self.mimetype = mimetypes.guess_type(name)[0].split(";")[0]
  759 
  760 
  761 def getFile(*a, **kw):
  762     file = pisaFileObject(*a, **kw)
  763     if file.notFound():
  764         return None
  765     return file
  766 
  767 
  768 COLOR_BY_NAME = {
  769     'activeborder': Color(212, 208, 200),
  770     'activecaption': Color(10, 36, 106),
  771     'aliceblue': Color(.941176, .972549, 1),
  772     'antiquewhite': Color(.980392, .921569, .843137),
  773     'appworkspace': Color(128, 128, 128),
  774     'aqua': Color(0, 1, 1),
  775     'aquamarine': Color(.498039, 1, .831373),
  776     'azure': Color(.941176, 1, 1),
  777     'background': Color(58, 110, 165),
  778     'beige': Color(.960784, .960784, .862745),
  779     'bisque': Color(1, .894118, .768627),
  780     'black': Color(0, 0, 0),
  781     'blanchedalmond': Color(1, .921569, .803922),
  782     'blue': Color(0, 0, 1),
  783     'blueviolet': Color(.541176, .168627, .886275),
  784     'brown': Color(.647059, .164706, .164706),
  785     'burlywood': Color(.870588, .721569, .529412),
  786     'buttonface': Color(212, 208, 200),
  787     'buttonhighlight': Color(255, 255, 255),
  788     'buttonshadow': Color(128, 128, 128),
  789     'buttontext': Color(0, 0, 0),
  790     'cadetblue': Color(.372549, .619608, .627451),
  791     'captiontext': Color(255, 255, 255),
  792     'chartreuse': Color(.498039, 1, 0),
  793     'chocolate': Color(.823529, .411765, .117647),
  794     'coral': Color(1, .498039, .313725),
  795     'cornflowerblue': Color(.392157, .584314, .929412),
  796     'cornsilk': Color(1, .972549, .862745),
  797     'crimson': Color(.862745, .078431, .235294),
  798     'cyan': Color(0, 1, 1),
  799     'darkblue': Color(0, 0, .545098),
  800     'darkcyan': Color(0, .545098, .545098),
  801     'darkgoldenrod': Color(.721569, .52549, .043137),
  802     'darkgray': Color(.662745, .662745, .662745),
  803     'darkgreen': Color(0, .392157, 0),
  804     'darkgrey': Color(.662745, .662745, .662745),
  805     'darkkhaki': Color(.741176, .717647, .419608),
  806     'darkmagenta': Color(.545098, 0, .545098),
  807     'darkolivegreen': Color(.333333, .419608, .184314),
  808     'darkorange': Color(1, .54902, 0),
  809     'darkorchid': Color(.6, .196078, .8),
  810     'darkred': Color(.545098, 0, 0),
  811     'darksalmon': Color(.913725, .588235, .478431),
  812     'darkseagreen': Color(.560784, .737255, .560784),
  813     'darkslateblue': Color(.282353, .239216, .545098),
  814     'darkslategray': Color(.184314, .309804, .309804),
  815     'darkslategrey': Color(.184314, .309804, .309804),
  816     'darkturquoise': Color(0, .807843, .819608),
  817     'darkviolet': Color(.580392, 0, .827451),
  818     'deeppink': Color(1, .078431, .576471),
  819     'deepskyblue': Color(0, .74902, 1),
  820     'dimgray': Color(.411765, .411765, .411765),
  821     'dimgrey': Color(.411765, .411765, .411765),
  822     'dodgerblue': Color(.117647, .564706, 1),
  823     'firebrick': Color(.698039, .133333, .133333),
  824     'floralwhite': Color(1, .980392, .941176),
  825     'forestgreen': Color(.133333, .545098, .133333),
  826     'fuchsia': Color(1, 0, 1),
  827     'gainsboro': Color(.862745, .862745, .862745),
  828     'ghostwhite': Color(.972549, .972549, 1),
  829     'gold': Color(1, .843137, 0),
  830     'goldenrod': Color(.854902, .647059, .12549),
  831     'gray': Color(.501961, .501961, .501961),
  832     'graytext': Color(128, 128, 128),
  833     'green': Color(0, .501961, 0),
  834     'greenyellow': Color(.678431, 1, .184314),
  835     'grey': Color(.501961, .501961, .501961),
  836     'highlight': Color(10, 36, 106),
  837     'highlighttext': Color(255, 255, 255),
  838     'honeydew': Color(.941176, 1, .941176),
  839     'hotpink': Color(1, .411765, .705882),
  840     'inactiveborder': Color(212, 208, 200),
  841     'inactivecaption': Color(128, 128, 128),
  842     'inactivecaptiontext': Color(212, 208, 200),
  843     'indianred': Color(.803922, .360784, .360784),
  844     'indigo': Color(.294118, 0, .509804),
  845     'infobackground': Color(255, 255, 225),
  846     'infotext': Color(0, 0, 0),
  847     'ivory': Color(1, 1, .941176),
  848     'khaki': Color(.941176, .901961, .54902),
  849     'lavender': Color(.901961, .901961, .980392),
  850     'lavenderblush': Color(1, .941176, .960784),
  851     'lawngreen': Color(.486275, .988235, 0),
  852     'lemonchiffon': Color(1, .980392, .803922),
  853     'lightblue': Color(.678431, .847059, .901961),
  854     'lightcoral': Color(.941176, .501961, .501961),
  855     'lightcyan': Color(.878431, 1, 1),
  856     'lightgoldenrodyellow': Color(.980392, .980392, .823529),
  857     'lightgray': Color(.827451, .827451, .827451),
  858     'lightgreen': Color(.564706, .933333, .564706),
  859     'lightgrey': Color(.827451, .827451, .827451),
  860     'lightpink': Color(1, .713725, .756863),
  861     'lightsalmon': Color(1, .627451, .478431),
  862     'lightseagreen': Color(.12549, .698039, .666667),
  863     'lightskyblue': Color(.529412, .807843, .980392),
  864     'lightslategray': Color(.466667, .533333, .6),
  865     'lightslategrey': Color(.466667, .533333, .6),
  866     'lightsteelblue': Color(.690196, .768627, .870588),
  867     'lightyellow': Color(1, 1, .878431),
  868     'lime': Color(0, 1, 0),
  869     'limegreen': Color(.196078, .803922, .196078),
  870     'linen': Color(.980392, .941176, .901961),
  871     'magenta': Color(1, 0, 1),
  872     'maroon': Color(.501961, 0, 0),
  873     'mediumaquamarine': Color(.4, .803922, .666667),
  874     'mediumblue': Color(0, 0, .803922),
  875     'mediumorchid': Color(.729412, .333333, .827451),
  876     'mediumpurple': Color(.576471, .439216, .858824),
  877     'mediumseagreen': Color(.235294, .701961, .443137),
  878     'mediumslateblue': Color(.482353, .407843, .933333),
  879     'mediumspringgreen': Color(0, .980392, .603922),
  880     'mediumturquoise': Color(.282353, .819608, .8),
  881     'mediumvioletred': Color(.780392, .082353, .521569),
  882     'menu': Color(212, 208, 200),
  883     'menutext': Color(0, 0, 0),
  884     'midnightblue': Color(.098039, .098039, .439216),
  885     'mintcream': Color(.960784, 1, .980392),
  886     'mistyrose': Color(1, .894118, .882353),
  887     'moccasin': Color(1, .894118, .709804),
  888     'navajowhite': Color(1, .870588, .678431),
  889     'navy': Color(0, 0, .501961),
  890     'oldlace': Color(.992157, .960784, .901961),
  891     'olive': Color(.501961, .501961, 0),
  892     'olivedrab': Color(.419608, .556863, .137255),
  893     'orange': Color(1, .647059, 0),
  894     'orangered': Color(1, .270588, 0),
  895     'orchid': Color(.854902, .439216, .839216),
  896     'palegoldenrod': Color(.933333, .909804, .666667),
  897     'palegreen': Color(.596078, .984314, .596078),
  898     'paleturquoise': Color(.686275, .933333, .933333),
  899     'palevioletred': Color(.858824, .439216, .576471),
  900     'papayawhip': Color(1, .937255, .835294),
  901     'peachpuff': Color(1, .854902, .72549),
  902     'peru': Color(.803922, .521569, .247059),
  903     'pink': Color(1, .752941, .796078),
  904     'plum': Color(.866667, .627451, .866667),
  905     'powderblue': Color(.690196, .878431, .901961),
  906     'purple': Color(.501961, 0, .501961),
  907     'red': Color(1, 0, 0),
  908     'rosybrown': Color(.737255, .560784, .560784),
  909     'royalblue': Color(.254902, .411765, .882353),
  910     'saddlebrown': Color(.545098, .270588, .07451),
  911     'salmon': Color(.980392, .501961, .447059),
  912     'sandybrown': Color(.956863, .643137, .376471),
  913     'scrollbar': Color(212, 208, 200),
  914     'seagreen': Color(.180392, .545098, .341176),
  915     'seashell': Color(1, .960784, .933333),
  916     'sienna': Color(.627451, .321569, .176471),
  917     'silver': Color(.752941, .752941, .752941),
  918     'skyblue': Color(.529412, .807843, .921569),
  919     'slateblue': Color(.415686, .352941, .803922),
  920     'slategray': Color(.439216, .501961, .564706),
  921     'slategrey': Color(.439216, .501961, .564706),
  922     'snow': Color(1, .980392, .980392),
  923     'springgreen': Color(0, 1, .498039),
  924     'steelblue': Color(.27451, .509804, .705882),
  925     'tan': Color(.823529, .705882, .54902),
  926     'teal': Color(0, .501961, .501961),
  927     'thistle': Color(.847059, .74902, .847059),
  928     'threeddarkshadow': Color(64, 64, 64),
  929     'threedface': Color(212, 208, 200),
  930     'threedhighlight': Color(255, 255, 255),
  931     'threedlightshadow': Color(212, 208, 200),
  932     'threedshadow': Color(128, 128, 128),
  933     'tomato': Color(1, .388235, .278431),
  934     'turquoise': Color(.25098, .878431, .815686),
  935     'violet': Color(.933333, .509804, .933333),
  936     'wheat': Color(.960784, .870588, .701961),
  937     'white': Color(1, 1, 1),
  938     'whitesmoke': Color(.960784, .960784, .960784),
  939     'window': Color(255, 255, 255),
  940     'windowframe': Color(0, 0, 0),
  941     'windowtext': Color(0, 0, 0),
  942     'yellow': Color(1, 1, 0),
  943     'yellowgreen': Color(.603922, .803922, .196078)
  944 }
  945 
  946 
  947 def get_default_asian_font():
  948     lower_font_list = []
  949     upper_font_list = []
  950 
  951     font_dict = copy(reportlab.pdfbase._cidfontdata.defaultUnicodeEncodings)
  952     fonts = font_dict.keys()
  953 
  954     for font in fonts:
  955         upper_font_list.append(font)
  956         lower_font_list.append(font.lower())
  957     default_asian_font = {lower_font_list[i]: upper_font_list[i] for i in range(len(lower_font_list))}
  958 
  959     return default_asian_font
  960 
  961 
  962 def set_asian_fonts(fontname):
  963     font_dict = copy(reportlab.pdfbase._cidfontdata.defaultUnicodeEncodings)
  964     fonts = font_dict.keys()
  965     if fontname in fonts:
  966         pdfmetrics.registerFont(UnicodeCIDFont(fontname))
  967 
  968 
  969 def detect_language(name):
  970     asian_language_list = xhtml2pdf.default.DEFAULT_LANGUAGE_LIST
  971     if name in asian_language_list:
  972         return name
  973 
  974 
  975 def arabic_format(text, language):
  976     # Note: right now all of the languages are treated the same way.
  977     # But maybe in the future we have to for example implement something
  978     # for "hebrew" that isn't used in "arabic"
  979     if detect_language(language) in ('arabic', 'hebrew', 'persian', 'urdu', 'pashto', 'sindhi'):
  980         ar = arabic_reshaper.reshape(text)
  981         return get_display(ar)
  982     else:
  983         return None
  984 
  985 
  986 def frag_text_language_check(context, frag_text):
  987     if hasattr(context, 'language'):
  988         language = context.__getattribute__('language')
  989         detect_language_result = arabic_format(frag_text, language)
  990         if detect_language_result:
  991             return detect_language_result