comparison vendor/plugins/rfpdf/lib/tcpdf.rb @ 441:cbce1fd3b1b7 redmine-1.2

Update to Redmine 1.2-stable branch (Redmine SVN rev 6000)
author Chris Cannam
date Mon, 06 Jun 2011 14:24:13 +0100
parents
children cbb26bc654de
comparison
equal deleted inserted replaced
245:051f544170fe 441:cbce1fd3b1b7
1 #============================================================+
2 # File name : tcpdf.rb
3 # Begin : 2002-08-03
4 # Last Update : 2007-03-20
5 # Author : Nicola Asuni
6 # Version : 1.53.0.TC031
7 # License : GNU LGPL (http://www.gnu.org/copyleft/lesser.html)
8 #
9 # Description : This is a Ruby class for generating PDF files
10 # on-the-fly without requiring external
11 # extensions.
12 #
13 # IMPORTANT:
14 # This class is an extension and improvement of the Public Domain
15 # FPDF class by Olivier Plathey (http://www.fpdf.org).
16 #
17 # Main changes by Nicola Asuni:
18 # Ruby porting;
19 # UTF-8 Unicode support;
20 # code refactoring;
21 # source code clean up;
22 # code style and formatting;
23 # source code documentation using phpDocumentor (www.phpdoc.org);
24 # All ISO page formats were included;
25 # image scale factor;
26 # includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small;
27 # includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/);
28 # defines standard Header() and Footer() methods.
29 #
30 # Ported to Ruby by Ed Moss 2007-08-06
31 #
32 #============================================================+
33
34 #
35 # TCPDF Class.
36 # @package com.tecnick.tcpdf
37 #
38
39 @@version = "1.53.0.TC031"
40 @@fpdf_charwidths = {}
41
42 PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)'
43
44 module TCPDFFontDescriptor
45 @@descriptors = { 'freesans' => {} }
46 @@font_name = 'freesans'
47
48 def self.font(font_name)
49 @@descriptors[font_name.gsub(".rb", "")]
50 end
51
52 def self.define(font_name = 'freesans')
53 @@descriptors[font_name] ||= {}
54 yield @@descriptors[font_name]
55 end
56 end
57
58 # This is a Ruby class for generating PDF files on-the-fly without requiring external extensions.<br>
59 # This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org).<br>
60 # This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor]<br>
61 # TCPDF project (http://tcpdf.sourceforge.net) is based on the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org).<br>
62 # To add your own TTF fonts please read /fonts/README.TXT
63 # @name TCPDF
64 # @package com.tecnick.tcpdf
65 # @@version 1.53.0.TC031
66 # @author Nicola Asuni
67 # @link http://tcpdf.sourceforge.net
68 # @license http://www.gnu.org/copyleft/lesser.html LGPL
69 #
70 class TCPDF
71 include RFPDF
72 include Core::RFPDF
73 include RFPDF::Math
74
75 cattr_accessor :k_cell_height_ratio
76 @@k_cell_height_ratio = 1.25
77
78 cattr_accessor :k_blank_image
79 @@k_blank_image = ""
80
81 cattr_accessor :k_small_ratio
82 @@k_small_ratio = 2/3.0
83
84 cattr_accessor :k_path_cache
85 @@k_path_cache = File.join(RAILS_ROOT, 'tmp')
86
87 cattr_accessor :k_path_url_cache
88 @@k_path_url_cache = File.join(RAILS_ROOT, 'tmp')
89
90 cattr_accessor :decoder
91
92 attr_accessor :barcode
93
94 attr_accessor :buffer
95
96 attr_accessor :diffs
97
98 attr_accessor :color_flag
99
100 attr_accessor :default_table_columns
101
102 attr_accessor :default_font
103
104 attr_accessor :draw_color
105
106 attr_accessor :encoding
107
108 attr_accessor :fill_color
109
110 attr_accessor :fonts
111
112 attr_accessor :font_family
113
114 attr_accessor :font_files
115
116 cattr_accessor :font_path
117
118 attr_accessor :font_style
119
120 attr_accessor :font_size_pt
121
122 attr_accessor :header_width
123
124 attr_accessor :header_logo
125
126 attr_accessor :header_logo_width
127
128 attr_accessor :header_title
129
130 attr_accessor :header_string
131
132 attr_accessor :images
133
134 attr_accessor :img_scale
135
136 attr_accessor :in_footer
137
138 attr_accessor :is_unicode
139
140 attr_accessor :lasth
141
142 attr_accessor :links
143
144 attr_accessor :listordered
145
146 attr_accessor :listcount
147
148 attr_accessor :lispacer
149
150 attr_accessor :n
151
152 attr_accessor :offsets
153
154 attr_accessor :orientation_changes
155
156 attr_accessor :page
157
158 attr_accessor :page_links
159
160 attr_accessor :pages
161
162 attr_accessor :pdf_version
163
164 attr_accessor :prevfill_color
165
166 attr_accessor :prevtext_color
167
168 attr_accessor :print_header
169
170 attr_accessor :print_footer
171
172 attr_accessor :state
173
174 attr_accessor :tableborder
175
176 attr_accessor :tdbegin
177
178 attr_accessor :tdwidth
179
180 attr_accessor :tdheight
181
182 attr_accessor :tdalign
183
184 attr_accessor :tdfill
185
186 attr_accessor :tempfontsize
187
188 attr_accessor :text_color
189
190 attr_accessor :underline
191
192 attr_accessor :ws
193
194 #
195 # This is the class constructor.
196 # It allows to set up the page format, the orientation and
197 # the measure unit used in all the methods (except for the font sizes).
198 # @since 1.0
199 # @param string :orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li></ul>
200 # @param string :unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
201 # @param mixed :format The format used for pages. It can be either one of the following values (case insensitive) or a custom format in the form of a two-element array containing the width and the height (expressed in the unit given by unit).<ul><li>4A0</li><li>2A0</li><li>A0</li><li>A1</li><li>A2</li><li>A3</li><li>A4 (default)</li><li>A5</li><li>A6</li><li>A7</li><li>A8</li><li>A9</li><li>A10</li><li>B0</li><li>B1</li><li>B2</li><li>B3</li><li>B4</li><li>B5</li><li>B6</li><li>B7</li><li>B8</li><li>B9</li><li>B10</li><li>C0</li><li>C1</li><li>C2</li><li>C3</li><li>C4</li><li>C5</li><li>C6</li><li>C7</li><li>C8</li><li>C9</li><li>C10</li><li>RA0</li><li>RA1</li><li>RA2</li><li>RA3</li><li>RA4</li><li>SRA0</li><li>SRA1</li><li>SRA2</li><li>SRA3</li><li>SRA4</li><li>LETTER</li><li>LEGAL</li><li>EXECUTIVE</li><li>FOLIO</li></ul>
202 # @param boolean :unicode TRUE means that the input text is unicode (default = true)
203 # @param String :encoding charset encoding; default is UTF-8
204 #
205 def initialize(orientation = 'P', unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8")
206
207 # Set internal character encoding to ASCII#
208 #FIXME 2007-05-25 (EJM) Level=0 -
209 # if (respond_to?("mb_internal_encoding") and mb_internal_encoding())
210 # @internal_encoding = mb_internal_encoding();
211 # mb_internal_encoding("ASCII");
212 # }
213
214 #Some checks
215 dochecks();
216
217 begin
218 @@decoder = HTMLEntities.new
219 rescue
220 @@decoder = nil
221 end
222
223 #Initialization of properties
224 @barcode ||= false
225 @buffer ||= ''
226 @diffs ||= []
227 @color_flag ||= false
228 @default_table_columns ||= 4
229 @default_font ||= "FreeSans" if unicode
230 @default_font ||= "Helvetica"
231 @draw_color ||= '0 G'
232 @encoding ||= "UTF-8"
233 @fill_color ||= '0 g'
234 @fonts ||= {}
235 @font_family ||= ''
236 @font_files ||= {}
237 @font_style ||= ''
238 @font_size ||= 12
239 @font_size_pt ||= 12
240 @header_width ||= 0
241 @header_logo ||= ""
242 @header_logo_width ||= 30
243 @header_title ||= ""
244 @header_string ||= ""
245 @images ||= {}
246 @img_scale ||= 1
247 @in_footer ||= false
248 @is_unicode = unicode
249 @lasth ||= 0
250 @links ||= []
251 @listordered ||= false
252 @listcount ||= 0
253 @lispacer ||= ""
254 @n ||= 2
255 @offsets ||= []
256 @orientation_changes ||= []
257 @page ||= 0
258 @page_links ||= {}
259 @pages ||= []
260 @pdf_version ||= "1.3"
261 @prevfill_color ||= [255,255,255]
262 @prevtext_color ||= [0,0,0]
263 @print_header ||= false
264 @print_footer ||= false
265 @state ||= 0
266 @tableborder ||= 0
267 @tdbegin ||= false
268 @tdwidth ||= 0
269 @tdheight ||= 0
270 @tdalign ||= "L"
271 @tdfill ||= 0
272 @tempfontsize ||= 10
273 @text_color ||= '0 g'
274 @underline ||= false
275 @ws ||= 0
276
277 #Standard Unicode fonts
278 @core_fonts = {
279 'courier'=>'Courier',
280 'courierB'=>'Courier-Bold',
281 'courierI'=>'Courier-Oblique',
282 'courierBI'=>'Courier-BoldOblique',
283 'helvetica'=>'Helvetica',
284 'helveticaB'=>'Helvetica-Bold',
285 'helveticaI'=>'Helvetica-Oblique',
286 'helveticaBI'=>'Helvetica-BoldOblique',
287 'times'=>'Times-Roman',
288 'timesB'=>'Times-Bold',
289 'timesI'=>'Times-Italic',
290 'timesBI'=>'Times-BoldItalic',
291 'symbol'=>'Symbol',
292 'zapfdingbats'=>'ZapfDingbats'}
293
294 #Scale factor
295 case unit.downcase
296 when 'pt' ; @k=1
297 when 'mm' ; @k=72/25.4
298 when 'cm' ; @k=72/2.54
299 when 'in' ; @k=72
300 else Error("Incorrect unit: #{unit}")
301 end
302
303 #Page format
304 if format.is_a?(String)
305 # Page formats (45 standard ISO paper formats and 4 american common formats).
306 # Paper cordinates are calculated in this way: (inches# 72) where (1 inch = 2.54 cm)
307 case (format.upcase)
308 when '4A0' ; format = [4767.87,6740.79]
309 when '2A0' ; format = [3370.39,4767.87]
310 when 'A0' ; format = [2383.94,3370.39]
311 when 'A1' ; format = [1683.78,2383.94]
312 when 'A2' ; format = [1190.55,1683.78]
313 when 'A3' ; format = [841.89,1190.55]
314 when 'A4' ; format = [595.28,841.89] # ; default
315 when 'A5' ; format = [419.53,595.28]
316 when 'A6' ; format = [297.64,419.53]
317 when 'A7' ; format = [209.76,297.64]
318 when 'A8' ; format = [147.40,209.76]
319 when 'A9' ; format = [104.88,147.40]
320 when 'A10' ; format = [73.70,104.88]
321 when 'B0' ; format = [2834.65,4008.19]
322 when 'B1' ; format = [2004.09,2834.65]
323 when 'B2' ; format = [1417.32,2004.09]
324 when 'B3' ; format = [1000.63,1417.32]
325 when 'B4' ; format = [708.66,1000.63]
326 when 'B5' ; format = [498.90,708.66]
327 when 'B6' ; format = [354.33,498.90]
328 when 'B7' ; format = [249.45,354.33]
329 when 'B8' ; format = [175.75,249.45]
330 when 'B9' ; format = [124.72,175.75]
331 when 'B10' ; format = [87.87,124.72]
332 when 'C0' ; format = [2599.37,3676.54]
333 when 'C1' ; format = [1836.85,2599.37]
334 when 'C2' ; format = [1298.27,1836.85]
335 when 'C3' ; format = [918.43,1298.27]
336 when 'C4' ; format = [649.13,918.43]
337 when 'C5' ; format = [459.21,649.13]
338 when 'C6' ; format = [323.15,459.21]
339 when 'C7' ; format = [229.61,323.15]
340 when 'C8' ; format = [161.57,229.61]
341 when 'C9' ; format = [113.39,161.57]
342 when 'C10' ; format = [79.37,113.39]
343 when 'RA0' ; format = [2437.80,3458.27]
344 when 'RA1' ; format = [1729.13,2437.80]
345 when 'RA2' ; format = [1218.90,1729.13]
346 when 'RA3' ; format = [864.57,1218.90]
347 when 'RA4' ; format = [609.45,864.57]
348 when 'SRA0' ; format = [2551.18,3628.35]
349 when 'SRA1' ; format = [1814.17,2551.18]
350 when 'SRA2' ; format = [1275.59,1814.17]
351 when 'SRA3' ; format = [907.09,1275.59]
352 when 'SRA4' ; format = [637.80,907.09]
353 when 'LETTER' ; format = [612.00,792.00]
354 when 'LEGAL' ; format = [612.00,1008.00]
355 when 'EXECUTIVE' ; format = [521.86,756.00]
356 when 'FOLIO' ; format = [612.00,936.00]
357 #else then Error("Unknown page format: #{format}"
358 end
359 @fw_pt = format[0]
360 @fh_pt = format[1]
361 else
362 @fw_pt = format[0]*@k
363 @fh_pt = format[1]*@k
364 end
365
366 @fw = @fw_pt/@k
367 @fh = @fh_pt/@k
368
369 #Page orientation
370 orientation = orientation.downcase
371 if orientation == 'p' or orientation == 'portrait'
372 @def_orientation = 'P'
373 @w_pt = @fw_pt
374 @h_pt = @fh_pt
375 elsif orientation == 'l' or orientation == 'landscape'
376 @def_orientation = 'L'
377 @w_pt = @fh_pt
378 @h_pt = @fw_pt
379 else
380 Error("Incorrect orientation: #{orientation}")
381 end
382
383 @cur_orientation = @def_orientation
384 @w = @w_pt/@k
385 @h = @h_pt/@k
386 #Page margins (1 cm)
387 margin = 28.35/@k
388 SetMargins(margin, margin)
389 #Interior cell margin (1 mm)
390 @c_margin = margin / 10
391 #Line width (0.2 mm)
392 @line_width = 0.567 / @k
393 #Automatic page break
394 SetAutoPageBreak(true, 2 * margin)
395 #Full width display mode
396 SetDisplayMode('fullwidth')
397 #Compression
398 SetCompression(true)
399 #Set default PDF version number
400 @pdf_version = "1.3"
401
402 @encoding = encoding
403 @b = 0
404 @i = 0
405 @u = 0
406 @href = ''
407 @fontlist = ["arial", "times", "courier", "helvetica", "symbol"]
408 @issetfont = false
409 @issetcolor = false
410
411 SetFillColor(200, 200, 200, true)
412 SetTextColor(0, 0, 0, true)
413 end
414
415 #
416 # Set the image scale.
417 # @param float :scale image scale.
418 # @author Nicola Asuni
419 # @since 1.5.2
420 #
421 def SetImageScale(scale)
422 @img_scale = scale;
423 end
424 alias_method :set_image_scale, :SetImageScale
425
426 #
427 # Returns the image scale.
428 # @return float image scale.
429 # @author Nicola Asuni
430 # @since 1.5.2
431 #
432 def GetImageScale()
433 return @img_scale;
434 end
435 alias_method :get_image_scale, :GetImageScale
436
437 #
438 # Returns the page width in units.
439 # @return int page width.
440 # @author Nicola Asuni
441 # @since 1.5.2
442 #
443 def GetPageWidth()
444 return @w;
445 end
446 alias_method :get_page_width, :GetPageWidth
447
448 #
449 # Returns the page height in units.
450 # @return int page height.
451 # @author Nicola Asuni
452 # @since 1.5.2
453 #
454 def GetPageHeight()
455 return @h;
456 end
457 alias_method :get_page_height, :GetPageHeight
458
459 #
460 # Returns the page break margin.
461 # @return int page break margin.
462 # @author Nicola Asuni
463 # @since 1.5.2
464 #
465 def GetBreakMargin()
466 return @b_margin;
467 end
468 alias_method :get_break_margin, :GetBreakMargin
469
470 #
471 # Returns the scale factor (number of points in user unit).
472 # @return int scale factor.
473 # @author Nicola Asuni
474 # @since 1.5.2
475 #
476 def GetScaleFactor()
477 return @k;
478 end
479 alias_method :get_scale_factor, :GetScaleFactor
480
481 #
482 # Defines the left, top and right margins. By default, they equal 1 cm. Call this method to change them.
483 # @param float :left Left margin.
484 # @param float :top Top margin.
485 # @param float :right Right margin. Default value is the left one.
486 # @since 1.0
487 # @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
488 #
489 def SetMargins(left, top, right=-1)
490 #Set left, top and right margins
491 @l_margin = left
492 @t_margin = top
493 if (right == -1)
494 right = left
495 end
496 @r_margin = right
497 end
498 alias_method :set_margins, :SetMargins
499
500 #
501 # Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
502 # @param float :margin The margin.
503 # @since 1.4
504 # @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
505 #
506 def SetLeftMargin(margin)
507 #Set left margin
508 @l_margin = margin
509 if ((@page>0) and (@x < margin))
510 @x = margin
511 end
512 end
513 alias_method :set_left_margin, :SetLeftMargin
514
515 #
516 # Defines the top margin. The method can be called before creating the first page.
517 # @param float :margin The margin.
518 # @since 1.5
519 # @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
520 #
521 def SetTopMargin(margin)
522 #Set top margin
523 @t_margin = margin
524 end
525 alias_method :set_top_margin, :SetTopMargin
526
527 #
528 # Defines the right margin. The method can be called before creating the first page.
529 # @param float :margin The margin.
530 # @since 1.5
531 # @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
532 #
533 def SetRightMargin(margin)
534 #Set right margin
535 @r_margin = margin
536 end
537 alias_method :set_right_margin, :SetRightMargin
538
539 #
540 # Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
541 # @param boolean :auto Boolean indicating if mode should be on or off.
542 # @param float :margin Distance from the bottom of the page.
543 # @since 1.0
544 # @see Cell(), MultiCell(), AcceptPageBreak()
545 #
546 def SetAutoPageBreak(auto, margin=0)
547 #Set auto page break mode and triggering margin
548 @auto_page_break = auto
549 @b_margin = margin
550 @page_break_trigger = @h - margin
551 end
552 alias_method :set_auto_page_break, :SetAutoPageBreak
553
554 #
555 # Defines the way the document is to be displayed by the viewer. The zoom level can be set: pages can be displayed entirely on screen, occupy the full width of the window, use real size, be scaled by a specific zooming factor or use viewer default (configured in the Preferences menu of Acrobat). The page layout can be specified too: single at once, continuous display, two columns or viewer default. By default, documents use the full width mode with continuous display.
556 # @param mixed :zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
557 # @param string :layout The page layout. Possible values are:<ul><li>single: displays one page at once</li><li>continuous: displays pages continuously (default)</li><li>two: displays two pages on two columns</li><li>default: uses viewer default mode</li></ul>
558 # @since 1.2
559 #
560 def SetDisplayMode(zoom, layout = 'continuous')
561 #Set display mode in viewer
562 if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or !zoom.is_a?(String))
563 @zoom_mode = zoom
564 else
565 Error("Incorrect zoom display mode: #{zoom}")
566 end
567 if (layout == 'single' or layout == 'continuous' or layout == 'two' or layout == 'default')
568 @layout_mode = layout
569 else
570 Error("Incorrect layout display mode: #{layout}")
571 end
572 end
573 alias_method :set_display_mode, :SetDisplayMode
574
575 #
576 # Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
577 # Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
578 # @param boolean :compress Boolean indicating if compression must be enabled.
579 # @since 1.4
580 #
581 def SetCompression(compress)
582 #Set page compression
583 if (respond_to?('gzcompress'))
584 @compress = compress
585 else
586 @compress = false
587 end
588 end
589 alias_method :set_compression, :SetCompression
590
591 #
592 # Defines the title of the document.
593 # @param string :title The title.
594 # @since 1.2
595 # @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
596 #
597 def SetTitle(title)
598 #Title of document
599 @title = title
600 end
601 alias_method :set_title, :SetTitle
602
603 #
604 # Defines the subject of the document.
605 # @param string :subject The subject.
606 # @since 1.2
607 # @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
608 #
609 def SetSubject(subject)
610 #Subject of document
611 @subject = subject
612 end
613 alias_method :set_subject, :SetSubject
614
615 #
616 # Defines the author of the document.
617 # @param string :author The name of the author.
618 # @since 1.2
619 # @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
620 #
621 def SetAuthor(author)
622 #Author of document
623 @author = author
624 end
625 alias_method :set_author, :SetAuthor
626
627 #
628 # Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
629 # @param string :keywords The list of keywords.
630 # @since 1.2
631 # @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
632 #
633 def SetKeywords(keywords)
634 #Keywords of document
635 @keywords = keywords
636 end
637 alias_method :set_keywords, :SetKeywords
638
639 #
640 # Defines the creator of the document. This is typically the name of the application that generates the PDF.
641 # @param string :creator The name of the creator.
642 # @since 1.2
643 # @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
644 #
645 def SetCreator(creator)
646 #Creator of document
647 @creator = creator
648 end
649 alias_method :set_creator, :SetCreator
650
651 #
652 # Defines an alias for the total number of pages. It will be substituted as the document is closed.<br />
653 # <b>Example:</b><br />
654 # <pre>
655 # class PDF extends TCPDF {
656 # def Footer()
657 # #Go to 1.5 cm from bottom
658 # SetY(-15);
659 # #Select Arial italic 8
660 # SetFont('Arial','I',8);
661 # #Print current and total page numbers
662 # Cell(0,10,'Page '.PageNo().'/{nb}',0,0,'C');
663 # end
664 # }
665 # :pdf=new PDF();
666 # :pdf->alias_nb_pages();
667 # </pre>
668 # @param string :alias The alias. Default valuenb}.
669 # @since 1.4
670 # @see PageNo(), Footer()
671 #
672 def AliasNbPages(alias_nb ='{nb}')
673 #Define an alias for total number of pages
674 @alias_nb_pages = escapetext(alias_nb)
675 end
676 alias_method :alias_nb_pages, :AliasNbPages
677
678 #
679 # This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
680 # 2004-06-11 :: Nicola Asuni : changed bold tag with strong
681 # @param string :msg The error message
682 # @since 1.0
683 #
684 def Error(msg)
685 #Fatal error
686 raise ("TCPDF error: #{msg}")
687 end
688 alias_method :error, :Error
689
690 #
691 # This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically.
692 # Note: no page is created by this method
693 # @since 1.0
694 # @see AddPage(), Close()
695 #
696 def Open()
697 #Begin document
698 @state = 1
699 end
700 # alias_method :open, :Open
701
702 #
703 # Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document.
704 # @since 1.0
705 # @see Open(), Output()
706 #
707 def Close()
708 #Terminate document
709 if (@state==3)
710 return;
711 end
712 if (@page==0)
713 AddPage();
714 end
715 #Page footer
716 @in_footer=true;
717 Footer();
718 @in_footer=false;
719 #Close page
720 endpage();
721 #Close document
722 enddoc();
723 end
724 # alias_method :close, :Close
725
726 #
727 # Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer. Then the page is added, the current position set to the top-left corner according to the left and top margins, and Header() is called to display the header.
728 # The font which was set before calling is automatically restored. There is no need to call SetFont() again if you want to continue with the same font. The same is true for colors and line width.
729 # The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
730 # @param string :orientation Page orientation. Possible values are (case insensitive):<ul><li>P or Portrait</li><li>L or Landscape</li></ul> The default value is the one passed to the constructor.
731 # @since 1.0
732 # @see TCPDF(), Header(), Footer(), SetMargins()
733 #
734 def AddPage(orientation='')
735 #Start a new page
736 if (@state==0)
737 Open();
738 end
739 family=@font_family;
740 style=@font_style + (@underline ? 'U' : '');
741 size=@font_size_pt;
742 lw=@line_width;
743 dc=@draw_color;
744 fc=@fill_color;
745 tc=@text_color;
746 cf=@color_flag;
747 if (@page>0)
748 #Page footer
749 @in_footer=true;
750 Footer();
751 @in_footer=false;
752 #Close page
753 endpage();
754 end
755 #Start new page
756 beginpage(orientation);
757 #Set line cap style to square
758 out('2 J');
759 #Set line width
760 @line_width = lw;
761 out(sprintf('%.2f w', lw*@k));
762 #Set font
763 if (family)
764 SetFont(family, style, size);
765 end
766 #Set colors
767 @draw_color = dc;
768 if (dc!='0 G')
769 out(dc);
770 end
771 @fill_color = fc;
772 if (fc!='0 g')
773 out(fc);
774 end
775 @text_color = tc;
776 @color_flag = cf;
777 #Page header
778 Header();
779 #Restore line width
780 if (@line_width != lw)
781 @line_width = lw;
782 out(sprintf('%.2f w', lw*@k));
783 end
784 #Restore font
785 if (family)
786 SetFont(family, style, size);
787 end
788 #Restore colors
789 if (@draw_color != dc)
790 @draw_color = dc;
791 out(dc);
792 end
793 if (@fill_color != fc)
794 @fill_color = fc;
795 out(fc);
796 end
797 @text_color = tc;
798 @color_flag = cf;
799 end
800 alias_method :add_page, :AddPage
801
802 #
803 # Rotate object.
804 # @param float :angle angle in degrees for counter-clockwise rotation
805 # @param int :x abscissa of the rotation center. Default is current x position
806 # @param int :y ordinate of the rotation center. Default is current y position
807 #
808 def Rotate(angle, x="", y="")
809
810 if (x == '')
811 x = @x;
812 end
813
814 if (y == '')
815 y = @y;
816 end
817
818 if (@rtl)
819 x = @w - x;
820 angle = -@angle;
821 end
822
823 y = (@h - y) * @k;
824 x *= @k;
825
826 # calculate elements of transformation matrix
827 tm = []
828 tm[0] = ::Math::cos(deg2rad(angle));
829 tm[1] = ::Math::sin(deg2rad(angle));
830 tm[2] = -tm[1];
831 tm[3] = tm[0];
832 tm[4] = x + tm[1] * y - tm[0] * x;
833 tm[5] = y - tm[0] * y - tm[1] * x;
834
835 # generate the transformation matrix
836 Transform(tm);
837 end
838 alias_method :rotate, :Rotate
839
840 #
841 # Starts a 2D tranformation saving current graphic state.
842 # This function must be called before scaling, mirroring, translation, rotation and skewing.
843 # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
844 #
845 def StartTransform
846 out('q');
847 end
848 alias_method :start_transform, :StartTransform
849
850 #
851 # Stops a 2D tranformation restoring previous graphic state.
852 # This function must be called after scaling, mirroring, translation, rotation and skewing.
853 # Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
854 #
855 def StopTransform
856 out('Q');
857 end
858 alias_method :stop_transform, :StopTransform
859
860 #
861 # Apply graphic transformations.
862 # @since 2.1.000 (2008-01-07)
863 # @see StartTransform(), StopTransform()
864 #
865 def Transform(tm)
866 x = out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5]));
867 end
868 alias_method :transform, :Transform
869
870 #
871 # Set header data.
872 # @param string :ln header image logo
873 # @param string :lw header image logo width in mm
874 # @param string :ht string to print as title on document header
875 # @param string :hs string to print on document header
876 #
877 def SetHeaderData(ln="", lw=0, ht="", hs="")
878 @header_logo = ln || ""
879 @header_logo_width = lw || 0
880 @header_title = ht || ""
881 @header_string = hs || ""
882 end
883 alias_method :set_header_data, :SetHeaderData
884
885 #
886 # Set header margin.
887 # (minimum distance between header and top page margin)
888 # @param int :hm distance in millimeters
889 #
890 def SetHeaderMargin(hm=10)
891 @header_margin = hm;
892 end
893 alias_method :set_header_margin, :SetHeaderMargin
894
895 #
896 # Set footer margin.
897 # (minimum distance between footer and bottom page margin)
898 # @param int :fm distance in millimeters
899 #
900 def SetFooterMargin(fm=10)
901 @footer_margin = fm;
902 end
903 alias_method :set_footer_margin, :SetFooterMargin
904
905 #
906 # Set a flag to print page header.
907 # @param boolean :val set to true to print the page header (default), false otherwise.
908 #
909 def SetPrintHeader(val=true)
910 @print_header = val;
911 end
912 alias_method :set_print_header, :SetPrintHeader
913
914 #
915 # Set a flag to print page footer.
916 # @param boolean :value set to true to print the page footer (default), false otherwise.
917 #
918 def SetPrintFooter(val=true)
919 @print_footer = val;
920 end
921 alias_method :set_print_footer, :SetPrintFooter
922
923 #
924 # This method is used to render the page header.
925 # It is automatically called by AddPage() and could be overwritten in your own inherited class.
926 #
927 def Header()
928 if (@print_header)
929 if (@original_l_margin.nil?)
930 @original_l_margin = @l_margin;
931 end
932 if (@original_r_margin.nil?)
933 @original_r_margin = @r_margin;
934 end
935
936 #set current position
937 SetXY(@original_l_margin, @header_margin);
938
939 if ((@header_logo) and (@header_logo != @@k_blank_image))
940 Image(@header_logo, @original_l_margin, @header_margin, @header_logo_width);
941 else
942 @img_rb_y = GetY();
943 end
944
945 cell_height = ((@@k_cell_height_ratio * @header_font[2]) / @k).round(2)
946
947 header_x = @original_l_margin + (@header_logo_width * 1.05); #set left margin for text data cell
948
949 # header title
950 SetFont(@header_font[0], 'B', @header_font[2] + 1);
951 SetX(header_x);
952 Cell(@header_width, cell_height, @header_title, 0, 1, 'L');
953
954 # header string
955 SetFont(@header_font[0], @header_font[1], @header_font[2]);
956 SetX(header_x);
957 MultiCell(@header_width, cell_height, @header_string, 0, 'L', 0);
958
959 # print an ending header line
960 if (@header_width)
961 #set style for cell border
962 SetLineWidth(0.3);
963 SetDrawColor(0, 0, 0);
964 SetY(1 + (@img_rb_y > GetY() ? @img_rb_y : GetY()));
965 SetX(@original_l_margin);
966 Cell(0, 0, '', 'T', 0, 'C');
967 end
968
969 #restore position
970 SetXY(@original_l_margin, @t_margin);
971 end
972 end
973 alias_method :header, :Header
974
975 #
976 # This method is used to render the page footer.
977 # It is automatically called by AddPage() and could be overwritten in your own inherited class.
978 #
979 def Footer()
980 if (@print_footer)
981
982 if (@original_l_margin.nil?)
983 @original_l_margin = @l_margin;
984 end
985 if (@original_r_margin.nil?)
986 @original_r_margin = @r_margin;
987 end
988
989 #set font
990 SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]);
991 #set style for cell border
992 line_width = 0.3;
993 SetLineWidth(line_width);
994 SetDrawColor(0, 0, 0);
995
996 footer_height = ((@@k_cell_height_ratio * @footer_font[2]) / @k).round; #footer height, was , 2)
997 #get footer y position
998 footer_y = @h - @footer_margin - footer_height;
999 #set current position
1000 SetXY(@original_l_margin, footer_y);
1001
1002 #print document barcode
1003 if (@barcode)
1004 Ln();
1005 barcode_width = ((@w - @original_l_margin - @original_r_margin)).round; #max width
1006 writeBarcode(@original_l_margin, footer_y + line_width, barcode_width, footer_height - line_width, "C128B", false, false, 2, @barcode);
1007 end
1008
1009 SetXY(@original_l_margin, footer_y);
1010
1011 #Print page number
1012 Cell(0, footer_height, @l['w_page'] + " " + PageNo().to_s + ' / {nb}', 'T', 0, 'R');
1013 end
1014 end
1015 alias_method :footer, :Footer
1016
1017 #
1018 # Returns the current page number.
1019 # @return int page number
1020 # @since 1.0
1021 # @see alias_nb_pages()
1022 #
1023 def PageNo()
1024 #Get current page number
1025 return @page;
1026 end
1027 alias_method :page_no, :PageNo
1028
1029 #
1030 # Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1031 # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1032 # @param int :g Green component (between 0 and 255)
1033 # @param int :b Blue component (between 0 and 255)
1034 # @since 1.3
1035 # @see SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
1036 #
1037 def SetDrawColor(r, g=-1, b=-1)
1038 #Set color for all stroking operations
1039 if ((r==0 and g==0 and b==0) or g==-1)
1040 @draw_color=sprintf('%.3f G', r/255.0);
1041 else
1042 @draw_color=sprintf('%.3f %.3f %.3f RG', r/255.0, g/255.0, b/255.0);
1043 end
1044 if (@page>0)
1045 out(@draw_color);
1046 end
1047 end
1048 alias_method :set_draw_color, :SetDrawColor
1049
1050 #
1051 # Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1052 # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1053 # @param int :g Green component (between 0 and 255)
1054 # @param int :b Blue component (between 0 and 255)
1055 # @param boolean :storeprev if true stores the RGB array on :prevfill_color variable.
1056 # @since 1.3
1057 # @see SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
1058 #
1059 def SetFillColor(r, g=-1, b=-1, storeprev=false)
1060 #Set color for all filling operations
1061 if ((r==0 and g==0 and b==0) or g==-1)
1062 @fill_color=sprintf('%.3f g', r/255.0);
1063 else
1064 @fill_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0);
1065 end
1066 @color_flag=(@fill_color!=@text_color);
1067 if (@page>0)
1068 out(@fill_color);
1069 end
1070 if (storeprev)
1071 # store color as previous value
1072 @prevfill_color = [r, g, b]
1073 end
1074 end
1075 alias_method :set_fill_color, :SetFillColor
1076
1077 # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors
1078 def SetCmykFillColor(c, m, y, k, storeprev=false)
1079 #Set color for all filling operations
1080 @fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k);
1081 @color_flag=(@fill_color!=@text_color);
1082 if (storeprev)
1083 # store color as previous value
1084 @prevtext_color = [c, m, y, k]
1085 end
1086 if (@page>0)
1087 out(@fill_color);
1088 end
1089 end
1090 alias_method :set_cmyk_fill_color, :SetCmykFillColor
1091
1092 #
1093 # Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1094 # @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1095 # @param int :g Green component (between 0 and 255)
1096 # @param int :b Blue component (between 0 and 255)
1097 # @param boolean :storeprev if true stores the RGB array on :prevtext_color variable.
1098 # @since 1.3
1099 # @see SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
1100 #
1101 def SetTextColor(r, g=-1, b=-1, storeprev=false)
1102 #Set color for text
1103 if ((r==0 and :g==0 and :b==0) or :g==-1)
1104 @text_color=sprintf('%.3f g', r/255.0);
1105 else
1106 @text_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0);
1107 end
1108 @color_flag=(@fill_color!=@text_color);
1109 if (storeprev)
1110 # store color as previous value
1111 @prevtext_color = [r, g, b]
1112 end
1113 end
1114 alias_method :set_text_color, :SetTextColor
1115
1116 # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors
1117 def SetCmykTextColor(c, m, y, k, storeprev=false)
1118 #Set color for text
1119 @text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k);
1120 @color_flag=(@fill_color!=@text_color);
1121 if (storeprev)
1122 # store color as previous value
1123 @prevtext_color = [c, m, y, k]
1124 end
1125 end
1126 alias_method :set_cmyk_text_color, :SetCmykTextColor
1127
1128 #
1129 # Returns the length of a string in user unit. A font must be selected.<br>
1130 # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02]
1131 # @param string :s The string whose length is to be computed
1132 # @return int
1133 # @since 1.2
1134 #
1135 def GetStringWidth(s)
1136 #Get width of a string in the current font
1137 s = s.to_s;
1138 cw = @current_font['cw']
1139 w = 0;
1140 if (@is_unicode)
1141 unicode = UTF8StringToArray(s);
1142 unicode.each do |char|
1143 if (!cw[char].nil?)
1144 w += cw[char];
1145 # This should not happen. UTF8StringToArray should guarentee the array is ascii values.
1146 # elsif (c!cw[char[0]].nil?)
1147 # w += cw[char[0]];
1148 # elsif (!cw[char.chr].nil?)
1149 # w += cw[char.chr];
1150 elsif (!@current_font['desc']['MissingWidth'].nil?)
1151 w += @current_font['desc']['MissingWidth']; # set default size
1152 else
1153 w += 500;
1154 end
1155 end
1156 else
1157 s.each_byte do |c|
1158 if cw[c.chr]
1159 w += cw[c.chr];
1160 elsif cw[?c.chr]
1161 w += cw[?c.chr]
1162 end
1163 end
1164 end
1165 return (w * @font_size / 1000.0);
1166 end
1167 alias_method :get_string_width, :GetStringWidth
1168
1169 #
1170 # Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
1171 # @param float :width The width.
1172 # @since 1.0
1173 # @see Line(), Rect(), Cell(), MultiCell()
1174 #
1175 def SetLineWidth(width)
1176 #Set line width
1177 @line_width = width;
1178 if (@page>0)
1179 out(sprintf('%.2f w', width*@k));
1180 end
1181 end
1182 alias_method :set_line_width, :SetLineWidth
1183
1184 #
1185 # Draws a line between two points.
1186 # @param float :x1 Abscissa of first point
1187 # @param float :y1 Ordinate of first point
1188 # @param float :x2 Abscissa of second point
1189 # @param float :y2 Ordinate of second point
1190 # @since 1.0
1191 # @see SetLineWidth(), SetDrawColor()
1192 #
1193 def Line(x1, y1, x2, y2)
1194 #Draw a line
1195 out(sprintf('%.2f %.2f m %.2f %.2f l S', x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k));
1196 end
1197 alias_method :line, :Line
1198
1199 def Circle(mid_x, mid_y, radius, style='')
1200 mid_y = (@h-mid_y)*@k
1201 out(sprintf("q\n")) # postscript content in pdf
1202 # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc.
1203 out(sprintf("1 j\n")) # line join
1204 # translate ("move") circle to mid_y, mid_y
1205 out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y))
1206 kappa = 0.5522847498307933984022516322796
1207 # Quadrant 1
1208 x_s = 0.0 # 12 o'clock
1209 y_s = 0.0 + radius
1210 x_e = 0.0 + radius # 3 o'clock
1211 y_e = 0.0
1212 out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock
1213 # cubic bezier control point 1, start height and kappa * radius to the right
1214 bx_e1 = x_s + (radius * kappa)
1215 by_e1 = y_s
1216 # cubic bezier control point 2, end and kappa * radius above
1217 bx_e2 = x_e
1218 by_e2 = y_e + (radius * kappa)
1219 # draw cubic bezier from current point to x_e/y_e with bx_e1/by_e1 and bx_e2/by_e2 as bezier control points
1220 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1221 # Quadrant 2
1222 x_s = x_e
1223 y_s = y_e # 3 o'clock
1224 x_e = 0.0
1225 y_e = 0.0 - radius # 6 o'clock
1226 bx_e1 = x_s # cubic bezier point 1
1227 by_e1 = y_s - (radius * kappa)
1228 bx_e2 = x_e + (radius * kappa) # cubic bezier point 2
1229 by_e2 = y_e
1230 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1231 # Quadrant 3
1232 x_s = x_e
1233 y_s = y_e # 6 o'clock
1234 x_e = 0.0 - radius
1235 y_e = 0.0 # 9 o'clock
1236 bx_e1 = x_s - (radius * kappa) # cubic bezier point 1
1237 by_e1 = y_s
1238 bx_e2 = x_e # cubic bezier point 2
1239 by_e2 = y_e - (radius * kappa)
1240 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1241 # Quadrant 4
1242 x_s = x_e
1243 y_s = y_e # 9 o'clock
1244 x_e = 0.0
1245 y_e = 0.0 + radius # 12 o'clock
1246 bx_e1 = x_s # cubic bezier point 1
1247 by_e1 = y_s + (radius * kappa)
1248 bx_e2 = x_e - (radius * kappa) # cubic bezier point 2
1249 by_e2 = y_e
1250 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1251 if style=='F'
1252 op='f'
1253 elsif style=='FD' or style=='DF'
1254 op='b'
1255 else
1256 op='s'
1257 end
1258 out(sprintf("#{op}\n")) # stroke circle, do not fill and close path
1259 # for filling etc. b, b*, f, f*
1260 out(sprintf("Q\n")) # finish postscript in PDF
1261 end
1262 alias_method :circle, :Circle
1263
1264 #
1265 # Outputs a rectangle. It can be drawn (border only), filled (with no border) or both.
1266 # @param float :x Abscissa of upper-left corner
1267 # @param float :y Ordinate of upper-left corner
1268 # @param float :w Width
1269 # @param float :h Height
1270 # @param string :style Style of rendering. Possible values are:<ul><li>D or empty string: draw (default)</li><li>F: fill</li><li>DF or FD: draw and fill</li></ul>
1271 # @since 1.0
1272 # @see SetLineWidth(), SetDrawColor(), SetFillColor()
1273 #
1274 def Rect(x, y, w, h, style='')
1275 #Draw a rectangle
1276 if (style=='F')
1277 op='f';
1278 elsif (style=='FD' or style=='DF')
1279 op='B';
1280 else
1281 op='S';
1282 end
1283 out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op));
1284 end
1285 alias_method :rect, :Rect
1286
1287 #
1288 # Imports a TrueType or Type1 font and makes it available. It is necessary to generate a font definition file first with the makefont.rb utility. The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
1289 # Support UTF-8 Unicode [Nicola Asuni, 2005-01-02].
1290 # <b>Example</b>:<br />
1291 # <pre>
1292 # :pdf->AddFont('Comic','I');
1293 # # is equivalent to:
1294 # :pdf->AddFont('Comic','I','comici.rb');
1295 # </pre>
1296 # @param string :family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
1297 # @param string :style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
1298 # @param string :file The font definition file. By default, the name is built from the family and style, in lower case with no space.
1299 # @since 1.5
1300 # @see SetFont()
1301 #
1302 def AddFont(family, style='', file='')
1303 if (family.empty?)
1304 return;
1305 end
1306
1307 #Add a TrueType or Type1 font
1308 family = family.downcase
1309 if ((!@is_unicode) and (family == 'arial'))
1310 family = 'helvetica';
1311 end
1312
1313 style=style.upcase
1314 style=style.gsub('U','');
1315 if (style == 'IB')
1316 style = 'BI';
1317 end
1318
1319 fontkey = family + style;
1320 # check if the font has been already added
1321 if !@fonts[fontkey].nil?
1322 return;
1323 end
1324
1325 if (file=='')
1326 file = family.gsub(' ', '') + style.downcase + '.rb';
1327 end
1328 font_file_name = getfontpath(file)
1329 if (font_file_name.nil?)
1330 # try to load the basic file without styles
1331 file = family.gsub(' ', '') + '.rb';
1332 font_file_name = getfontpath(file)
1333 end
1334 if font_file_name.nil?
1335 Error("Could not find font #{file}.")
1336 end
1337 require(getfontpath(file))
1338 font_desc = TCPDFFontDescriptor.font(file)
1339
1340 if (font_desc[:name].nil? and @@fpdf_charwidths.nil?)
1341 Error('Could not include font definition file');
1342 end
1343
1344 i = @fonts.length+1;
1345 if (@is_unicode)
1346 @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'cMap' => font_desc[:cMap], 'registry' => font_desc[:registry]}
1347 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1348 else
1349 @fonts[fontkey]={'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]}
1350 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1351 end
1352
1353 if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?))
1354 #Search existing encodings
1355 d=0;
1356 nb=@diffs.length;
1357 1.upto(nb) do |i|
1358 if (@diffs[i]== font_desc[:diff])
1359 d = i;
1360 break;
1361 end
1362 end
1363 if (d==0)
1364 d = nb+1;
1365 @diffs[d] = font_desc[:diff];
1366 end
1367 @fonts[fontkey]['diff'] = d;
1368 end
1369 if (font_desc[:file] and font_desc[:file].length > 0)
1370 if (font_desc[:type] == "TrueType") or (font_desc[:type] == "TrueTypeUnicode")
1371 @font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize]}
1372 else
1373 @font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2]}
1374 end
1375 end
1376 end
1377 alias_method :add_font, :AddFont
1378
1379 #
1380 # Sets the font used to print character strings. It is mandatory to call this method at least once before printing text or the resulting document would not be valid.
1381 # The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
1382 # The method can be called before the first page is created and the font is retained from page to page.
1383 # If you just wish to change the current font size, it is simpler to call SetFontSize().
1384 # Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the FPDF_FONTPATH constant</li></ul><br />
1385 # Example for the last case (note the trailing slash):<br />
1386 # <pre>
1387 # define('FPDF_FONTPATH','/home/www/font/');
1388 # require('tcpdf.rb');
1389 #
1390 # #Times regular 12
1391 # :pdf->SetFont('Times');
1392 # #Arial bold 14
1393 # :pdf->SetFont('Arial','B',14);
1394 # #Removes bold
1395 # :pdf->SetFont('');
1396 # #Times bold, italic and underlined 14
1397 # :pdf->SetFont('Times','BIU');
1398 # </pre><br />
1399 # If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated.
1400 # @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):<ul><li>Courier (fixed-width)</li><li>Helvetica or Arial (synonymous; sans serif)</li><li>Times (serif)</li><li>Symbol (symbolic)</li><li>ZapfDingbats (symbolic)</li></ul>It is also possible to pass an empty string. In that case, the current family is retained.
1401 # @param string :style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li></ul>or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats
1402 # @param float :size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
1403 # @since 1.0
1404 # @see AddFont(), SetFontSize(), Cell(), MultiCell(), Write()
1405 #
1406 def SetFont(family, style='', size=0)
1407 # save previous values
1408 @prevfont_family = @font_family;
1409 @prevfont_style = @font_style;
1410
1411 family=family.downcase;
1412 if (family=='')
1413 family=@font_family;
1414 end
1415 if ((!@is_unicode) and (family == 'arial'))
1416 family = 'helvetica';
1417 elsif ((family=="symbol") or (family=="zapfdingbats"))
1418 style='';
1419 end
1420
1421 style=style.upcase;
1422
1423 if (style.include?('U'))
1424 @underline=true;
1425 style= style.gsub('U','');
1426 else
1427 @underline=false;
1428 end
1429 if (style=='IB')
1430 style='BI';
1431 end
1432 if (size==0)
1433 size=@font_size_pt;
1434 end
1435
1436 # try to add font (if not already added)
1437 AddFont(family, style);
1438
1439 #Test if font is already selected
1440 if ((@font_family == family) and (@font_style == style) and (@font_size_pt == size))
1441 return;
1442 end
1443
1444 fontkey = family + style;
1445 style = '' if (@fonts[fontkey].nil? and !@fonts[family].nil?)
1446
1447 #Test if used for the first time
1448 if (@fonts[fontkey].nil?)
1449 #Check if one of the standard fonts
1450 if (!@core_fonts[fontkey].nil?)
1451 if @@fpdf_charwidths[fontkey].nil?
1452 #Load metric file
1453 file = family;
1454 if ((family!='symbol') and (family!='zapfdingbats'))
1455 file += style.downcase;
1456 end
1457 if (getfontpath(file + '.rb').nil?)
1458 # try to load the basic file without styles
1459 file = family;
1460 fontkey = family;
1461 end
1462 require(getfontpath(file + '.rb'));
1463 font_desc = TCPDFFontDescriptor.font(file)
1464 if ((@is_unicode and ctg.nil?) or ((!@is_unicode) and (@@fpdf_charwidths[fontkey].nil?)) )
1465 Error("Could not include font metric file [" + fontkey + "]: " + getfontpath(file + ".rb"));
1466 end
1467 end
1468 i = @fonts.length + 1;
1469
1470 if (@is_unicode)
1471 @fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg]}
1472 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1473 else
1474 @fonts[fontkey] = {'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]}
1475 @@fpdf_charwidths[fontkey] = font_desc[:cw];
1476 end
1477 else
1478 Error('Undefined font: ' + family + ' ' + style);
1479 end
1480 end
1481 #Select it
1482 @font_family = family;
1483 @font_style = style;
1484 @font_size_pt = size;
1485 @font_size = size / @k;
1486 @current_font = @fonts[fontkey]; # was & may need deep copy?
1487 if (@page>0)
1488 out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt));
1489 end
1490 end
1491 alias_method :set_font, :SetFont
1492
1493 #
1494 # Defines the size of the current font.
1495 # @param float :size The size (in points)
1496 # @since 1.0
1497 # @see SetFont()
1498 #
1499 def SetFontSize(size)
1500 #Set font size in points
1501 if (@font_size_pt== size)
1502 return;
1503 end
1504 @font_size_pt = size;
1505 @font_size = size.to_f / @k;
1506 if (@page > 0)
1507 out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt));
1508 end
1509 end
1510 alias_method :set_font_size, :SetFontSize
1511
1512 #
1513 # Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
1514 # The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
1515 # @since 1.5
1516 # @see Cell(), Write(), Image(), Link(), SetLink()
1517 #
1518 def AddLink()
1519 #Create a new internal link
1520 n=@links.length+1;
1521 @links[n]=[0,0];
1522 return n;
1523 end
1524 alias_method :add_link, :AddLink
1525
1526 #
1527 # Defines the page and position a link points to
1528 # @param int :link The link identifier returned by AddLink()
1529 # @param float :y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
1530 # @param int :page Number of target page; -1 indicates the current page. This is the default value
1531 # @since 1.5
1532 # @see AddLink()
1533 #
1534 def SetLink(link, y=0, page=-1)
1535 #Set destination of internal link
1536 if (y==-1)
1537 y=@y;
1538 end
1539 if (page==-1)
1540 page=@page;
1541 end
1542 @links[link] = [page, y]
1543 end
1544 alias_method :set_link, :SetLink
1545
1546 #
1547 # Puts a link on a rectangular area of the page. Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
1548 # @param float :x Abscissa of the upper-left corner of the rectangle
1549 # @param float :y Ordinate of the upper-left corner of the rectangle
1550 # @param float :w Width of the rectangle
1551 # @param float :h Height of the rectangle
1552 # @param mixed :link URL or identifier returned by AddLink()
1553 # @since 1.5
1554 # @see AddLink(), Cell(), Write(), Image()
1555 #
1556 def Link(x, y, w, h, link)
1557 #Put a link on the page
1558 @page_links ||= Array.new
1559 @page_links[@page] ||= Array.new
1560 @page_links[@page].push([x * @k, @h_pt - y * @k, w * @k, h*@k, link]);
1561 end
1562 alias_method :link, :Link
1563
1564 #
1565 # Prints a character string. The origin is on the left of the first charcter, on the baseline. This method allows to place a string precisely on the page, but it is usually easier to use Cell(), MultiCell() or Write() which are the standard methods to print text.
1566 # @param float :x Abscissa of the origin
1567 # @param float :y Ordinate of the origin
1568 # @param string :txt String to print
1569 # @since 1.0
1570 # @see SetFont(), SetTextColor(), Cell(), MultiCell(), Write()
1571 #
1572 def Text(x, y, txt)
1573 #Output a string
1574 s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt));
1575 if (@underline and (txt!=''))
1576 s += ' ' + dounderline(x, y, txt);
1577 end
1578 if (@color_flag)
1579 s='q ' + @text_color + ' ' + s + ' Q';
1580 end
1581 out(s);
1582 end
1583 alias_method :text, :Text
1584
1585 #
1586 # Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
1587 # This method is called automatically and should not be called directly by the application.<br />
1588 # <b>Example:</b><br />
1589 # The method is overriden in an inherited class in order to obtain a 3 column layout:<br />
1590 # <pre>
1591 # class PDF extends TCPDF {
1592 # var :col=0;
1593 #
1594 # def SetCol(col)
1595 # #Move position to a column
1596 # @col = col;
1597 # :x=10+:col*65;
1598 # SetLeftMargin(x);
1599 # SetX(x);
1600 # end
1601 #
1602 # def AcceptPageBreak()
1603 # if (@col<2)
1604 # #Go to next column
1605 # SetCol(@col+1);
1606 # SetY(10);
1607 # return false;
1608 # end
1609 # else
1610 # #Go back to first column and issue page break
1611 # SetCol(0);
1612 # return true;
1613 # end
1614 # end
1615 # }
1616 #
1617 # :pdf=new PDF();
1618 # :pdf->Open();
1619 # :pdf->AddPage();
1620 # :pdf->SetFont('Arial','',12);
1621 # for(i=1;:i<=300;:i++)
1622 # :pdf->Cell(0,5,"Line :i",0,1);
1623 # }
1624 # :pdf->Output();
1625 # </pre>
1626 # @return boolean
1627 # @since 1.4
1628 # @see SetAutoPageBreak()
1629 #
1630 def AcceptPageBreak()
1631 #Accept automatic page break or not
1632 return @auto_page_break;
1633 end
1634 alias_method :accept_page_break, :AcceptPageBreak
1635
1636 def BreakThePage?(h)
1637 if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
1638 true
1639 else
1640 false
1641 end
1642 end
1643 alias_method :break_the_page?, :BreakThePage?
1644 #
1645 # Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
1646 # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
1647 # @param float :w Cell width. If 0, the cell extends up to the right margin.
1648 # @param float :h Cell height. Default value: 0.
1649 # @param string :txt String to print. Default value: empty string.
1650 # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
1651 # @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
1652 # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
1653 # @param string :align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li></ul>
1654 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
1655 # @param mixed :link URL or identifier returned by AddLink().
1656 # @since 1.0
1657 # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
1658 #
1659 def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil)
1660 #Output a cell
1661 k=@k;
1662 if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
1663 #Automatic page break
1664 x = @x;
1665 ws = @ws;
1666 if (ws > 0)
1667 @ws = 0;
1668 out('0 Tw');
1669 end
1670 AddPage(@cur_orientation);
1671 @x = x;
1672 if (ws > 0)
1673 @ws = ws;
1674 out(sprintf('%.3f Tw', ws * k));
1675 end
1676 end
1677 if (w == 0)
1678 w = @w - @r_margin - @x;
1679 end
1680 s = '';
1681 if ((fill.to_i == 1) or (border.to_i == 1))
1682 if (fill.to_i == 1)
1683 op = (border.to_i == 1) ? 'B' : 'f';
1684 else
1685 op = 'S';
1686 end
1687 s = sprintf('%.2f %.2f %.2f %.2f re %s ', @x * k, (@h - @y) * k, w * k, -h * k, op);
1688 end
1689 if (border.is_a?(String))
1690 x=@x;
1691 y=@y;
1692 if (border.include?('L'))
1693 s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-y)*k, x*k,(@h-(y+h))*k);
1694 end
1695 if (border.include?('T'))
1696 s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-y)*k,(x+w)*k,(@h-y)*k);
1697 end
1698 if (border.include?('R'))
1699 s<<sprintf('%.2f %.2f m %.2f %.2f l S ',(x+w)*k,(@h-y)*k,(x+w)*k,(@h-(y+h))*k);
1700 end
1701 if (border.include?('B'))
1702 s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-(y+h))*k,(x+w)*k,(@h-(y+h))*k);
1703 end
1704 end
1705 if (txt != '')
1706 width = GetStringWidth(txt);
1707 if (align == 'R' || align == 'right')
1708 dx = w - @c_margin - width;
1709 elsif (align=='C' || align == 'center')
1710 dx = (w - width)/2;
1711 else
1712 dx = @c_margin;
1713 end
1714 if (@color_flag)
1715 s << 'q ' + @text_color + ' ';
1716 end
1717 txt2 = escapetext(txt);
1718 s<<sprintf('BT %.2f %.2f Td (%s) Tj ET', (@x + dx) * k, (@h - (@y + 0.5 * h + 0.3 * @font_size)) * k, txt2);
1719 if (@underline)
1720 s<<' ' + dounderline(@x + dx, @y + 0.5 * h + 0.3 * @font_size, txt);
1721 end
1722 if (@color_flag)
1723 s<<' Q';
1724 end
1725 if link && !link.empty?
1726 Link(@x + dx, @y + 0.5 * h - 0.5 * @font_size, width, @font_size, link);
1727 end
1728 end
1729 if (s)
1730 out(s);
1731 end
1732 @lasth = h;
1733 if (ln.to_i>0)
1734 # Go to next line
1735 @y += h;
1736 if (ln == 1)
1737 @x = @l_margin;
1738 end
1739 else
1740 @x += w;
1741 end
1742 end
1743 alias_method :cell, :Cell
1744
1745 #
1746 # This method allows printing text with line breaks. They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
1747 # Text can be aligned, centered or justified. The cell block can be framed and the background painted.
1748 # @param float :w Width of cells. If 0, they extend up to the right margin of the page.
1749 # @param float :h Height of cells.
1750 # @param string :txt String to print
1751 # @param mixed :border Indicates if borders must be drawn around the cell block. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
1752 # @param string :align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value)</li></ul>
1753 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
1754 # @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
1755 # @since 1.3
1756 # @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
1757 #
1758 def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1)
1759
1760 # save current position
1761 prevx = @x;
1762 prevy = @y;
1763
1764 #Output text with automatic or explicit line breaks
1765
1766 if (w == 0)
1767 w = @w - @r_margin - @x;
1768 end
1769
1770 wmax = (w - 2 * @c_margin);
1771
1772 s = txt.gsub("\r", ''); # remove carriage returns
1773 nb = s.length;
1774
1775 b=0;
1776 if (border)
1777 if (border==1)
1778 border='LTRB';
1779 b='LRT';
1780 b2='LR';
1781 elsif border.is_a?(String)
1782 b2='';
1783 if (border.include?('L'))
1784 b2<<'L';
1785 end
1786 if (border.include?('R'))
1787 b2<<'R';
1788 end
1789 b=(border.include?('T')) ? b2 + 'T' : b2;
1790 end
1791 end
1792 sep=-1;
1793 to_index=0;
1794 from_j=0;
1795 l=0;
1796 ns=0;
1797 nl=1;
1798
1799 while to_index < nb
1800 #Get next character
1801 c = s[to_index];
1802 if c == "\n"[0]
1803 #Explicit line break
1804 if @ws > 0
1805 @ws = 0
1806 out('0 Tw')
1807 end
1808 #Ed Moss - change begin
1809 end_i = to_index == 0 ? 0 : to_index - 1
1810 # Changed from s[from_j..to_index] to fix bug reported by Hans Allis.
1811 from_j = to_index == 0 ? 1 : from_j
1812 Cell(w, h, s[from_j..end_i], b, 2, align, fill)
1813 #change end
1814 to_index += 1
1815 sep=-1
1816 from_j=to_index
1817 l=0
1818 ns=0
1819 nl += 1
1820 b = b2 if border and nl==2
1821 next
1822 end
1823 if (c == " "[0])
1824 sep = to_index;
1825 ls = l;
1826 ns += 1;
1827 end
1828
1829 l = GetStringWidth(s[from_j, to_index - from_j + 1]);
1830
1831 if (l > wmax)
1832 #Automatic line break
1833 if (sep == -1)
1834 if (to_index == from_j)
1835 to_index += 1;
1836 end
1837 if (@ws > 0)
1838 @ws = 0;
1839 out('0 Tw');
1840 end
1841 Cell(w, h, s[from_j..to_index-1], b, 2, align, fill) # my FPDF version
1842 else
1843 if (align=='J' || align=='justify' || align=='justified')
1844 @ws = (ns>1) ? (wmax-ls)/(ns-1) : 0;
1845 out(sprintf('%.3f Tw', @ws * @k));
1846 end
1847 Cell(w, h, s[from_j..sep], b, 2, align, fill);
1848 to_index = sep + 1;
1849 end
1850 sep=-1;
1851 from_j = to_index;
1852 l=0;
1853 ns=0;
1854 nl += 1;
1855 if (border and (nl==2))
1856 b = b2;
1857 end
1858 else
1859 to_index += 1;
1860 end
1861 end
1862 #Last chunk
1863 if (@ws>0)
1864 @ws=0;
1865 out('0 Tw');
1866 end
1867 if (border.is_a?(String) and border.include?('B'))
1868 b<<'B';
1869 end
1870 Cell(w, h, s[from_j, to_index-from_j], b, 2, align, fill);
1871
1872 # move cursor to specified position
1873 # since 2007-03-03
1874 if (ln == 1)
1875 # go to the beginning of the next line
1876 @x = @l_margin;
1877 elsif (ln == 0)
1878 # go to the top-right of the cell
1879 @y = prevy;
1880 @x = prevx + w;
1881 elsif (ln == 2)
1882 # go to the bottom-left of the cell
1883 @x = prevx;
1884 end
1885 end
1886 alias_method :multi_cell, :MultiCell
1887
1888 #
1889 # This method prints text from the current position. When the right margin is reached (or the \n character is met) a line break occurs and text continues from the left margin. Upon method exit, the current position is left just at the end of the text. It is possible to put a link on the text.<br />
1890 # <b>Example:</b><br />
1891 # <pre>
1892 # #Begin with regular font
1893 # :pdf->SetFont('Arial','',14);
1894 # :pdf->Write(5,'Visit ');
1895 # #Then put a blue underlined link
1896 # :pdf->SetTextColor(0,0,255);
1897 # :pdf->SetFont('','U');
1898 # :pdf->Write(5,'www.tecnick.com','http://www.tecnick.com');
1899 # </pre>
1900 # @param float :h Line height
1901 # @param string :txt String to print
1902 # @param mixed :link URL or identifier returned by AddLink()
1903 # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
1904 # @since 1.5
1905 # @see SetFont(), SetTextColor(), AddLink(), MultiCell(), SetAutoPageBreak()
1906 #
1907 def Write(h, txt, link=nil, fill=0)
1908
1909 #Output text in flowing mode
1910 w = @w - @r_margin - @x;
1911 wmax = (w - 2 * @c_margin);
1912
1913 s = txt.gsub("\r", '');
1914 nb = s.length;
1915
1916 # handle single space character
1917 if ((nb==1) and (s == " "))
1918 @x += GetStringWidth(s);
1919 return;
1920 end
1921
1922 sep=-1;
1923 i=0;
1924 j=0;
1925 l=0;
1926 nl=1;
1927 while(i<nb)
1928 #Get next character
1929 c = s[i];
1930 if (c == "\n"[0])
1931 #Explicit line break
1932 Cell(w, h, s[j,i-j], 0, 2, '', fill, link);
1933 i += 1;
1934 sep = -1;
1935 j = i;
1936 l = 0;
1937 if (nl == 1)
1938 @x = @l_margin;
1939 w = @w - @r_margin - @x;
1940 wmax = (w - 2 * @c_margin);
1941 end
1942 nl += 1;
1943 next
1944 end
1945 if (c == " "[0])
1946 sep= i;
1947 end
1948 l = GetStringWidth(s[j, i - j + 1]);
1949 if (l > wmax)
1950 #Automatic line break (word wrapping)
1951 if (sep == -1)
1952 if (@x > @l_margin)
1953 #Move to next line
1954 @x = @l_margin;
1955 @y += h;
1956 w=@w - @r_margin - @x;
1957 wmax=(w - 2 * @c_margin);
1958 i += 1
1959 nl += 1
1960 next
1961 end
1962 if (i == j)
1963 i += 1
1964 end
1965 Cell(w, h, s[j, (i-1)], 0, 2, '', fill, link);
1966 else
1967 Cell(w, h, s[j, (sep-j)], 0, 2, '', fill, link);
1968 i = sep+1;
1969 end
1970 sep = -1;
1971 j = i;
1972 l = 0;
1973 if (nl==1)
1974 @x = @l_margin;
1975 w = @w - @r_margin - @x;
1976 wmax = (w - 2 * @c_margin);
1977 end
1978 nl += 1;
1979 else
1980 i += 1;
1981 end
1982 end
1983 #Last chunk
1984 if (i != j)
1985 Cell(GetStringWidth(s[j..i]), h, s[j..i], 0, 0, '', fill, link);
1986 end
1987 end
1988 alias_method :write, :Write
1989
1990 #
1991 # Puts an image in the page. The upper-left corner must be given. The dimensions can be specified in different ways:<ul><li>explicit width and height (expressed in user unit)</li><li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li><li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
1992 # Supported formats are JPEG and PNG.
1993 # For JPEG, all flavors are allowed:<ul><li>gray scales</li><li>true colors (24 bits)</li><li>CMYK (32 bits)</li></ul>
1994 # For PNG, are allowed:<ul><li>gray scales on at most 8 bits (256 levels)</li><li>indexed colors</li><li>true colors (24 bits)</li></ul>
1995 # but are not supported:<ul><li>Interlacing</li><li>Alpha channel</li></ul>
1996 # If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above).<br />
1997 # The format can be specified explicitly or inferred from the file extension.<br />
1998 # It is possible to put a link on the image.<br />
1999 # Remark: if an image is used several times, only one copy will be embedded in the file.<br />
2000 # @param string :file Name of the file containing the image.
2001 # @param float :x Abscissa of the upper-left corner.
2002 # @param float :y Ordinate of the upper-left corner.
2003 # @param float :w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
2004 # @param float :h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
2005 # @param string :type Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension.
2006 # @param mixed :link URL or identifier returned by AddLink().
2007 # @since 1.1
2008 # @see AddLink()
2009 #
2010 def Image(file, x, y, w=0, h=0, type='', link=nil)
2011 #Put an image on the page
2012 if (@images[file].nil?)
2013 #First use of image, get info
2014 if (type == '')
2015 pos = file.rindex('.');
2016 if (pos == 0)
2017 Error('Image file has no extension and no type was specified: ' + file);
2018 end
2019 type = file[pos+1..-1];
2020 end
2021 type.downcase!
2022 if (type == 'jpg' or type == 'jpeg')
2023 info=parsejpg(file);
2024 elsif (type == 'png')
2025 info=parsepng(file);
2026 else
2027 #Allow for additional formats
2028 mtd='parse' + type;
2029 if (!self.respond_to?(mtd))
2030 Error('Unsupported image type: ' + type);
2031 end
2032 info=send(mtd, file);
2033 end
2034 info['i']=@images.length+1;
2035 @images[file] = info;
2036 else
2037 info=@images[file];
2038 end
2039 #Automatic width and height calculation if needed
2040 if ((w == 0) and (h == 0))
2041 #Put image at 72 dpi
2042 # 2004-06-14 :: Nicola Asuni, scale factor where added
2043 w = info['w'] / (@img_scale * @k);
2044 h = info['h'] / (@img_scale * @k);
2045 end
2046 if (w == 0)
2047 w = h * info['w'] / info['h'];
2048 end
2049 if (h == 0)
2050 h = w * info['h'] / info['w'];
2051 end
2052 out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i']));
2053 if (link)
2054 Link(x, y, w, h, link);
2055 end
2056
2057 #2002-07-31 - Nicola Asuni
2058 # set right-bottom corner coordinates
2059 @img_rb_x = x + w;
2060 @img_rb_y = y + h;
2061 end
2062 alias_method :image, :Image
2063
2064 #
2065 # Performs a line break. The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
2066 # @param float :h The height of the break. By default, the value equals the height of the last printed cell.
2067 # @since 1.0
2068 # @see Cell()
2069 #
2070 def Ln(h='')
2071 #Line feed; default value is last cell height
2072 @x=@l_margin;
2073 if (h.is_a?(String))
2074 @y += @lasth;
2075 else
2076 @y += h;
2077 end
2078 end
2079 alias_method :ln, :Ln
2080
2081 #
2082 # Returns the abscissa of the current position.
2083 # @return float
2084 # @since 1.2
2085 # @see SetX(), GetY(), SetY()
2086 #
2087 def GetX()
2088 #Get x position
2089 return @x;
2090 end
2091 alias_method :get_x, :GetX
2092
2093 #
2094 # Defines the abscissa of the current position. If the passed value is negative, it is relative to the right of the page.
2095 # @param float :x The value of the abscissa.
2096 # @since 1.2
2097 # @see GetX(), GetY(), SetY(), SetXY()
2098 #
2099 def SetX(x)
2100 #Set x position
2101 if (x>=0)
2102 @x = x;
2103 else
2104 @x=@w+x;
2105 end
2106 end
2107 alias_method :set_x, :SetX
2108
2109 #
2110 # Returns the ordinate of the current position.
2111 # @return float
2112 # @since 1.0
2113 # @see SetY(), GetX(), SetX()
2114 #
2115 def GetY()
2116 #Get y position
2117 return @y;
2118 end
2119 alias_method :get_y, :GetY
2120
2121 #
2122 # Moves the current abscissa back to the left margin and sets the ordinate. If the passed value is negative, it is relative to the bottom of the page.
2123 # @param float :y The value of the ordinate.
2124 # @since 1.0
2125 # @see GetX(), GetY(), SetY(), SetXY()
2126 #
2127 def SetY(y)
2128 #Set y position and reset x
2129 @x=@l_margin;
2130 if (y>=0)
2131 @y = y;
2132 else
2133 @y=@h+y;
2134 end
2135 end
2136 alias_method :set_y, :SetY
2137
2138 #
2139 # Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page.
2140 # @param float :x The value of the abscissa
2141 # @param float :y The value of the ordinate
2142 # @since 1.2
2143 # @see SetX(), SetY()
2144 #
2145 def SetXY(x, y)
2146 #Set x and y positions
2147 SetY(y);
2148 SetX(x);
2149 end
2150 alias_method :set_xy, :SetXY
2151
2152 #
2153 # Send the document to a given destination: string, local file or browser. In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
2154 # The method first calls Close() if necessary to terminate the document.
2155 # @param string :name The name of the file. If not given, the document will be sent to the browser (destination I) with the name doc.pdf.
2156 # @param string :dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local file with the name given by name.</li><li>S: return the document as a string. name is ignored.</li></ul>If the parameter is not specified but a name is given, destination is F. If no parameter is specified at all, destination is I.<br />
2157 # @since 1.0
2158 # @see Close()
2159 #
2160 def Output(name='', dest='')
2161 #Output PDF to some destination
2162 #Finish document if necessary
2163 if (@state < 3)
2164 Close();
2165 end
2166 #Normalize parameters
2167 # Boolean no longer supported
2168 # if (dest.is_a?(Boolean))
2169 # dest = dest ? 'D' : 'F';
2170 # end
2171 dest = dest.upcase
2172 if (dest=='')
2173 if (name=='')
2174 name='doc.pdf';
2175 dest='I';
2176 else
2177 dest='F';
2178 end
2179 end
2180 case (dest)
2181 when 'I'
2182 # This is PHP specific code
2183 ##Send to standard output
2184 # if (ob_get_contents())
2185 # Error('Some data has already been output, can\'t send PDF file');
2186 # end
2187 # if (php_sapi_name()!='cli')
2188 # #We send to a browser
2189 # header('Content-Type: application/pdf');
2190 # if (headers_sent())
2191 # Error('Some data has already been output to browser, can\'t send PDF file');
2192 # end
2193 # header('Content-Length: ' + @buffer.length);
2194 # header('Content-disposition: inline; filename="' + name + '"');
2195 # end
2196 return @buffer;
2197
2198 when 'D'
2199 # PHP specific
2200 #Download file
2201 # if (ob_get_contents())
2202 # Error('Some data has already been output, can\'t send PDF file');
2203 # end
2204 # if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE'))
2205 # header('Content-Type: application/force-download');
2206 # else
2207 # header('Content-Type: application/octet-stream');
2208 # end
2209 # if (headers_sent())
2210 # Error('Some data has already been output to browser, can\'t send PDF file');
2211 # end
2212 # header('Content-Length: '+ @buffer.length);
2213 # header('Content-disposition: attachment; filename="' + name + '"');
2214 return @buffer;
2215
2216 when 'F'
2217 open(name,'wb') do |f|
2218 f.write(@buffer)
2219 end
2220 # PHP code
2221 # #Save to local file
2222 # f=open(name,'wb');
2223 # if (!f)
2224 # Error('Unable to create output file: ' + name);
2225 # end
2226 # fwrite(f,@buffer,@buffer.length);
2227 # f.close
2228
2229 when 'S'
2230 #Return as a string
2231 return @buffer;
2232 else
2233 Error('Incorrect output destination: ' + dest);
2234
2235 end
2236 return '';
2237 end
2238 alias_method :output, :Output
2239
2240 # Protected methods
2241
2242 #
2243 # Check for locale-related bug
2244 # @access protected
2245 #
2246 def dochecks()
2247 #Check for locale-related bug
2248 if (1.1==1)
2249 Error('Don\'t alter the locale before including class file');
2250 end
2251 #Check for decimal separator
2252 if (sprintf('%.1f',1.0)!='1.0')
2253 setlocale(LC_NUMERIC,'C');
2254 end
2255 end
2256
2257 #
2258 # Return fonts path
2259 # @access protected
2260 #
2261 def getfontpath(file)
2262 # Is it in the @@font_path?
2263 if @@font_path
2264 fpath = File.join @@font_path, file
2265 if File.exists?(fpath)
2266 return fpath
2267 end
2268 end
2269 # Is it in this plugin's font folder?
2270 fpath = File.join File.dirname(__FILE__), 'fonts', file
2271 if File.exists?(fpath)
2272 return fpath
2273 end
2274 # Could not find it.
2275 nil
2276 end
2277
2278 #
2279 # Start document
2280 # @access protected
2281 #
2282 def begindoc()
2283 #Start document
2284 @state=1;
2285 out('%PDF-1.3');
2286 end
2287
2288 #
2289 # putpages
2290 # @access protected
2291 #
2292 def putpages()
2293 nb = @page;
2294 if (@alias_nb_pages)
2295 nbstr = UTF8ToUTF16BE(nb.to_s, false);
2296 #Replace number of pages
2297 1.upto(nb) do |n|
2298 @pages[n].gsub!(@alias_nb_pages, nbstr)
2299 end
2300 end
2301 if @def_orientation=='P'
2302 w_pt=@fw_pt
2303 h_pt=@fh_pt
2304 else
2305 w_pt=@fh_pt
2306 h_pt=@fw_pt
2307 end
2308 filter=(@compress) ? '/Filter /FlateDecode ' : ''
2309 1.upto(nb) do |n|
2310 #Page
2311 newobj
2312 out('<</Type /Page')
2313 out('/Parent 1 0 R')
2314 unless @orientation_changes[n].nil?
2315 out(sprintf('/MediaBox [0 0 %.2f %.2f]', h_pt, w_pt))
2316 end
2317 out('/Resources 2 0 R')
2318 if @page_links[n]
2319 #Links
2320 annots='/Annots ['
2321 @page_links[n].each do |pl|
2322 rect=sprintf('%.2f %.2f %.2f %.2f', pl[0], pl[1], pl[0]+pl[2], pl[1]-pl[3]);
2323 annots<<'<</Type /Annot /Subtype /Link /Rect [' + rect + '] /Border [0 0 0] ';
2324 if (pl[4].is_a?(String))
2325 annots<<'/A <</S /URI /URI (' + escape(pl[4]) + ')>>>>';
2326 else
2327 l=@links[pl[4]];
2328 h=!@orientation_changes[l[0]].nil? ? w_pt : h_pt;
2329 annots<<sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',1+2*l[0], h-l[1]*@k);
2330 end
2331 end
2332 out(annots + ']');
2333 end
2334 out('/Contents ' + (@n+1).to_s + ' 0 R>>');
2335 out('endobj');
2336 #Page content
2337 p=(@compress) ? gzcompress(@pages[n]) : @pages[n];
2338 newobj();
2339 out('<<' + filter + '/Length '+ p.length.to_s + '>>');
2340 putstream(p);
2341 out('endobj');
2342 end
2343 #Pages root
2344 @offsets[1]=@buffer.length;
2345 out('1 0 obj');
2346 out('<</Type /Pages');
2347 kids='/Kids [';
2348 0.upto(nb) do |i|
2349 kids<<(3+2*i).to_s + ' 0 R ';
2350 end
2351 out(kids + ']');
2352 out('/Count ' + nb.to_s);
2353 out(sprintf('/MediaBox [0 0 %.2f %.2f]', w_pt, h_pt));
2354 out('>>');
2355 out('endobj');
2356 end
2357
2358 #
2359 # Adds fonts
2360 # putfonts
2361 # @access protected
2362 #
2363 def putfonts()
2364 nf=@n;
2365 @diffs.each do |diff|
2366 #Encodings
2367 newobj();
2368 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' + diff + ']>>');
2369 out('endobj');
2370 end
2371 @font_files.each do |file, info|
2372 #Font file embedding
2373 newobj();
2374 @font_files[file]['n']=@n;
2375 font='';
2376 open(getfontpath(file),'rb') do |f|
2377 font = f.read();
2378 end
2379 compressed=(file[-2,2]=='.z');
2380 if (!compressed && !info['length2'].nil?)
2381 header=((font[0][0])==128);
2382 if (header)
2383 #Strip first binary header
2384 font=font[6];
2385 end
2386 if header && (font[info['length1']][0] == 128)
2387 #Strip second binary header
2388 font=font[0..info['length1']] + font[info['length1']+6];
2389 end
2390 end
2391 out('<</Length '+ font.length.to_s);
2392 if (compressed)
2393 out('/Filter /FlateDecode');
2394 end
2395 out('/Length1 ' + info['length1'].to_s);
2396 if (!info['length2'].nil?)
2397 out('/Length2 ' + info['length2'].to_s + ' /Length3 0');
2398 end
2399 out('>>');
2400 open(getfontpath(file),'rb') do |f|
2401 putstream(font)
2402 end
2403 out('endobj');
2404 end
2405 @fonts.each do |k, font|
2406 #Font objects
2407 @fonts[k]['n']=@n+1;
2408 type = font['type'];
2409 name = font['name'];
2410 if (type=='core')
2411 #Standard font
2412 newobj();
2413 out('<</Type /Font');
2414 out('/BaseFont /' + name);
2415 out('/Subtype /Type1');
2416 if (name!='Symbol' && name!='ZapfDingbats')
2417 out('/Encoding /WinAnsiEncoding');
2418 end
2419 out('>>');
2420 out('endobj');
2421 elsif type == 'Type0'
2422 putType0(font)
2423 elsif (type=='Type1' || type=='TrueType')
2424 #Additional Type1 or TrueType font
2425 newobj();
2426 out('<</Type /Font');
2427 out('/BaseFont /' + name);
2428 out('/Subtype /' + type);
2429 out('/FirstChar 32 /LastChar 255');
2430 out('/Widths ' + (@n+1).to_s + ' 0 R');
2431 out('/FontDescriptor ' + (@n+2).to_s + ' 0 R');
2432 if (font['enc'])
2433 if (!font['diff'].nil?)
2434 out('/Encoding ' + (nf+font['diff']).to_s + ' 0 R');
2435 else
2436 out('/Encoding /WinAnsiEncoding');
2437 end
2438 end
2439 out('>>');
2440 out('endobj');
2441 #Widths
2442 newobj();
2443 cw=font['cw']; # &
2444 s='[';
2445 32.upto(255) do |i|
2446 s << cw[i.chr] + ' ';
2447 end
2448 out(s + ']');
2449 out('endobj');
2450 #Descriptor
2451 newobj();
2452 s='<</Type /FontDescriptor /FontName /' + name;
2453 font['desc'].each do |k, v|
2454 s<<' /' + k + ' ' + v;
2455 end
2456 file = font['file'];
2457 if (file)
2458 s<<' /FontFile' + (type=='Type1' ? '' : '2') + ' ' + @font_files[file]['n'] + ' 0 R';
2459 end
2460 out(s + '>>');
2461 out('endobj');
2462 else
2463 #Allow for additional types
2464 mtd='put' + type.downcase;
2465 if (!self.respond_to?(mtd))
2466 Error('Unsupported font type: ' + type)
2467 else
2468 self.send(mtd,font)
2469 end
2470 end
2471 end
2472 end
2473
2474 def putType0(font)
2475 #Type0
2476 out('<</Type /Font')
2477 out('/Subtype /Type0')
2478 out('/BaseFont /'+font['name']+'-'+font['cMap'])
2479 out('/Encoding /'+font['cMap'])
2480 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
2481 out('>>')
2482 out('endobj')
2483 #CIDFont
2484 newobj()
2485 out('<</Type /Font')
2486 out('/Subtype /CIDFontType0')
2487 out('/BaseFont /'+font['name'])
2488 out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
2489 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
2490 w='/W [1 ['
2491 font['cw'].keys.sort.each {|key|
2492 w+=font['cw'][key].to_s + " "
2493 # ActionController::Base::logger.debug key.to_s
2494 # ActionController::Base::logger.debug font['cw'][key].to_s
2495 }
2496 out(w+'] 231 325 500 631 [500] 326 389 500]')
2497 out('>>')
2498 out('endobj')
2499 #Font descriptor
2500 newobj()
2501 out('<</Type /FontDescriptor')
2502 out('/FontName /'+font['name'])
2503 out('/Flags 6')
2504 out('/FontBBox [0 -200 1000 900]')
2505 out('/ItalicAngle 0')
2506 out('/Ascent 800')
2507 out('/Descent -200')
2508 out('/CapHeight 800')
2509 out('/StemV 60')
2510 out('>>')
2511 out('endobj')
2512 end
2513
2514 #
2515 # putimages
2516 # @access protected
2517 #
2518 def putimages()
2519 filter=(@compress) ? '/Filter /FlateDecode ' : '';
2520 @images.each do |file, info| # was while(list(file, info)=each(@images))
2521 newobj();
2522 @images[file]['n']=@n;
2523 out('<</Type /XObject');
2524 out('/Subtype /Image');
2525 out('/Width ' + info['w'].to_s);
2526 out('/Height ' + info['h'].to_s);
2527 if (info['cs']=='Indexed')
2528 out('/ColorSpace [/Indexed /DeviceRGB ' + (info['pal'].length/3-1) + ' ' + (@n+1) + ' 0 R]');
2529 else
2530 out('/ColorSpace /' + info['cs']);
2531 if (info['cs']=='DeviceCMYK')
2532 out('/Decode [1 0 1 0 1 0 1 0]');
2533 end
2534 end
2535 out('/BitsPerComponent ' + info['bpc'].to_s);
2536 if (!info['f'].nil?)
2537 out('/Filter /' + info['f']);
2538 end
2539 if (!info['parms'].nil?)
2540 out(info['parms']);
2541 end
2542 if (!info['trns'].nil? and info['trns'].kind_of?(Array))
2543 trns='';
2544 0.upto(info['trns'].length) do |i|
2545 trns << info['trns'][i] + ' ' + info['trns'][i] + ' ';
2546 end
2547 out('/Mask [' + trns + ']');
2548 end
2549 out('/Length ' + info['data'].length.to_s + '>>');
2550 putstream(info['data']);
2551 @images[file]['data']=nil
2552 out('endobj');
2553 #Palette
2554 if (info['cs']=='Indexed')
2555 newobj();
2556 pal=(@compress) ? gzcompress(info['pal']) : :info['pal'];
2557 out('<<' + filter + '/Length ' + pal.length.to_s + '>>');
2558 putstream(pal);
2559 out('endobj');
2560 end
2561 end
2562 end
2563
2564 #
2565 # putxobjectdict
2566 # @access protected
2567 #
2568 def putxobjectdict()
2569 @images.each_value do |image|
2570 out('/I' + image['i'].to_s + ' ' + image['n'].to_s + ' 0 R');
2571 end
2572 end
2573
2574 #
2575 # putresourcedict
2576 # @access protected
2577 #
2578 def putresourcedict()
2579 out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
2580 out('/Font <<');
2581 @fonts.each_value do |font|
2582 out('/F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R');
2583 end
2584 out('>>');
2585 out('/XObject <<');
2586 putxobjectdict();
2587 out('>>');
2588 end
2589
2590 #
2591 # putresources
2592 # @access protected
2593 #
2594 def putresources()
2595 putfonts();
2596 putimages();
2597 #Resource dictionary
2598 @offsets[2]=@buffer.length;
2599 out('2 0 obj');
2600 out('<<');
2601 putresourcedict();
2602 out('>>');
2603 out('endobj');
2604 end
2605
2606 #
2607 # putinfo
2608 # @access protected
2609 #
2610 def putinfo()
2611 out('/Producer ' + textstring(PDF_PRODUCER));
2612 if (!@title.nil?)
2613 out('/Title ' + textstring(@title));
2614 end
2615 if (!@subject.nil?)
2616 out('/Subject ' + textstring(@subject));
2617 end
2618 if (!@author.nil?)
2619 out('/Author ' + textstring(@author));
2620 end
2621 if (!@keywords.nil?)
2622 out('/Keywords ' + textstring(@keywords));
2623 end
2624 if (!@creator.nil?)
2625 out('/Creator ' + textstring(@creator));
2626 end
2627 out('/CreationDate ' + textstring('D:' + Time.now.strftime('%Y%m%d%H%M%S')));
2628 end
2629
2630 #
2631 # putcatalog
2632 # @access protected
2633 #
2634 def putcatalog()
2635 out('/Type /Catalog');
2636 out('/Pages 1 0 R');
2637 if (@zoom_mode=='fullpage')
2638 out('/OpenAction [3 0 R /Fit]');
2639 elsif (@zoom_mode=='fullwidth')
2640 out('/OpenAction [3 0 R /FitH null]');
2641 elsif (@zoom_mode=='real')
2642 out('/OpenAction [3 0 R /XYZ null null 1]');
2643 elsif (!@zoom_mode.is_a?(String))
2644 out('/OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100) + ']');
2645 end
2646 if (@layout_mode=='single')
2647 out('/PageLayout /SinglePage');
2648 elsif (@layout_mode=='continuous')
2649 out('/PageLayout /OneColumn');
2650 elsif (@layout_mode=='two')
2651 out('/PageLayout /TwoColumnLeft');
2652 end
2653 end
2654
2655 #
2656 # puttrailer
2657 # @access protected
2658 #
2659 def puttrailer()
2660 out('/Size ' + (@n+1).to_s);
2661 out('/Root ' + @n.to_s + ' 0 R');
2662 out('/Info ' + (@n-1).to_s + ' 0 R');
2663 end
2664
2665 #
2666 # putheader
2667 # @access protected
2668 #
2669 def putheader()
2670 out('%PDF-' + @pdf_version);
2671 end
2672
2673 #
2674 # enddoc
2675 # @access protected
2676 #
2677 def enddoc()
2678 putheader();
2679 putpages();
2680 putresources();
2681 #Info
2682 newobj();
2683 out('<<');
2684 putinfo();
2685 out('>>');
2686 out('endobj');
2687 #Catalog
2688 newobj();
2689 out('<<');
2690 putcatalog();
2691 out('>>');
2692 out('endobj');
2693 #Cross-ref
2694 o=@buffer.length;
2695 out('xref');
2696 out('0 ' + (@n+1).to_s);
2697 out('0000000000 65535 f ');
2698 1.upto(@n) do |i|
2699 out(sprintf('%010d 00000 n ',@offsets[i]));
2700 end
2701 #Trailer
2702 out('trailer');
2703 out('<<');
2704 puttrailer();
2705 out('>>');
2706 out('startxref');
2707 out(o);
2708 out('%%EOF');
2709 @state=3;
2710 end
2711
2712 #
2713 # beginpage
2714 # @access protected
2715 #
2716 def beginpage(orientation)
2717 @page += 1;
2718 @pages[@page]='';
2719 @state=2;
2720 @x=@l_margin;
2721 @y=@t_margin;
2722 @font_family='';
2723 #Page orientation
2724 if (orientation.empty?)
2725 orientation=@def_orientation;
2726 else
2727 orientation.upcase!
2728 if (orientation!=@def_orientation)
2729 @orientation_changes[@page]=true;
2730 end
2731 end
2732 if (orientation!=@cur_orientation)
2733 #Change orientation
2734 if (orientation=='P')
2735 @w_pt=@fw_pt;
2736 @h_pt=@fh_pt;
2737 @w=@fw;
2738 @h=@fh;
2739 else
2740 @w_pt=@fh_pt;
2741 @h_pt=@fw_pt;
2742 @w=@fh;
2743 @h=@fw;
2744 end
2745 @page_break_trigger=@h-@b_margin;
2746 @cur_orientation = orientation;
2747 end
2748 end
2749
2750 #
2751 # End of page contents
2752 # @access protected
2753 #
2754 def endpage()
2755 @state=1;
2756 end
2757
2758 #
2759 # Begin a new object
2760 # @access protected
2761 #
2762 def newobj()
2763 @n += 1;
2764 @offsets[@n]=@buffer.length;
2765 out(@n.to_s + ' 0 obj');
2766 end
2767
2768 #
2769 # Underline text
2770 # @access protected
2771 #
2772 def dounderline(x, y, txt)
2773 up = @current_font['up'];
2774 ut = @current_font['ut'];
2775 w = GetStringWidth(txt) + @ws * txt.count(' ');
2776 sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - (y - up / 1000.0 * @font_size)) * @k, w * @k, -ut / 1000.0 * @font_size_pt);
2777 end
2778
2779 #
2780 # Extract info from a JPEG file
2781 # @access protected
2782 #
2783 def parsejpg(file)
2784 a=getimagesize(file);
2785 if (a.empty?)
2786 Error('Missing or incorrect image file: ' + file);
2787 end
2788 if (a[2]!='JPEG')
2789 Error('Not a JPEG file: ' + file);
2790 end
2791 if (a['channels'].nil? or a['channels']==3)
2792 colspace='DeviceRGB';
2793 elsif (a['channels']==4)
2794 colspace='DeviceCMYK';
2795 else
2796 colspace='DeviceGray';
2797 end
2798 bpc=!a['bits'].nil? ? a['bits'] : 8;
2799 #Read whole file
2800 data='';
2801 open(file,'rb') do |f|
2802 data<<f.read();
2803 end
2804 return {'w' => a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data}
2805 end
2806
2807 #
2808 # Extract info from a PNG file
2809 # @access protected
2810 #
2811 def parsepng(file)
2812 f=open(file,'rb');
2813 #Check signature
2814 if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr)
2815 Error('Not a PNG file: ' + file);
2816 end
2817 #Read header chunk
2818 f.read(4);
2819 if (f.read(4)!='IHDR')
2820 Error('Incorrect PNG file: ' + file);
2821 end
2822 w=freadint(f);
2823 h=freadint(f);
2824 bpc=f.read(1)[0];
2825 if (bpc>8)
2826 Error('16-bit depth not supported: ' + file);
2827 end
2828 ct=f.read(1)[0];
2829 if (ct==0)
2830 colspace='DeviceGray';
2831 elsif (ct==2)
2832 colspace='DeviceRGB';
2833 elsif (ct==3)
2834 colspace='Indexed';
2835 else
2836 Error('Alpha channel not supported: ' + file);
2837 end
2838 if (f.read(1)[0] != 0)
2839 Error('Unknown compression method: ' + file);
2840 end
2841 if (f.read(1)[0]!=0)
2842 Error('Unknown filter method: ' + file);
2843 end
2844 if (f.read(1)[0]!=0)
2845 Error('Interlacing not supported: ' + file);
2846 end
2847 f.read(4);
2848 parms='/DecodeParms <</Predictor 15 /Colors ' + (ct==2 ? 3 : 1).to_s + ' /BitsPerComponent ' + bpc.to_s + ' /Columns ' + w.to_s + '>>';
2849 #Scan chunks looking for palette, transparency and image data
2850 pal='';
2851 trns='';
2852 data='';
2853 begin
2854 n=freadint(f);
2855 type=f.read(4);
2856 if (type=='PLTE')
2857 #Read palette
2858 pal=f.read( n);
2859 f.read(4);
2860 elsif (type=='tRNS')
2861 #Read transparency info
2862 t=f.read( n);
2863 if (ct==0)
2864 trns = t[1][0]
2865 elsif (ct==2)
2866 trns = t[[1][0], t[3][0], t[5][0]]
2867 else
2868 pos=t.include?(0.chr);
2869 if (pos!=false)
2870 trns = [pos]
2871 end
2872 end
2873 f.read(4);
2874 elsif (type=='IDAT')
2875 #Read image data block
2876 data<<f.read( n);
2877 f.read(4);
2878 elsif (type=='IEND')
2879 break;
2880 else
2881 f.read( n+4);
2882 end
2883 end while(n)
2884 if (colspace=='Indexed' and pal.empty?)
2885 Error('Missing palette in ' + file);
2886 end
2887 f.close
2888 return {'w' => w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data}
2889 end
2890
2891 #
2892 # Read a 4-byte integer from file
2893 # @access protected
2894 #
2895 def freadint(f)
2896 # Read a 4-byte integer from file
2897 a = f.read(4).unpack('N')
2898 return a[0]
2899 end
2900
2901 #
2902 # Format a text string
2903 # @access protected
2904 #
2905 def textstring(s)
2906 if (@is_unicode)
2907 #Convert string to UTF-16BE
2908 s = UTF8ToUTF16BE(s, true);
2909 end
2910 return '(' + escape(s) + ')';
2911 end
2912
2913 #
2914 # Format a text string
2915 # @access protected
2916 #
2917 def escapetext(s)
2918 if (@is_unicode)
2919 #Convert string to UTF-16BE
2920 s = UTF8ToUTF16BE(s, false);
2921 end
2922 return escape(s);
2923 end
2924
2925 #
2926 # Add \ before \, ( and )
2927 # @access protected
2928 #
2929 def escape(s)
2930 # Add \ before \, ( and )
2931 s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r')
2932 end
2933
2934 #
2935 #
2936 # @access protected
2937 #
2938 def putstream(s)
2939 out('stream');
2940 out(s);
2941 out('endstream');
2942 end
2943
2944 #
2945 # Add a line to the document
2946 # @access protected
2947 #
2948 def out(s)
2949 if (@state==2)
2950 @pages[@page] << s.to_s + "\n";
2951 else
2952 @buffer << s.to_s + "\n";
2953 end
2954 end
2955
2956 #
2957 # Adds unicode fonts.<br>
2958 # Based on PDF Reference 1.3 (section 5)
2959 # @access protected
2960 # @author Nicola Asuni
2961 # @since 1.52.0.TC005 (2005-01-05)
2962 #
2963 def puttruetypeunicode(font)
2964 # Type0 Font
2965 # A composite font composed of other fonts, organized hierarchically
2966 newobj();
2967 out('<</Type /Font');
2968 out('/Subtype /Type0');
2969 out('/BaseFont /' + font['name'] + '');
2970 out('/Encoding /Identity-H'); #The horizontal identity mapping for 2-byte CIDs; may be used with CIDFonts using any Registry, Ordering, and Supplement values.
2971 out('/DescendantFonts [' + (@n + 1).to_s + ' 0 R]');
2972 out('/ToUnicode ' + (@n + 2).to_s + ' 0 R');
2973 out('>>');
2974 out('endobj');
2975
2976 # CIDFontType2
2977 # A CIDFont whose glyph descriptions are based on TrueType font technology
2978 newobj();
2979 out('<</Type /Font');
2980 out('/Subtype /CIDFontType2');
2981 out('/BaseFont /' + font['name'] + '');
2982 out('/CIDSystemInfo ' + (@n + 2).to_s + ' 0 R');
2983 out('/FontDescriptor ' + (@n + 3).to_s + ' 0 R');
2984 if (!font['desc']['MissingWidth'].nil?)
2985 out('/DW ' + font['desc']['MissingWidth'].to_s + ''); # The default width for glyphs in the CIDFont MissingWidth
2986 end
2987 w = "";
2988 font['cw'].each do |cid, width|
2989 w << '' + cid.to_s + ' [' + width.to_s + '] '; # define a specific width for each individual CID
2990 end
2991 out('/W [' + w + ']'); # A description of the widths for the glyphs in the CIDFont
2992 out('/CIDToGIDMap ' + (@n + 4).to_s + ' 0 R');
2993 out('>>');
2994 out('endobj');
2995
2996 # ToUnicode
2997 # is a stream object that contains the definition of the CMap
2998 # (PDF Reference 1.3 chap. 5.9)
2999 newobj();
3000 out('<</Length 383>>');
3001 out('stream');
3002 out('/CIDInit /ProcSet findresource begin');
3003 out('12 dict begin');
3004 out('begincmap');
3005 out('/CIDSystemInfo');
3006 out('<</Registry (Adobe)');
3007 out('/Ordering (UCS)');
3008 out('/Supplement 0');
3009 out('>> def');
3010 out('/CMapName /Adobe-Identity-UCS def');
3011 out('/CMapType 2 def');
3012 out('1 begincodespacerange');
3013 out('<0000> <FFFF>');
3014 out('endcodespacerange');
3015 out('1 beginbfrange');
3016 out('<0000> <FFFF> <0000>');
3017 out('endbfrange');
3018 out('endcmap');
3019 out('CMapName currentdict /CMap defineresource pop');
3020 out('end');
3021 out('end');
3022 out('endstream');
3023 out('endobj');
3024
3025 # CIDSystemInfo dictionary
3026 # A dictionary containing entries that define the character collection of the CIDFont.
3027 newobj();
3028 out('<</Registry (Adobe)'); # A string identifying an issuer of character collections
3029 out('/Ordering (UCS)'); # A string that uniquely names a character collection issued by a specific registry
3030 out('/Supplement 0'); # The supplement number of the character collection.
3031 out('>>');
3032 out('endobj');
3033
3034 # Font descriptor
3035 # A font descriptor describing the CIDFont default metrics other than its glyph widths
3036 newobj();
3037 out('<</Type /FontDescriptor');
3038 out('/FontName /' + font['name']);
3039 font['desc'].each do |key, value|
3040 out('/' + key.to_s + ' ' + value.to_s);
3041 end
3042 if (font['file'])
3043 # A stream containing a TrueType font program
3044 out('/FontFile2 ' + @font_files[font['file']]['n'].to_s + ' 0 R');
3045 end
3046 out('>>');
3047 out('endobj');
3048
3049 # Embed CIDToGIDMap
3050 # A specification of the mapping from CIDs to glyph indices
3051 newobj();
3052 ctgfile = getfontpath(font['ctg'])
3053 if (!ctgfile)
3054 Error('Font file not found: ' + ctgfile);
3055 end
3056 size = File.size(ctgfile);
3057 out('<</Length ' + size.to_s + '');
3058 if (ctgfile[-2,2] == '.z') # check file extension
3059 # Decompresses data encoded using the public-domain
3060 # zlib/deflate compression method, reproducing the
3061 # original text or binary data#
3062 out('/Filter /FlateDecode');
3063 end
3064 out('>>');
3065 open(ctgfile, "rb") do |f|
3066 putstream(f.read())
3067 end
3068 out('endobj');
3069 end
3070
3071 #
3072 # Converts UTF-8 strings to codepoints array.<br>
3073 # Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
3074 # Based on: http://www.faqs.org/rfcs/rfc3629.html
3075 # <pre>
3076 # Char. number range | UTF-8 octet sequence
3077 # (hexadecimal) | (binary)
3078 # --------------------+-----------------------------------------------
3079 # 0000 0000-0000 007F | 0xxxxxxx
3080 # 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
3081 # 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
3082 # 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
3083 # ---------------------------------------------------------------------
3084 #
3085 # ABFN notation:
3086 # ---------------------------------------------------------------------
3087 # UTF8-octets =#( UTF8-char )
3088 # UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
3089 # UTF8-1 = %x00-7F
3090 # UTF8-2 = %xC2-DF UTF8-tail
3091 #
3092 # UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
3093 # %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
3094 # UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
3095 # %xF4 %x80-8F 2( UTF8-tail )
3096 # UTF8-tail = %x80-BF
3097 # ---------------------------------------------------------------------
3098 # </pre>
3099 # @param string :str string to process.
3100 # @return array containing codepoints (UTF-8 characters values)
3101 # @access protected
3102 # @author Nicola Asuni
3103 # @since 1.53.0.TC005 (2005-01-05)
3104 #
3105 def UTF8StringToArray(str)
3106 if (!@is_unicode)
3107 return str; # string is not in unicode
3108 end
3109
3110 unicode = [] # array containing unicode values
3111 bytes = [] # array containing single character byte sequences
3112 numbytes = 1; # number of octetc needed to represent the UTF-8 character
3113
3114 str = str.to_s; # force :str to be a string
3115
3116 str.each_byte do |char|
3117 if (bytes.length == 0) # get starting octect
3118 if (char <= 0x7F)
3119 unicode << char # use the character "as is" because is ASCII
3120 numbytes = 1
3121 elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN)
3122 bytes << ((char - 0xC0) << 0x06)
3123 numbytes = 2
3124 elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN)
3125 bytes << ((char - 0xE0) << 0x0C)
3126 numbytes = 3
3127 elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN)
3128 bytes << ((char - 0xF0) << 0x12)
3129 numbytes = 4
3130 else
3131 # use replacement character for other invalid sequences
3132 unicode << 0xFFFD
3133 bytes = []
3134 numbytes = 1
3135 end
3136 elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN
3137 bytes << (char - 0x80)
3138 if (bytes.length == numbytes)
3139 # compose UTF-8 bytes to a single unicode value
3140 char = bytes[0]
3141 1.upto(numbytes-1) do |j|
3142 char += (bytes[j] << ((numbytes - j - 1) * 0x06))
3143 end
3144 if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF))
3145 # The definition of UTF-8 prohibits encoding character numbers between
3146 # U+D800 and U+DFFF, which are reserved for use with the UTF-16
3147 # encoding form (as surrogate pairs) and do not directly represent
3148 # characters
3149 unicode << 0xFFFD; # use replacement character
3150 else
3151 unicode << char; # add char to array
3152 end
3153 # reset data for next char
3154 bytes = []
3155 numbytes = 1;
3156 end
3157 else
3158 # use replacement character for other invalid sequences
3159 unicode << 0xFFFD;
3160 bytes = []
3161 numbytes = 1;
3162 end
3163 end
3164 return unicode;
3165 end
3166
3167 #
3168 # Converts UTF-8 strings to UTF16-BE.<br>
3169 # Based on: http://www.faqs.org/rfcs/rfc2781.html
3170 # <pre>
3171 # Encoding UTF-16:
3172 #
3173 # Encoding of a single character from an ISO 10646 character value to
3174 # UTF-16 proceeds as follows. Let U be the character number, no greater
3175 # than 0x10FFFF.
3176 #
3177 # 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
3178 # terminate.
3179 #
3180 # 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
3181 # U' must be less than or equal to 0xFFFFF. That is, U' can be
3182 # represented in 20 bits.
3183 #
3184 # 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
3185 # 0xDC00, respectively. These integers each have 10 bits free to
3186 # encode the character value, for a total of 20 bits.
3187 #
3188 # 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
3189 # bits of W1 and the 10 low-order bits of U' to the 10 low-order
3190 # bits of W2. Terminate.
3191 #
3192 # Graphically, steps 2 through 4 look like:
3193 # U' = yyyyyyyyyyxxxxxxxxxx
3194 # W1 = 110110yyyyyyyyyy
3195 # W2 = 110111xxxxxxxxxx
3196 # </pre>
3197 # @param string :str string to process.
3198 # @param boolean :setbom if true set the Byte Order Mark (BOM = 0xFEFF)
3199 # @return string
3200 # @access protected
3201 # @author Nicola Asuni
3202 # @since 1.53.0.TC005 (2005-01-05)
3203 # @uses UTF8StringToArray
3204 #
3205 def UTF8ToUTF16BE(str, setbom=true)
3206 if (!@is_unicode)
3207 return str; # string is not in unicode
3208 end
3209 outstr = ""; # string to be returned
3210 unicode = UTF8StringToArray(str); # array containing UTF-8 unicode values
3211 numitems = unicode.length;
3212
3213 if (setbom)
3214 outstr << "\xFE\xFF"; # Byte Order Mark (BOM)
3215 end
3216 unicode.each do |char|
3217 if (char == 0xFFFD)
3218 outstr << "\xFF\xFD"; # replacement character
3219 elsif (char < 0x10000)
3220 outstr << (char >> 0x08).chr;
3221 outstr << (char & 0xFF).chr;
3222 else
3223 char -= 0x10000;
3224 w1 = 0xD800 | (char >> 0x10);
3225 w2 = 0xDC00 | (char & 0x3FF);
3226 outstr << (w1 >> 0x08).chr;
3227 outstr << (w1 & 0xFF).chr;
3228 outstr << (w2 >> 0x08).chr;
3229 outstr << (w2 & 0xFF).chr;
3230 end
3231 end
3232 return outstr;
3233 end
3234
3235 # ====================================================
3236
3237 #
3238 # Set header font.
3239 # @param array :font font
3240 # @since 1.1
3241 #
3242 def SetHeaderFont(font)
3243 @header_font = font;
3244 end
3245 alias_method :set_header_font, :SetHeaderFont
3246
3247 #
3248 # Set footer font.
3249 # @param array :font font
3250 # @since 1.1
3251 #
3252 def SetFooterFont(font)
3253 @footer_font = font;
3254 end
3255 alias_method :set_footer_font, :SetFooterFont
3256
3257 #
3258 # Set language array.
3259 # @param array :language
3260 # @since 1.1
3261 #
3262 def SetLanguageArray(language)
3263 @l = language;
3264 end
3265 alias_method :set_language_array, :SetLanguageArray
3266 #
3267 # Set document barcode.
3268 # @param string :bc barcode
3269 #
3270 def SetBarcode(bc="")
3271 @barcode = bc;
3272 end
3273
3274 #
3275 # Print Barcode.
3276 # @param int :x x position in user units
3277 # @param int :y y position in user units
3278 # @param int :w width in user units
3279 # @param int :h height position in user units
3280 # @param string :type type of barcode (I25, C128A, C128B, C128C, C39)
3281 # @param string :style barcode style
3282 # @param string :font font for text
3283 # @param int :xres x resolution
3284 # @param string :code code to print
3285 #
3286 def writeBarcode(x, y, w, h, type, style, font, xres, code)
3287 require(File.dirname(__FILE__) + "/barcode/barcode.rb");
3288 require(File.dirname(__FILE__) + "/barcode/i25object.rb");
3289 require(File.dirname(__FILE__) + "/barcode/c39object.rb");
3290 require(File.dirname(__FILE__) + "/barcode/c128aobject.rb");
3291 require(File.dirname(__FILE__) + "/barcode/c128bobject.rb");
3292 require(File.dirname(__FILE__) + "/barcode/c128cobject.rb");
3293
3294 if (code.empty?)
3295 return;
3296 end
3297
3298 if (style.empty?)
3299 style = BCS_ALIGN_LEFT;
3300 style |= BCS_IMAGE_PNG;
3301 style |= BCS_TRANSPARENT;
3302 #:style |= BCS_BORDER;
3303 #:style |= BCS_DRAW_TEXT;
3304 #:style |= BCS_STRETCH_TEXT;
3305 #:style |= BCS_REVERSE_COLOR;
3306 end
3307 if (font.empty?) then font = BCD_DEFAULT_FONT; end
3308 if (xres.empty?) then xres = BCD_DEFAULT_XRES; end
3309
3310 scale_factor = 1.5 * xres * @k;
3311 bc_w = (w * scale_factor).round #width in points
3312 bc_h = (h * scale_factor).round #height in points
3313
3314 case (type.upcase)
3315 when "I25"
3316 obj = I25Object.new(bc_w, bc_h, style, code);
3317 when "C128A"
3318 obj = C128AObject.new(bc_w, bc_h, style, code);
3319 when "C128B"
3320 obj = C128BObject.new(bc_w, bc_h, style, code);
3321 when "C128C"
3322 obj = C128CObject.new(bc_w, bc_h, style, code);
3323 when "C39"
3324 obj = C39Object.new(bc_w, bc_h, style, code);
3325 end
3326
3327 obj.SetFont(font);
3328 obj.DrawObject(xres);
3329
3330 #use a temporary file....
3331 tmpName = tempnam(@@k_path_cache,'img');
3332 imagepng(obj.getImage(), tmpName);
3333 Image(tmpName, x, y, w, h, 'png');
3334 obj.DestroyObject();
3335 obj = nil
3336 unlink(tmpName);
3337 end
3338
3339 #
3340 # Returns the PDF data.
3341 #
3342 def GetPDFData()
3343 if (@state < 3)
3344 Close();
3345 end
3346 return @buffer;
3347 end
3348
3349 # --- HTML PARSER FUNCTIONS ---
3350
3351 #
3352 # Allows to preserve some HTML formatting.<br />
3353 # Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small
3354 # @param string :html text to display
3355 # @param boolean :ln if true add a new line after text (default = true)
3356 # @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
3357 #
3358 def writeHTML(html, ln=true, fill=0, h=0)
3359
3360 @lasth = h if h > 0
3361 if (@lasth == 0)
3362 #set row height
3363 @lasth = @font_size * @@k_cell_height_ratio;
3364 end
3365
3366 @href = nil
3367 @style = {}
3368 html.gsub!(/[\t\r\n\f]/, "")#\0\x0B
3369 html.split(/(<[^>]+>)/).each do |element|
3370 if "<" == element[0,1]
3371 #Tag
3372 if (element[1, 1] == '/')
3373 closedHTMLTagHandler(element[2..-2].downcase);
3374 else
3375 #Extract attributes
3376 # get tag name
3377 tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
3378 tag = tag[0].downcase;
3379
3380 # get attributes
3381 attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
3382 attrs = {}
3383 attr_array.each do |name, value|
3384 attrs[name.downcase] = value;
3385 end
3386 openHTMLTagHandler(tag, attrs, fill);
3387 end
3388
3389 else
3390 #Text
3391 if (@href)
3392 addHtmlLink(@href, element, fill);
3393 elsif (@tdbegin)
3394 if ((element.strip.length > 0) and (element != "&nbsp;"))
3395 Cell(@tdwidth, @tdheight, unhtmlentities(element.strip), @tableborder, 0, @tdalign, @tdfill);
3396 elsif (element == "&nbsp;")
3397 Cell(@tdwidth, @tdheight, '', @tableborder, 0, @tdalign, @tdfill);
3398 end
3399 elsif ((element.strip.length > 0) and (element != "&nbsp;"))
3400 Write(@lasth, unhtmlentities(element), '', fill);
3401 end
3402 end
3403 end
3404
3405 if (ln)
3406 Ln(@lasth);
3407 end
3408 end
3409 alias_method :write_html, :writeHTML
3410
3411 #
3412 # Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
3413 # If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
3414 # @param float :w Cell width. If 0, the cell extends up to the right margin.
3415 # @param float :h Cell minimum height. The cell extends automatically if needed.
3416 # @param float :x upper-left corner X coordinate
3417 # @param float :y upper-left corner Y coordinate
3418 # @param string :html html text to print. Default value: empty string.
3419 # @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
3420 # @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
3421 # Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
3422 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3423 # @see Cell()
3424 #
3425 def writeHTMLCell(w, h, x, y, html='', border=0, ln=0, fill=0)
3426
3427 if (@lasth == 0)
3428 #set row height
3429 @lasth = @font_size * @@k_cell_height_ratio;
3430 end
3431
3432 if (x == 0)
3433 x = GetX();
3434 end
3435 if (y == 0)
3436 y = GetY();
3437 end
3438
3439 # get current page number
3440 pagenum = @page;
3441
3442 SetX(x);
3443 SetY(y);
3444
3445 if (w == 0)
3446 w = @fw - x - @r_margin;
3447 end
3448
3449 # store original margin values
3450 l_margin = @l_margin;
3451 r_margin = @r_margin;
3452
3453 # set new margin values
3454 SetLeftMargin(x);
3455 SetRightMargin(@fw - x - w);
3456
3457 # calculate remaining vertical space on page
3458 restspace = GetPageHeight() - GetY() - GetBreakMargin();
3459
3460 writeHTML(html, true, fill); # write html text
3461
3462 currentY = GetY();
3463
3464 # check if a new page has been created
3465 if (@page > pagenum)
3466 # design a cell around the text on first page
3467 currentpage = @page;
3468 @page = pagenum;
3469 SetY(GetPageHeight() - restspace - GetBreakMargin());
3470 h = restspace - 1;
3471 Cell(w, h, "", border, ln, 'L', 0);
3472 # design a cell around the text on last page
3473 @page = currentpage;
3474 h = currentY - @t_margin;
3475 SetY(@t_margin); # put cursor at the beginning of text
3476 Cell(w, h, "", border, ln, 'L', 0);
3477 else
3478 h = [h, (currentY - y)].max;
3479 SetY(y); # put cursor at the beginning of text
3480 # design a cell around the text
3481 Cell(w, h, "", border, ln, 'L', 0);
3482 end
3483
3484 # restore original margin values
3485 SetLeftMargin(l_margin);
3486 SetRightMargin(r_margin);
3487
3488 if (ln)
3489 Ln(0);
3490 end
3491 end
3492 alias_method :write_html_cell, :writeHTMLCell
3493
3494 #
3495 # Process opening tags.
3496 # @param string :tag tag name (in upcase)
3497 # @param string :attr tag attribute (in upcase)
3498 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3499 # @access private
3500 #
3501 def openHTMLTagHandler(tag, attrs, fill=0)
3502 #Opening tag
3503 case (tag)
3504 when 'table'
3505 if attrs['border'].nil? or attrs['border'] == ''
3506 @tableborder = 0;
3507 else
3508 @tableborder = attrs['border'];
3509 end
3510 when 'tr', 'td', 'th'
3511 # SetStyle('b', true) if tag == 'th'
3512
3513 if ((!attrs['width'].nil?) and (attrs['width'] != ''))
3514 @tdwidth = (attrs['width'].to_i/4);
3515 else
3516 @tdwidth = ((@w - @l_margin - @r_margin) / @default_table_columns);
3517 end
3518 if ((!attrs['height'].nil?) and (attrs['height'] != ''))
3519 @tdheight=(attrs['height'].to_i / @k);
3520 else
3521 @tdheight = @lasth;
3522 end
3523 if ((!attrs['align'].nil?) and (attrs['align'] != ''))
3524 case (attrs['align'])
3525 when 'center'
3526 @tdalign = "C";
3527 when 'right'
3528 @tdalign = "R";
3529 when 'left'
3530 @tdalign = "L";
3531 end
3532 end
3533 if ((!attrs['bgcolor'].nil?) and (attrs['bgcolor'] != ''))
3534 coul = convertColorHexToDec(attrs['bgcolor']);
3535 SetFillColor(coul['R'], coul['G'], coul['B']);
3536 @tdfill=1;
3537 end
3538 @tdbegin=true;
3539
3540 when 'hr'
3541 Ln();
3542 if ((!attrs['width'].nil?) and (attrs['width'] != ''))
3543 hrWidth = attrs['width'];
3544 else
3545 hrWidth = @w - @l_margin - @r_margin;
3546 end
3547 x = GetX();
3548 y = GetY();
3549 SetLineWidth(0.2);
3550 Line(x, y, x + hrWidth, y);
3551 SetLineWidth(0.2);
3552 Ln();
3553
3554 when 'strong'
3555 SetStyle('b', true);
3556
3557 when 'em'
3558 SetStyle('i', true);
3559
3560 when 'b', 'i', 'u'
3561 SetStyle(tag, true);
3562
3563 when 'a'
3564 @href = attrs['href'];
3565
3566 when 'img'
3567 if (!attrs['src'].nil?)
3568 # replace relative path with real server path
3569 attrs['src'] = attrs['src'].gsub(@@k_path_url_cache, @@k_path_cache);
3570 if (attrs['width'].nil?)
3571 attrs['width'] = 0;
3572 end
3573 if (attrs['height'].nil?)
3574 attrs['height'] = 0;
3575 end
3576
3577 Image(attrs['src'], GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height']));
3578 #SetX(@img_rb_x);
3579 SetY(@img_rb_y);
3580
3581 end
3582
3583 when 'ul'
3584 @listordered = false;
3585 @listcount = 0;
3586
3587 when 'ol'
3588 @listordered = true;
3589 @listcount = 0;
3590
3591 when 'li'
3592 Ln();
3593 if (@listordered)
3594 @listcount += 1
3595 @lispacer = " " + (@listcount).to_s + ". ";
3596 else
3597 #unordered list simbol
3598 @lispacer = " - ";
3599 end
3600 Write(@lasth, @lispacer, '', fill);
3601
3602 when 'blockquote', 'br'
3603 Ln();
3604 if (@lispacer.length > 0)
3605 @x += GetStringWidth(@lispacer);
3606 end
3607
3608 when 'p'
3609 Ln();
3610 Ln();
3611
3612 when 'sup'
3613 currentfont_size = @font_size;
3614 @tempfontsize = @font_size_pt;
3615 SetFontSize(@font_size_pt * @@k_small_ratio);
3616 SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio)));
3617
3618 when 'sub'
3619 currentfont_size = @font_size;
3620 @tempfontsize = @font_size_pt;
3621 SetFontSize(@font_size_pt * @@k_small_ratio);
3622 SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio)));
3623
3624 when 'small'
3625 currentfont_size = @font_size;
3626 @tempfontsize = @font_size_pt;
3627 SetFontSize(@font_size_pt * @@k_small_ratio);
3628 SetXY(GetX(), GetY() + ((currentfont_size - @font_size)/3));
3629
3630 when 'font'
3631 if (!attrs['color'].nil? and attrs['color']!='')
3632 coul = convertColorHexToDec(attrs['color']);
3633 SetTextColor(coul['R'], coul['G'], coul['B']);
3634 @issetcolor=true;
3635 end
3636 if (!attrs['face'].nil? and @fontlist.include?(attrs['face'].downcase))
3637 SetFont(attrs['face'].downcase);
3638 @issetfont=true;
3639 end
3640 if (!attrs['size'].nil?)
3641 headsize = attrs['size'].to_i;
3642 else
3643 headsize = 0;
3644 end
3645 currentfont_size = @font_size;
3646 @tempfontsize = @font_size_pt;
3647 SetFontSize(@font_size_pt + headsize);
3648 @lasth = @font_size * @@k_cell_height_ratio;
3649
3650 when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
3651 headsize = (4 - tag[1,1].to_f) * 2
3652 @tempfontsize = @font_size_pt;
3653 SetFontSize(@font_size_pt + headsize);
3654 SetStyle('b', true);
3655 @lasth = @font_size * @@k_cell_height_ratio;
3656
3657 end
3658 end
3659
3660 #
3661 # Process closing tags.
3662 # @param string :tag tag name (in upcase)
3663 # @access private
3664 #
3665 def closedHTMLTagHandler(tag)
3666 #Closing tag
3667 case (tag)
3668 when 'td','th'
3669 @tdbegin = false;
3670 @tdwidth = 0;
3671 @tdheight = 0;
3672 @tdalign = "L";
3673 @tdfill = 0;
3674 SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]);
3675
3676 when 'tr'
3677 Ln();
3678
3679 when 'table'
3680 @tableborder=0;
3681
3682 when 'strong'
3683 SetStyle('b', false);
3684
3685 when 'em'
3686 SetStyle('i', false);
3687
3688 when 'b', 'i', 'u'
3689 SetStyle(tag, false);
3690
3691 when 'a'
3692 @href = nil;
3693
3694 when 'sup'
3695 currentfont_size = @font_size;
3696 SetFontSize(@tempfontsize);
3697 @tempfontsize = @font_size_pt;
3698 SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio)));
3699
3700 when 'sub'
3701 currentfont_size = @font_size;
3702 SetFontSize(@tempfontsize);
3703 @tempfontsize = @font_size_pt;
3704 SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio)));
3705
3706 when 'small'
3707 currentfont_size = @font_size;
3708 SetFontSize(@tempfontsize);
3709 @tempfontsize = @font_size_pt;
3710 SetXY(GetX(), GetY() - ((@font_size - currentfont_size)/3));
3711
3712 when 'font'
3713 if (@issetcolor == true)
3714 SetTextColor(@prevtext_color[0], @prevtext_color[1], @prevtext_color[2]);
3715 end
3716 if (@issetfont)
3717 @font_family = @prevfont_family;
3718 @font_style = @prevfont_style;
3719 SetFont(@font_family);
3720 @issetfont = false;
3721 end
3722 currentfont_size = @font_size;
3723 SetFontSize(@tempfontsize);
3724 @tempfontsize = @font_size_pt;
3725 #@text_color = @prevtext_color;
3726 @lasth = @font_size * @@k_cell_height_ratio;
3727
3728 when 'ul'
3729 Ln();
3730
3731 when 'ol'
3732 Ln();
3733
3734 when 'li'
3735 @lispacer = "";
3736
3737 when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
3738 SetFontSize(@tempfontsize);
3739 @tempfontsize = @font_size_pt;
3740 SetStyle('b', false);
3741 Ln();
3742 @lasth = @font_size * @@k_cell_height_ratio;
3743
3744 end
3745 end
3746
3747 #
3748 # Sets font style.
3749 # @param string :tag tag name (in lowercase)
3750 # @param boolean :enable
3751 # @access private
3752 #
3753 def SetStyle(tag, enable)
3754 #Modify style and select corresponding font
3755 style='';
3756 ['b', 'i', 'u'].each do |s|
3757 style << s if tag.downcase == s and enable
3758 end
3759 SetFont('', style);
3760 end
3761
3762 #
3763 # Output anchor link.
3764 # @param string :url link URL
3765 # @param string :name link name
3766 # @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3767 # @access public
3768 #
3769 def addHtmlLink(url, name, fill=0)
3770 #Put a hyperlink
3771 SetTextColor(0, 0, 255);
3772 SetStyle('u', true);
3773 Write(@lasth, name, url, fill);
3774 SetStyle('u', false);
3775 SetTextColor(0);
3776 end
3777
3778 #
3779 # Returns an associative array (keys: R,G,B) from
3780 # a hex html code (e.g. #3FE5AA).
3781 # @param string :color hexadecimal html color [#rrggbb]
3782 # @return array
3783 # @access private
3784 #
3785 def convertColorHexToDec(color = "#000000")
3786 tbl_color = {}
3787 tbl_color['R'] = color[1,2].hex.to_i;
3788 tbl_color['G'] = color[3,2].hex.to_i;
3789 tbl_color['B'] = color[5,2].hex.to_i;
3790 return tbl_color;
3791 end
3792
3793 #
3794 # Converts pixels to millimeters in 72 dpi.
3795 # @param int :px pixels
3796 # @return float millimeters
3797 # @access private
3798 #
3799 def pixelsToMillimeters(px)
3800 return px.to_f * 25.4 / 72;
3801 end
3802
3803 #
3804 # Reverse function for htmlentities.
3805 # Convert entities in UTF-8.
3806 #
3807 # @param :text_to_convert Text to convert.
3808 # @return string converted
3809 #
3810 def unhtmlentities(string)
3811 if @@decoder.nil?
3812 CGI.unescapeHTML(string)
3813 else
3814 @@decoder.decode(string)
3815 end
3816 end
3817
3818 end # END OF CLASS
3819
3820 #TODO 2007-05-25 (EJM) Level=0 -
3821 #Handle special IE contype request
3822 # if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype'))
3823 # header('Content-Type: application/pdf');
3824 # exit;
3825 # }