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