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