#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ****************************************************************************
# * Software: FPDF for python                                                *
# * Version:  1.7.1a                                                          *
# * Date:     2010-09-10                                                     *
# * Last update: 2019-01-01                                                  *
# * License:  LGPL v3.0                                                      *
# *                                                                          *
# * Original Author (PHP):  Olivier PLATHEY 2004-12-31                       *
# * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05               *
# * Maintainer:  Mariano Reingart (reingart@gmail.com) et al since 2008 est. *
# * NOTE: 'I' and 'D' destinations are disabled, and simply print to STDOUT  *
# * Updated only for PYMETRICK 01/01/2019 by javtamvi@pymetrick.org          * 
# ****************************************************************************

from __future__ import division

import sys
# sin bytecode
sys.dont_write_bytecode = True

from datetime import datetime
from functools import wraps
import math
import errno
import os, zlib, struct, re, tempfile, struct
import logging
import logging.handlers

'''
DEBUG - debug message
INFO - info message
WARNING - warn message
ERROR - error message
CRITICAL - critical message
'''
LOG_LEVEL_VALUE = {'DEBUG':10,'INFO':20,'WARNING':30,'ERROR':40,'CRITICAL':50}
if str(os.environ.get('PYMETRICK_LOG_LEVEL',None)).upper() in LOG_LEVEL_VALUE:
    LOG_LEVEL = LOG_LEVEL_VALUE[str(os.environ.get('PYMETRICK_LOG_LEVEL')).upper()]
else:
    LOG_LEVEL = 30
LOG_FILENAME = '-'.join([os.path.abspath(__file__).split(os.sep)[len(os.path.abspath(__file__).split(os.sep))-1],])[:-3]
LOG = logging.getLogger(LOG_FILENAME)

if 'LD_LIBRARY_PATH' in list(os.environ.keys()):
    # CGI environment
    sys.stdout = sys.stderr
    logging.basicConfig(stream = sys.stderr, level=LOG_LEVEL, format='%(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
else:
    # not CGI environment
    logging.basicConfig(stream=sys.stderr)
    hdlr = logging.handlers.RotatingFileHandler(filename=LOG_FILENAME+'.log',mode='a', encoding='utf-8', maxBytes=1048576, backupCount=3)
    formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
    hdlr.setFormatter(formatter)
    LOG.addHandler(hdlr)
    LOG.setLevel(LOG_LEVEL)

from pymetrick.ttfonts import TTFontFile
from pymetrick.fonts import fpdf_charwidths
from pymetrick.php import substr, sprintf, print_r, UTF8ToUTF16BE, UTF8StringToArray
from pymetrick.py3k import PY3K, pickle, urlopen, Image, basestring, unicode, exception, b, hashpath

# Global variables
FPDF_VERSION = '1.7.1a'
FPDF_FONT_DIR = os.path.join(os.path.dirname(__file__),'font')
FPDF_CACHE_MODE = 0 # 0 - in same foder, 1 - none, 2 - hash
FPDF_CACHE_DIR = None
SYSTEM_TTFONTS = None


def set_global(var, val):
    globals()[var] = val


class FPDF(object):
    "PDF Generation class"

    def __init__(self, orientation='P',unit='mm',format='A4'):
        try:
            # Some checks
            self._dochecks()
            # Initialization of properties
            self.offsets={}                 # array of object offsets
            self.page=0                     # current page number
            self.n=2                        # current object number
            self.buffer=''                  # buffer holding in-memory PDF
            self.pages={}                   # array containing pages
            self.orientation_changes={}     # array indicating orientation changes
            self.state=0                    # current document state
            self.fonts={}                   # array of used fonts
            self.font_files={}              # array of font files
            self.diffs={}                   # array of encoding differences
            self.images={}                  # array of used images
            self.page_links={}              # array of links in pages
            self.links={}                   # array of internal links
            self.in_footer=0                # flag set when processing footer
            self.lastw=0
            self.lasth=0                    # height of last cell printed
            self.font_family=''             # current font family
            self.font_style=''              # current font style
            self.font_size_pt=12            # current font size in points
            self.underline=0                # underlining flag
            self.draw_color='0 G'
            self.fill_color='0 g'
            self.text_color='0 g'
            self.color_flag=0               # indicates whether fill and text colors are different
            self.ws=0                       # word spacing
            self.angle=0
            # Standard fonts
            self.core_fonts={'courier':'Courier','courierB':'Courier-Bold','courierI':'Courier-Oblique','courierBI':'Courier-BoldOblique',
                'helvetica':'Helvetica','helveticaB':'Helvetica-Bold','helveticaI':'Helvetica-Oblique','helveticaBI':'Helvetica-BoldOblique',
                'times':'Times-Roman','timesB':'Times-Bold','timesI':'Times-Italic','timesBI':'Times-BoldItalic',
                'symbol':'Symbol','zapfdingbats':'ZapfDingbats'}
            # Scale factor
            if(unit=='pt'):
                self.k=1
            elif(unit=='mm'):
                self.k=72/25.4
            elif(unit=='cm'):
                self.k=72/2.54
            elif(unit=='in'):
                self.k=72.
            else:
                self.error('Incorrect unit: '+unit)
            # Page format
            if(isinstance(format,basestring)):
                format=format.lower()
                if(format=='a3'):
                    format=(841.89,1190.55)
                elif(format=='a4'):
                    format=(595.28,841.89)
                elif(format=='a5'):
                    format=(420.94,595.28)
                elif(format=='letter'):
                    format=(612,792)
                elif(format=='legal'):
                    format=(612,1008)
                else:
                    self.error('Unknown page format: '+format)
                self.fw_pt=format[0]
                self.fh_pt=format[1]
            else:
                self.fw_pt=format[0]*self.k
                self.fh_pt=format[1]*self.k
            self.fw=self.fw_pt/self.k
            self.fh=self.fh_pt/self.k
            # Page orientation
            orientation=orientation.lower()
            if(orientation=='p' or orientation=='portrait'):
                self.def_orientation='P'
                self.w_pt=self.fw_pt
                self.h_pt=self.fh_pt
            elif(orientation=='l' or orientation=='landscape'):
                self.def_orientation='L'
                self.w_pt=self.fh_pt
                self.h_pt=self.fw_pt
            else:
                self.error('Incorrect orientation: '+orientation)
            self.cur_orientation=self.def_orientation
            self.w=self.w_pt/self.k
            self.h=self.h_pt/self.k
            # Page margins (1 cm)
            margin=28.35/self.k
            self.set_margins(margin,margin)
            # Interior cell margin (1 mm)
            self.c_margin=margin/10.0
            # line width (0.2 mm)
            self.line_width=.567/self.k
            # Automatic page break
            self.set_auto_page_break(1,2*margin)
            # Full width display mode
            self.set_display_mode('fullwidth')
            # Enable compression
            self.set_compression(1)
            # Set default PDF version number
            self.pdf_version='1.3'
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    def __call__(self, orientation='P',unit='mm', format='A4'):
        """Redefine the same object as if it were new"""
        try:
            # Scale factor
            if(unit=='pt'):
                self.k=1
            elif(unit=='mm'):
                self.k=72/25.4
            elif(unit=='cm'):
                self.k=72/2.54
            elif(unit=='in'):
                self.k=72.
            else:
                self.error('Incorrect unit: '+unit)
            # Page format
            if(isinstance(format,basestring)):
                format=format.lower()
                if(format=='a3'):
                    format=(841.89,1190.55)
                elif(format=='a4'):
                    format=(595.28,841.89)
                elif(format=='a5'):
                    format=(420.94,595.28)
                elif(format=='letter'):
                    format=(612,792)
                elif(format=='legal'):
                    format=(612,1008)
                else:
                    self.error('Unknown page format: '+format)
                self.fw_pt=format[0]
                self.fh_pt=format[1]
            else:
                self.fw_pt=format[0]*self.k
                self.fh_pt=format[1]*self.k
            self.fw=self.fw_pt/self.k
            self.fh=self.fh_pt/self.k
            # Page orientation
            if self.def_orientation=='P':
                self.w_pt=self.fw_pt
                self.h_pt=self.fh_pt
            elif self.def_orientation=='L':
                self.w_pt=self.fh_pt
                self.h_pt=self.fw_pt
            else:
                self.error('Incorrect orientation: '+orientation)
            self.w=self.w_pt/self.k
            self.h=self.h_pt/self.k
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            

    def check_page(fn):
        "Decorator to protect drawing methods"
        @wraps(fn)
        def wrapper(self, *args, **kwargs):
            if not self.page and not kwargs.get('split_only'):
                self.error("No page open, you need to call add_page() first")
            else:
                return fn(self, *args, **kwargs)
        return wrapper

    def set_margins(self, left,top,right=-1):
        "Set left, top and right margins"
        self.l_margin=left
        self.t_margin=top
        if(right==-1):
            right=left
        self.r_margin=right

    def set_left_margin(self, margin):
        "Set left margin"
        self.l_margin=margin
        if(self.page>0 and self.x<margin):
            self.x=margin

    def set_top_margin(self, margin):
        "Set top margin"
        self.t_margin=margin

    def set_right_margin(self, margin):
        "Set right margin"
        self.r_margin=margin

    def set_auto_page_break(self, auto,margin=0):
        "Set auto page break mode and triggering margin"
        self.auto_page_break=auto
        self.b_margin=margin
        self.page_break_trigger=self.h-margin

    def set_display_mode(self, zoom,layout='continuous'):
        """Set display mode in viewer

        The "zoom" argument may be 'fullpage', 'fullwidth', 'real',
        'default', or a number, interpreted as a percentage."""

        if(zoom=='fullpage' or zoom=='fullwidth' or zoom=='real' or zoom=='default' or not isinstance(zoom,basestring)):
            self.zoom_mode=zoom
        else:
            self.error('Incorrect zoom display mode: '+zoom)
        if(layout=='single' or layout=='continuous' or layout=='two' or layout=='default'):
            self.layout_mode=layout
        else:
            self.error('Incorrect layout display mode: '+layout)

    def set_compression(self, compress):
        "Set page compression"
        self.compress=compress

    def set_title(self, title):
        "Title of document"
        self.title=title

    def set_subject(self, subject):
        "Subject of document"
        self.subject=subject

    def set_author(self, author):
        "Author of document"
        self.author=author

    def set_keywords(self, keywords):
        "Keywords of document"
        self.keywords=keywords

    def set_creator(self, creator):
        "Creator of document"
        self.creator=creator

    def alias_nb_pages(self, alias='{nb}'):
        "Define an alias for total number of pages"
        self.str_alias_nb_pages=alias
        return alias

    def error(self, msg):
        "Fatal error"
        raise RuntimeError('FPDF error: '+msg)

    def open(self):
        "Begin document"
        self.state=1

    def close(self):
        "Terminate document"
        if(self.state==3):
            return
        if(self.page==0):
            self.add_page()
        #Page footer
        self.in_footer=1
        self.footer()
        self.in_footer=0
        #close page
        self._endpage()
        #close document
        self._enddoc()

    def add_page(self, orientation=''):
        "Start a new page"
        try:
            if(self.state==0):
                self.open()
            family=self.font_family
            if self.underline:
                style = self.font_style + 'U'
            else:
                style = self.font_style
            size=self.font_size_pt
            lw=self.line_width
            dc=self.draw_color
            fc=self.fill_color
            tc=self.text_color
            cf=self.color_flag
            if(self.page>0):
                #Page footer
                self.in_footer=1
                self.footer()
                self.in_footer=0
                #close page
                self._endpage()
            #Start new page
            self._beginpage(orientation)
            #Set line cap style to square
            self._out('2 J')
            #Set line width
            self.line_width=lw
            self._out(sprintf('%.2f w',lw*self.k))
            #Set font
            if(family):
                self.set_font(family,style,size)
            #Set colors
            self.draw_color=dc
            if(dc!='0 G'):
                self._out(dc)
            self.fill_color=fc
            if(fc!='0 g'):
                self._out(fc)
            self.text_color=tc
            self.color_flag=cf
            #Page header
            self.header()
            #Restore line width
            if(self.line_width!=lw):
                self.line_width=lw
                self._out(sprintf('%.2f w',lw*self.k))
            #Restore font
            if(family):
                self.set_font(family,style,size)
            #Restore colors
            if(self.draw_color!=dc):
                self.draw_color=dc
                self._out(dc)
            if(self.fill_color!=fc):
                self.fill_color=fc
                self._out(fc)
            self.text_color=tc
            self.color_flag=cf
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def header(self):
        "Header to be implemented in your own inherited class"
        pass

    def footer(self):
        "Footer to be implemented in your own inherited class"
        pass

    def page_no(self):
        "Get current page number"
        return self.page

    def set_draw_color(self, r,g=-1,b=-1):
        "Set color for all stroking operations"
        if((r==0 and g==0 and b==0) or g==-1):
            self.draw_color=sprintf('%.3f G',r/255.0)
        else:
            self.draw_color=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0)
        if(self.page>0):
            self._out(self.draw_color)

    def set_fill_color(self,r,g=-1,b=-1):
        "Set color for all filling operations"
        if((r==0 and g==0 and b==0) or g==-1):
            self.fill_color=sprintf('%.3f g',r/255.0)
        else:
            self.fill_color=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
        self.color_flag=(self.fill_color!=self.text_color)
        if(self.page>0):
            self._out(self.fill_color)

    def set_text_color(self, r,g=-1,b=-1):
        "Set color for text"
        if((r==0 and g==0 and b==0) or g==-1):
            self.text_color=sprintf('%.3f g',r/255.0)
        else:
            self.text_color=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
        self.color_flag=(self.fill_color!=self.text_color)

    def get_string_width(self, s):
        "Get width of a string in the current font"
        try:
            s = self.normalize_text(s)
            cw=self.current_font['cw']
            w=0
            l=len(s)
            if self.unifontsubset:
                for char in s:
                    char = ord(char)
                    if len(cw) > char:
                        w += cw[char] # ord(cw[2*char])<<8 + ord(cw[2*char+1])
                    #elif (char>0 and char<128 and isset($cw[chr($char)])) { $w += $cw[chr($char)]; }
                    elif (self.current_font['desc']['MissingWidth']) :
                        w += self.current_font['desc']['MissingWidth']
                    #elif (isset($this->CurrentFont['MissingWidth'])) { $w += $this->CurrentFont['MissingWidth']; }
                    else:
                        w += 500
            else:
                for i in range(0, l):
                    w += cw.get(s[i],0)
            return w*self.font_size/1000.0
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def set_line_width(self, width):
        "Set line width"
        self.line_width=width
        if(self.page>0):
            self._out(sprintf('%.2f w',width*self.k))

    @check_page
    def line(self, x1,y1,x2,y2):
        "Draw a line"
        self._out(sprintf('%.2f %.2f m %.2f %.2f l S',x1*self.k,(self.h-y1)*self.k,x2*self.k,(self.h-y2)*self.k))

    def _set_dash(self, dash_length=False, space_length=False):
        if(dash_length and space_length):
            s = sprintf('[%.3f %.3f] 0 d', dash_length*self.k, space_length*self.k)
        else:
            s = '[] 0 d'
        self._out(s)

    @check_page
    def dashed_line(self, x1,y1,x2,y2, dash_length=1, space_length=1):
        """Draw a dashed line. Same interface as line() except:
           - dash_length: Length of the dash
           - space_length: Length of the space between dashes"""
        self._set_dash(dash_length, space_length)
        self.line(x1, y1, x2, y2)
        self._set_dash()

    @check_page
    def rect(self, x,y,w,h,style=''):
        "Draw a rectangle"
        if(style=='F'):
            op='f'
        elif(style=='FD' or style=='DF'):
            op='B'
        else:
            op='S'
        self._out(sprintf('%.2f %.2f %.2f %.2f re %s',x*self.k,(self.h-y)*self.k,w*self.k,-h*self.k,op))

    @check_page
    def rounded_rect(self,x, y, w, h, r,corners='1234',style=''):
        "Draw a rounded rectangle"
        if(style=='F'):
            op='f'
        elif(style=='FD' or style=='DF'):
            op='B'
        else:
            op='S'

        myArc = 4/3 * (math.sqrt(2) - 1);
        self._out(sprintf('%.2f %.2f m',(x+r)*self.k,(self.h-y)*self.k ))

        xc = x+w-r;
        yc = y+r;
        self._out(sprintf('%.2f %.2f l', xc*self.k,(self.h-y)*self.k ))
        if (corners.find('2') < 0):
            self._out(sprintf('%.2f %.2f l', (x+w)*self.k,(self.h-y)*self.k ))
        else:
            self._arc(xc + r*myArc, yc - r, xc + r, yc - r*myArc, xc + r, yc)

        xc = x+w-r;
        yc = y+h-r;
        self._out(sprintf('%.2f %.2f l',(x+w)*self.k,(self.h-yc)*self.k));
        if (corners.find('3') < 0):
            self._out(sprintf('%.2f %.2f l',(x+w)*self.k,(self.h-(y+h))*self.k));
        else:
            self._arc(xc + r, yc + r*myArc, xc + r*myArc, yc + r, xc, yc + r)

        xc = x+r;
        yc = y+h-r;
        self._out(sprintf('%.2f %.2f l',xc*self.k,(self.h-(y+h))*self.k));
        if (corners.find('4') < 0):
            self._out(sprintf('%.2f %.2f l',(x)*self.k,(self.h-(y+h))*self.k))
        else:
            self._arc(xc - r*myArc, yc + r, xc - r, yc + r*myArc, xc - r, yc)

        xc = x+r ;
        yc = y+r;
        self._out(sprintf('%.2f %.2f l',(x)*self.k,(self.h-yc)*self.k ))
        if (corners.find('1') < 0):
            self._out(sprintf('%.2f %.2f l',(x)*self.k,(self.hp-y)*self.k ))
            self._out(sprintf('%.2f %.2f l',(x+r)*self.k,(self.h-y)*self.k ))
        else:
            self._arc(xc - r, yc - r*myArc, xc - r*myArc, yc - r, xc, yc - r)
        self._out(op);


    def _arc(self, x1, y1, x2, y2, x3, y3):
        h = self.h
        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c', x1*self.k, (h-y1)*self.k,
            x2*self.k, (h-y2)*self.k, x3*self.k, (h-y3)*self.k))


    @check_page
    def ellipse(self, x,y,w,h,style=''):
        "Draw a ellipse"
        if(style=='F'):
            op='f'
        elif(style=='FD' or style=='DF'):
            op='B'
        else:
            op='S'

        cx = x + w/2.0
        cy = y + h/2.0
        rx = w/2.0
        ry = h/2.0

        lx = 4.0/3.0*(math.sqrt(2)-1)*rx
        ly = 4.0/3.0*(math.sqrt(2)-1)*ry

        self._out(sprintf('%.2f %.2f m %.2f %.2f %.2f %.2f %.2f %.2f c',
            (cx+rx)*self.k, (self.h-cy)*self.k,
            (cx+rx)*self.k, (self.h-(cy-ly))*self.k,
            (cx+lx)*self.k, (self.h-(cy-ry))*self.k,
            cx*self.k, (self.h-(cy-ry))*self.k))
        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c',
            (cx-lx)*self.k, (self.h-(cy-ry))*self.k,
            (cx-rx)*self.k, (self.h-(cy-ly))*self.k,
            (cx-rx)*self.k, (self.h-cy)*self.k))
        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c',
            (cx-rx)*self.k, (self.h-(cy+ly))*self.k,
            (cx-lx)*self.k, (self.h-(cy+ry))*self.k,
            cx*self.k, (self.h-(cy+ry))*self.k))
        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c %s',
            (cx+lx)*self.k, (self.h-(cy+ry))*self.k,
            (cx+rx)*self.k, (self.h-(cy+ly))*self.k,
            (cx+rx)*self.k, (self.h-cy)*self.k,
            op))

    def add_font(self, family, style='', fname='', uni=False):
        "Add a TrueType or Type1 font"
        try:
            family = family.lower()
            if (fname == ''):
                fname = family.replace(' ','') + style.lower() + '.pkl'
            if (family == 'arial'):
                family = 'helvetica'
            style = style.upper()
            if (style == 'IB'):
                style = 'BI'
            fontkey = family+style
            if fontkey in self.fonts:
                # Font already added!
                return
            if (uni):
                global SYSTEM_TTFONTS, FPDF_CACHE_MODE, FPDF_CACHE_DIR
                if os.path.exists(fname):
                    ttffilename = fname
                elif (FPDF_FONT_DIR and
                    os.path.exists(os.path.join(FPDF_FONT_DIR, fname))):
                    ttffilename = os.path.join(FPDF_FONT_DIR, fname)
                elif (SYSTEM_TTFONTS and
                    os.path.exists(os.path.join(SYSTEM_TTFONTS, fname))):
                    ttffilename = os.path.join(SYSTEM_TTFONTS, fname)
                else:
                    raise RuntimeError("TTF Font file not found: %s" % fname)
                name = ''
                if FPDF_CACHE_MODE == 0:
                    unifilename = os.path.splitext(ttffilename)[0] + '.pkl'
                elif FPDF_CACHE_MODE == 2:
                    unifilename = os.path.join(FPDF_CACHE_DIR, \
                        hashpath(ttffilename) + ".pkl")
                else:
                    unifilename = None
                if unifilename and os.path.exists(unifilename):
                    fh = open(unifilename, "rb")
                    try:
                        font_dict = pickle.load(fh)
                    finally:
                        fh.close()
                else:
                    ttf = TTFontFile()
                    ttf.getMetrics(ttffilename)
                    desc = {
                        'Ascent': int(round(ttf.ascent, 0)),
                        'Descent': int(round(ttf.descent, 0)),
                        'CapHeight': int(round(ttf.capHeight, 0)),
                        'Flags': ttf.flags,
                        'FontBBox': "[%s %s %s %s]" % (
                            int(round(ttf.bbox[0], 0)),
                            int(round(ttf.bbox[1], 0)),
                            int(round(ttf.bbox[2], 0)),
                            int(round(ttf.bbox[3], 0))),
                        'ItalicAngle': int(ttf.italicAngle),
                        'StemV': int(round(ttf.stemV, 0)),
                        'MissingWidth': int(round(ttf.defaultWidth, 0)),
                        }
                    # Generate metrics .pkl file
                    font_dict = {
                        'name': re.sub('[ ()]', '', ttf.fullName),
                        'type': 'TTF',
                        'desc': desc,
                        'up': round(ttf.underlinePosition),
                        'ut': round(ttf.underlineThickness),
                        'ttffile': ttffilename,
                        'fontkey': fontkey,
                        'originalsize': os.stat(ttffilename).st_size,
                        'cw': ttf.charWidths,
                        }
                    if unifilename:
                        try:
                            fh = open(unifilename, "wb")
                            pickle.dump(font_dict, fh)
                            fh.close()
                        except IOError:
                            if not exception().errno == errno.EACCES:
                                raise  # Not a permission error.
                    del ttf
                if hasattr(self,'str_alias_nb_pages'):
                    sbarr = list(range(0,57))   # include numbers in the subset!
                else:
                    sbarr = list(range(0,32))
                self.fonts[fontkey] = {
                    'i': len(self.fonts)+1, 'type': font_dict['type'],
                    'name': font_dict['name'], 'desc': font_dict['desc'],
                    'up': font_dict['up'], 'ut': font_dict['ut'],
                    'cw': font_dict['cw'],
                    'ttffile': font_dict['ttffile'], 'fontkey': fontkey,
                    'subset': sbarr, 'unifilename': unifilename,
                    }
                self.font_files[fontkey] = {'length1': font_dict['originalsize'],
                                            'type': "TTF", 'ttffile': ttffilename}
                self.font_files[fname] = {'type': "TTF"}
            else:
                fontfile = open(fname)
                try:
                    font_dict = pickle.load(fontfile)
                finally:
                    fontfile.close()
                self.fonts[fontkey] = {'i': len(self.fonts)+1}
                self.fonts[fontkey].update(font_dict)
                if (diff):
                    #Search existing encodings
                    d = 0
                    nb = len(self.diffs)
                    for i in range(1, nb+1):
                        if(self.diffs[i] == diff):
                            d = i
                            break
                    if (d == 0):
                        d = nb + 1
                        self.diffs[d] = diff
                    self.fonts[fontkey]['diff'] = d
                filename = font_dict.get('filename')
                if (filename):
                    if (type == 'TrueType'):
                        self.font_files[filename]={'length1': originalsize}
                    else:
                        self.font_files[filename]={'length1': size1,
                                                   'length2': size2}
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def set_font(self, family,style='',size=0):
        "Select a font; size given in points"
        try:
            family=family.lower()
            if(family==''):
                family=self.font_family
            if(family=='arial'):
                family='helvetica'
            elif(family=='symbol' or family=='zapfdingbats'):
                style=''
            style=style.upper()
            if('U' in style):
                self.underline=1
                style=style.replace('U','')
            else:
                self.underline=0
            if(style=='IB'):
                style='BI'
            if(size==0):
                size=self.font_size_pt
            #Test if font is already selected
            if(self.font_family==family and self.font_style==style and self.font_size_pt==size):
                return
            #Test if used for the first time
            fontkey=family+style
            if fontkey not in self.fonts:
                #Check if one of the standard fonts
                if fontkey in self.core_fonts:
                    if fontkey not in fpdf_charwidths:
                        #Load metric file
                        name=os.path.join(FPDF_FONT_DIR,family)
                        if(family=='times' or family=='helvetica'):
                            name+=style.lower()
                        exec(compile(open(name+'.font').read(), name+'.font', 'exec'))
                        if fontkey not in fpdf_charwidths:
                            self.error('Could not include font metric file for'+fontkey)
                    i=len(self.fonts)+1
                    self.fonts[fontkey]={'i':i,'type':'core','name':self.core_fonts[fontkey],'up':-100,'ut':50,'cw':fpdf_charwidths[fontkey]}
                else:
                    self.error('Undefined font: '+family+' '+style)
            #Select it
            self.font_family=family
            self.font_style=style
            self.font_size_pt=size
            self.font_size=size/self.k
            self.current_font=self.fonts[fontkey]
            self.unifontsubset = (self.fonts[fontkey]['type'] == 'TTF')
            if(self.page>0):
                self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt))
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def set_font_size(self, size):
        "Set font size in points"
        try:
            if(self.font_size_pt==size):
                return
            self.font_size_pt=size
            self.font_size=size/self.k
            if(self.page>0):
                self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt))
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def add_link(self):
        "Create a new internal link"
        n=len(self.links)+1
        self.links[n]=(0,0)
        return n

    def set_link(self, link,y=0,page=-1):
        "Set destination of internal link"
        if(y==-1):
            y=self.y
        if(page==-1):
            page=self.page
        self.links[link]=[page,y]

    def link(self, x,y,w,h,link):
        "Put a link on the page"
        if not self.page in self.page_links:
            self.page_links[self.page] = []
        self.page_links[self.page] += [(x*self.k,self.h_pt-y*self.k,w*self.k,h*self.k,link),]

    @check_page
    def text(self, x, y, txt=''):
        "Output a string"
        try:
            txt = self.normalize_text(txt)
            if (self.unifontsubset):
                txt2 = self._escape(UTF8ToUTF16BE(txt, False))
                for uni in UTF8StringToArray(txt):
                    self.current_font['subset'].append(uni)
            else:
                txt2 = self._escape(txt)
            s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*self.k,(self.h-y)*self.k, txt2)
            if(self.underline and txt!=''):
                s+=' '+self._dounderline(x,y,txt)
            if(self.color_flag):
                s='q '+self.text_color+' '+s+' Q'
            self._out(s)
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    @check_page
    def rotate(self, angle, x=None, y=None):
        try:
            if x is None:
                x = self.x
            if y is None:
                y = self.y;
            if self.angle!=0:
                self._out('Q')
            self.angle = angle
            if angle!=0:
                angle *= math.pi/180;
                c = math.cos(angle);
                s = math.sin(angle);
                cx = x*self.k;
                cy = (self.h-y)*self.k
                s = sprintf('q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm',c,s,-s,c,cx,cy,-cx,-cy)
                self._out(s)
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def accept_page_break(self):
        "Accept automatic page break or not"
        return self.auto_page_break

    @check_page
    def cell(self, w,h=0,txt='',border=0,ln=0,align='',fill=0,link=''):
        "Output a cell"
        try:
            txt = self.normalize_text(txt)
            k=self.k
            if(self.y+h>self.page_break_trigger and not self.in_footer and self.accept_page_break()):
                #Automatic page break
                x=self.x
                ws=self.ws
                if(ws>0):
                    self.ws=0
                    self._out('0 Tw')
                self.add_page(self.cur_orientation)
                self.x=x
                if(ws>0):
                    self.ws=ws
                    self._out(sprintf('%.3f Tw',ws*k))
            if(w==0):
                w=self.w-self.r_margin-self.x
            s=''
            if(fill==1 or border==1):
                if(fill==1):
                    if border==1:
                        op='B'
                    else:
                        op='f'
                else:
                    op='S'
                s=sprintf('%.2f %.2f %.2f %.2f re %s ',self.x*k,(self.h-self.y)*k,w*k,-h*k,op)
            if(isinstance(border,basestring)):
                x=self.x
                y=self.y
                if('L' in border):
                    s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-y)*k,x*k,(self.h-(y+h))*k)
                if('T' in border):
                    s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-y)*k,(x+w)*k,(self.h-y)*k)
                if('R' in border):
                    s+=sprintf('%.2f %.2f m %.2f %.2f l S ',(x+w)*k,(self.h-y)*k,(x+w)*k,(self.h-(y+h))*k)
                if('B' in border):
                    s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-(y+h))*k,(x+w)*k,(self.h-(y+h))*k)
            if(txt!=''):
                if(align=='R'):
                    dx=w-self.c_margin-self.get_string_width(txt)
                elif(align=='C'):
                    dx=(w-self.get_string_width(txt))/2.0
                else:
                    dx=self.c_margin
                if(self.color_flag):
                    s+='q '+self.text_color+' '

                # If multibyte, Tw has no effect - do word spacing using an adjustment before each space
                if (self.ws and self.unifontsubset):
                    for uni in UTF8StringToArray(txt):
                        self.current_font['subset'].append(uni)
                    space = self._escape(UTF8ToUTF16BE(' ', False))
                    s += sprintf('BT 0 Tw %.2F %.2F Td [',(self.x + dx) * k,(self.h - (self.y + 0.5*h+ 0.3 * self.font_size)) * k)
                    t = txt.split(' ')
                    numt = len(t)
                    for i in range(numt):
                        tx = t[i]
                        tx = '(' + self._escape(UTF8ToUTF16BE(tx, False)) + ')'
                        s += sprintf('%s ', tx);
                        if ((i+1)<numt):
                            adj = -(self.ws * self.k) * 1000 / self.font_size_pt
                            s += sprintf('%d(%s) ', adj, space)
                    s += '] TJ'
                    s += ' ET'
                else:
                    if (self.unifontsubset):
                        txt2 = self._escape(UTF8ToUTF16BE(txt, False))
                        for uni in UTF8StringToArray(txt):
                            self.current_font['subset'].append(uni)
                    else:
                        txt2 = self._escape(txt)
                    s += sprintf('BT %.2f %.2f Td (%s) Tj ET',(self.x+dx)*k,(self.h-(self.y+.5*h+.3*self.font_size))*k,txt2)

                if(self.underline):
                    s+=' '+self._dounderline(self.x+dx,self.y+.5*h+.3*self.font_size,txt)
                if(self.color_flag):
                    s+=' Q'
                if(link):
                    self.link(self.x+dx,self.y+.5*h-.5*self.font_size,self.get_string_width(txt),self.font_size,link)
            if(s):
                self._out(s)
            self.lasth=h
            if(ln>0):
                #Go to next line
                self.y+=h
                if(ln==1):
                    self.x=self.l_margin
            else:
                self.x+=w
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    @check_page
    def multi_cell(self, w, h, txt='', border=0, align='J', fill=0, split_only=False):
        "Output text with automatic or explicit line breaks"
        try:
            txt = self.normalize_text(txt)
            ret = [] # if split_only = True, returns splited text cells
            cw=self.current_font['cw']
            if(w==0):
                w=self.w-self.r_margin-self.x
            wmax=(w-2*self.c_margin)*1000.0/self.font_size
            s=txt.replace("\r",'')
            nb=len(s)
            if(nb>0 and s[nb-1]=="\n"):
                nb-=1
            b=0
            if(border):
                if(border==1):
                    border='LTRB'
                    b='LRT'
                    b2='LR'
                else:
                    b2=''
                    if('L' in border):
                        b2+='L'
                    if('R' in border):
                        b2+='R'
                    if ('T' in border):
                        b=b2+'T'
                    else:
                        b=b2
            sep=-1
            i=0
            j=0
            l=0
            ns=0
            nl=1
            while(i<nb):
                #Get next character
                c=s[i]
                if(c=="\n"):
                    #Explicit line break
                    if(self.ws>0):
                        self.ws=0
                        if not split_only:
                            self._out('0 Tw')
                    if not split_only:
                        self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
                    else:
                        ret.append(substr(s,j,i-j))
                    i+=1
                    sep=-1
                    j=i
                    l=0
                    ns=0
                    nl+=1
                    if(border and nl==2):
                        b=b2
                    continue
                if(c==' '):
                    sep=i
                    ls=l
                    ns+=1
                if self.unifontsubset:
                    l += self.get_string_width(c) / self.font_size*1000.0
                else:
                    l += cw.get(c,0)
                if(l>wmax):
                    #Automatic line break
                    if(sep==-1):
                        if(i==j):
                            i+=1
                        if(self.ws>0):
                            self.ws=0
                            if not split_only:
                                self._out('0 Tw')
                        if not split_only:
                            self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
                        else:
                            ret.append(substr(s,j,i-j))
                    else:
                        if(align=='J'):
                            if ns>1:
                                self.ws=(wmax-ls)/1000.0*self.font_size/(ns-1)
                            else:
                                self.ws=0
                            if not split_only:
                                self._out(sprintf('%.3f Tw',self.ws*self.k))
                        if not split_only:
                            self.cell(w,h,substr(s,j,sep-j),b,2,align,fill)
                        else:
                            ret.append(substr(s,j,sep-j))
                        i=sep+1
                    sep=-1
                    j=i
                    l=0
                    ns=0
                    nl+=1
                    if(border and nl==2):
                        b=b2
                else:
                    i+=1
            #Last chunk
            if(self.ws>0):
                self.ws=0
                if not split_only:
                    self._out('0 Tw')
            if(border and 'B' in border):
                b+='B'
            if not split_only:
                self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
                self.x=self.l_margin
            else:
                ret.append(substr(s,j,i-j))
            return ret
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    @check_page
    def write(self, h, txt='', link=''):
        "Output text in flowing mode"
        try:
            txt = self.normalize_text(txt)
            cw=self.current_font['cw']
            w=self.w-self.r_margin-self.x
            wmax=(w-2*self.c_margin)*1000.0/self.font_size
            s=txt.replace("\r",'')
            nb=len(s)
            sep=-1
            i=0
            j=0
            l=0
            nl=1
            while(i<nb):
                #Get next character
                c=s[i]
                if(c=="\n"):
                    #Explicit line break
                    self.cell(w,h,substr(s,j,i-j),0,2,'',0,link)
                    i+=1
                    sep=-1
                    j=i
                    l=0
                    if(nl==1):
                        self.x=self.l_margin
                        w=self.w-self.r_margin-self.x
                        wmax=(w-2*self.c_margin)*1000.0/self.font_size
                    nl+=1
                    continue
                if(c==' '):
                    sep=i
                if self.unifontsubset:
                    l += self.get_string_width(c) / self.font_size*1000.0
                else:
                    l += cw.get(c,0)
                if(l>wmax):
                    #Automatic line break
                    if(sep==-1):
                        if(self.x>self.l_margin):
                            #Move to next line
                            self.x=self.l_margin
                            self.y+=h
                            w=self.w-self.r_margin-self.x
                            wmax=(w-2*self.c_margin)*1000.0/self.font_size
                            i+=1
                            nl+=1
                            continue
                        if(i==j):
                            i+=1
                        self.cell(w,h,substr(s,j,i-j),0,2,'',0,link)
                    else:
                        self.cell(w,h,substr(s,j,sep-j),0,2,'',0,link)
                        i=sep+1
                    sep=-1
                    j=i
                    l=0
                    if(nl==1):
                        self.x=self.l_margin
                        w=self.w-self.r_margin-self.x
                        wmax=(w-2*self.c_margin)*1000.0/self.font_size
                    nl+=1
                else:
                    i+=1
            #Last chunk
            if(i!=j):
                self.cell(l/1000.0*self.font_size,h,substr(s,j),0,0,'',0,link)
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    @check_page
    def image(self, name, x=None, y=None, w=0, h=0, type='', link=''):
        "Put an image on the page"
        try:
            if not name in self.images:
                #First use of image, get info
                if(type==''):
                    pos=name.rfind('.')
                    if(not pos):
                        self.error('image file has no extension and no type was specified: '+name)
                    type=substr(name,pos+1)
                type=type.lower()
                if(type=='jpg' or type=='jpeg'):
                    info=self._parsejpg(name)
                elif(type=='png'):
                    info=self._parsepng(name)
                else:
                    #Allow for additional formats
                    #maybe the image is not showing the correct extension,
                    #but the header is OK,
                    succeed_parsing = False
                    #try all the parsing functions
                    parsing_functions = [self._parsejpg,self._parsepng,self._parsegif]
                    for pf in parsing_functions:
                        try:
                            info = pf(name)
                            succeed_parsing = True
                            break;
                        except:
                            pass
                    #last resource
                    if not succeed_parsing:
                        mtd='_parse'+type
                        if not hasattr(self,mtd):
                            self.error('Unsupported image type: '+type)
                        info=getattr(self, mtd)(name)
                    mtd='_parse'+type
                    if not hasattr(self,mtd):
                        self.error('Unsupported image type: '+type)
                    info=getattr(self, mtd)(name)
                info['i']=len(self.images)+1
                self.images[name]=info
            else:
                info=self.images[name]
            #Automatic width and height calculation if needed
            if(w==0 and h==0):
                #Put image at 72 dpi
                w=info['w']/self.k
                h=info['h']/self.k
            elif(w==0):
                w=h*info['w']/info['h']
            elif(h==0):
                h=w*info['h']/info['w']
            # Flowing mode
            if y is None:
                if (self.y + h > self.page_break_trigger and not self.in_footer and self.accept_page_break()):
                    #Automatic page break
                    x = self.x
                    self.add_page(self.cur_orientation)
                    self.x = x
                y = self.y
                self.y += h
            if x is None:
                x = self.x
            self._out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',w*self.k,h*self.k,x*self.k,(self.h-(y+h))*self.k,info['i']))
            if(link):
                self.link(x,y,w,h,link)
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    @check_page
    def ln(self, h=''):
        "Line Feed; default value is last cell height"
        try:
            self.x=self.l_margin
            if(isinstance(h, basestring)):
                self.y+=self.lasth
            else:
                self.y+=h
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    def get_x(self):
        "Get x position"
        return self.x

    def set_x(self, x):
        "Set x position"
        if(x>=0):
            self.x=x
        else:
            self.x=self.w+x

    def get_y(self):
        "Get y position"
        return self.y

    def set_y(self, y):
        "Set y position and reset x"
        self.x=self.l_margin
        if(y>=0):
            self.y=y
        else:
            self.y=self.h+y

    def set_xy(self, x,y):
        "Set x and y positions"
        self.set_y(y)
        self.set_x(x)

    def output(self, name='',dest=''):
        "Output PDF to some destination"
        try:
            #Finish document if necessary
            if(self.state<3):
                self.close()
            dest=dest.upper()
            if(dest==''):
                if(name==''):
                    name='doc.pdf'
                    dest='I'
                else:
                    dest='F'
            if dest=='I':
                print(self.buffer)
            elif dest=='D':
                print(self.buffer)
            elif dest=='F':
                #Save to local file
                f=open(name,'wb')
                if(not f):
                    self.error('Unable to create output file: '+name)
                if PY3K:
                    # manage binary data as latin9 until PEP461 or similar is implemented
                    f.write(self.buffer.encode("latin9"))
                else:
                    f.write(self.buffer)
                f.close()
            elif dest=='S':
                #Return as a string
                return self.buffer
            else:
                self.error('Incorrect output destination: '+dest)
            return ''
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    def normalize_text(self, txt):
        "Check that text input is in the correct format/encoding"
        # - for TTF unicode fonts: unicode object (utf8 encoding)
        # - for built-in fonts: string instances (latin 1 encoding)
        try:
            if self.unifontsubset and isinstance(txt, str) and not PY3K:
                txt = txt.decode('utf8')
            elif not self.unifontsubset and isinstance(txt, unicode) and not PY3K:
                txt = txt.encode('latin9')
            return txt
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))


    def _dochecks(self):
        #Check for locale-related bug
#        if(1.1==1):
#            self.error("Don\'t alter the locale before including class file");
        #Check for decimal separator
        try:
            if(sprintf('%.1f',1.0)!='1.0'):
                import locale
                locale.setlocale(locale.LC_NUMERIC,'C')
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))

    def _getfontpath(self):
        return FPDF_FONT_DIR+'/'

    def _putpages(self):
        nb=self.page
        if hasattr(self,'str_alias_nb_pages'):
            # Replace number of pages in fonts using subsets (unicode)
            alias = UTF8ToUTF16BE(self.str_alias_nb_pages, False)
            r = UTF8ToUTF16BE(str(nb), False)
            for n in range(1, nb+1):
                self.pages[n] = self.pages[n].replace(alias, r)
            # Now repeat for no pages in non-subset fonts
            for n in range(1,nb+1):
                self.pages[n]=self.pages[n].replace(self.str_alias_nb_pages,str(nb))
        if(self.def_orientation=='P'):
            w_pt=self.fw_pt
            h_pt=self.fh_pt
        else:
            w_pt=self.fh_pt
            h_pt=self.fw_pt
        if self.compress:
            filter='/Filter /FlateDecode '
        else:
            filter=''
        for n in range(1,nb+1):
            #Page
            self._newobj()
            self._out('<</Type /Page')
            self._out('/Parent 1 0 R')
            if n in self.orientation_changes:
                self._out(sprintf('/MediaBox [0 0 %.2f %.2f]',h_pt,w_pt))
            self._out('/Resources 2 0 R')
            if self.page_links and n in self.page_links:
                #Links
                annots='/Annots ['
                for pl in self.page_links[n]:
                    rect=sprintf('%.2f %.2f %.2f %.2f',pl[0],pl[1],pl[0]+pl[2],pl[1]-pl[3])
                    annots+='<</Type /Annot /Subtype /Link /Rect ['+rect+'] /Border [0 0 0] '
                    if(isinstance(pl[4],basestring)):
                        annots+='/A <</S /URI /URI '+self._textstring(pl[4])+'>>>>'
                    else:
                        l=self.links[pl[4]]
                        if l[0] in self.orientation_changes:
                            h=w_pt
                        else:
                            h=h_pt
                        annots+=sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',1+2*l[0],h-l[1]*self.k)
                self._out(annots+']')
            if(self.pdf_version>'1.3'):
                self._out('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>')
            self._out('/Contents '+str(self.n+1)+' 0 R>>')
            self._out('endobj')
            #Page content
            if self.compress:
                # manage binary data as latin9 until PEP461 or similar is implemented
                p = self.pages[n].encode("latin9") if PY3K else self.pages[n]
                p = zlib.compress(p)
            else:
                p = self.pages[n]
            self._newobj()
            self._out('<<'+filter+'/Length '+str(len(p))+'>>')
            self._putstream(p)
            self._out('endobj')
        #Pages root
        self.offsets[1]=len(self.buffer)
        self._out('1 0 obj')
        self._out('<</Type /Pages')
        kids='/Kids ['
        for i in range(0,nb):
            kids+=str(3+2*i)+' 0 R '
        self._out(kids+']')
        self._out('/Count '+str(nb))
        self._out(sprintf('/MediaBox [0 0 %.2f %.2f]',w_pt,h_pt))
        self._out('>>')
        self._out('endobj')

    def _putfonts(self):
        nf=self.n
        for diff in self.diffs:
            #Encodings
            self._newobj()
            self._out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+self.diffs[diff]+']>>')
            self._out('endobj')
        for name,info in self.font_files.items():
            if 'type' in info and info['type'] != 'TTF':
                #Font file embedding
                self._newobj()
                self.font_files[name]['n']=self.n
                font=''
                f=open(self._getfontpath()+name,'rb',1)
                if(not f):
                    self.error('Font file not found')
                font=f.read()
                f.close()
                compressed=(substr(name,-2)=='.z')
                if(not compressed and 'length2' in info):
                    header=(ord(font[0])==128)
                    if(header):
                        #Strip first binary header
                        font=substr(font,6)
                    if(header and ord(font[info['length1']])==128):
                        #Strip second binary header
                        font=substr(font,0,info['length1'])+substr(font,info['length1']+6)
                self._out('<</Length '+str(len(font)))
                if(compressed):
                    self._out('/Filter /FlateDecode')
                self._out('/Length1 '+str(info['length1']))
                if('length2' in info):
                    self._out('/Length2 '+str(info['length2'])+' /Length3 0')
                self._out('>>')
                self._putstream(font)
                self._out('endobj')
        flist = [(x[1]["i"],x[0],x[1]) for x in self.fonts.items()]
        flist.sort()
        for idx,k,font in flist:
            #Font objects
            self.fonts[k]['n']=self.n+1
            type=font['type']
            name=font['name']
            if(type=='core'):
                #Standard font
                self._newobj()
                self._out('<</Type /Font')
                self._out('/BaseFont /'+name)
                self._out('/Subtype /Type1')
                if(name!='Symbol' and name!='ZapfDingbats'):
                    self._out('/Encoding /WinAnsiEncoding')
                self._out('>>')
                self._out('endobj')
            elif(type=='Type1' or type=='TrueType'):
                #Additional Type1 or TrueType font
                self._newobj()
                self._out('<</Type /Font')
                self._out('/BaseFont /'+name)
                self._out('/Subtype /'+type)
                self._out('/FirstChar 32 /LastChar 255')
                self._out('/Widths '+str(self.n+1)+' 0 R')
                self._out('/FontDescriptor '+str(self.n+2)+' 0 R')
                if(font['enc']):
                    if('diff' in font):
                        self._out('/Encoding '+str(nf+font['diff'])+' 0 R')
                    else:
                        self._out('/Encoding /WinAnsiEncoding')
                self._out('>>')
                self._out('endobj')
                #Widths
                self._newobj()
                cw=font['cw']
                s='['
                for i in range(32,256):
                    # Get doesn't rise exception; returns 0 instead of None if not set
                    s+=str(cw.get(chr(i)) or 0)+' '
                self._out(s+']')
                self._out('endobj')
                #Descriptor
                self._newobj()
                s='<</Type /FontDescriptor /FontName /'+name
                for k in ('Ascent', 'Descent', 'CapHeight', 'Falgs', 'FontBBox', 'ItalicAngle', 'StemV', 'MissingWidth'):
                    s += ' /%s %s' % (k, font['desc'][k])
                filename=font['file']
                if(filename):
                    s+=' /FontFile'
                    if type!='Type1':
                        s+='2'
                    s+=' '+str(self.font_files[filename]['n'])+' 0 R'
                self._out(s+'>>')
                self._out('endobj')
            elif (type == 'TTF'):
                self.fonts[k]['n'] = self.n + 1
                ttf = TTFontFile()
                fontname = 'MPDFAA' + '+' + font['name']
                subset = font['subset']
                del subset[0]
                ttfontstream = ttf.makeSubset(font['ttffile'], subset)
                ttfontsize = len(ttfontstream)
                fontstream = zlib.compress(ttfontstream)
                codeToGlyph = ttf.codeToGlyph
                ##del codeToGlyph[0]
                # Type0 Font
                # A composite font - a font composed of other fonts, organized hierarchically
                self._newobj()
                self._out('<</Type /Font');
                self._out('/Subtype /Type0');
                self._out('/BaseFont /' + fontname + '');
                self._out('/Encoding /Identity-H');
                self._out('/DescendantFonts [' + str(self.n + 1) + ' 0 R]')
                self._out('/ToUnicode ' + str(self.n + 2) + ' 0 R')
                self._out('>>')
                self._out('endobj')

                # CIDFontType2
                # A CIDFont whose glyph descriptions are based on TrueType font technology
                self._newobj()
                self._out('<</Type /Font')
                self._out('/Subtype /CIDFontType2')
                self._out('/BaseFont /' + fontname + '')
                self._out('/CIDSystemInfo ' + str(self.n + 2) + ' 0 R')
                self._out('/FontDescriptor ' + str(self.n + 3) + ' 0 R')
                if (font['desc'].get('MissingWidth')):
                    self._out('/DW %d' % font['desc']['MissingWidth'])
                self._putTTfontwidths(font, ttf.maxUni)
                self._out('/CIDToGIDMap ' + str(self.n + 4) + ' 0 R')
                self._out('>>')
                self._out('endobj')

                # ToUnicode
                self._newobj()
                toUni = "/CIDInit /ProcSet findresource begin\n" \
                        "12 dict begin\n" \
                        "begincmap\n" \
                        "/CIDSystemInfo\n" \
                        "<</Registry (Adobe)\n" \
                        "/Ordering (UCS)\n" \
                        "/Supplement 0\n" \
                        ">> def\n" \
                        "/CMapName /Adobe-Identity-UCS def\n" \
                        "/CMapType 2 def\n" \
                        "1 begincodespacerange\n" \
                        "<0000> <FFFF>\n" \
                        "endcodespacerange\n" \
                        "1 beginbfrange\n" \
                        "<0000> <FFFF> <0000>\n" \
                        "endbfrange\n" \
                        "endcmap\n" \
                        "CMapName currentdict /CMap defineresource pop\n" \
                        "end\n" \
                        "end"
                self._out('<</Length ' + str(len(toUni)) + '>>')
                self._putstream(toUni)
                self._out('endobj')

                # CIDSystemInfo dictionary
                self._newobj()
                self._out('<</Registry (Adobe)')
                self._out('/Ordering (UCS)')
                self._out('/Supplement 0')
                self._out('>>')
                self._out('endobj')

                # Font descriptor
                self._newobj()
                self._out('<</Type /FontDescriptor')
                self._out('/FontName /' + fontname)
                for kd in ('Ascent', 'Descent', 'CapHeight', 'Flags', 'FontBBox', 'ItalicAngle', 'StemV', 'MissingWidth'):
                    v = font['desc'][kd]
                    if (kd == 'Flags'):
                        v = v | 4;
                        v = v & ~32; # SYMBOLIC font flag
                    self._out(' /%s %s' % (kd, v))
                self._out('/FontFile2 ' + str(self.n + 2) + ' 0 R')
                self._out('>>')
                self._out('endobj')

                # Embed CIDToGIDMap
                # A specification of the mapping from CIDs to glyph indices
                cidtogidmap = '';
                cidtogidmap = ["\x00"] * 256*256*2
                for cc, glyph in codeToGlyph.items():
                    cidtogidmap[cc*2] = chr(glyph >> 8)
                    cidtogidmap[cc*2 + 1] = chr(glyph & 0xFF)
                cidtogidmap = ''.join(cidtogidmap)
                if PY3K:
                    # manage binary data as latin9 until PEP461-like function is implemented
                    cidtogidmap = cidtogidmap.encode("latin9")
                cidtogidmap = zlib.compress(cidtogidmap);
                self._newobj()
                self._out('<</Length ' + str(len(cidtogidmap)) + '')
                self._out('/Filter /FlateDecode')
                self._out('>>')
                self._putstream(cidtogidmap)
                self._out('endobj')

                #Font file
                self._newobj()
                self._out('<</Length ' + str(len(fontstream)))
                self._out('/Filter /FlateDecode')
                self._out('/Length1 ' + str(ttfontsize))
                self._out('>>')
                self._putstream(fontstream)
                self._out('endobj')
                del ttf
            else:
                #Allow for additional types
                mtd='_put'+type.lower()
                if(not method_exists(self,mtd)):
                    self.error('Unsupported font type: '+type)
                self.mtd(font)

    def _putTTfontwidths(self, font, maxUni):
        if font['unifilename']:
            cw127fname = os.path.splitext(font['unifilename'])[0] + '.cw127.pkl'
        else:
            cw127fname = None
        if cw127fname and os.path.exists(cw127fname):
            fh = open(cw127fname, "rb");
            try:
                font_dict = pickle.load(fh)
            finally:
                fh.close()
            rangeid = font_dict['rangeid']
            range_ = font_dict['range']
            prevcid = font_dict['prevcid']
            prevwidth = font_dict['prevwidth']
            interval = font_dict['interval']
            range_interval = font_dict['range_interval']
            startcid = 128
        else:
            rangeid = 0
            range_ = {}
            range_interval = {}
            prevcid = -2
            prevwidth = -1
            interval = False
            startcid = 1
        cwlen = maxUni + 1

        # for each character
        for cid in range(startcid, cwlen):
            if cid == 128 and cw127fname and not os.path.exists(cw127fname):
                try:
                    fh = open(cw127fname, "wb")
                    font_dict = {}
                    font_dict['rangeid'] = rangeid
                    font_dict['prevcid'] = prevcid
                    font_dict['prevwidth'] = prevwidth
                    font_dict['interval'] = interval
                    font_dict['range_interval'] = range_interval
                    font_dict['range'] = range_
                    pickle.dump(font_dict, fh)
                    fh.close()
                except IOError:
                    if not exception().errno == errno.EACCES:
                        raise  # Not a permission error.
            if (font['cw'][cid] == 0):
                continue
            width = font['cw'][cid]
            if (width == 65535): width = 0
            if (cid > 255 and (cid not in font['subset']) or not cid): #
                continue
            if ('dw' not in font or (font['dw'] and width != font['dw'])):
                if (cid == (prevcid + 1)):
                    if (width == prevwidth):
                        if (width == range_[rangeid][0]):
                            range_.setdefault(rangeid, []).append(width)
                        else:
                            range_[rangeid].pop()
                            # new range
                            rangeid = prevcid
                            range_[rangeid] = [prevwidth, width]
                        interval = True
                        range_interval[rangeid] = True
                    else:
                        if (interval):
                            # new range
                            rangeid = cid
                            range_[rangeid] = [width]
                        else:
                            range_[rangeid].append(width)
                        interval = False
                else:
                    rangeid = cid
                    range_[rangeid] = [width]
                    interval = False
                prevcid = cid
                prevwidth = width
        prevk = -1
        nextk = -1
        prevint = False
        for k, ws in sorted(range_.items()):
            cws = len(ws)
            if (k == nextk and not prevint and (not k in range_interval or cws < 3)):
                if (k in range_interval):
                    del range_interval[k]
                range_[prevk] = range_[prevk] + range_[k]
                del range_[k]
            else:
                prevk = k
            nextk = k + cws
            if (k in range_interval):
                prevint = (cws > 3)
                del range_interval[k]
                nextk -= 1
            else:
                prevint = False
        w = []
        for k, ws in sorted(range_.items()):
            if (len(set(ws)) == 1):
                w.append(' %s %s %s' % (k, k + len(ws) - 1, ws[0]))
            else:
                w.append(' %s [ %s ]\n' % (k, ' '.join([str(int(h)) for h in ws]))) ##
        self._out('/W [%s]' % ''.join(w))

    def _putimages(self):
        filter=''
        if self.compress:
            filter='/Filter /FlateDecode '
        i = [(x[1]["i"],x[1]) for x in self.images.items()]
        i.sort()
        for idx,info in i:
            self._putimage(info)
            del info['data']
            if 'smask' in info:
                del info['smask']

    def _putimage(self, info):
        if 'data' in info:
            self._newobj()
            info['n']=self.n
            self._out('<</Type /XObject')
            self._out('/Subtype /Image')
            self._out('/Width '+str(info['w']))
            self._out('/Height '+str(info['h']))
            if(info['cs']=='Indexed'):
                self._out('/ColorSpace [/Indexed /DeviceRGB '+str(int(len(info['pal'])/3)-1)+' '+str(self.n+1)+' 0 R]')
            else:
                self._out('/ColorSpace /'+info['cs'])
                if(info['cs']=='DeviceCMYK'):
                    self._out('/Decode [1 0 1 0 1 0 1 0]')
            self._out('/BitsPerComponent '+str(info['bpc']))
            if 'f' in info:
                self._out('/Filter /'+info['f'])
            if 'dp' in info:
                self._out('/DecodeParms <<' + info['dp'] + '>>')
            if('trns' in info and isinstance(info['trns'], list)):
                trns=''
                for i in range(0,len(info['trns'])):
                    trns+=str(info['trns'][i])+' '+str(info['trns'][i])+' '
                self._out('/Mask ['+trns+']')
            if('smask' in info):
                self._out('/SMask ' + str(self.n+1) + ' 0 R');
            self._out('/Length '+str(len(info['data']))+'>>')
            self._putstream(info['data'])
            self._out('endobj')
            # Soft mask
            if('smask' in info):
                dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' + str(info['w'])
                smask = {'w': info['w'], 'h': info['h'], 'cs': 'DeviceGray', 'bpc': 8, 'f': info['f'], 'dp': dp, 'data': info['smask']}
                self._putimage(smask)
            #Palette
            if(info['cs']=='Indexed'):
                self._newobj()
                filter = self.compress and '/Filter /FlateDecode ' or ''
                if self.compress:
                    pal=zlib.compress(info['pal'])
                else:
                    pal=info['pal']
                self._out('<<'+filter+'/Length '+str(len(pal))+'>>')
                self._putstream(pal)
                self._out('endobj')

    def _putxobjectdict(self):
        i = [(x["i"],x["n"]) for x in self.images.values()]
        i.sort()
        for idx,n in i:
            self._out('/I'+str(idx)+' '+str(n)+' 0 R')

    def _putresourcedict(self):
        self._out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]')
        self._out('/Font <<')
        f = [(x["i"],x["n"]) for x in self.fonts.values()]
        f.sort()
        for idx,n in f:
            self._out('/F'+str(idx)+' '+str(n)+' 0 R')
        self._out('>>')
        self._out('/XObject <<')
        self._putxobjectdict()
        self._out('>>')

    def _putresources(self):
        self._putfonts()
        self._putimages()
        #Resource dictionary
        self.offsets[2]=len(self.buffer)
        self._out('2 0 obj')
        self._out('<<')
        self._putresourcedict()
        self._out('>>')
        self._out('endobj')

    def _putinfo(self):
        self._out('/Producer '+self._textstring('PyFPDF '+FPDF_VERSION+' http://https://github.com/reingart/pyfpdf'))
        if hasattr(self,'title'):
            self._out('/Title '+self._textstring(self.title))
        if hasattr(self,'subject'):
            self._out('/Subject '+self._textstring(self.subject))
        if hasattr(self,'author'):
            self._out('/Author '+self._textstring(self.author))
        if hasattr (self,'keywords'):
            self._out('/Keywords '+self._textstring(self.keywords))
        if hasattr(self,'creator'):
            self._out('/Creator '+self._textstring(self.creator))
        self._out('/CreationDate '+self._textstring('D:'+datetime.now().strftime('%Y%m%d%H%M%S')))

    def _putcatalog(self):
        self._out('/Type /Catalog')
        self._out('/Pages 1 0 R')
        if(self.zoom_mode=='fullpage'):
            self._out('/OpenAction [3 0 R /Fit]')
        elif(self.zoom_mode=='fullwidth'):
            self._out('/OpenAction [3 0 R /FitH null]')
        elif(self.zoom_mode=='real'):
            self._out('/OpenAction [3 0 R /XYZ null null 1]')
        elif(not isinstance(self.zoom_mode,basestring)):
            self._out(sprintf('/OpenAction [3 0 R /XYZ null null %s]',self.zoom_mode/100))
        if(self.layout_mode=='single'):
            self._out('/PageLayout /SinglePage')
        elif(self.layout_mode=='continuous'):
            self._out('/PageLayout /OneColumn')
        elif(self.layout_mode=='two'):
            self._out('/PageLayout /TwoColumnLeft')

    def _putheader(self):
        self._out('%PDF-'+self.pdf_version)

    def _puttrailer(self):
        self._out('/Size '+str(self.n+1))
        self._out('/Root '+str(self.n)+' 0 R')
        self._out('/Info '+str(self.n-1)+' 0 R')

    def _enddoc(self):
        self._putheader()
        self._putpages()
        self._putresources()
        #Info
        self._newobj()
        self._out('<<')
        self._putinfo()
        self._out('>>')
        self._out('endobj')
        #Catalog
        self._newobj()
        self._out('<<')
        self._putcatalog()
        self._out('>>')
        self._out('endobj')
        #Cross-ref
        o=len(self.buffer)
        self._out('xref')
        self._out('0 '+(str(self.n+1)))
        self._out('0000000000 65535 f ')
        for i in range(1,self.n+1):
            self._out(sprintf('%010d 00000 n ',self.offsets[i]))
        #Trailer
        self._out('trailer')
        self._out('<<')
        self._puttrailer()
        self._out('>>')
        self._out('startxref')
        self._out(o)
        self._out('%%EOF')
        self.state=3

    def _beginpage(self, orientation):
        self.page+=1
        self.pages[self.page]=''
        self.state=2
        self.x=self.l_margin
        self.y=self.t_margin
        self.font_family=''
        #Page orientation
        if(not orientation):
            orientation=self.def_orientation
        else:
            orientation=orientation[0].upper()
            if(orientation!=self.def_orientation):
                self.orientation_changes[self.page]=1
        if(orientation!=self.cur_orientation):
            #Change orientation
            if(orientation=='P'):
                self.w_pt=self.fw_pt
                self.h_pt=self.fh_pt
                self.w=self.fw
                self.h=self.fh
            else:
                self.w_pt=self.fh_pt
                self.h_pt=self.fw_pt
                self.w=self.fh
                self.h=self.fw
            self.page_break_trigger=self.h-self.b_margin
            self.cur_orientation=orientation

    def _endpage(self):
        #End of page contents
        self.state=1

    def _newobj(self):
        #Begin a new object
        self.n+=1
        self.offsets[self.n]=len(self.buffer)
        self._out(str(self.n)+' 0 obj')

    def _dounderline(self, x,y,txt):
        #Underline text
        up=self.current_font['up']
        ut=self.current_font['ut']
        w=self.get_string_width(txt)+self.ws*txt.count(' ')
        return sprintf('%.2f %.2f %.2f %.2f re f',x*self.k,(self.h-(y-up/1000.0*self.font_size))*self.k,w*self.k,-ut/1000.0*self.font_size_pt)

    def _parsejpg(self, filename):
        # Extract info from a JPEG file
        try:
            f = open(filename, 'rb')
            while True:
                markerHigh, markerLow = struct.unpack('BB', f.read(2))
                if markerHigh != 0xFF or markerLow < 0xC0:
                    raise SyntaxError('No JPEG marker found')
                elif markerLow == 0xDA: # SOS
                    raise SyntaxError('No JPEG SOF marker found')
                elif (markerLow == 0xC8 or # JPG
                      (markerLow >= 0xD0 and markerLow <= 0xD9) or # RSTx
                      (markerLow >= 0xF0 and markerLow <= 0xFD)): # JPGx
                    pass
                else:
                    dataSize, = struct.unpack('>H', f.read(2))
                    data = f.read(dataSize - 2) if dataSize > 2 else ''
                    if ((markerLow >= 0xC0 and markerLow <= 0xC3) or # SOF0 - SOF3
                        (markerLow >= 0xC5 and markerLow <= 0xC7) or # SOF4 - SOF7
                        (markerLow >= 0xC9 and markerLow <= 0xCB) or # SOF9 - SOF11
                        (markerLow >= 0xCD and markerLow <= 0xCF)): # SOF13 - SOF15
                        bpc, height, width, layers = struct.unpack_from('>BHHB', data)
                        colspace = 'DeviceRGB' if layers == 3 else ('DeviceCMYK' if layers == 4 else 'DeviceGray')
                        break
        except Exception:
            self.error('Missing or incorrect image file: %s. error: %s' % (filename, str(exception())))

        # Read whole file from the start
        f.seek(0)
        data = f.read()
        f.close()
        return {'w':width,'h':height,'cs':colspace,'bpc':bpc,'f':'DCTDecode','data':data}

    def _parsegif(self, filename):
        # Extract info from a GIF file (via PNG conversion)
        if Image is None:
            self.error('PIL is required for GIF support')
        try:
            im = Image.open(filename)
        except Exception:
            self.error('Missing or incorrect image file: %s. error: %s' % (filename, str(exception())))
        else:
            # Use temporary file
            f = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
            tmp = f.name
            f.close()
            if "transparency" in im.info:
                im.save(tmp, transparency = im.info['transparency'])
            else:
                im.save(tmp)
            info = self._parsepng(tmp)
            os.unlink(tmp)
        return info

    def _parsepng(self, name):
        #Extract info from a PNG file
        if name.startswith("http://") or name.startswith("https://"):
            f = urlopen(name)
        else:
            f=open(name,'rb')
        if(not f):
            self.error("Can't open image file: "+name)
        #Check signature
        magic = f.read(8).decode("latin9")
        signature = '\x89'+'PNG'+'\r'+'\n'+'\x1a'+'\n'
        if not PY3K: signature = signature.decode("latin9")
        if(magic!=signature):
            self.error('Not a PNG file: '+name)
        #Read header chunk
        f.read(4)
        chunk = f.read(4).decode("latin9")
        if(chunk!='IHDR'):
            self.error('Incorrect PNG file: '+name)
        w=self._freadint(f)
        h=self._freadint(f)
        bpc=ord(f.read(1))
        if(bpc>8):
            self.error('16-bit depth not supported: '+name)
        ct=ord(f.read(1))
        if(ct==0 or ct==4):
            colspace='DeviceGray'
        elif(ct==2 or ct==6):
            colspace='DeviceRGB'
        elif(ct==3):
            colspace='Indexed'
        else:
            self.error('Unknown color type: '+name)
        if(ord(f.read(1))!=0):
            self.error('Unknown compression method: '+name)
        if(ord(f.read(1))!=0):
            self.error('Unknown filter method: '+name)
        if(ord(f.read(1))!=0):
            self.error('Interlacing not supported: '+name)
        f.read(4)
        dp='/Predictor 15 /Colors '
        if colspace == 'DeviceRGB':
            dp+='3'
        else:
            dp+='1'
        dp+=' /BitsPerComponent '+str(bpc)+' /Columns '+str(w)+''
        #Scan chunks looking for palette, transparency and image data
        pal=''
        trns=''
        data=bytes() if PY3K else str()
        n=1
        while n != None:
            n=self._freadint(f)
            type=f.read(4).decode("latin9")
            if(type=='PLTE'):
                #Read palette
                pal=f.read(n)
                f.read(4)
            elif(type=='tRNS'):
                #Read transparency info
                t=f.read(n)
                if(ct==0):
                    trns=[ord(substr(t,1,1)),]
                elif(ct==2):
                    trns=[ord(substr(t,1,1)),ord(substr(t,3,1)),ord(substr(t,5,1))]
                else:
                    pos=t.find('\x00'.encode("latin9"))
                    if(pos!=-1):
                        trns=[pos,]
                f.read(4)
            elif(type=='IDAT'):
                #Read image data block
                data+=f.read(n)
                f.read(4)
            elif(type=='IEND'):
                break
            else:
                f.read(n+4)
        if(colspace=='Indexed' and not pal):
            self.error('Missing palette in '+name)
        f.close()
        info = {'w':w,'h':h,'cs':colspace,'bpc':bpc,'f':'FlateDecode','dp':dp,'pal':pal,'trns':trns,}
        if(ct>=4):
            # Extract alpha channel
            data = zlib.decompress(data)
            color = b('')
            alpha = b('')
            if(ct==4):
                # Gray image
                length = 2*w
                for i in range(h):
                    pos = (1+length)*i
                    color += b(data[pos])
                    alpha += b(data[pos])
                    line = substr(data, pos+1, length)
                    re_c = re.compile('(.).'.encode("ascii"), flags=re.DOTALL)
                    re_a = re.compile('.(.)'.encode("ascii"), flags=re.DOTALL)
                    color += re_c.sub(lambda m: m.group(1), line)
                    alpha += re_a.sub(lambda m: m.group(1), line)
            else:
                # RGB image
                length = 4*w
                for i in range(h):
                    pos = (1+length)*i
                    color += b(data[pos])
                    alpha += b(data[pos])
                    line = substr(data, pos+1, length)
                    re_c = re.compile('(...).'.encode("ascii"), flags=re.DOTALL)
                    re_a = re.compile('...(.)'.encode("ascii"), flags=re.DOTALL)
                    color += re_c.sub(lambda m: m.group(1), line)
                    alpha += re_a.sub(lambda m: m.group(1), line)
            del data
            data = zlib.compress(color)
            info['smask'] = zlib.compress(alpha)
            if (self.pdf_version < '1.4'):
                self.pdf_version = '1.4'
        info['data'] = data
        return info

    def _freadint(self, f):
        #Read a 4-byte integer from file
        try:
            return struct.unpack('>I', f.read(4))[0]
        except:
            return None

    def _textstring(self, s):
        #Format a text string
        return '('+self._escape(s)+')'

    def _escape(self, s):
        #Add \ before \, ( and )
        try:
            if isinstance(s, (str,bytes)):
                return s.replace('\\','\\\\').replace(')','\\)').replace('(','\\(').replace('\r','\\r')
            elif isinstance(s, (list, tuple)):
                for n in range(0,len(s)-1):
                    s[n] = self._escape(s[n])
                return s
            else:
                return s
                    
        except Exception as e:
            tb = sys.exc_info()[2]
            LOG.error('<%s> in line %s !!!' % (str(e),tb.tb_lineno))
            
    def _putstream(self, s):
        self._out('stream')
        self._out(s)
        self._out('endstream')

    def _out(self, s):
        #Add a line to the document
        if PY3K and isinstance(s, bytes):
            # manage binary data as latin9 until PEP461-like function is implemented
            s = s.decode("latin9")
        elif not PY3K and isinstance(s, unicode):
            s = s.encode("latin9")    # default encoding (font name and similar)
        elif not isinstance(s, basestring):
            s = str(s)
        if(self.state==2):
            self.pages[self.page]+=s+"\n"
        else:
            self.buffer+=s+"\n"

    @check_page
    def interleaved2of5(self, txt, x, y, w=1.0, h=10.0):
        '''Barcode I2of5 (numeric), adds a 0 if odd lenght'''
        narrow = w / 3.0
        wide = w

        # wide/narrow codes for the digits
        bar_char={'0': 'nnwwn', '1': 'wnnnw', '2': 'nwnnw', '3': 'wwnnn',
                  '4': 'nnwnw', '5': 'wnwnn', '6': 'nwwnn', '7': 'nnnww',
                  '8': 'wnnwn', '9': 'nwnwn', 'A': 'nn', 'Z': 'wn'}

        self.set_fill_color(0)
        code = txt
        # add leading zero if code-length is odd
        if len(code) % 2 != 0:
            code = '0' + code

        # add start and stop codes
        code = 'AA' + code.lower() + 'ZA'

        for i in range(0, len(code), 2):
            # choose next pair of digits
            char_bar = code[i]
            char_space = code[i+1]
            # check whether it is a valid digit
            if not char_bar in bar_char.keys():
                raise RuntimeError ('Char "%s" invalid for I25: ' % char_bar)
            if not char_space in bar_char.keys():
                raise RuntimeError ('Char "%s" invalid for I25: ' % char_space)

            # create a wide/narrow-seq (first digit=bars, second digit=spaces)
            seq = ''
            for s in range(0, len(bar_char[char_bar])):
                seq += bar_char[char_bar][s] + bar_char[char_space][s]

            for bar in range(0, len(seq)):
                # set line_width depending on value
                if seq[bar] == 'n':
                    line_width = narrow
                else:
                    line_width = wide

                # draw every second value, the other is represented by space
                if bar % 2 == 0:
                    self.rect(x, y, line_width, h, 'F')

                x += line_width


    @check_page
    def code39(self, txt, x, y, w=1.5, h=5.0):
        """Barcode 3of9"""
        dim = {'w': w, 'n': w/3.}
        n = x
        m = y
        chars = {
            '0': 'nnnwwnwnn', '1': 'wnnwnnnnw', '2': 'nnwwnnnnw',
            '3': 'wnwwnnnnn', '4': 'nnnwwnnnw', '5': 'wnnwwnnnn',
            '6': 'nnwwwnnnn', '7': 'nnnwnnwnw', '8': 'wnnwnnwnn',
            '9': 'nnwwnnwnn', 'A': 'wnnnnwnnw', 'B': 'nnwnnwnnw',
            'C': 'wnwnnwnnn', 'D': 'nnnnwwnnw', 'E': 'wnnnwwnnn',
            'F': 'nnwnwwnnn', 'G': 'nnnnnwwnw', 'H': 'wnnnnwwnn',
            'I': 'nnwnnwwnn', 'J': 'nnnnwwwnn', 'K': 'wnnnnnnww',
            'L': 'nnwnnnnww', 'M': 'wnwnnnnwn', 'N': 'nnnnwnnww',
            'O': 'wnnnwnnwn', 'P': 'nnwnwnnwn', 'Q': 'nnnnnnwww',
            'R': 'wnnnnnwwn', 'S': 'nnwnnnwwn', 'T': 'nnnnwnwwn',
            'U': 'wwnnnnnnw', 'V': 'nwwnnnnnw', 'W': 'wwwnnnnnn',
            'X': 'nwnnwnnnw', 'Y': 'wwnnwnnnn', 'Z': 'nwwnwnnnn',
            '-': 'nwnnnnwnw', '.': 'wwnnnnwnn', ' ': 'nwwnnnwnn',
            '*': 'nwnnwnwnn', '$': 'nwnwnwnnn', '/': 'nwnwnnnwn',
            '+': 'nwnnnwnwn', '%': 'nnnwnwnwn',
        }
        self.set_fill_color(0)
        
        for c in txt.upper():
            if c not in chars:
                raise RuntimeError('Invalid char "%s" for Code39' % c)
            for i, d in enumerate(chars[c]):
                if i % 2 == 0:
                    self.rect(x, y, dim[d], h, 'F')
                x += dim[d]
            x += dim['n']
        #Print text under barcode
        self.set_font('Arial','',11);
        #self.text(n,m+h+11/self.k,txt)
        self.text(x-(len(txt)*self.k),y+h+11/self.k,'*'+txt+'*')

    # EAN14 and UPC-A
    @check_page
    def EAN13(self, x, y, barcode_, h=16, w=.35):
        self.__barcode__(x,y,barcode_,h,w,13)

    @check_page
    def UPC_A(self, x, y, barcode_, h=16, w=.35):
        self.__barcode__(x,y,barcode_,h,w,12)

    def getCheckDigit(self, barcode_):
        # Compute the check digit
        sum=0
        for i in range(1,12,2):
            sum += 3*int(barcode_[i])
        for i in range(0,11,2):
            sum+=int(barcode_[i])
        r=sum%10
        if(r>0):
            r=10-r
        return str(r)

    def testCheckDigit(self, barcode_):
        #Test validity of check digit
        sum=0
        for i in range(1,12,2):
            sum += 3*int(barcode_[i])
        for i in range(0,11,2):
            sum += int(barcode_[i])
        return (sum+int(barcode_[-1:]))%10==0


    def __barcode__(self, x, y, barcode_, h, w, lng):
        # Padding
        barcode_=barcode_.rjust(lng-1,'0')
        if lng==12:
            barcode_ = '0' + barcode_
        # Add or control the check digit
        if len(barcode_) == 12:
            barcode_ += self.getCheckDigit(barcode_)
        elif not self.testCheckDigit(barcode_):
            self.error('Incorrect check digit')
        #Convert digits to bars
        codes={
            'A':{
                '0':'0001101','1':'0011001','2':'0010011','3':'0111101','4':'0100011',
                '5':'0110001','6':'0101111','7':'0111011','8':'0110111','9':'0001011'},
            'B':{
                '0':'0100111','1':'0110011','2':'0011011','3':'0100001','4':'0011101',
                '5':'0111001','6':'0000101','7':'0010001','8':'0001001','9':'0010111'},
            'C':{
                '0':'1110010','1':'1100110','2':'1101100','3':'1000010','4':'1011100',
                '5':'1001110','6':'1010000','7':'1000100','8':'1001000','9':'1110100'}
            }
        parities={
            '0':['A','A','A','A','A','A'],
            '1':['A','A','B','A','B','B'],
            '2':['A','A','B','B','A','B'],
            '3':['A','A','B','B','B','A'],
            '4':['A','B','A','A','B','B'],
            '5':['A','B','B','A','A','B'],
            '6':['A','B','B','B','A','A'],
            '7':['A','B','A','B','A','B'],
            '8':['A','B','A','B','B','A'],
            '9':['A','B','B','A','B','A']
            }
        code_='101';
        p=parities[barcode_[0]]
        for i in range(1,7):
            code_ +=codes[p[i-1]][barcode_[i]]
        code_ +='01010'
        for i in range(7,13):
            code_ +=codes['C'][barcode_[i]]
        code_ +='101'
        #Draw bars
        for i in range(0,len(code_)):
            if code_[i]=='1':
                self.rect(x+i*w,y,w,h,'F')
        #Print text uder barcode
        self.set_font('Arial','',12);
        self.text(x,y+h+11/self.k,barcode_[-lng:])


