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