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 | |
---|
16 | from __future__ import division, with_statement |
---|
17 | |
---|
18 | from datetime import datetime |
---|
19 | from functools import wraps |
---|
20 | import math |
---|
21 | import errno |
---|
22 | import os, sys, zlib, struct, re, tempfile, struct |
---|
23 | |
---|
24 | from .ttfonts import TTFontFile |
---|
25 | from .fonts import fpdf_charwidths |
---|
26 | from .php import substr, sprintf, print_r, UTF8ToUTF16BE, UTF8StringToArray |
---|
27 | from .py3k import PY3K, pickle, urlopen, BytesIO, Image, basestring, unicode, exception, b, hashpath |
---|
28 | |
---|
29 | # Global variables |
---|
30 | FPDF_VERSION = '1.7.2' |
---|
31 | FPDF_FONT_DIR = os.path.join(os.path.dirname(__file__),'font') |
---|
32 | FPDF_CACHE_MODE = 0 # 0 - in same folder, 1 - none, 2 - hash |
---|
33 | FPDF_CACHE_DIR = None |
---|
34 | SYSTEM_TTFONTS = None |
---|
35 | |
---|
36 | PAGE_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 | |
---|
44 | def set_global(var, val): |
---|
45 | globals()[var] = val |
---|
46 | |
---|
47 | def 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 | |
---|
57 | class 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 | |
---|