#!/usr/bin/env python # -*- coding: latin-1 -*- # **************************************************************************** # * Software: FPDF for python * # * Version: 1.7.1 * # * Date: 2010-09-10 * # * Last update: 2012-08-16 * # * 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 * # **************************************************************************** from __future__ import division from datetime import datetime from functools import wraps import math import errno import os, sys, zlib, struct, re, tempfile, struct from .ttfonts import TTFontFile from .fonts import fpdf_charwidths from .php import substr, sprintf, print_r, UTF8ToUTF16BE, UTF8StringToArray from .py3k import PY3K, pickle, urlopen, Image, basestring, unicode, exception, b, hashpath # Global variables FPDF_VERSION = '1.7.2' 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'): # 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' 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.x0): #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 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" 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 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 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" 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} def set_font(self, family,style='',size=0): "Select a font; size given in points" 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)) def set_font_size(self, size): "Set font size in points" 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)) 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" 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) @check_page def rotate(self, angle, x=None, y=None): 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) 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" 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)0): #Go to next line self.y+=h if(ln==1): self.x=self.l_margin else: self.x+=w @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" 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(i0): 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 @check_page def write(self, h, txt='', link=''): "Output text in flowing mode" 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(iwmax): #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) @check_page def image(self, name, x=None, y=None, w=0,h=0,type='',link=''): "Put an image on the page" 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) @check_page def ln(self, h=''): "Line Feed; default value is last cell height" self.x=self.l_margin if(isinstance(h, basestring)): self.y+=self.lasth else: self.y+=h 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" #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 latin1 until PEP461 or similar is implemented f.write(self.buffer.encode("latin1")) 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 '' 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) 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('latin1') return txt 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 if(sprintf('%.1f',1.0)!='1.0'): import locale locale.setlocale(locale.LC_NUMERIC,'C') 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('<>>>' 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 <>') self._out('/Contents '+str(self.n+1)+' 0 R>>') self._out('endobj') #Page content if self.compress: # manage binary data as latin1 until PEP461 or similar is implemented p = self.pages[n].encode("latin1") 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('<>') self._out('endobj') def _putfonts(self): nf=self.n for diff in self.diffs: #Encodings self._newobj() self._out('<>') 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('<>') 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('<>') self._out('endobj') elif(type=='Type1' or type=='TrueType'): #Additional Type1 or TrueType font self._newobj() 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='<>') 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('<>') self._out('endobj') # CIDFontType2 # A CIDFont whose glyph descriptions are based on TrueType font technology self._newobj() self._out('<>') self._out('endobj') # ToUnicode self._newobj() toUni = "/CIDInit /ProcSet findresource begin\n" \ "12 dict begin\n" \ "begincmap\n" \ "/CIDSystemInfo\n" \ "<> def\n" \ "/CMapName /Adobe-Identity-UCS def\n" \ "/CMapType 2 def\n" \ "1 begincodespacerange\n" \ "<0000> \n" \ "endcodespacerange\n" \ "1 beginbfrange\n" \ "<0000> <0000>\n" \ "endbfrange\n" \ "endcmap\n" \ "CMapName currentdict /CMap defineresource pop\n" \ "end\n" \ "end" self._out('<>') self._putstream(toUni) self._out('endobj') # CIDSystemInfo dictionary self._newobj() self._out('<>') self._out('endobj') # Font descriptor self._newobj() 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 latin1 until PEP461-like function is implemented cidtogidmap = cidtogidmap.encode("latin1") cidtogidmap = zlib.compress(cidtogidmap); self._newobj() self._out('<>') self._putstream(cidtogidmap) self._out('endobj') #Font file self._newobj() 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('<>') 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://pyfpdf.googlecode.com/')) 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("latin1") signature = '\x89'+'PNG'+'\r'+'\n'+'\x1a'+'\n' if not PY3K: signature = signature.decode("latin1") if(magic!=signature): self.error('Not a PNG file: '+name) #Read header chunk f.read(4) chunk = f.read(4).decode("latin1") 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("latin1") 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("latin1")) 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 ) return s.replace('\\','\\\\').replace(')','\\)').replace('(','\\(').replace('\r','\\r') 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 latin1 until PEP461-like function is implemented s = s.decode("latin1") elif not PY3K and isinstance(s, unicode): s = s.encode("latin1") # 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.} 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']