comparison .svn/pristine/56/56813b39ed887e573bb8518a2b58c7a03d6e9767.svn-base @ 1296:038ba2d95de8 redmine-2.2

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