source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/contrib/fpdf/fpdf.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 76.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: latin-1 -*-
3# ****************************************************************************
4# * Software: FPDF for python                                                *
5# * Version:  1.7.1                                                          *
6# * Date:     2010-09-10                                                     *
7# * Last update: 2012-08-16                                                  *
8# * License:  LGPL v3.0                                                      *
9# *                                                                          *
10# * Original Author (PHP):  Olivier PLATHEY 2004-12-31                       *
11# * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05               *
12# * Maintainer:  Mariano Reingart (reingart@gmail.com) et al since 2008 est. *
13# * NOTE: 'I' and 'D' destinations are disabled, and simply print to STDOUT  *
14# ****************************************************************************
15
16from __future__ import division, with_statement
17
18from datetime import datetime
19from functools import wraps
20import math
21import errno
22import os, sys, zlib, struct, re, tempfile, struct
23
24from .ttfonts import TTFontFile
25from .fonts import fpdf_charwidths
26from .php import substr, sprintf, print_r, UTF8ToUTF16BE, UTF8StringToArray
27from .py3k import PY3K, pickle, urlopen, BytesIO, Image, basestring, unicode, exception, b, hashpath
28
29# Global variables
30FPDF_VERSION = '1.7.2'
31FPDF_FONT_DIR = os.path.join(os.path.dirname(__file__),'font')
32FPDF_CACHE_MODE = 0 # 0 - in same folder, 1 - none, 2 - hash
33FPDF_CACHE_DIR = None
34SYSTEM_TTFONTS = None
35
36PAGE_FORMATS = {
37    "a3": (841.89, 1190.55),
38    "a4": (595.28, 841.89),
39    "a5": (420.94, 595.28),
40    "letter": (612, 792),
41    "legal": (612, 1008),
42}
43
44def set_global(var, val):
45    globals()[var] = val
46
47def load_cache(filename):
48    """Return unpickled object, or None if cache unavailable"""
49    if not filename:
50        return None
51    try:
52        with open(filename, "rb") as fh:
53            return pickle.load(fh)
54    except (IOError, ValueError):  # File missing, unsupported pickle, etc
55        return None
56
57class FPDF(object):
58    "PDF Generation class"
59
60    def __init__(self, orientation = 'P', unit = 'mm', format = 'A4'):
61        # Some checks
62        self._dochecks()
63        # Initialization of properties
64        self.offsets = {}               # array of object offsets
65        self.page = 0                   # current page number
66        self.n = 2                      # current object number
67        self.buffer = ''                # buffer holding in-memory PDF
68        self.pages = {}                 # array containing pages and metadata
69        self.state = 0                  # current document state
70        self.fonts = {}                 # array of used fonts
71        self.font_files = {}            # array of font files
72        self.diffs = {}                 # array of encoding differences
73        self.images = {}                # array of used images
74        self.page_links = {}            # array of links in pages
75        self.links = {}                 # array of internal links
76        self.in_footer = 0              # flag set when processing footer
77        self.lastw = 0
78        self.lasth = 0                  # height of last cell printed
79        self.font_family = ''           # current font family
80        self.font_style = ''            # current font style
81        self.font_size_pt = 12          # current font size in points
82        self.font_stretching = 100      # current font stretching
83        self.underline = 0              # underlining flag
84        self.draw_color = '0 G'
85        self.fill_color = '0 g'
86        self.text_color = '0 g'
87        self.color_flag = 0             # indicates whether fill and text colors are different
88        self.ws = 0                     # word spacing
89        self.angle = 0
90        # Standard fonts
91        self.core_fonts={'courier': 'Courier', 'courierB': 'Courier-Bold',
92            'courierI': 'Courier-Oblique', 'courierBI': 'Courier-BoldOblique',
93            'helvetica': 'Helvetica', 'helveticaB': 'Helvetica-Bold',
94            'helveticaI': 'Helvetica-Oblique',
95            'helveticaBI': 'Helvetica-BoldOblique',
96            'times': 'Times-Roman', 'timesB': 'Times-Bold',
97            'timesI': 'Times-Italic', 'timesBI': 'Times-BoldItalic',
98            'symbol': 'Symbol', 'zapfdingbats': 'ZapfDingbats'}
99        self.core_fonts_encoding = "latin-1"
100        # Scale factor
101        if unit == "pt":
102            self.k = 1
103        elif unit == "mm":
104            self.k = 72 / 25.4
105        elif unit == "cm":
106            self.k = 72 / 2.54
107        elif unit == 'in':
108            self.k = 72.
109        else:
110            self.error("Incorrect unit: " + unit)
111        # Page format
112        self.fw_pt, self.fh_pt = self.get_page_format(format, self.k)
113        self.dw_pt = self.fw_pt
114        self.dh_pt = self.fh_pt
115        self.fw = self.fw_pt / self.k
116        self.fh = self.fh_pt / self.k
117        # Page orientation
118        orientation = orientation.lower()
119        if orientation in ('p', 'portrait'):
120            self.def_orientation = 'P'
121            self.w_pt = self.fw_pt
122            self.h_pt = self.fh_pt
123        elif orientation in ('l', 'landscape'):
124            self.def_orientation = 'L'
125            self.w_pt = self.fh_pt
126            self.h_pt = self.fw_pt
127        else:
128            self.error('Incorrect orientation: ' + orientation)
129        self.cur_orientation = self.def_orientation
130        self.w = self.w_pt / self.k
131        self.h = self.h_pt / self.k
132        # Page margins (1 cm)
133        margin = 28.35 / self.k
134        self.set_margins(margin, margin)
135        # Interior cell margin (1 mm)
136        self.c_margin = margin / 10.0
137        # line width (0.2 mm)
138        self.line_width = .567 / self.k
139        # Automatic page break
140        self.set_auto_page_break(1, 2 * margin)
141        # Full width display mode
142        self.set_display_mode('fullwidth')
143        # Enable compression
144        self.set_compression(1)
145        # Set default PDF version number
146        self.pdf_version = '1.3'
147
148    @staticmethod
149    def get_page_format(format, k):
150        "Return scale factor, page w and h size in points"
151        if isinstance(format, basestring):
152            format = format.lower()
153            if format in PAGE_FORMATS:
154                return PAGE_FORMATS[format]
155            else:
156                raise RuntimeError("Unknown page format: " + format)
157        else:
158            return (format[0] * k, format[1] * k)
159
160    def check_page(fn):
161        "Decorator to protect drawing methods"
162        @wraps(fn)
163        def wrapper(self, *args, **kwargs):
164            if not self.page and not kwargs.get('split_only'):
165                self.error("No page open, you need to call add_page() first")
166            else:
167                return fn(self, *args, **kwargs)
168        return wrapper
169
170    def set_margins(self, left,top,right=-1):
171        "Set left, top and right margins"
172        self.l_margin=left
173        self.t_margin=top
174        if(right==-1):
175            right=left
176        self.r_margin=right
177
178    def set_left_margin(self, margin):
179        "Set left margin"
180        self.l_margin=margin
181        if(self.page>0 and self.x<margin):
182            self.x=margin
183
184    def set_top_margin(self, margin):
185        "Set top margin"
186        self.t_margin=margin
187
188    def set_right_margin(self, margin):
189        "Set right margin"
190        self.r_margin=margin
191
192    def set_auto_page_break(self, auto,margin=0):
193        "Set auto page break mode and triggering margin"
194        self.auto_page_break=auto
195        self.b_margin=margin
196        self.page_break_trigger=self.h-margin
197
198    def set_display_mode(self, zoom,layout='continuous'):
199        """Set display mode in viewer
200       
201        The "zoom" argument may be 'fullpage', 'fullwidth', 'real',
202        'default', or a number, interpreted as a percentage."""
203       
204        if(zoom=='fullpage' or zoom=='fullwidth' or zoom=='real' or zoom=='default' or not isinstance(zoom,basestring)):
205            self.zoom_mode=zoom
206        else:
207            self.error('Incorrect zoom display mode: '+zoom)
208        if(layout=='single' or layout=='continuous' or layout=='two' or layout=='default'):
209            self.layout_mode=layout
210        else:
211            self.error('Incorrect layout display mode: '+layout)
212
213    def set_compression(self, compress):
214        "Set page compression"
215        self.compress=compress
216
217    def set_title(self, title):
218        "Title of document"
219        self.title=title
220
221    def set_subject(self, subject):
222        "Subject of document"
223        self.subject=subject
224
225    def set_author(self, author):
226        "Author of document"
227        self.author=author
228
229    def set_keywords(self, keywords):
230        "Keywords of document"
231        self.keywords=keywords
232
233    def set_creator(self, creator):
234        "Creator of document"
235        self.creator=creator
236
237    def set_doc_option(self, opt, value):
238        "Set document option"
239        if opt == "core_fonts_encoding":
240            self.core_fonts_encoding = value
241        else:
242            self.error("Unknown document option \"%s\"" % str(opt))
243
244    def alias_nb_pages(self, alias='{nb}'):
245        "Define an alias for total number of pages"
246        self.str_alias_nb_pages=alias
247        return alias
248
249    def error(self, msg):
250        "Fatal error"
251        raise RuntimeError('FPDF error: '+msg)
252
253    def open(self):
254        "Begin document"
255        self.state=1
256
257    def close(self):
258        "Terminate document"
259        if(self.state==3):
260            return
261        if(self.page==0):
262            self.add_page()
263        #Page footer
264        self.in_footer=1
265        self.footer()
266        self.in_footer=0
267        #close page
268        self._endpage()
269        #close document
270        self._enddoc()
271
272    def add_page(self, orientation = '', format = '', same = False):
273        "Start a new page, if same page format will be same as previous"
274        if(self.state==0):
275            self.open()
276        family=self.font_family
277        if self.underline:
278            style = self.font_style + 'U'
279        else:
280            style = self.font_style
281        size=self.font_size_pt
282        lw=self.line_width
283        dc=self.draw_color
284        fc=self.fill_color
285        tc=self.text_color
286        cf=self.color_flag
287        stretching=self.font_stretching
288        if(self.page>0):
289            #Page footer
290            self.in_footer=1
291            self.footer()
292            self.in_footer=0
293            #close page
294            self._endpage()
295        #Start new page
296        self._beginpage(orientation, format, same)
297        #Set line cap style to square
298        self._out('2 J')
299        #Set line width
300        self.line_width=lw
301        self._out(sprintf('%.2f w',lw*self.k))
302        #Set font
303        if(family):
304            self.set_font(family,style,size)
305        #Set colors
306        self.draw_color=dc
307        if(dc!='0 G'):
308            self._out(dc)
309        self.fill_color=fc
310        if(fc!='0 g'):
311            self._out(fc)
312        self.text_color=tc
313        self.color_flag=cf
314        #Page header
315        self.header()
316        #Restore line width
317        if(self.line_width!=lw):
318            self.line_width=lw
319            self._out(sprintf('%.2f w',lw*self.k))
320        #Restore font
321        if(family):
322            self.set_font(family,style,size)
323        #Restore colors
324        if(self.draw_color!=dc):
325            self.draw_color=dc
326            self._out(dc)
327        if(self.fill_color!=fc):
328            self.fill_color=fc
329            self._out(fc)
330        self.text_color=tc
331        self.color_flag=cf
332        #Restore stretching
333        if(stretching != 100):
334            self.set_stretching(stretching)
335
336    def header(self):
337        "Header to be implemented in your own inherited class"
338        pass
339
340    def footer(self):
341        "Footer to be implemented in your own inherited class"
342        pass
343
344    def page_no(self):
345        "Get current page number"
346        return self.page
347
348    def set_draw_color(self, r,g=-1,b=-1):
349        "Set color for all stroking operations"
350        if((r==0 and g==0 and b==0) or g==-1):
351            self.draw_color=sprintf('%.3f G',r/255.0)
352        else:
353            self.draw_color=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0)
354        if(self.page>0):
355            self._out(self.draw_color)
356
357    def set_fill_color(self,r,g=-1,b=-1):
358        "Set color for all filling operations"
359        if((r==0 and g==0 and b==0) or g==-1):
360            self.fill_color=sprintf('%.3f g',r/255.0)
361        else:
362            self.fill_color=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
363        self.color_flag=(self.fill_color!=self.text_color)
364        if(self.page>0):
365            self._out(self.fill_color)
366
367    def set_text_color(self, r,g=-1,b=-1):
368        "Set color for text"
369        if((r==0 and g==0 and b==0) or g==-1):
370            self.text_color=sprintf('%.3f g',r/255.0)
371        else:
372            self.text_color=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
373        self.color_flag=(self.fill_color!=self.text_color)
374
375    def get_string_width(self, s, normalized = False):
376        "Get width of a string in the current font"
377        # normalized is parameter for internal use
378        s = s if normalized else self.normalize_text(s)
379        cw=self.current_font['cw']
380        w=0
381        l=len(s)
382        if self.unifontsubset:
383            for char in s:
384                char = ord(char)
385                if len(cw) > char:
386                    w += cw[char] # ord(cw[2*char])<<8 + ord(cw[2*char+1])
387                #elif (char>0 and char<128 and isset($cw[chr($char)])) { $w += $cw[chr($char)]; }
388                elif (self.current_font['desc']['MissingWidth']) :
389                    w += self.current_font['desc']['MissingWidth']
390                #elif (isset($this->CurrentFont['MissingWidth'])) { $w += $this->CurrentFont['MissingWidth']; }
391                else:
392                    w += 500
393        else:
394            for i in range(0, l):
395                w += cw.get(s[i],0)
396        if self.font_stretching != 100:
397            w = w * self.font_stretching / 100.0
398        return w * self.font_size / 1000.0
399
400    def set_line_width(self, width):
401        "Set line width"
402        self.line_width=width
403        if(self.page>0):
404            self._out(sprintf('%.2f w',width*self.k))
405
406    @check_page
407    def line(self, x1,y1,x2,y2):
408        "Draw a line"
409        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))
410
411    def _set_dash(self, dash_length=False, space_length=False):
412        if(dash_length and space_length):
413            s = sprintf('[%.3f %.3f] 0 d', dash_length*self.k, space_length*self.k)
414        else:
415            s = '[] 0 d'
416        self._out(s)
417
418    @check_page
419    def dashed_line(self, x1,y1,x2,y2, dash_length=1, space_length=1):
420        """Draw a dashed line. Same interface as line() except:
421           - dash_length: Length of the dash
422           - space_length: Length of the space between dashes"""
423        self._set_dash(dash_length, space_length)
424        self.line(x1, y1, x2, y2)
425        self._set_dash()
426
427    @check_page
428    def rect(self, x,y,w,h,style=''):
429        "Draw a rectangle"
430        if(style=='F'):
431            op='f'
432        elif(style=='FD' or style=='DF'):
433            op='B'
434        else:
435            op='S'
436        self._out(sprintf('%.2f %.2f %.2f %.2f re %s',x*self.k,(self.h-y)*self.k,w*self.k,-h*self.k,op))
437
438    @check_page
439    def ellipse(self, x,y,w,h,style=''):
440        "Draw a ellipse"
441        if(style=='F'):
442            op='f'
443        elif(style=='FD' or style=='DF'):
444            op='B'
445        else:
446            op='S'
447
448        cx = x + w/2.0
449        cy = y + h/2.0
450        rx = w/2.0
451        ry = h/2.0
452
453        lx = 4.0/3.0*(math.sqrt(2)-1)*rx
454        ly = 4.0/3.0*(math.sqrt(2)-1)*ry
455
456        self._out(sprintf('%.2f %.2f m %.2f %.2f %.2f %.2f %.2f %.2f c',
457            (cx+rx)*self.k, (self.h-cy)*self.k,
458            (cx+rx)*self.k, (self.h-(cy-ly))*self.k,
459            (cx+lx)*self.k, (self.h-(cy-ry))*self.k,
460            cx*self.k, (self.h-(cy-ry))*self.k))
461        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c',
462            (cx-lx)*self.k, (self.h-(cy-ry))*self.k,
463            (cx-rx)*self.k, (self.h-(cy-ly))*self.k,
464            (cx-rx)*self.k, (self.h-cy)*self.k))
465        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c',
466            (cx-rx)*self.k, (self.h-(cy+ly))*self.k,
467            (cx-lx)*self.k, (self.h-(cy+ry))*self.k,
468            cx*self.k, (self.h-(cy+ry))*self.k))
469        self._out(sprintf('%.2f %.2f %.2f %.2f %.2f %.2f c %s',
470            (cx+lx)*self.k, (self.h-(cy+ry))*self.k,
471            (cx+rx)*self.k, (self.h-(cy+ly))*self.k,
472            (cx+rx)*self.k, (self.h-cy)*self.k,
473            op))
474
475    def add_font(self, family, style='', fname='', uni=False):
476        "Add a TrueType or Type1 font"
477        family = family.lower()
478        if (fname == ''):
479            fname = family.replace(' ','') + style.lower() + '.pkl'
480        if (family == 'arial'):
481            family = 'helvetica'
482        style = style.upper()
483        if (style == 'IB'):
484            style = 'BI'
485        fontkey = family+style
486        if fontkey in self.fonts:
487            # Font already added!
488            return
489        if (uni):
490            global SYSTEM_TTFONTS, FPDF_CACHE_MODE, FPDF_CACHE_DIR
491            if os.path.exists(fname):
492                ttffilename = fname
493            elif (FPDF_FONT_DIR and
494                os.path.exists(os.path.join(FPDF_FONT_DIR, fname))):
495                ttffilename = os.path.join(FPDF_FONT_DIR, fname)
496            elif (SYSTEM_TTFONTS and
497                os.path.exists(os.path.join(SYSTEM_TTFONTS, fname))):
498                ttffilename = os.path.join(SYSTEM_TTFONTS, fname)
499            else:
500                raise RuntimeError("TTF Font file not found: %s" % fname)
501            name = ''
502            if FPDF_CACHE_MODE == 0:
503                unifilename = os.path.splitext(ttffilename)[0] + '.pkl'
504            elif FPDF_CACHE_MODE == 2:               
505                unifilename = os.path.join(FPDF_CACHE_DIR, \
506                    hashpath(ttffilename) + ".pkl")
507            else:
508                unifilename = None
509            font_dict = load_cache(unifilename)
510            if font_dict is None:
511                ttf = TTFontFile()
512                ttf.getMetrics(ttffilename)
513                desc = {
514                    'Ascent': int(round(ttf.ascent, 0)),
515                    'Descent': int(round(ttf.descent, 0)),
516                    'CapHeight': int(round(ttf.capHeight, 0)),
517                    'Flags': ttf.flags,
518                    'FontBBox': "[%s %s %s %s]" % (
519                        int(round(ttf.bbox[0], 0)),
520                        int(round(ttf.bbox[1], 0)),
521                        int(round(ttf.bbox[2], 0)),
522                        int(round(ttf.bbox[3], 0))),
523                    'ItalicAngle': int(ttf.italicAngle),
524                    'StemV': int(round(ttf.stemV, 0)),
525                    'MissingWidth': int(round(ttf.defaultWidth, 0)),
526                    }
527                # Generate metrics .pkl file
528                font_dict = {
529                    'name': re.sub('[ ()]', '', ttf.fullName),
530                    'type': 'TTF',
531                    'desc': desc,
532                    'up': round(ttf.underlinePosition),
533                    'ut': round(ttf.underlineThickness),
534                    'ttffile': ttffilename,
535                    'fontkey': fontkey,
536                    'originalsize': os.stat(ttffilename).st_size,
537                    'cw': ttf.charWidths,
538                    }
539                if unifilename:
540                    try:
541                        with open(unifilename, "wb") as fh:
542                            pickle.dump(font_dict, fh)
543                    except IOError:
544                        if not exception().errno == errno.EACCES:
545                            raise  # Not a permission error.
546                del ttf
547            if hasattr(self,'str_alias_nb_pages'):
548                sbarr = list(range(0,57))   # include numbers in the subset!
549            else:
550                sbarr = list(range(0,32))
551            self.fonts[fontkey] = {
552                'i': len(self.fonts)+1, 'type': font_dict['type'],
553                'name': font_dict['name'], 'desc': font_dict['desc'],
554                'up': font_dict['up'], 'ut': font_dict['ut'],
555                'cw': font_dict['cw'],
556                'ttffile': font_dict['ttffile'], 'fontkey': fontkey,
557                'subset': sbarr, 'unifilename': unifilename,
558                }
559            self.font_files[fontkey] = {'length1': font_dict['originalsize'],
560                                        'type': "TTF", 'ttffile': ttffilename}
561            self.font_files[fname] = {'type': "TTF"}
562        else:
563            with open(fname, 'rb') as fontfile:
564                font_dict = pickle.load(fontfile)
565            self.fonts[fontkey] = {'i': len(self.fonts)+1}
566            self.fonts[fontkey].update(font_dict)
567            diff = font_dict.get('diff')
568            if (diff):
569                #Search existing encodings
570                d = 0
571                nb = len(self.diffs)
572                for i in range(1, nb+1):
573                    if(self.diffs[i] == diff):
574                        d = i
575                        break
576                if (d == 0):
577                    d = nb + 1
578                    self.diffs[d] = diff
579                self.fonts[fontkey]['diff'] = d
580            filename = font_dict.get('filename')
581            if (filename):
582                if (font_dict['type'] == 'TrueType'):
583                    originalsize = font_dict['originalsize']
584                    self.font_files[filename]={'length1': originalsize}
585                else:
586                    self.font_files[filename]={'length1': font_dict['size1'],
587                                               'length2': font_dict['size2']}
588
589    def set_font(self, family,style='',size=0):
590        "Select a font; size given in points"
591        family=family.lower()
592        if(family==''):
593            family=self.font_family
594        if(family=='arial'):
595            family='helvetica'
596        elif(family=='symbol' or family=='zapfdingbats'):
597            style=''
598        style=style.upper()
599        if('U' in style):
600            self.underline=1
601            style=style.replace('U','')
602        else:
603            self.underline=0
604        if(style=='IB'):
605            style='BI'
606        if(size==0):
607            size=self.font_size_pt
608        #Test if font is already selected
609        if(self.font_family==family and self.font_style==style and self.font_size_pt==size):
610            return
611        #Test if used for the first time
612        fontkey=family+style
613        if fontkey not in self.fonts:
614            #Check if one of the standard fonts
615            if fontkey in self.core_fonts:
616                if fontkey not in fpdf_charwidths:
617                    #Load metric file
618                    name=os.path.join(FPDF_FONT_DIR,family)
619                    if(family=='times' or family=='helvetica'):
620                        name+=style.lower()
621                    with open(name+'.font') as file:
622                        exec(compile(file.read(), name+'.font', 'exec'))
623                    if fontkey not in fpdf_charwidths:
624                        self.error('Could not include font metric file for'+fontkey)
625                i=len(self.fonts)+1
626                self.fonts[fontkey]={'i':i,'type':'core','name':self.core_fonts[fontkey],'up':-100,'ut':50,'cw':fpdf_charwidths[fontkey]}
627            else:
628                self.error('Undefined font: '+family+' '+style)
629        #Select it
630        self.font_family=family
631        self.font_style=style
632        self.font_size_pt=size
633        self.font_size=size/self.k
634        self.current_font=self.fonts[fontkey]
635        self.unifontsubset = (self.fonts[fontkey]['type'] == 'TTF')
636        if(self.page>0):
637            self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt))
638
639    def set_font_size(self, size):
640        "Set font size in points"
641        if(self.font_size_pt==size):
642            return
643        self.font_size_pt=size
644        self.font_size=size/self.k
645        if(self.page>0):
646            self._out(sprintf('BT /F%d %.2f Tf ET',self.current_font['i'],self.font_size_pt))
647
648    def set_stretching(self, factor):
649        "Set from stretch factor percents (default: 100.0)"
650        if(self.font_stretching == factor):
651            return
652        self.font_stretching = factor
653        if (self.page > 0):
654            self._out(sprintf('BT %.2f Tz ET', self.font_stretching))
655
656    def add_link(self):
657        "Create a new internal link"
658        n=len(self.links)+1
659        self.links[n]=(0,0)
660        return n
661
662    def set_link(self, link,y=0,page=-1):
663        "Set destination of internal link"
664        if(y==-1):
665            y=self.y
666        if(page==-1):
667            page=self.page
668        self.links[link]=[page,y]
669
670    def link(self, x,y,w,h,link):
671        "Put a link on the page"
672        if not self.page in self.page_links:
673            self.page_links[self.page] = []
674        self.page_links[self.page] += [(x*self.k,self.h_pt-y*self.k,w*self.k,h*self.k,link),]
675
676    @check_page
677    def text(self, x, y, txt=''):
678        "Output a string"
679        txt = self.normalize_text(txt)
680        if (self.unifontsubset):
681            txt2 = self._escape(UTF8ToUTF16BE(txt, False))
682            for uni in UTF8StringToArray(txt):
683                self.current_font['subset'].append(uni)
684        else:
685            txt2 = self._escape(txt)
686        s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*self.k,(self.h-y)*self.k, txt2)
687        if(self.underline and txt!=''):
688            s+=' '+self._dounderline(x,y,txt)
689        if(self.color_flag):
690            s='q '+self.text_color+' '+s+' Q'
691        self._out(s)
692
693    @check_page
694    def rotate(self, angle, x=None, y=None):
695        if x is None:
696            x = self.x
697        if y is None:
698            y = self.y;
699        if self.angle!=0:
700            self._out('Q')
701        self.angle = angle
702        if angle!=0:
703            angle *= math.pi/180;
704            c = math.cos(angle);
705            s = math.sin(angle);
706            cx = x*self.k;
707            cy = (self.h-y)*self.k
708            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)
709            self._out(s)
710
711    def accept_page_break(self):
712        "Accept automatic page break or not"
713        return self.auto_page_break
714
715    @check_page
716    def cell(self, w,h=0,txt='',border=0,ln=0,align='',fill=0,link=''):
717        "Output a cell"
718        txt = self.normalize_text(txt)
719        k=self.k
720        if(self.y+h>self.page_break_trigger and not self.in_footer and self.accept_page_break()):
721            #Automatic page break
722            x=self.x
723            ws=self.ws
724            if(ws>0):
725                self.ws=0
726                self._out('0 Tw')
727            self.add_page(same = True)
728            self.x=x
729            if(ws>0):
730                self.ws=ws
731                self._out(sprintf('%.3f Tw',ws*k))
732        if(w==0):
733            w=self.w-self.r_margin-self.x
734        s=''
735        if(fill==1 or border==1):
736            if(fill==1):
737                if border==1:
738                    op='B'
739                else:
740                    op='f'
741            else:
742                op='S'
743            s=sprintf('%.2f %.2f %.2f %.2f re %s ',self.x*k,(self.h-self.y)*k,w*k,-h*k,op)
744        if(isinstance(border,basestring)):
745            x=self.x
746            y=self.y
747            if('L' in border):
748                s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-y)*k,x*k,(self.h-(y+h))*k)
749            if('T' in border):
750                s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-y)*k,(x+w)*k,(self.h-y)*k)
751            if('R' in border):
752                s+=sprintf('%.2f %.2f m %.2f %.2f l S ',(x+w)*k,(self.h-y)*k,(x+w)*k,(self.h-(y+h))*k)
753            if('B' in border):
754                s+=sprintf('%.2f %.2f m %.2f %.2f l S ',x*k,(self.h-(y+h))*k,(x+w)*k,(self.h-(y+h))*k)
755        if(txt!=''):
756            if(align=='R'):
757                dx=w-self.c_margin-self.get_string_width(txt, True)
758            elif(align=='C'):
759                dx=(w-self.get_string_width(txt, True))/2.0
760            else:
761                dx=self.c_margin
762            if(self.color_flag):
763                s+='q '+self.text_color+' '
764
765            # If multibyte, Tw has no effect - do word spacing using an adjustment before each space
766            if (self.ws and self.unifontsubset):
767                for uni in UTF8StringToArray(txt):
768                    self.current_font['subset'].append(uni)
769                space = self._escape(UTF8ToUTF16BE(' ', False))
770                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)
771                t = txt.split(' ')
772                numt = len(t)
773                for i in range(numt):
774                    tx = t[i]
775                    tx = '(' + self._escape(UTF8ToUTF16BE(tx, False)) + ')'
776                    s += sprintf('%s ', tx);
777                    if ((i+1)<numt):
778                        adj = -(self.ws * self.k) * 1000 / self.font_size_pt
779                        s += sprintf('%d(%s) ', adj, space)
780                s += '] TJ'
781                s += ' ET'
782            else:
783                if (self.unifontsubset):
784                    txt2 = self._escape(UTF8ToUTF16BE(txt, False))
785                    for uni in UTF8StringToArray(txt):
786                        self.current_font['subset'].append(uni)
787                else:
788                    txt2 = self._escape(txt)
789                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)
790
791            if(self.underline):
792                s+=' '+self._dounderline(self.x+dx,self.y+.5*h+.3*self.font_size,txt)
793            if(self.color_flag):
794                s+=' Q'
795            if(link):
796                self.link(self.x+dx,self.y+.5*h-.5*self.font_size,self.get_string_width(txt, True),self.font_size,link)
797        if(s):
798            self._out(s)
799        self.lasth=h
800        if(ln>0):
801            #Go to next line
802            self.y+=h
803            if(ln==1):
804                self.x=self.l_margin
805        else:
806            self.x+=w
807
808    @check_page
809    def multi_cell(self, w, h, txt='', border=0, align='J', fill=0, split_only=False):
810        "Output text with automatic or explicit line breaks"
811        txt = self.normalize_text(txt)
812        ret = [] # if split_only = True, returns splited text cells
813        cw=self.current_font['cw']
814        if(w==0):
815            w=self.w-self.r_margin-self.x
816        wmax=(w-2*self.c_margin)*1000.0/self.font_size
817        s=txt.replace("\r",'')
818        nb=len(s)
819        if(nb>0 and s[nb-1]=="\n"):
820            nb-=1
821        b=0
822        if(border):
823            if(border==1):
824                border='LTRB'
825                b='LRT'
826                b2='LR'
827            else:
828                b2=''
829                if('L' in border):
830                    b2+='L'
831                if('R' in border):
832                    b2+='R'
833                if ('T' in border):
834                    b=b2+'T'
835                else:
836                    b=b2
837        sep=-1
838        i=0
839        j=0
840        l=0
841        ns=0
842        nl=1
843        while(i<nb):
844            #Get next character
845            c=s[i]
846            if(c=="\n"):
847                #Explicit line break
848                if(self.ws>0):
849                    self.ws=0
850                    if not split_only:
851                        self._out('0 Tw')
852                if not split_only:
853                    self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
854                else:
855                    ret.append(substr(s,j,i-j))
856                i+=1
857                sep=-1
858                j=i
859                l=0
860                ns=0
861                nl+=1
862                if(border and nl==2):
863                    b=b2
864                continue
865            if(c==' '):
866                sep=i
867                ls=l
868                ns+=1
869            if self.unifontsubset:
870                l += self.get_string_width(c, True) / self.font_size*1000.0
871            else:
872                l += cw.get(c,0)
873            if(l>wmax):
874                #Automatic line break
875                if(sep==-1):
876                    if(i==j):
877                        i+=1
878                    if(self.ws>0):
879                        self.ws=0
880                        if not split_only:
881                            self._out('0 Tw')
882                    if not split_only:
883                        self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
884                    else:
885                        ret.append(substr(s,j,i-j))
886                else:
887                    if(align=='J'):
888                        if ns>1:
889                            self.ws=(wmax-ls)/1000.0*self.font_size/(ns-1)
890                        else:
891                            self.ws=0
892                        if not split_only:
893                            self._out(sprintf('%.3f Tw',self.ws*self.k))
894                    if not split_only:
895                        self.cell(w,h,substr(s,j,sep-j),b,2,align,fill)
896                    else:
897                        ret.append(substr(s,j,sep-j))
898                    i=sep+1
899                sep=-1
900                j=i
901                l=0
902                ns=0
903                nl+=1
904                if(border and nl==2):
905                    b=b2
906            else:
907                i+=1
908        #Last chunk
909        if(self.ws>0):
910            self.ws=0
911            if not split_only:
912                self._out('0 Tw')
913        if(border and 'B' in border):
914            b+='B'
915        if not split_only:
916            self.cell(w,h,substr(s,j,i-j),b,2,align,fill)
917            self.x=self.l_margin
918        else:
919            ret.append(substr(s,j,i-j))
920        return ret
921
922    @check_page
923    def write(self, h, txt='', link=''):
924        "Output text in flowing mode"
925        txt = self.normalize_text(txt)
926        cw=self.current_font['cw']
927        w=self.w-self.r_margin-self.x
928        wmax=(w-2*self.c_margin)*1000.0/self.font_size
929        s=txt.replace("\r",'')
930        nb=len(s)
931        sep=-1
932        i=0
933        j=0
934        l=0
935        nl=1
936        while(i<nb):
937            #Get next character
938            c=s[i]
939            if(c=="\n"):
940                #Explicit line break
941                self.cell(w,h,substr(s,j,i-j),0,2,'',0,link)
942                i+=1
943                sep=-1
944                j=i
945                l=0
946                if(nl==1):
947                    self.x=self.l_margin
948                    w=self.w-self.r_margin-self.x
949                    wmax=(w-2*self.c_margin)*1000.0/self.font_size
950                nl+=1
951                continue
952            if(c==' '):
953                sep=i
954            if self.unifontsubset:
955                l += self.get_string_width(c, True) / self.font_size*1000.0
956            else:
957                l += cw.get(c,0)
958            if(l>wmax):
959                #Automatic line break
960                if(sep==-1):
961                    if(self.x>self.l_margin):
962                        #Move to next line
963                        self.x=self.l_margin
964                        self.y+=h
965                        w=self.w-self.r_margin-self.x
966                        wmax=(w-2*self.c_margin)*1000.0/self.font_size
967                        i+=1
968                        nl+=1
969                        continue
970                    if(i==j):
971                        i+=1
972                    self.cell(w,h,substr(s,j,i-j),0,2,'',0,link)
973                else:
974                    self.cell(w,h,substr(s,j,sep-j),0,2,'',0,link)
975                    i=sep+1
976                sep=-1
977                j=i
978                l=0
979                if(nl==1):
980                    self.x=self.l_margin
981                    w=self.w-self.r_margin-self.x
982                    wmax=(w-2*self.c_margin)*1000.0/self.font_size
983                nl+=1
984            else:
985                i+=1
986        #Last chunk
987        if(i!=j):
988            self.cell(l/1000.0*self.font_size,h,substr(s,j),0,0,'',0,link)
989
990    @check_page
991    def image(self, name, x=None, y=None, w=0,h=0,type='',link=''):
992        "Put an image on the page"
993        if not name in self.images:
994            #First use of image, get info
995            if(type==''):
996                pos=name.rfind('.')
997                if(not pos):
998                    self.error('image file has no extension and no type was specified: '+name)
999                type=substr(name,pos+1)
1000            type=type.lower()
1001            if(type=='jpg' or type=='jpeg'):
1002                info=self._parsejpg(name)
1003            elif(type=='png'):
1004                info=self._parsepng(name)
1005            else:
1006                #Allow for additional formats
1007                #maybe the image is not showing the correct extension,
1008                #but the header is OK,
1009                succeed_parsing = False
1010                #try all the parsing functions
1011                parsing_functions = [self._parsejpg,self._parsepng,self._parsegif]
1012                for pf in parsing_functions:
1013                    try:
1014                        info = pf(name)
1015                        succeed_parsing = True
1016                        break;
1017                    except:
1018                        pass
1019                #last resource
1020                if not succeed_parsing:
1021                    mtd='_parse'+type
1022                    if not hasattr(self,mtd):
1023                        self.error('Unsupported image type: '+type)
1024                    info=getattr(self, mtd)(name)
1025                mtd='_parse'+type
1026                if not hasattr(self,mtd):
1027                    self.error('Unsupported image type: '+type)
1028                info=getattr(self, mtd)(name)
1029            info['i']=len(self.images)+1
1030            self.images[name]=info
1031        else:
1032            info=self.images[name]
1033        #Automatic width and height calculation if needed
1034        if(w==0 and h==0):
1035            #Put image at 72 dpi
1036            w=info['w']/self.k
1037            h=info['h']/self.k
1038        elif(w==0):
1039            w=h*info['w']/info['h']
1040        elif(h==0):
1041            h=w*info['h']/info['w']
1042        # Flowing mode
1043        if y is None:
1044            if (self.y + h > self.page_break_trigger and not self.in_footer and self.accept_page_break()):
1045                #Automatic page break
1046                x = self.x
1047                self.add_page(same = True)
1048                self.x = x
1049            y = self.y
1050            self.y += h
1051        if x is None:
1052            x = self.x
1053        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']))
1054        if(link):
1055            self.link(x,y,w,h,link)
1056
1057    @check_page
1058    def ln(self, h=''):
1059        "Line Feed; default value is last cell height"
1060        self.x=self.l_margin
1061        if(isinstance(h, basestring)):
1062            self.y+=self.lasth
1063        else:
1064            self.y+=h
1065
1066    def get_x(self):
1067        "Get x position"
1068        return self.x
1069
1070    def set_x(self, x):
1071        "Set x position"
1072        if(x>=0):
1073            self.x=x
1074        else:
1075            self.x=self.w+x
1076
1077    def get_y(self):
1078        "Get y position"
1079        return self.y
1080
1081    def set_y(self, y):
1082        "Set y position and reset x"
1083        self.x=self.l_margin
1084        if(y>=0):
1085            self.y=y
1086        else:
1087            self.y=self.h+y
1088
1089    def set_xy(self, x,y):
1090        "Set x and y positions"
1091        self.set_y(y)
1092        self.set_x(x)
1093
1094    def output(self, name='',dest=''):
1095        """Output PDF to some destination
1096       
1097        By default the PDF is written to sys.stdout. If a name is given, the
1098        PDF is written to a new file. If dest='S' is given, the PDF data is
1099        returned as a byte string."""
1100       
1101        #Finish document if necessary
1102        if(self.state<3):
1103            self.close()
1104        dest=dest.upper()
1105        if(dest==''):
1106            if(name==''):
1107                dest='I'
1108            else:
1109                dest='F'
1110        if PY3K:
1111            # manage binary data as latin1 until PEP461 or similar is implemented
1112            buffer = self.buffer.encode("latin1")
1113        else:
1114            buffer = self.buffer
1115        if dest in ('I', 'D'):
1116            # Python < 3 writes byte data transparently without "buffer"
1117            stdout = getattr(sys.stdout, 'buffer', sys.stdout)
1118            stdout.write(buffer)
1119        elif dest=='F':
1120            #Save to local file
1121            with open(name,'wb') as f:
1122                f.write(buffer)
1123        elif dest=='S':
1124            #Return as a byte string
1125            return buffer
1126        else:
1127            self.error('Incorrect output destination: '+dest)
1128
1129    def normalize_text(self, txt):
1130        "Check that text input is in the correct format/encoding"
1131        # - for TTF unicode fonts: unicode object (utf8 encoding)
1132        # - for built-in fonts: string instances (encoding: latin-1, cp1252)
1133        if not PY3K:
1134            if self.unifontsubset and isinstance(txt, str):
1135                return txt.decode("utf-8")
1136            elif not self.unifontsubset and isinstance(txt, unicode):
1137                return txt.encode(self.core_fonts_encoding)
1138        else:
1139            if not self.unifontsubset and self.core_fonts_encoding:
1140                return txt.encode(self.core_fonts_encoding).decode("latin-1")
1141        return txt
1142
1143    def _dochecks(self):
1144        #Check for locale-related bug
1145#        if(1.1==1):
1146#            self.error("Don\'t alter the locale before including class file");
1147        #Check for decimal separator
1148        if(sprintf('%.1f',1.0)!='1.0'):
1149            import locale
1150            locale.setlocale(locale.LC_NUMERIC,'C')
1151
1152    def _getfontpath(self):
1153        return FPDF_FONT_DIR+'/'
1154
1155    def _putpages(self):
1156        nb = self.page
1157        if hasattr(self, 'str_alias_nb_pages'):
1158            # Replace number of pages in fonts using subsets (unicode)
1159            alias = UTF8ToUTF16BE(self.str_alias_nb_pages, False)
1160            r = UTF8ToUTF16BE(str(nb), False)
1161            for n in range(1, nb + 1):
1162                self.pages[n]["content"] = \
1163                    self.pages[n]["content"].replace(alias, r)
1164            # Now repeat for no pages in non-subset fonts
1165            for n in range(1,nb + 1):
1166                self.pages[n]["content"] = \
1167                    self.pages[n]["content"].replace(self.str_alias_nb_pages,
1168                        str(nb))
1169        if self.def_orientation == 'P':
1170            dw_pt = self.dw_pt
1171            dh_pt = self.dh_pt
1172        else:
1173            dw_pt = self.dh_pt
1174            dh_pt = self.dw_pt
1175        if self.compress:
1176            filter = '/Filter /FlateDecode '
1177        else:
1178            filter = ''
1179        for n in range(1, nb + 1):
1180            # Page
1181            self._newobj()
1182            self._out('<</Type /Page')
1183            self._out('/Parent 1 0 R')
1184            w_pt = self.pages[n]["w_pt"]
1185            h_pt = self.pages[n]["h_pt"]
1186            if w_pt != dw_pt or h_pt != dh_pt:
1187                self._out(sprintf('/MediaBox [0 0 %.2f %.2f]', w_pt, h_pt))
1188            self._out('/Resources 2 0 R')
1189            if self.page_links and n in self.page_links:
1190                # Links
1191                annots = '/Annots ['
1192                for pl in self.page_links[n]:
1193                    rect = sprintf('%.2f %.2f %.2f %.2f', pl[0], pl[1],
1194                        pl[0] + pl[2], pl[1] - pl[3])
1195                    annots += '<</Type /Annot /Subtype /Link /Rect [' + \
1196                        rect + '] /Border [0 0 0] '
1197                    if isinstance(pl[4], basestring):
1198                        annots += '/A <</S /URI /URI ' + \
1199                            self._textstring(pl[4]) + '>>>>'
1200                    else:
1201                        l = self.links[pl[4]]
1202                        if l[0] in self.orientation_changes:
1203                            h = w_pt
1204                        else:
1205                            h = h_pt
1206                        annots += sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',
1207                            1 + 2 * l[0], h - l[1] * self.k)
1208                self._out(annots + ']')
1209            if self.pdf_version > '1.3':
1210                self._out('/Group <</Type /Group /S /Transparency"\
1211                    "/CS /DeviceRGB>>')
1212            self._out('/Contents ' + str(self.n + 1) + ' 0 R>>')
1213            self._out('endobj')
1214            # Page content
1215            content = self.pages[n]["content"]
1216            if self.compress:
1217                # manage binary data as latin1 until PEP461 or similar is implemented
1218                p = content.encode("latin1") if PY3K else content
1219                p = zlib.compress(p)
1220            else:
1221                p = content
1222            self._newobj()
1223            self._out('<<' + filter + '/Length ' + str(len(p)) + '>>')
1224            self._putstream(p)
1225            self._out('endobj')
1226        # Pages root
1227        self.offsets[1] = len(self.buffer)
1228        self._out('1 0 obj')
1229        self._out('<</Type /Pages')
1230        kids = '/Kids ['
1231        for i in range(0, nb):
1232            kids += str(3 + 2 * i) + ' 0 R '
1233        self._out(kids + ']')
1234        self._out('/Count ' + str(nb))
1235        self._out(sprintf('/MediaBox [0 0 %.2f %.2f]', dw_pt, dh_pt))
1236        self._out('>>')
1237        self._out('endobj')
1238
1239    def _putfonts(self):
1240        nf=self.n
1241        for diff in self.diffs:
1242            #Encodings
1243            self._newobj()
1244            self._out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+self.diffs[diff]+']>>')
1245            self._out('endobj')
1246        for name,info in self.font_files.items():
1247            if 'type' in info and info['type'] != 'TTF':
1248                #Font file embedding
1249                self._newobj()
1250                self.font_files[name]['n']=self.n
1251                with open(self._getfontpath()+name,'rb',1) as f:
1252                    font=f.read()
1253                compressed=(substr(name,-2)=='.z')
1254                if(not compressed and 'length2' in info):
1255                    header=(ord(font[0])==128)
1256                    if(header):
1257                        #Strip first binary header
1258                        font=substr(font,6)
1259                    if(header and ord(font[info['length1']])==128):
1260                        #Strip second binary header
1261                        font=substr(font,0,info['length1'])+substr(font,info['length1']+6)
1262                self._out('<</Length '+str(len(font)))
1263                if(compressed):
1264                    self._out('/Filter /FlateDecode')
1265                self._out('/Length1 '+str(info['length1']))
1266                if('length2' in info):
1267                    self._out('/Length2 '+str(info['length2'])+' /Length3 0')
1268                self._out('>>')
1269                self._putstream(font)
1270                self._out('endobj')
1271        flist = [(x[1]["i"],x[0],x[1]) for x in self.fonts.items()]
1272        flist.sort()
1273        for idx,k,font in flist:
1274            #Font objects
1275            self.fonts[k]['n']=self.n+1
1276            type=font['type']
1277            name=font['name']
1278            if(type=='core'):
1279                #Standard font
1280                self._newobj()
1281                self._out('<</Type /Font')
1282                self._out('/BaseFont /'+name)
1283                self._out('/Subtype /Type1')
1284                if(name!='Symbol' and name!='ZapfDingbats'):
1285                    self._out('/Encoding /WinAnsiEncoding')
1286                self._out('>>')
1287                self._out('endobj')
1288            elif(type=='Type1' or type=='TrueType'):
1289                #Additional Type1 or TrueType font
1290                self._newobj()
1291                self._out('<</Type /Font')
1292                self._out('/BaseFont /'+name)
1293                self._out('/Subtype /'+type)
1294                self._out('/FirstChar 32 /LastChar 255')
1295                self._out('/Widths '+str(self.n+1)+' 0 R')
1296                self._out('/FontDescriptor '+str(self.n+2)+' 0 R')
1297                if(font['enc']):
1298                    if('diff' in font):
1299                        self._out('/Encoding '+str(nf+font['diff'])+' 0 R')
1300                    else:
1301                        self._out('/Encoding /WinAnsiEncoding')
1302                self._out('>>')
1303                self._out('endobj')
1304                #Widths
1305                self._newobj()
1306                cw=font['cw']
1307                s='['
1308                for i in range(32,256):
1309                    # Get doesn't raise exception; returns 0 instead of None if not set
1310                    s+=str(cw.get(chr(i)) or 0)+' '
1311                self._out(s+']')
1312                self._out('endobj')
1313                #Descriptor
1314                self._newobj()
1315                s='<</Type /FontDescriptor /FontName /'+name
1316                for k in ('Ascent', 'Descent', 'CapHeight', 'Flags', 'FontBBox', 'ItalicAngle', 'StemV', 'MissingWidth'):
1317                    s += ' /%s %s' % (k, font['desc'][k])
1318                filename=font['file']
1319                if(filename):
1320                    s+=' /FontFile'
1321                    if type!='Type1':
1322                        s+='2'
1323                    s+=' '+str(self.font_files[filename]['n'])+' 0 R'
1324                self._out(s+'>>')
1325                self._out('endobj')
1326            elif (type == 'TTF'):
1327                self.fonts[k]['n'] = self.n + 1
1328                ttf = TTFontFile()
1329                fontname = 'MPDFAA' + '+' + font['name']
1330                subset = font['subset']
1331                del subset[0]
1332                ttfontstream = ttf.makeSubset(font['ttffile'], subset)
1333                ttfontsize = len(ttfontstream)
1334                fontstream = zlib.compress(ttfontstream)
1335                codeToGlyph = ttf.codeToGlyph
1336                ##del codeToGlyph[0]
1337                # Type0 Font
1338                # A composite font - a font composed of other fonts, organized hierarchically
1339                self._newobj()
1340                self._out('<</Type /Font');
1341                self._out('/Subtype /Type0');
1342                self._out('/BaseFont /' + fontname + '');
1343                self._out('/Encoding /Identity-H');
1344                self._out('/DescendantFonts [' + str(self.n + 1) + ' 0 R]')
1345                self._out('/ToUnicode ' + str(self.n + 2) + ' 0 R')
1346                self._out('>>')
1347                self._out('endobj')
1348
1349                # CIDFontType2
1350                # A CIDFont whose glyph descriptions are based on TrueType font technology
1351                self._newobj()
1352                self._out('<</Type /Font')
1353                self._out('/Subtype /CIDFontType2')
1354                self._out('/BaseFont /' + fontname + '')
1355                self._out('/CIDSystemInfo ' + str(self.n + 2) + ' 0 R')
1356                self._out('/FontDescriptor ' + str(self.n + 3) + ' 0 R')
1357                if (font['desc'].get('MissingWidth')):
1358                    self._out('/DW %d' % font['desc']['MissingWidth'])
1359                self._putTTfontwidths(font, ttf.maxUni)
1360                self._out('/CIDToGIDMap ' + str(self.n + 4) + ' 0 R')
1361                self._out('>>')
1362                self._out('endobj')
1363
1364                # ToUnicode
1365                self._newobj()
1366                toUni = "/CIDInit /ProcSet findresource begin\n" \
1367                        "12 dict begin\n" \
1368                        "begincmap\n" \
1369                        "/CIDSystemInfo\n" \
1370                        "<</Registry (Adobe)\n" \
1371                        "/Ordering (UCS)\n" \
1372                        "/Supplement 0\n" \
1373                        ">> def\n" \
1374                        "/CMapName /Adobe-Identity-UCS def\n" \
1375                        "/CMapType 2 def\n" \
1376                        "1 begincodespacerange\n" \
1377                        "<0000> <FFFF>\n" \
1378                        "endcodespacerange\n" \
1379                        "1 beginbfrange\n" \
1380                        "<0000> <FFFF> <0000>\n" \
1381                        "endbfrange\n" \
1382                        "endcmap\n" \
1383                        "CMapName currentdict /CMap defineresource pop\n" \
1384                        "end\n" \
1385                        "end"
1386                self._out('<</Length ' + str(len(toUni)) + '>>')
1387                self._putstream(toUni)
1388                self._out('endobj')
1389
1390                # CIDSystemInfo dictionary
1391                self._newobj()
1392                self._out('<</Registry (Adobe)')
1393                self._out('/Ordering (UCS)')
1394                self._out('/Supplement 0')
1395                self._out('>>')
1396                self._out('endobj')
1397
1398                # Font descriptor
1399                self._newobj()
1400                self._out('<</Type /FontDescriptor')
1401                self._out('/FontName /' + fontname)
1402                for kd in ('Ascent', 'Descent', 'CapHeight', 'Flags', 'FontBBox', 'ItalicAngle', 'StemV', 'MissingWidth'):
1403                    v = font['desc'][kd]
1404                    if (kd == 'Flags'):
1405                        v = v | 4;
1406                        v = v & ~32; # SYMBOLIC font flag
1407                    self._out(' /%s %s' % (kd, v))
1408                self._out('/FontFile2 ' + str(self.n + 2) + ' 0 R')
1409                self._out('>>')
1410                self._out('endobj')
1411
1412                # Embed CIDToGIDMap
1413                # A specification of the mapping from CIDs to glyph indices
1414                cidtogidmap = '';
1415                cidtogidmap = ["\x00"] * 256*256*2
1416                for cc, glyph in codeToGlyph.items():
1417                    cidtogidmap[cc*2] = chr(glyph >> 8)
1418                    cidtogidmap[cc*2 + 1] = chr(glyph & 0xFF)
1419                cidtogidmap = ''.join(cidtogidmap)
1420                if PY3K:
1421                    # manage binary data as latin1 until PEP461-like function is implemented
1422                    cidtogidmap = cidtogidmap.encode("latin1")
1423                cidtogidmap = zlib.compress(cidtogidmap);
1424                self._newobj()
1425                self._out('<</Length ' + str(len(cidtogidmap)) + '')
1426                self._out('/Filter /FlateDecode')
1427                self._out('>>')
1428                self._putstream(cidtogidmap)
1429                self._out('endobj')
1430
1431                #Font file
1432                self._newobj()
1433                self._out('<</Length ' + str(len(fontstream)))
1434                self._out('/Filter /FlateDecode')
1435                self._out('/Length1 ' + str(ttfontsize))
1436                self._out('>>')
1437                self._putstream(fontstream)
1438                self._out('endobj')
1439                del ttf
1440            else:
1441                #Allow for additional types
1442                mtd='_put'+type.lower()
1443                if(not method_exists(self,mtd)):
1444                    self.error('Unsupported font type: '+type)
1445                self.mtd(font)
1446
1447    def _putTTfontwidths(self, font, maxUni):
1448        if font['unifilename']:
1449            cw127fname = os.path.splitext(font['unifilename'])[0] + '.cw127.pkl'
1450        else:
1451            cw127fname = None
1452        font_dict = load_cache(cw127fname)
1453        if font_dict is None:   
1454            rangeid = 0
1455            range_ = {}
1456            range_interval = {}
1457            prevcid = -2
1458            prevwidth = -1
1459            interval = False
1460            startcid = 1
1461        else:
1462            rangeid = font_dict['rangeid']
1463            range_ = font_dict['range']
1464            prevcid = font_dict['prevcid']
1465            prevwidth = font_dict['prevwidth']
1466            interval = font_dict['interval']
1467            range_interval = font_dict['range_interval']
1468            startcid = 128
1469        cwlen = maxUni + 1
1470
1471        # for each character
1472        subset = set(font['subset'])
1473        for cid in range(startcid, cwlen):
1474            if cid == 128 and cw127fname and not os.path.exists(cw127fname):
1475                try:
1476                    with open(cw127fname, "wb") as fh:
1477                        font_dict = {}
1478                        font_dict['rangeid'] = rangeid
1479                        font_dict['prevcid'] = prevcid
1480                        font_dict['prevwidth'] = prevwidth
1481                        font_dict['interval'] = interval
1482                        font_dict['range_interval'] = range_interval
1483                        font_dict['range'] = range_
1484                        pickle.dump(font_dict, fh)
1485                except IOError:
1486                    if not exception().errno == errno.EACCES:
1487                        raise  # Not a permission error.
1488            if cid > 255 and (cid not in subset): #
1489                continue
1490            width = font['cw'][cid]
1491            if (width == 0):
1492                continue
1493            if (width == 65535): width = 0
1494            if ('dw' not in font or (font['dw'] and width != font['dw'])):
1495                if (cid == (prevcid + 1)):
1496                    if (width == prevwidth):
1497                        if (width == range_[rangeid][0]):
1498                            range_.setdefault(rangeid, []).append(width)
1499                        else:
1500                            range_[rangeid].pop()
1501                            # new range
1502                            rangeid = prevcid
1503                            range_[rangeid] = [prevwidth, width]
1504                        interval = True
1505                        range_interval[rangeid] = True
1506                    else:
1507                        if (interval):
1508                            # new range
1509                            rangeid = cid
1510                            range_[rangeid] = [width]
1511                        else:
1512                            range_[rangeid].append(width)
1513                        interval = False
1514                else:
1515                    rangeid = cid
1516                    range_[rangeid] = [width]
1517                    interval = False
1518                prevcid = cid
1519                prevwidth = width
1520        prevk = -1
1521        nextk = -1
1522        prevint = False
1523        for k, ws in sorted(range_.items()):
1524            cws = len(ws)
1525            if (k == nextk and not prevint and (not k in range_interval or cws < 3)):
1526                if (k in range_interval):
1527                    del range_interval[k]
1528                range_[prevk] = range_[prevk] + range_[k]
1529                del range_[k]
1530            else:
1531                prevk = k
1532            nextk = k + cws
1533            if (k in range_interval):
1534                prevint = (cws > 3)
1535                del range_interval[k]
1536                nextk -= 1
1537            else:
1538                prevint = False
1539        w = []
1540        for k, ws in sorted(range_.items()):
1541            if (len(set(ws)) == 1):
1542                w.append(' %s %s %s' % (k, k + len(ws) - 1, ws[0]))
1543            else:
1544                w.append(' %s [ %s ]\n' % (k, ' '.join([str(int(h)) for h in ws]))) ##
1545        self._out('/W [%s]' % ''.join(w))
1546
1547    def _putimages(self):
1548        filter=''
1549        if self.compress:
1550            filter='/Filter /FlateDecode '           
1551        i = [(x[1]["i"],x[1]) for x in self.images.items()]
1552        i.sort()
1553        for idx,info in i:
1554            self._putimage(info)
1555            del info['data']
1556            if 'smask' in info:
1557                del info['smask']
1558
1559    def _putimage(self, info):
1560        if 'data' in info:
1561            self._newobj()
1562            info['n']=self.n
1563            self._out('<</Type /XObject')
1564            self._out('/Subtype /Image')
1565            self._out('/Width '+str(info['w']))
1566            self._out('/Height '+str(info['h']))
1567            if(info['cs']=='Indexed'):
1568                self._out('/ColorSpace [/Indexed /DeviceRGB '+str(len(info['pal'])//3-1)+' '+str(self.n+1)+' 0 R]')
1569            else:
1570                self._out('/ColorSpace /'+info['cs'])
1571                if(info['cs']=='DeviceCMYK'):
1572                    self._out('/Decode [1 0 1 0 1 0 1 0]')
1573            self._out('/BitsPerComponent '+str(info['bpc']))
1574            if 'f' in info:
1575                self._out('/Filter /'+info['f'])
1576            if 'dp' in info:
1577                self._out('/DecodeParms <<' + info['dp'] + '>>')
1578            if('trns' in info and isinstance(info['trns'], list)):
1579                trns=''
1580                for i in range(0,len(info['trns'])):
1581                    trns+=str(info['trns'][i])+' '+str(info['trns'][i])+' '
1582                self._out('/Mask ['+trns+']')
1583            if('smask' in info):
1584                self._out('/SMask ' + str(self.n+1) + ' 0 R');
1585            self._out('/Length '+str(len(info['data']))+'>>')
1586            self._putstream(info['data'])
1587            self._out('endobj')
1588            # Soft mask
1589            if('smask' in info):
1590                dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' + str(info['w'])
1591                smask = {'w': info['w'], 'h': info['h'], 'cs': 'DeviceGray', 'bpc': 8, 'f': info['f'], 'dp': dp, 'data': info['smask']}
1592                self._putimage(smask)
1593            #Palette
1594            if(info['cs']=='Indexed'):
1595                self._newobj()
1596                filter = self.compress and '/Filter /FlateDecode ' or ''
1597                if self.compress:
1598                    pal=zlib.compress(info['pal'])
1599                else:
1600                    pal=info['pal']
1601                self._out('<<'+filter+'/Length '+str(len(pal))+'>>')
1602                self._putstream(pal)
1603                self._out('endobj')
1604
1605    def _putxobjectdict(self):
1606        i = [(x["i"],x["n"]) for x in self.images.values()]
1607        i.sort()
1608        for idx,n in i:
1609            self._out('/I'+str(idx)+' '+str(n)+' 0 R')
1610
1611    def _putresourcedict(self):
1612        self._out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]')
1613        self._out('/Font <<')
1614        f = [(x["i"],x["n"]) for x in self.fonts.values()]
1615        f.sort()
1616        for idx,n in f:
1617            self._out('/F'+str(idx)+' '+str(n)+' 0 R')
1618        self._out('>>')
1619        self._out('/XObject <<')
1620        self._putxobjectdict()
1621        self._out('>>')
1622
1623    def _putresources(self):
1624        self._putfonts()
1625        self._putimages()
1626        #Resource dictionary
1627        self.offsets[2]=len(self.buffer)
1628        self._out('2 0 obj')
1629        self._out('<<')
1630        self._putresourcedict()
1631        self._out('>>')
1632        self._out('endobj')
1633
1634    def _putinfo(self):
1635        self._out('/Producer '+self._textstring('PyFPDF '+FPDF_VERSION+' http://pyfpdf.googlecode.com/'))
1636        if hasattr(self,'title'):
1637            self._out('/Title '+self._textstring(self.title))
1638        if hasattr(self,'subject'):
1639            self._out('/Subject '+self._textstring(self.subject))
1640        if hasattr(self,'author'):
1641            self._out('/Author '+self._textstring(self.author))
1642        if hasattr (self,'keywords'):
1643            self._out('/Keywords '+self._textstring(self.keywords))
1644        if hasattr(self,'creator'):
1645            self._out('/Creator '+self._textstring(self.creator))
1646        self._out('/CreationDate '+self._textstring('D:'+datetime.now().strftime('%Y%m%d%H%M%S')))
1647
1648    def _putcatalog(self):
1649        self._out('/Type /Catalog')
1650        self._out('/Pages 1 0 R')
1651        if(self.zoom_mode=='fullpage'):
1652            self._out('/OpenAction [3 0 R /Fit]')
1653        elif(self.zoom_mode=='fullwidth'):
1654            self._out('/OpenAction [3 0 R /FitH null]')
1655        elif(self.zoom_mode=='real'):
1656            self._out('/OpenAction [3 0 R /XYZ null null 1]')
1657        elif(not isinstance(self.zoom_mode,basestring)):
1658            self._out(sprintf('/OpenAction [3 0 R /XYZ null null %s]',self.zoom_mode/100))
1659        if(self.layout_mode=='single'):
1660            self._out('/PageLayout /SinglePage')
1661        elif(self.layout_mode=='continuous'):
1662            self._out('/PageLayout /OneColumn')
1663        elif(self.layout_mode=='two'):
1664            self._out('/PageLayout /TwoColumnLeft')
1665
1666    def _putheader(self):
1667        self._out('%PDF-'+self.pdf_version)
1668
1669    def _puttrailer(self):
1670        self._out('/Size '+str(self.n+1))
1671        self._out('/Root '+str(self.n)+' 0 R')
1672        self._out('/Info '+str(self.n-1)+' 0 R')
1673
1674    def _enddoc(self):
1675        self._putheader()
1676        self._putpages()
1677        self._putresources()
1678        #Info
1679        self._newobj()
1680        self._out('<<')
1681        self._putinfo()
1682        self._out('>>')
1683        self._out('endobj')
1684        #Catalog
1685        self._newobj()
1686        self._out('<<')
1687        self._putcatalog()
1688        self._out('>>')
1689        self._out('endobj')
1690        #Cross-ref
1691        o=len(self.buffer)
1692        self._out('xref')
1693        self._out('0 '+(str(self.n+1)))
1694        self._out('0000000000 65535 f ')
1695        for i in range(1,self.n+1):
1696            self._out(sprintf('%010d 00000 n ',self.offsets[i]))
1697        #Trailer
1698        self._out('trailer')
1699        self._out('<<')
1700        self._puttrailer()
1701        self._out('>>')
1702        self._out('startxref')
1703        self._out(o)
1704        self._out('%%EOF')
1705        self.state=3
1706
1707    def _beginpage(self, orientation, format, same):
1708        self.page += 1
1709        self.pages[self.page] = {"content": ""}
1710        self.state = 2
1711        self.x = self.l_margin
1712        self.y = self.t_margin
1713        self.font_family = ''
1714        self.font_stretching = 100
1715        if not same:
1716            # Page format
1717            if format:
1718                # Change page format
1719                self.fw_pt, self.fh_pt = self.get_page_format(format, self.k)
1720            else:
1721                # Set to default format
1722                self.fw_pt = self.dw_pt
1723                self.fh_pt = self.dh_pt
1724            self.fw = self.fw_pt / self.k
1725            self.fh = self.fh_pt / self.k
1726            # Page orientation
1727            if not orientation:
1728                orientation = self.def_orientation
1729            else:
1730                orientation = orientation[0].upper()
1731            if orientation == 'P':
1732                self.w_pt = self.fw_pt
1733                self.h_pt = self.fh_pt
1734            else:                   
1735                self.w_pt = self.fh_pt
1736                self.h_pt = self.fw_pt
1737            self.w = self.w_pt / self.k
1738            self.h = self.h_pt / self.k
1739            self.cur_orientation = orientation
1740            self.page_break_trigger = self.h - self.b_margin
1741            self.cur_orientation = orientation
1742        self.pages[self.page]["w_pt"] = self.w_pt
1743        self.pages[self.page]["h_pt"] = self.h_pt
1744
1745    def _endpage(self):
1746        #End of page contents
1747        self.state=1
1748
1749    def _newobj(self):
1750        #Begin a new object
1751        self.n+=1
1752        self.offsets[self.n]=len(self.buffer)
1753        self._out(str(self.n)+' 0 obj')
1754
1755    def _dounderline(self, x, y, txt):
1756        #Underline text
1757        up=self.current_font['up']
1758        ut=self.current_font['ut']
1759        w=self.get_string_width(txt, True)+self.ws*txt.count(' ')
1760        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)
1761
1762    def load_resource(self, reason, filename):
1763        "Load external file"
1764        # by default loading from network is allowed for all images
1765        if reason == "image":
1766            if filename.startswith("http://") or filename.startswith("https://"):
1767                f = BytesIO(urlopen(filename).read())
1768            else:
1769                f = open(filename, "rb")
1770            return f
1771        else:
1772            self.error("Unknown resource loading reason \"%s\"" % reason)
1773
1774    def _parsejpg(self, filename):
1775        # Extract info from a JPEG file
1776        f = None
1777        try:
1778            f = self.load_resource("image", filename)
1779            while True:
1780                markerHigh, markerLow = struct.unpack('BB', f.read(2))
1781                if markerHigh != 0xFF or markerLow < 0xC0:
1782                    raise SyntaxError('No JPEG marker found')
1783                elif markerLow == 0xDA: # SOS
1784                    raise SyntaxError('No JPEG SOF marker found')
1785                elif (markerLow == 0xC8 or # JPG
1786                      (markerLow >= 0xD0 and markerLow <= 0xD9) or # RSTx
1787                      (markerLow >= 0xF0 and markerLow <= 0xFD)): # JPGx
1788                    pass
1789                else:
1790                    dataSize, = struct.unpack('>H', f.read(2))
1791                    data = f.read(dataSize - 2) if dataSize > 2 else ''
1792                    if ((markerLow >= 0xC0 and markerLow <= 0xC3) or # SOF0 - SOF3
1793                        (markerLow >= 0xC5 and markerLow <= 0xC7) or # SOF4 - SOF7
1794                        (markerLow >= 0xC9 and markerLow <= 0xCB) or # SOF9 - SOF11
1795                        (markerLow >= 0xCD and markerLow <= 0xCF)): # SOF13 - SOF15
1796                        bpc, height, width, layers = struct.unpack_from('>BHHB', data)
1797                        colspace = 'DeviceRGB' if layers == 3 else ('DeviceCMYK' if layers == 4 else 'DeviceGray')
1798                        break
1799        except Exception:
1800            if f:
1801                f.close()
1802            self.error('Missing or incorrect image file: %s. error: %s' % (filename, str(exception())))
1803
1804        with f:
1805            # Read whole file from the start
1806            f.seek(0)
1807            data = f.read()
1808        return {'w':width,'h':height,'cs':colspace,'bpc':bpc,'f':'DCTDecode','data':data}
1809
1810    def _parsegif(self, filename):
1811        # Extract info from a GIF file (via PNG conversion)
1812        if Image is None:
1813            self.error('PIL is required for GIF support')
1814        try:
1815            im = Image.open(filename)
1816        except Exception:
1817            self.error('Missing or incorrect image file: %s. error: %s' % (filename, str(exception())))
1818        else:
1819            # Use temporary file
1820            with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as \
1821                    f:
1822                tmp = f.name
1823            if "transparency" in im.info:
1824                im.save(tmp, transparency = im.info['transparency'])
1825            else:
1826                im.save(tmp)
1827            info = self._parsepng(tmp)
1828            os.unlink(tmp)
1829        return info
1830
1831    def _parsepng(self, filename):
1832        #Extract info from a PNG file
1833        f = self.load_resource("image", filename)
1834        #Check signature
1835        magic = f.read(8).decode("latin1")
1836        signature = '\x89'+'PNG'+'\r'+'\n'+'\x1a'+'\n'
1837        if not PY3K: signature = signature.decode("latin1")
1838        if(magic!=signature):
1839            self.error('Not a PNG file: ' + filename)
1840        #Read header chunk
1841        f.read(4)
1842        chunk = f.read(4).decode("latin1")
1843        if(chunk!='IHDR'):
1844            self.error('Incorrect PNG file: ' + filename)
1845        w=self._freadint(f)
1846        h=self._freadint(f)
1847        bpc=ord(f.read(1))
1848        if(bpc>8):
1849            self.error('16-bit depth not supported: ' + filename)
1850        ct=ord(f.read(1))
1851        if(ct==0 or ct==4):
1852            colspace='DeviceGray'
1853        elif(ct==2 or ct==6):
1854            colspace='DeviceRGB'
1855        elif(ct==3):
1856            colspace='Indexed'
1857        else:
1858            self.error('Unknown color type: ' + filename)
1859        if(ord(f.read(1))!=0):
1860            self.error('Unknown compression method: ' + filename)
1861        if(ord(f.read(1))!=0):
1862            self.error('Unknown filter method: ' + filename)
1863        if(ord(f.read(1))!=0):
1864            self.error('Interlacing not supported: ' + filename)
1865        f.read(4)
1866        dp='/Predictor 15 /Colors '
1867        if colspace == 'DeviceRGB':
1868            dp+='3'
1869        else:
1870            dp+='1'
1871        dp+=' /BitsPerComponent '+str(bpc)+' /Columns '+str(w)+''
1872        #Scan chunks looking for palette, transparency and image data
1873        pal=''
1874        trns=''
1875        data=bytes() if PY3K else str()
1876        n=1
1877        while n != None:
1878            n=self._freadint(f)
1879            type=f.read(4).decode("latin1")
1880            if(type=='PLTE'):
1881                #Read palette
1882                pal=f.read(n)
1883                f.read(4)
1884            elif(type=='tRNS'):
1885                #Read transparency info
1886                t=f.read(n)
1887                if(ct==0):
1888                    trns=[ord(substr(t,1,1)),]
1889                elif(ct==2):
1890                    trns=[ord(substr(t,1,1)),ord(substr(t,3,1)),ord(substr(t,5,1))]
1891                else:
1892                    pos=t.find('\x00'.encode("latin1"))
1893                    if(pos!=-1):
1894                        trns=[pos,]
1895                f.read(4)
1896            elif(type=='IDAT'):
1897                #Read image data block
1898                data+=f.read(n)
1899                f.read(4)
1900            elif(type=='IEND'):
1901                break
1902            else:
1903                f.read(n+4)
1904        if(colspace=='Indexed' and not pal):
1905            self.error('Missing palette in ' + filename)
1906        f.close()
1907        info = {'w':w,'h':h,'cs':colspace,'bpc':bpc,'f':'FlateDecode','dp':dp,'pal':pal,'trns':trns,}
1908        if(ct>=4):
1909            # Extract alpha channel
1910            data = zlib.decompress(data)
1911            color = b('')
1912            alpha = b('')
1913            if(ct==4):
1914                # Gray image
1915                length = 2*w
1916                for i in range(h):
1917                    pos = (1+length)*i
1918                    color += b(data[pos])
1919                    alpha += b(data[pos])
1920                    line = substr(data, pos+1, length)
1921                    re_c = re.compile('(.).'.encode("ascii"), flags=re.DOTALL)
1922                    re_a = re.compile('.(.)'.encode("ascii"), flags=re.DOTALL)
1923                    color += re_c.sub(lambda m: m.group(1), line)
1924                    alpha += re_a.sub(lambda m: m.group(1), line)
1925            else:
1926                # RGB image
1927                length = 4*w
1928                for i in range(h):
1929                    pos = (1+length)*i
1930                    color += b(data[pos])
1931                    alpha += b(data[pos])
1932                    line = substr(data, pos+1, length)
1933                    re_c = re.compile('(...).'.encode("ascii"), flags=re.DOTALL)
1934                    re_a = re.compile('...(.)'.encode("ascii"), flags=re.DOTALL)
1935                    color += re_c.sub(lambda m: m.group(1), line)
1936                    alpha += re_a.sub(lambda m: m.group(1), line)
1937            del data
1938            data = zlib.compress(color)
1939            info['smask'] = zlib.compress(alpha)
1940            if (self.pdf_version < '1.4'):
1941                self.pdf_version = '1.4'
1942        info['data'] = data
1943        return info
1944
1945    def _freadint(self, f):
1946        #Read a 4-byte integer from file
1947        try:
1948            return struct.unpack('>I', f.read(4))[0]
1949        except:
1950            return None
1951
1952    def _textstring(self, s):
1953        #Format a text string
1954        return '('+self._escape(s)+')'
1955
1956    def _escape(self, s):
1957        #Add \ before \, ( and )
1958        return s.replace('\\','\\\\').replace(')','\\)').replace('(','\\(').replace('\r','\\r')
1959
1960    def _putstream(self, s):
1961        self._out('stream')
1962        self._out(s)
1963        self._out('endstream')
1964
1965    def _out(self, s):
1966        #Add a line to the document
1967        if PY3K and isinstance(s, bytes):
1968            # manage binary data as latin1 until PEP461-like function is implemented
1969            s = s.decode("latin1")         
1970        elif not PY3K and isinstance(s, unicode):
1971            s = s.encode("latin1")    # default encoding (font name and similar)     
1972        elif not isinstance(s, basestring):
1973            s = str(s)
1974        if(self.state == 2):
1975            self.pages[self.page]["content"] += (s + "\n")
1976        else:
1977            self.buffer += (s + "\n")
1978
1979    @check_page
1980    def interleaved2of5(self, txt, x, y, w=1.0, h=10.0):
1981        "Barcode I2of5 (numeric), adds a 0 if odd lenght"
1982        narrow = w / 3.0
1983        wide = w
1984
1985        # wide/narrow codes for the digits
1986        bar_char={'0': 'nnwwn', '1': 'wnnnw', '2': 'nwnnw', '3': 'wwnnn',
1987                  '4': 'nnwnw', '5': 'wnwnn', '6': 'nwwnn', '7': 'nnnww',
1988                  '8': 'wnnwn', '9': 'nwnwn', 'A': 'nn', 'Z': 'wn'}
1989
1990        self.set_fill_color(0)
1991        code = txt
1992        # add leading zero if code-length is odd
1993        if len(code) % 2 != 0:
1994            code = '0' + code
1995
1996        # add start and stop codes
1997        code = 'AA' + code.lower() + 'ZA'
1998
1999        for i in range(0, len(code), 2):
2000            # choose next pair of digits
2001            char_bar = code[i]
2002            char_space = code[i+1]
2003            # check whether it is a valid digit
2004            if not char_bar in bar_char.keys():
2005                raise RuntimeError ('Char "%s" invalid for I25: ' % char_bar)
2006            if not char_space in bar_char.keys():
2007                raise RuntimeError ('Char "%s" invalid for I25: ' % char_space)
2008
2009            # create a wide/narrow-seq (first digit=bars, second digit=spaces)
2010            seq = ''
2011            for s in range(0, len(bar_char[char_bar])):
2012                seq += bar_char[char_bar][s] + bar_char[char_space][s]
2013
2014            for bar in range(0, len(seq)):
2015                # set line_width depending on value
2016                if seq[bar] == 'n':
2017                    line_width = narrow
2018                else:
2019                    line_width = wide
2020
2021                # draw every second value, the other is represented by space
2022                if bar % 2 == 0:
2023                    self.rect(x, y, line_width, h, 'F')
2024
2025                x += line_width
2026
2027
2028    @check_page
2029    def code39(self, txt, x, y, w=1.5, h=5.0):
2030        """Barcode 3of9"""
2031        dim = {'w': w, 'n': w/3.}
2032        chars = {
2033            '0': 'nnnwwnwnn', '1': 'wnnwnnnnw', '2': 'nnwwnnnnw',
2034            '3': 'wnwwnnnnn', '4': 'nnnwwnnnw', '5': 'wnnwwnnnn',
2035            '6': 'nnwwwnnnn', '7': 'nnnwnnwnw', '8': 'wnnwnnwnn',
2036            '9': 'nnwwnnwnn', 'A': 'wnnnnwnnw', 'B': 'nnwnnwnnw',
2037            'C': 'wnwnnwnnn', 'D': 'nnnnwwnnw', 'E': 'wnnnwwnnn',
2038            'F': 'nnwnwwnnn', 'G': 'nnnnnwwnw', 'H': 'wnnnnwwnn',
2039            'I': 'nnwnnwwnn', 'J': 'nnnnwwwnn', 'K': 'wnnnnnnww',
2040            'L': 'nnwnnnnww', 'M': 'wnwnnnnwn', 'N': 'nnnnwnnww',
2041            'O': 'wnnnwnnwn', 'P': 'nnwnwnnwn', 'Q': 'nnnnnnwww',
2042            'R': 'wnnnnnwwn', 'S': 'nnwnnnwwn', 'T': 'nnnnwnwwn',
2043            'U': 'wwnnnnnnw', 'V': 'nwwnnnnnw', 'W': 'wwwnnnnnn',
2044            'X': 'nwnnwnnnw', 'Y': 'wwnnwnnnn', 'Z': 'nwwnwnnnn',
2045            '-': 'nwnnnnwnw', '.': 'wwnnnnwnn', ' ': 'nwwnnnwnn',
2046            '*': 'nwnnwnwnn', '$': 'nwnwnwnnn', '/': 'nwnwnnnwn',
2047            '+': 'nwnnnwnwn', '%': 'nnnwnwnwn',
2048        }
2049        self.set_fill_color(0)
2050        for c in txt.upper():
2051            if c not in chars:
2052                raise RuntimeError('Invalid char "%s" for Code39' % c)
2053            for i, d in enumerate(chars[c]):
2054                if i % 2 == 0:
2055                    self.rect(x, y, dim[d], h, 'F')
2056                x += dim[d]
2057            x += dim['n']
2058
2059
Note: See TracBrowser for help on using the repository browser.