To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 56 / 56813b39ed887e573bb8518a2b58c7a03d6e9767.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (122 KB)

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