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 / 09 / 09319bb04693b301a469674c2043f8c089fbdcbb.svn-base @ 1298:4f746d8966dd

History | View | Annotate | Download (122 KB)

1
#============================================================+
2
# File name   : tcpdf.rb
3
# Begin       : 2002-08-03
4
# Last Update : 2007-03-20
5
# Author      : Nicola Asuni
6
# Version     : 1.53.0.TC031
7
# License     : GNU LGPL (http://www.gnu.org/copyleft/lesser.html)
8
#
9
# Description : This is a Ruby class for generating PDF files 
10
#               on-the-fly without requiring external 
11
#               extensions.
12
#
13
# IMPORTANT:
14
# This class is an extension and improvement of the Public Domain 
15
# FPDF class by Olivier Plathey (http://www.fpdf.org).
16
#
17
# Main changes by Nicola Asuni:
18
#    Ruby porting;
19
#    UTF-8 Unicode support;
20
#    code refactoring;
21
#    source code clean up;
22
#    code style and formatting;
23
#    source code documentation using phpDocumentor (www.phpdoc.org);
24
#    All ISO page formats were included;
25
#    image scale factor;
26
#    includes methods to parse and printsome XHTML code, supporting the following elements: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small;
27
#    includes a method to print various barcode formats using an improved version of "Generic Barcode Render Class" by Karim Mribti (http://www.mribti.com/barcode/) (require GD library: http://www.boutell.com/gd/);
28
#    defines standard Header() and Footer() methods.
29
#
30
#   Ported to Ruby by Ed Moss 2007-08-06
31
#
32
#============================================================+
33

    
34
require 'tempfile'
35
require 'core/rmagick'
36

    
37
#
38
# TCPDF Class.
39
# @package com.tecnick.tcpdf
40
#
41
 
42
@@version = "1.53.0.TC031"
43
@@fpdf_charwidths = {}
44

    
45
PDF_PRODUCER = 'TCPDF via RFPDF 1.53.0.TC031 (http://tcpdf.sourceforge.net)'
46

    
47
module TCPDFFontDescriptor
48
  @@descriptors = { 'freesans' => {} }
49
  @@font_name = 'freesans'
50

    
51
  def self.font(font_name)
52
    @@descriptors[font_name.gsub(".rb", "")]
53
  end
54

    
55
  def self.define(font_name = 'freesans')
56
    @@descriptors[font_name] ||= {}
57
    yield @@descriptors[font_name]
58
  end
59
end
60

    
61
# This is a Ruby class for generating PDF files on-the-fly without requiring external extensions.<br>
62
# This class is an extension and improvement of the FPDF class by Olivier Plathey (http://www.fpdf.org).<br>
63
# This version contains some changes: [porting to Ruby, support for UTF-8 Unicode, code style and formatting, php documentation (www.phpdoc.org), ISO page formats, minor improvements, image scale factor]<br>
64
# TCPDF project (http://tcpdf.sourceforge.net) is based on the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org).<br>
65
# To add your own TTF fonts please read /fonts/README.TXT
66
# @name TCPDF
67
# @package com.tecnick.tcpdf
68
# @@version 1.53.0.TC031
69
# @author Nicola Asuni
70
# @link http://tcpdf.sourceforge.net
71
# @license http://www.gnu.org/copyleft/lesser.html LGPL
72
#
73
class TCPDF
74
  include RFPDF
75
  include Core::RFPDF
76
  include RFPDF::Math
77
  
78
  def logger
79
    Rails.logger
80
  end
81

    
82
  cattr_accessor :k_cell_height_ratio
83
  @@k_cell_height_ratio = 1.25
84

    
85
  cattr_accessor :k_blank_image
86
  @@k_blank_image = ""
87
  
88
  cattr_accessor :k_small_ratio  
89
  @@k_small_ratio = 2/3.0
90
  
91
  cattr_accessor :k_path_cache
92
  @@k_path_cache = Rails.root.join('tmp')
93
  
94
  cattr_accessor :k_path_url_cache
95
  @@k_path_url_cache = Rails.root.join('tmp')
96
  
97
	attr_accessor :barcode
98
	
99
	attr_accessor :buffer
100
	
101
	attr_accessor :diffs
102
	
103
	attr_accessor :color_flag
104
	
105
	attr_accessor :default_table_columns
106

    
107
	attr_accessor :max_table_columns
108
	
109
	attr_accessor :default_font
110

    
111
	attr_accessor :draw_color
112
	
113
	attr_accessor :encoding
114
	
115
	attr_accessor :fill_color
116
	
117
	attr_accessor :fonts
118
	
119
	attr_accessor :font_family
120
	
121
	attr_accessor :font_files
122
	
123
	cattr_accessor :font_path
124
	
125
	attr_accessor :font_style
126
	
127
	attr_accessor :font_size_pt
128
	
129
	attr_accessor :header_width
130
	
131
	attr_accessor :header_logo
132
	
133
	attr_accessor :header_logo_width
134
	
135
	attr_accessor :header_title
136
	
137
	attr_accessor :header_string
138
	
139
	attr_accessor :images
140
	
141
	attr_accessor :img_scale
142
	
143
	attr_accessor :in_footer
144
	
145
	attr_accessor :is_unicode
146

    
147
	attr_accessor :lasth
148
	
149
	attr_accessor :links
150
	
151
	attr_accessor :list_ordered
152
	
153
	attr_accessor :list_count
154
	
155
	attr_accessor :li_spacer
156
	
157
	attr_accessor :n
158
	
159
	attr_accessor :offsets
160
	
161
	attr_accessor :orientation_changes
162
	
163
	attr_accessor :page
164
	
165
	attr_accessor :page_links
166
	
167
	attr_accessor :pages
168
	
169
	attr_accessor :pdf_version
170
	
171
	attr_accessor :prevfill_color
172
	
173
	attr_accessor :prevtext_color
174
	
175
	attr_accessor :print_header
176
	
177
	attr_accessor :print_footer
178
	
179
	attr_accessor :state
180
	
181
	attr_accessor :tableborder
182
	
183
	attr_accessor :tdbegin
184
	
185
	attr_accessor :tdwidth
186
	
187
	attr_accessor :tdheight
188
	
189
	attr_accessor :tdalign
190
	
191
	attr_accessor :tdfill
192
	
193
	attr_accessor :tempfontsize
194
	
195
	attr_accessor :text_color
196
	
197
	attr_accessor :underline
198
	
199
	attr_accessor :ws
200
	
201
	#
202
	# This is the class constructor. 
203
	# It allows to set up the page format, the orientation and 
204
	# the measure unit used in all the methods (except for the font sizes).
205
	# @since 1.0
206
	# @param string :orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li></ul>
207
	# @param string :unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
208
	# @param mixed :format The format used for pages. It can be either one of the following values (case insensitive) or a custom format in the form of a two-element array containing the width and the height (expressed in the unit given by unit).<ul><li>4A0</li><li>2A0</li><li>A0</li><li>A1</li><li>A2</li><li>A3</li><li>A4 (default)</li><li>A5</li><li>A6</li><li>A7</li><li>A8</li><li>A9</li><li>A10</li><li>B0</li><li>B1</li><li>B2</li><li>B3</li><li>B4</li><li>B5</li><li>B6</li><li>B7</li><li>B8</li><li>B9</li><li>B10</li><li>C0</li><li>C1</li><li>C2</li><li>C3</li><li>C4</li><li>C5</li><li>C6</li><li>C7</li><li>C8</li><li>C9</li><li>C10</li><li>RA0</li><li>RA1</li><li>RA2</li><li>RA3</li><li>RA4</li><li>SRA0</li><li>SRA1</li><li>SRA2</li><li>SRA3</li><li>SRA4</li><li>LETTER</li><li>LEGAL</li><li>EXECUTIVE</li><li>FOLIO</li></ul>
209
	# @param boolean :unicode TRUE means that the input text is unicode (default = true)
210
	# @param String :encoding charset encoding; default is UTF-8
211
	#
212
	def initialize(orientation = 'P',  unit = 'mm', format = 'A4', unicode = true, encoding = "UTF-8")
213
		
214
		# Set internal character encoding to ASCII#
215
		#FIXME 2007-05-25 (EJM) Level=0 - 
216
		# if (respond_to?("mb_internal_encoding") and mb_internal_encoding())
217
		# 	@internal_encoding = mb_internal_encoding();
218
		# 	mb_internal_encoding("ASCII");
219
		# }
220
			
221
		#Some checks
222
		dochecks();
223
		
224
		#Initialization of properties
225
  	@barcode ||= false
226
		@buffer ||= ''
227
		@diffs ||= []
228
		@color_flag ||= false
229
  	@default_table_columns ||= 4
230
  	@table_columns ||= 0
231
  	@max_table_columns ||= []
232
  	@tr_id ||= 0
233
  	@max_td_page ||= []
234
  	@max_td_y ||= []
235
  	@t_columns ||= 0
236
  	@default_font ||= "FreeSans" if unicode
237
  	@default_font ||= "Helvetica"
238
		@draw_color ||= '0 G'
239
  	@encoding ||= "UTF-8"
240
		@fill_color ||= '0 g'
241
		@fonts ||= {}
242
		@font_family ||= ''
243
		@font_files ||= {}
244
		@font_style ||= ''
245
		@font_size ||= 12
246
		@font_size_pt ||= 12
247
  	@header_width ||= 0
248
  	@header_logo ||= ""
249
  	@header_logo_width ||= 30
250
  	@header_title ||= ""
251
  	@header_string ||= ""
252
		@images ||= {}
253
  	@img_scale ||= 1
254
		@in_footer ||= false
255
		@is_unicode = unicode
256
		@lasth ||= 0
257
		@links ||= []
258
  	@list_ordered ||= []
259
  	@list_count ||= []
260
  	@li_spacer ||= ""
261
  	@li_count ||= 0
262
  	@spacer ||= ""
263
  	@quote_count ||= 0
264
  	@prevquote_count ||= 0
265
  	@quote_top ||= []
266
  	@quote_page ||= []
267
		@n ||= 2
268
		@offsets ||= []
269
		@orientation_changes ||= []
270
		@page ||= 0
271
		@page_links ||= {}
272
		@pages ||= []
273
  	@pdf_version ||= "1.3"
274
  	@prevfill_color ||= [255,255,255]
275
  	@prevtext_color ||= [0,0,0]
276
  	@print_header ||= false
277
  	@print_footer ||= false
278
		@state ||= 0
279
  	@tableborder ||= 0
280
  	@tdbegin ||= false
281
	@tdtext ||= ''
282
  	@tdwidth ||= 0
283
  	@tdheight ||= 0
284
  	@tdalign ||= "L"
285
  	@tdfill ||= 0
286
  	@tempfontsize ||= 10
287
		@text_color ||= '0 g'
288
		@underline ||= false
289
		@deleted ||= false
290
		@ws ||= 0
291
		
292
		#Standard Unicode fonts
293
		@core_fonts = {
294
		'courier'=>'Courier',
295
		'courierB'=>'Courier-Bold',
296
		'courierI'=>'Courier-Oblique',
297
		'courierBI'=>'Courier-BoldOblique',
298
		'helvetica'=>'Helvetica',
299
		'helveticaB'=>'Helvetica-Bold',
300
		'helveticaI'=>'Helvetica-Oblique',
301
		'helveticaBI'=>'Helvetica-BoldOblique',
302
		'times'=>'Times-Roman',
303
		'timesB'=>'Times-Bold',
304
		'timesI'=>'Times-Italic',
305
		'timesBI'=>'Times-BoldItalic',
306
		'symbol'=>'Symbol',
307
		'zapfdingbats'=>'ZapfDingbats'}
308

    
309
		#Scale factor
310
		case unit.downcase
311
			when 'pt' ; @k=1
312
			when 'mm' ; @k=72/25.4
313
			when 'cm' ; @k=72/2.54
314
			when 'in' ; @k=72
315
			else Error("Incorrect unit: #{unit}")
316
		end
317

    
318
		#Page format
319
		if format.is_a?(String)
320
			# Page formats (45 standard ISO paper formats and 4 american common formats).
321
			# Paper cordinates are calculated in this way: (inches# 72) where (1 inch = 2.54 cm)
322
			case (format.upcase)
323
				when  '4A0' ; format = [4767.87,6740.79]
324
				when  '2A0' ; format = [3370.39,4767.87]
325
				when  'A0' ; format = [2383.94,3370.39]
326
				when  'A1' ; format = [1683.78,2383.94]
327
				when  'A2' ; format = [1190.55,1683.78]
328
				when  'A3' ; format = [841.89,1190.55]
329
				when  'A4' ; format = [595.28,841.89] # ; default
330
				when  'A5' ; format = [419.53,595.28]
331
				when  'A6' ; format = [297.64,419.53]
332
				when  'A7' ; format = [209.76,297.64]
333
				when  'A8' ; format = [147.40,209.76]
334
				when  'A9' ; format = [104.88,147.40]
335
				when  'A10' ; format = [73.70,104.88]
336
				when  'B0' ; format = [2834.65,4008.19]
337
				when  'B1' ; format = [2004.09,2834.65]
338
				when  'B2' ; format = [1417.32,2004.09]
339
				when  'B3' ; format = [1000.63,1417.32]
340
				when  'B4' ; format = [708.66,1000.63]
341
				when  'B5' ; format = [498.90,708.66]
342
				when  'B6' ; format = [354.33,498.90]
343
				when  'B7' ; format = [249.45,354.33]
344
				when  'B8' ; format = [175.75,249.45]
345
				when  'B9' ; format = [124.72,175.75]
346
				when  'B10' ; format = [87.87,124.72]
347
				when  'C0' ; format = [2599.37,3676.54]
348
				when  'C1' ; format = [1836.85,2599.37]
349
				when  'C2' ; format = [1298.27,1836.85]
350
				when  'C3' ; format = [918.43,1298.27]
351
				when  'C4' ; format = [649.13,918.43]
352
				when  'C5' ; format = [459.21,649.13]
353
				when  'C6' ; format = [323.15,459.21]
354
				when  'C7' ; format = [229.61,323.15]
355
				when  'C8' ; format = [161.57,229.61]
356
				when  'C9' ; format = [113.39,161.57]
357
				when  'C10' ; format = [79.37,113.39]
358
				when  'RA0' ; format = [2437.80,3458.27]
359
				when  'RA1' ; format = [1729.13,2437.80]
360
				when  'RA2' ; format = [1218.90,1729.13]
361
				when  'RA3' ; format = [864.57,1218.90]
362
				when  'RA4' ; format = [609.45,864.57]
363
				when  'SRA0' ; format = [2551.18,3628.35]
364
				when  'SRA1' ; format = [1814.17,2551.18]
365
				when  'SRA2' ; format = [1275.59,1814.17]
366
				when  'SRA3' ; format = [907.09,1275.59]
367
				when  'SRA4' ; format = [637.80,907.09]
368
				when  'LETTER' ; format = [612.00,792.00]
369
				when  'LEGAL' ; format = [612.00,1008.00]
370
				when  'EXECUTIVE' ; format = [521.86,756.00]
371
				when  'FOLIO' ; format = [612.00,936.00]
372
				#else then Error("Unknown page format: #{format}"
373
			end
374
			@fw_pt = format[0]
375
			@fh_pt = format[1]
376
		else
377
			@fw_pt = format[0]*@k
378
			@fh_pt = format[1]*@k
379
		end
380

    
381
		@fw = @fw_pt/@k
382
		@fh = @fh_pt/@k
383

    
384
		#Page orientation
385
		orientation = orientation.downcase
386
		if orientation == 'p' or orientation == 'portrait'
387
			@def_orientation = 'P'
388
			@w_pt = @fw_pt
389
			@h_pt = @fh_pt
390
		elsif orientation == 'l' or orientation == 'landscape'
391
			@def_orientation = 'L'
392
			@w_pt = @fh_pt
393
			@h_pt = @fw_pt
394
		else
395
			Error("Incorrect orientation: #{orientation}")
396
		end
397

    
398
    @fw = @w_pt/@k
399
    @fh = @h_pt/@k
400
    
401
		@cur_orientation = @def_orientation
402
		@w = @w_pt/@k
403
		@h = @h_pt/@k
404
		#Page margins (1 cm)
405
		margin = 28.35/@k
406
		SetMargins(margin, margin)
407
		#Interior cell margin (1 mm)
408
		@c_margin = margin / 10
409
		#Line width (0.2 mm)
410
		@line_width = 0.567 / @k
411
		#Automatic page break
412
		SetAutoPageBreak(true, 2 * margin)
413
		#Full width display mode
414
		SetDisplayMode('fullwidth')
415
		#Compression
416
		SetCompression(true)
417
		#Set default PDF version number
418
		@pdf_version = "1.3"
419
		
420
		@encoding = encoding
421
		@b = 0
422
		@i = 0
423
		@u = 0
424
		@href = ''
425
		@fontlist = ["arial", "times", "courier", "helvetica", "symbol"]
426
		@issetfont = false
427
		@issetcolor = false
428
	
429
		SetFillColor(200, 200, 200, true)
430
		SetTextColor(0, 0, 0, true)
431
	end
432
	
433
	#
434
	# Set the image scale.
435
	# @param float :scale image scale.
436
	# @author Nicola Asuni
437
	# @since 1.5.2
438
	#
439
	def SetImageScale(scale)
440
		@img_scale = scale;
441
	end
442
  alias_method :set_image_scale, :SetImageScale
443
	
444
	#
445
	# Returns the image scale.
446
	# @return float image scale.
447
	# @author Nicola Asuni
448
	# @since 1.5.2
449
	#
450
	def GetImageScale()
451
		return @img_scale;
452
	end
453
  alias_method :get_image_scale, :GetImageScale
454
  
455
	#
456
	# Returns the page width in units.
457
	# @return int page width.
458
	# @author Nicola Asuni
459
	# @since 1.5.2
460
	#
461
	def GetPageWidth()
462
		return @w;
463
	end
464
  alias_method :get_page_width, :GetPageWidth
465
  
466
	#
467
	# Returns the page height in units.
468
	# @return int page height.
469
	# @author Nicola Asuni
470
	# @since 1.5.2
471
	#
472
	def GetPageHeight()
473
		return @h;
474
	end
475
  alias_method :get_page_height, :GetPageHeight
476
  
477
	#
478
	# Returns the page break margin.
479
	# @return int page break margin.
480
	# @author Nicola Asuni
481
	# @since 1.5.2
482
	#
483
	def GetBreakMargin()
484
		return @b_margin;
485
	end
486
  alias_method :get_break_margin, :GetBreakMargin
487

    
488
	#
489
	# Returns the scale factor (number of points in user unit).
490
	# @return int scale factor.
491
	# @author Nicola Asuni
492
	# @since 1.5.2
493
	#
494
	def GetScaleFactor()
495
		return @k;
496
	end
497
  alias_method :get_scale_factor, :GetScaleFactor
498

    
499
	#
500
	# Defines the left, top and right margins. By default, they equal 1 cm. Call this method to change them.
501
	# @param float :left Left margin.
502
	# @param float :top Top margin.
503
	# @param float :right Right margin. Default value is the left one.
504
	# @since 1.0
505
	# @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
506
	#
507
	def SetMargins(left, top, right=-1)
508
		#Set left, top and right margins
509
		@l_margin = left
510
		@t_margin = top
511
		if (right == -1)
512
			right = left
513
		end
514
		@r_margin = right
515
	end
516
  alias_method :set_margins, :SetMargins
517

    
518
	#
519
	# Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
520
	# @param float :margin The margin.
521
	# @since 1.4
522
	# @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
523
	#
524
	def SetLeftMargin(margin)
525
		#Set left margin
526
		@l_margin = margin
527
		if ((@page>0) and (@x < margin))
528
			@x = margin
529
		end
530
	end
531
  alias_method :set_left_margin, :SetLeftMargin
532

    
533
	#
534
	# Defines the top margin. The method can be called before creating the first page.
535
	# @param float :margin The margin.
536
	# @since 1.5
537
	# @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
538
	#
539
	def SetTopMargin(margin)
540
		#Set top margin
541
		@t_margin = margin
542
	end
543
  alias_method :set_top_margin, :SetTopMargin
544

    
545
	#
546
	# Defines the right margin. The method can be called before creating the first page.
547
	# @param float :margin The margin.
548
	# @since 1.5
549
	# @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
550
	#
551
	def SetRightMargin(margin)
552
		#Set right margin
553
		@r_margin = margin
554
	end
555
  alias_method :set_right_margin, :SetRightMargin
556

    
557
	#
558
	# Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
559
	# @param boolean :auto Boolean indicating if mode should be on or off.
560
	# @param float :margin Distance from the bottom of the page.
561
	# @since 1.0
562
	# @see Cell(), MultiCell(), AcceptPageBreak()
563
	#
564
	def SetAutoPageBreak(auto, margin=0)
565
		#Set auto page break mode and triggering margin
566
		@auto_page_break = auto
567
		@b_margin = margin
568
		@page_break_trigger = @h - margin
569
	end
570
  alias_method :set_auto_page_break, :SetAutoPageBreak
571

    
572
	#
573
	# Defines the way the document is to be displayed by the viewer. The zoom level can be set: pages can be displayed entirely on screen, occupy the full width of the window, use real size, be scaled by a specific zooming factor or use viewer default (configured in the Preferences menu of Acrobat). The page layout can be specified too: single at once, continuous display, two columns or viewer default. By default, documents use the full width mode with continuous display.
574
	# @param mixed :zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
575
	# @param string :layout The page layout. Possible values are:<ul><li>single: displays one page at once</li><li>continuous: displays pages continuously (default)</li><li>two: displays two pages on two columns</li><li>default: uses viewer default mode</li></ul>
576
	# @since 1.2
577
	#
578
	def SetDisplayMode(zoom, layout = 'continuous')
579
		#Set display mode in viewer
580
		if (zoom == 'fullpage' or zoom == 'fullwidth' or zoom == 'real' or zoom == 'default' or !zoom.is_a?(String))
581
			@zoom_mode = zoom
582
		else
583
			Error("Incorrect zoom display mode: #{zoom}")
584
		end
585
		if (layout == 'single' or layout == 'continuous' or layout == 'two' or layout == 'default')
586
			@layout_mode = layout
587
		else
588
			Error("Incorrect layout display mode: #{layout}")
589
		end
590
	end
591
  alias_method :set_display_mode, :SetDisplayMode
592

    
593
	#
594
	# Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
595
	# Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
596
	# @param boolean :compress Boolean indicating if compression must be enabled.
597
	# @since 1.4
598
	#
599
	def SetCompression(compress)
600
		#Set page compression
601
		if (respond_to?('gzcompress'))
602
			@compress = compress
603
		else
604
			@compress = false
605
		end
606
	end
607
  alias_method :set_compression, :SetCompression
608

    
609
	#
610
	# Defines the title of the document.
611
	# @param string :title The title.
612
	# @since 1.2
613
	# @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
614
	#
615
	def SetTitle(title)
616
		#Title of document
617
		@title = title
618
	end
619
  alias_method :set_title, :SetTitle
620

    
621
	#
622
	# Defines the subject of the document.
623
	# @param string :subject The subject.
624
	# @since 1.2
625
	# @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
626
	#
627
	def SetSubject(subject)
628
		#Subject of document
629
		@subject = subject
630
	end
631
  alias_method :set_subject, :SetSubject
632

    
633
	#
634
	# Defines the author of the document.
635
	# @param string :author The name of the author.
636
	# @since 1.2
637
	# @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
638
	#
639
	def SetAuthor(author)
640
		#Author of document
641
		@author = author
642
	end
643
  alias_method :set_author, :SetAuthor
644

    
645
	#
646
	# Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
647
	# @param string :keywords The list of keywords.
648
	# @since 1.2
649
	# @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
650
	#
651
	def SetKeywords(keywords)
652
		#Keywords of document
653
		@keywords = keywords
654
	end
655
  alias_method :set_keywords, :SetKeywords
656

    
657
	#
658
	# Defines the creator of the document. This is typically the name of the application that generates the PDF.
659
	# @param string :creator The name of the creator.
660
	# @since 1.2
661
	# @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
662
	#
663
	def SetCreator(creator)
664
		#Creator of document
665
		@creator = creator
666
	end
667
  alias_method :set_creator, :SetCreator
668

    
669
	#
670
	# Defines an alias for the total number of pages. It will be substituted as the document is closed.<br />
671
	# <b>Example:</b><br />
672
	# <pre>
673
	# class PDF extends TCPDF {
674
	# 	def Footer()
675
	# 		#Go to 1.5 cm from bottom
676
	# 		SetY(-15);
677
	# 		#Select Arial italic 8
678
	# 		SetFont('Arial','I',8);
679
	# 		#Print current and total page numbers
680
	# 		Cell(0,10,'Page '.PageNo().'/{nb}',0,0,'C');
681
	# 	end
682
	# }
683
	# :pdf=new PDF();
684
	# :pdf->alias_nb_pages();
685
	# </pre>
686
	# @param string :alias The alias. Default valuenb}.
687
	# @since 1.4
688
	# @see PageNo(), Footer()
689
	#
690
	def AliasNbPages(alias_nb ='{nb}')
691
		#Define an alias for total number of pages
692
		@alias_nb_pages = escapetext(alias_nb)
693
	end
694
	alias_method :alias_nb_pages, :AliasNbPages
695

    
696
	#
697
	# This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
698
	# 2004-06-11 :: Nicola Asuni : changed bold tag with strong
699
	# @param string :msg The error message
700
	# @since 1.0
701
	#
702
	def Error(msg)
703
		#Fatal error
704
		raise ("TCPDF error: #{msg}")
705
	end
706
  alias_method :error, :Error
707

    
708
	#
709
	# This method begins the generation of the PDF document. It is not necessary to call it explicitly because AddPage() does it automatically.
710
	# Note: no page is created by this method
711
	# @since 1.0
712
	# @see AddPage(), Close()
713
	#
714
	def Open()
715
		#Begin document
716
		@state = 1
717
	end
718
  # alias_method :open, :Open
719

    
720
	#
721
	# Terminates the PDF document. It is not necessary to call this method explicitly because Output() does it automatically. If the document contains no page, AddPage() is called to prevent from getting an invalid document.
722
	# @since 1.0
723
	# @see Open(), Output()
724
	#
725
	def Close()
726
		#Terminate document
727
		if (@state==3)
728
			return;
729
		end
730
		if (@page==0)
731
			AddPage();
732
		end
733
		#Page footer
734
		@in_footer=true;
735
		Footer();
736
		@in_footer=false;
737
		#Close page
738
		endpage();
739
		#Close document
740
		enddoc();
741
	end
742
  # alias_method :close, :Close
743

    
744
	#
745
	# Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer. Then the page is added, the current position set to the top-left corner according to the left and top margins, and Header() is called to display the header.
746
	# The font which was set before calling is automatically restored. There is no need to call SetFont() again if you want to continue with the same font. The same is true for colors and line width.
747
	# The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
748
	# @param string :orientation Page orientation. Possible values are (case insensitive):<ul><li>P or Portrait</li><li>L or Landscape</li></ul> The default value is the one passed to the constructor.
749
	# @since 1.0
750
	# @see TCPDF(), Header(), Footer(), SetMargins()
751
	#
752
	def AddPage(orientation='')
753
		#Start a new page
754
		if (@state==0)
755
			Open();
756
		end
757
		family=@font_family;
758
		style=@font_style + (@underline ? 'U' : '') + (@deleted ? 'D' : '');
759
		size=@font_size_pt;
760
		lw=@line_width;
761
		dc=@draw_color;
762
		fc=@fill_color;
763
		tc=@text_color;
764
		cf=@color_flag;
765
		if (@page>0)
766
			#Page footer
767
			@in_footer=true;
768
			Footer();
769
			@in_footer=false;
770
			#Close page
771
			endpage();
772
		end
773
		#Start new page
774
		beginpage(orientation);
775
		#Set line cap style to square
776
		out('2 J');
777
		#Set line width
778
		@line_width = lw;
779
		out(sprintf('%.2f w', lw*@k));
780
		#Set font
781
		if (family)
782
			SetFont(family, style, size);
783
		end
784
		#Set colors
785
		@draw_color = dc;
786
		if (dc!='0 G')
787
			out(dc);
788
		end
789
		@fill_color = fc;
790
		if (fc!='0 g')
791
			out(fc);
792
		end
793
		@text_color = tc;
794
		@color_flag = cf;
795
		#Page header
796
		Header();
797
		#Restore line width
798
		if (@line_width != lw)
799
			@line_width = lw;
800
			out(sprintf('%.2f w', lw*@k));
801
		end
802
		#Restore font
803
		if (family)
804
			SetFont(family, style, size);
805
		end
806
		#Restore colors
807
		if (@draw_color != dc)
808
			@draw_color = dc;
809
			out(dc);
810
		end
811
		if (@fill_color != fc)
812
			@fill_color = fc;
813
			out(fc);
814
		end
815
		@text_color = tc;
816
		@color_flag = cf;
817
	end
818
	  alias_method :add_page, :AddPage
819
	
820
  #
821
  # Rotate object.
822
  # @param float :angle angle in degrees for counter-clockwise rotation
823
  # @param int :x abscissa of the rotation center. Default is current x position
824
  # @param int :y ordinate of the rotation center. Default is current y position
825
  #
826
  def Rotate(angle, x="", y="")
827

    
828
  	if (x == '')
829
  		x = @x;
830
  	end
831
  	
832
  	if (y == '')
833
  		y = @y;
834
  	end
835
  	
836
  	if (@rtl)
837
  		x = @w - x;
838
  		angle = -@angle;
839
  	end
840
  	
841
  	y = (@h - y) * @k;
842
  	x *= @k;
843

    
844
  	# calculate elements of transformation matrix
845
  	tm = []
846
  	tm[0] = ::Math::cos(deg2rad(angle));
847
  	tm[1] = ::Math::sin(deg2rad(angle));
848
  	tm[2] = -tm[1];
849
  	tm[3] = tm[0];
850
  	tm[4] = x + tm[1] * y - tm[0] * x;
851
  	tm[5] = y - tm[0] * y - tm[1] * x;
852

    
853
  	# generate the transformation matrix
854
  	Transform(tm);
855
  end
856
    alias_method :rotate, :Rotate
857
  
858
  #
859
	# Starts a 2D tranformation saving current graphic state.
860
	# This function must be called before scaling, mirroring, translation, rotation and skewing.
861
	# Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
862
	#
863
	def StartTransform
864
		out('q');
865
	end
866
	  alias_method :start_transform, :StartTransform
867
	
868
	#
869
	# Stops a 2D tranformation restoring previous graphic state.
870
	# This function must be called after scaling, mirroring, translation, rotation and skewing.
871
	# Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
872
	#
873
	def StopTransform
874
		out('Q');
875
	end
876
	  alias_method :stop_transform, :StopTransform
877
	
878
  #
879
	# Apply graphic transformations.
880
	# @since 2.1.000 (2008-01-07)
881
	# @see StartTransform(), StopTransform()
882
	#
883
	def Transform(tm)
884
		x = out(sprintf('%.3f %.3f %.3f %.3f %.3f %.3f cm', tm[0], tm[1], tm[2], tm[3], tm[4], tm[5]));
885
	end
886
	  alias_method :transform, :Transform
887
		
888
	#
889
 	# Set header data.
890
	# @param string :ln header image logo
891
	# @param string :lw header image logo width in mm
892
	# @param string :ht string to print as title on document header
893
	# @param string :hs string to print on document header
894
	#
895
	def SetHeaderData(ln="", lw=0, ht="", hs="")
896
		@header_logo = ln || ""
897
		@header_logo_width = lw || 0
898
		@header_title = ht || ""
899
		@header_string = hs || ""
900
	end
901
	  alias_method :set_header_data, :SetHeaderData
902
	
903
	#
904
 	# Set header margin.
905
	# (minimum distance between header and top page margin)
906
	# @param int :hm distance in millimeters
907
	#
908
	def SetHeaderMargin(hm=10)
909
		@header_margin = hm;
910
	end
911
	  alias_method :set_header_margin, :SetHeaderMargin
912
	
913
	#
914
 	# Set footer margin.
915
	# (minimum distance between footer and bottom page margin)
916
	# @param int :fm distance in millimeters
917
	#
918
	def SetFooterMargin(fm=10)
919
		@footer_margin = fm;
920
	end
921
	  alias_method :set_footer_margin, :SetFooterMargin
922
	
923
	#
924
 	# Set a flag to print page header.
925
	# @param boolean :val set to true to print the page header (default), false otherwise. 
926
	#
927
	def SetPrintHeader(val=true)
928
		@print_header = val;
929
	end
930
	  alias_method :set_print_header, :SetPrintHeader
931
	
932
	#
933
 	# Set a flag to print page footer.
934
	# @param boolean :value set to true to print the page footer (default), false otherwise. 
935
	#
936
	def SetPrintFooter(val=true)
937
		@print_footer = val;
938
	end
939
	  alias_method :set_print_footer, :SetPrintFooter
940
	
941
	#
942
 	# This method is used to render the page header.
943
 	# It is automatically called by AddPage() and could be overwritten in your own inherited class.
944
	#
945
	def Header()
946
		if (@print_header)
947
			if (@original_l_margin.nil?)
948
				@original_l_margin = @l_margin;
949
			end
950
			if (@original_r_margin.nil?)
951
				@original_r_margin = @r_margin;
952
			end
953
			
954
			#set current position
955
			SetXY(@original_l_margin, @header_margin);
956
			
957
			if ((@header_logo) and (@header_logo != @@k_blank_image))
958
				Image(@header_logo, @original_l_margin, @header_margin, @header_logo_width);
959
			else
960
				@img_rb_y = GetY();
961
			end
962
			
963
			cell_height = ((@@k_cell_height_ratio * @header_font[2]) / @k).round(2)
964
			
965
			header_x = @original_l_margin + (@header_logo_width * 1.05); #set left margin for text data cell
966
			
967
			# header title
968
			SetFont(@header_font[0], 'B', @header_font[2] + 1);
969
			SetX(header_x);
970
			Cell(@header_width, cell_height, @header_title, 0, 1, 'L'); 
971
			
972
			# header string
973
			SetFont(@header_font[0], @header_font[1], @header_font[2]);
974
			SetX(header_x);
975
			MultiCell(@header_width, cell_height, @header_string, 0, 'L', 0);
976
			
977
			# print an ending header line
978
			if (@header_width)
979
				#set style for cell border
980
				SetLineWidth(0.3);
981
				SetDrawColor(0, 0, 0);
982
				SetY(1 + (@img_rb_y > GetY() ? @img_rb_y : GetY()));
983
				SetX(@original_l_margin);
984
				Cell(0, 0, '', 'T', 0, 'C'); 
985
			end
986
			
987
			#restore position
988
			SetXY(@original_l_margin, @t_margin);
989
		end
990
	end
991
	  alias_method :header, :Header
992
	
993
	#
994
 	# This method is used to render the page footer. 
995
 	# It is automatically called by AddPage() and could be overwritten in your own inherited class.
996
	#
997
	def Footer()
998
		if (@print_footer)
999
			
1000
			if (@original_l_margin.nil?)
1001
				@original_l_margin = @l_margin;
1002
			end
1003
			if (@original_r_margin.nil?)
1004
				@original_r_margin = @r_margin;
1005
			end
1006
			
1007
			#set font
1008
			SetFont(@footer_font[0], @footer_font[1] , @footer_font[2]);
1009
			#set style for cell border
1010
			line_width = 0.3;
1011
			SetLineWidth(line_width);
1012
			SetDrawColor(0, 0, 0);
1013
			
1014
			footer_height = ((@@k_cell_height_ratio * @footer_font[2]) / @k).round; #footer height, was , 2)
1015
			#get footer y position
1016
			footer_y = @h - @footer_margin - footer_height;
1017
			#set current position
1018
			SetXY(@original_l_margin, footer_y); 
1019
			
1020
			#print document barcode
1021
			if (@barcode)
1022
				Ln();
1023
				barcode_width = ((@w - @original_l_margin - @original_r_margin)).round; #max width
1024
				writeBarcode(@original_l_margin, footer_y + line_width, barcode_width, footer_height - line_width, "C128B", false, false, 2, @barcode);
1025
			end
1026
			
1027
			SetXY(@original_l_margin, footer_y); 
1028
			
1029
			#Print page number
1030
			Cell(0, footer_height, @l['w_page'] + " " + PageNo().to_s + ' / {nb}', 'T', 0, 'R'); 
1031
		end
1032
	end
1033
	  alias_method :footer, :Footer
1034
	
1035
	#
1036
	# Returns the current page number.
1037
	# @return int page number
1038
	# @since 1.0
1039
	# @see alias_nb_pages()
1040
	#
1041
	def PageNo()
1042
		#Get current page number
1043
		return @page;
1044
	end
1045
  alias_method :page_no, :PageNo
1046

    
1047
	#
1048
	# Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1049
	# @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1050
	# @param int :g Green component (between 0 and 255)
1051
	# @param int :b Blue component (between 0 and 255)
1052
	# @since 1.3
1053
	# @see SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
1054
	#
1055
	def SetDrawColor(r, g=-1, b=-1)
1056
		#Set color for all stroking operations
1057
		if ((r==0 and g==0 and b==0) or g==-1)
1058
			@draw_color=sprintf('%.3f G', r/255.0);
1059
		else
1060
			@draw_color=sprintf('%.3f %.3f %.3f RG', r/255.0, g/255.0, b/255.0);
1061
		end
1062
		if (@page>0)
1063
			out(@draw_color);
1064
		end
1065
	end
1066
  alias_method :set_draw_color, :SetDrawColor
1067

    
1068
	#
1069
	# Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1070
	# @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1071
	# @param int :g Green component (between 0 and 255)
1072
	# @param int :b Blue component (between 0 and 255)
1073
	# @param boolean :storeprev if true stores the RGB array on :prevfill_color variable.
1074
	# @since 1.3
1075
	# @see SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
1076
	#
1077
	def SetFillColor(r, g=-1, b=-1, storeprev=false)
1078
		#Set color for all filling operations
1079
		if ((r==0 and g==0 and b==0) or g==-1)
1080
			@fill_color=sprintf('%.3f g', r/255.0);
1081
		else
1082
			@fill_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0);
1083
		end
1084
		@color_flag=(@fill_color!=@text_color);
1085
		if (@page>0)
1086
			out(@fill_color);
1087
		end
1088
		if (storeprev)
1089
			# store color as previous value
1090
			@prevfill_color = [r, g, b]
1091
		end
1092
	end
1093
  alias_method :set_fill_color, :SetFillColor
1094

    
1095
  # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors
1096
	def SetCmykFillColor(c, m, y, k, storeprev=false)
1097
		#Set color for all filling operations
1098
		@fill_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k);
1099
		@color_flag=(@fill_color!=@text_color);
1100
		if (storeprev)
1101
			# store color as previous value
1102
			@prevtext_color = [c, m, y, k]
1103
		end
1104
		if (@page>0)
1105
			out(@fill_color);
1106
		end
1107
	end
1108
  alias_method :set_cmyk_fill_color, :SetCmykFillColor
1109

    
1110
	#
1111
	# Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
1112
	# @param int :r If g et b are given, red component; if not, indicates the gray level. Value between 0 and 255
1113
	# @param int :g Green component (between 0 and 255)
1114
	# @param int :b Blue component (between 0 and 255)
1115
	# @param boolean :storeprev if true stores the RGB array on :prevtext_color variable.
1116
	# @since 1.3
1117
	# @see SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
1118
	#
1119
	def SetTextColor(r, g=-1, b=-1, storeprev=false)
1120
		#Set color for text
1121
		if ((r==0 and :g==0 and :b==0) or :g==-1)
1122
			@text_color=sprintf('%.3f g', r/255.0);
1123
		else
1124
			@text_color=sprintf('%.3f %.3f %.3f rg', r/255.0, g/255.0, b/255.0);
1125
		end
1126
		@color_flag=(@fill_color!=@text_color);
1127
		if (storeprev)
1128
			# store color as previous value
1129
			@prevtext_color = [r, g, b]
1130
		end
1131
	end
1132
  alias_method :set_text_color, :SetTextColor
1133

    
1134
  # This hasn't been ported from tcpdf, it's a variation on SetTextColor for setting cmyk colors
1135
	def SetCmykTextColor(c, m, y, k, storeprev=false)
1136
		#Set color for text
1137
		@text_color=sprintf('%.3f %.3f %.3f %.3f k', c, m, y, k);
1138
		@color_flag=(@fill_color!=@text_color);
1139
		if (storeprev)
1140
			# store color as previous value
1141
			@prevtext_color = [c, m, y, k]
1142
		end
1143
	end
1144
  alias_method :set_cmyk_text_color, :SetCmykTextColor
1145
  
1146
	#
1147
	# Returns the length of a string in user unit. A font must be selected.<br>
1148
	# Support UTF-8 Unicode [Nicola Asuni, 2005-01-02]
1149
	# @param string :s The string whose length is to be computed
1150
	# @return int
1151
	# @since 1.2
1152
	#
1153
	def GetStringWidth(s)
1154
		#Get width of a string in the current font
1155
		s = s.to_s;
1156
		cw = @current_font['cw']
1157
		w = 0;
1158
		if (@is_unicode)
1159
      unicode = UTF8StringToArray(s);
1160
      unicode.each do |char|
1161
				if (!cw[char].nil?)
1162
					w += cw[char];
1163
				# This should not happen. UTF8StringToArray should guarentee the array is ascii values.
1164
        # elsif (c!cw[char[0]].nil?)
1165
        #   w += cw[char[0]];
1166
        #         elsif (!cw[char.chr].nil?)
1167
        #           w += cw[char.chr];
1168
				elsif (!@current_font['desc']['MissingWidth'].nil?)
1169
					w += @current_font['desc']['MissingWidth']; # set default size
1170
				else
1171
					w += 500;
1172
				end
1173
			end
1174
		else
1175
		  s.each_byte do |c|
1176
				if cw[c.chr]
1177
					w += cw[c.chr];
1178
				elsif cw[?c.chr]
1179
					w += cw[?c.chr]
1180
				end
1181
			end
1182
		end
1183
		return (w * @font_size / 1000.0);
1184
	end
1185
  alias_method :get_string_width, :GetStringWidth
1186

    
1187
	#
1188
	# Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
1189
	# @param float :width The width.
1190
	# @since 1.0
1191
	# @see Line(), Rect(), Cell(), MultiCell()
1192
	#
1193
	def SetLineWidth(width)
1194
		#Set line width
1195
		@line_width = width;
1196
		if (@page>0)
1197
			out(sprintf('%.2f w', width*@k));
1198
		end
1199
	end
1200
  alias_method :set_line_width, :SetLineWidth
1201

    
1202
	#
1203
	# Draws a line between two points.
1204
	# @param float :x1 Abscissa of first point
1205
	# @param float :y1 Ordinate of first point
1206
	# @param float :x2 Abscissa of second point
1207
	# @param float :y2 Ordinate of second point
1208
	# @since 1.0
1209
	# @see SetLineWidth(), SetDrawColor()
1210
	#
1211
	def Line(x1, y1, x2, y2)
1212
		#Draw a line
1213
		out(sprintf('%.2f %.2f m %.2f %.2f l S', x1 * @k, (@h - y1) * @k, x2 * @k, (@h - y2) * @k));
1214
	end
1215
  alias_method :line, :Line
1216

    
1217
  def Circle(mid_x, mid_y, radius, style='')
1218
    mid_y = (@h-mid_y)*@k
1219
    out(sprintf("q\n")) # postscript content in pdf
1220
    # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc. 
1221
    out(sprintf("1 j\n")) # line join
1222
    # translate ("move") circle to mid_y, mid_y
1223
    out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y))
1224
    kappa = 0.5522847498307933984022516322796
1225
    # Quadrant 1 
1226
    x_s = 0.0 # 12 o'clock 
1227
    y_s = 0.0 + radius
1228
    x_e = 0.0 + radius # 3 o'clock 
1229
    y_e = 0.0
1230
    out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock 
1231
    # cubic bezier control point 1, start height and kappa * radius to the right 
1232
    bx_e1 = x_s + (radius * kappa)
1233
    by_e1 = y_s
1234
    # cubic bezier control point 2, end and kappa * radius above 
1235
    bx_e2 = x_e
1236
    by_e2 = y_e + (radius * kappa)
1237
    # draw cubic bezier from current point to x_e/y_e with bx_e1/by_e1 and bx_e2/by_e2 as bezier control points
1238
    out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1239
    # Quadrant 2 
1240
    x_s = x_e 
1241
    y_s = y_e # 3 o'clock 
1242
    x_e = 0.0 
1243
    y_e = 0.0 - radius # 6 o'clock 
1244
    bx_e1 = x_s # cubic bezier point 1 
1245
    by_e1 = y_s - (radius * kappa)
1246
    bx_e2 = x_e + (radius * kappa) # cubic bezier point 2 
1247
    by_e2 = y_e
1248
    out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1249
    # Quadrant 3 
1250
    x_s = x_e 
1251
    y_s = y_e # 6 o'clock 
1252
    x_e = 0.0 - radius
1253
    y_e = 0.0 # 9 o'clock 
1254
    bx_e1 = x_s - (radius * kappa) # cubic bezier point 1 
1255
    by_e1 = y_s
1256
    bx_e2 = x_e # cubic bezier point 2 
1257
    by_e2 = y_e - (radius * kappa)
1258
    out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1259
    # Quadrant 4 
1260
    x_s = x_e 
1261
    y_s = y_e # 9 o'clock 
1262
    x_e = 0.0 
1263
    y_e = 0.0 + radius # 12 o'clock 
1264
    bx_e1 = x_s # cubic bezier point 1 
1265
    by_e1 = y_s + (radius * kappa)
1266
    bx_e2 = x_e - (radius * kappa) # cubic bezier point 2 
1267
    by_e2 = y_e
1268
    out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
1269
    if style=='F'
1270
        op='f'
1271
    elsif style=='FD' or style=='DF'
1272
        op='b'
1273
    else
1274
        op='s'
1275
    end
1276
    out(sprintf("#{op}\n")) # stroke circle, do not fill and close path 
1277
    # for filling etc. b, b*, f, f*
1278
    out(sprintf("Q\n")) # finish postscript in PDF
1279
  end
1280
  alias_method :circle, :Circle
1281

    
1282
	#
1283
	# Outputs a rectangle. It can be drawn (border only), filled (with no border) or both.
1284
	# @param float :x Abscissa of upper-left corner
1285
	# @param float :y Ordinate of upper-left corner
1286
	# @param float :w Width
1287
	# @param float :h Height
1288
	# @param string :style Style of rendering. Possible values are:<ul><li>D or empty string: draw (default)</li><li>F: fill</li><li>DF or FD: draw and fill</li></ul>
1289
	# @since 1.0
1290
	# @see SetLineWidth(), SetDrawColor(), SetFillColor()
1291
	#
1292
	def Rect(x, y, w, h, style='')
1293
		#Draw a rectangle
1294
		if (style=='F')
1295
			op='f';
1296
		elsif (style=='FD' or style=='DF')
1297
			op='B';
1298
		else
1299
			op='S';
1300
		end
1301
		out(sprintf('%.2f %.2f %.2f %.2f re %s', x * @k, (@h - y) * @k, w * @k, -h * @k, op));
1302
	end
1303
  alias_method :rect, :Rect
1304

    
1305
	#
1306
	# Imports a TrueType or Type1 font and makes it available. It is necessary to generate a font definition file first with the makefont.rb utility. The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by FPDF_FONTPATH if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
1307
	# Support UTF-8 Unicode [Nicola Asuni, 2005-01-02].
1308
	# <b>Example</b>:<br />
1309
	# <pre>
1310
	# :pdf->AddFont('Comic','I');
1311
	# # is equivalent to:
1312
	# :pdf->AddFont('Comic','I','comici.rb');
1313
	# </pre>
1314
	# @param string :family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
1315
	# @param string :style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
1316
	# @param string :file The font definition file. By default, the name is built from the family and style, in lower case with no space.
1317
	# @since 1.5
1318
	# @see SetFont()
1319
	#
1320
	def AddFont(family, style='', file='')
1321
		if (family.empty?)
1322
			return;
1323
		end
1324

    
1325
		#Add a TrueType or Type1 font
1326
		family = family.downcase
1327
		if ((!@is_unicode) and (family == 'arial'))
1328
			family = 'helvetica';
1329
		end
1330

    
1331
		style=style.upcase
1332
		style=style.gsub('U','');
1333
		style=style.gsub('D','');
1334
		if (style == 'IB')
1335
			style = 'BI';
1336
		end
1337

    
1338
		fontkey = family + style;
1339
		# check if the font has been already added
1340
		if !@fonts[fontkey].nil?
1341
			return;
1342
		end
1343

    
1344
		if (file=='')
1345
			file = family.gsub(' ', '') + style.downcase + '.rb';
1346
		end
1347
		font_file_name = getfontpath(file)
1348
		if (font_file_name.nil?)
1349
			# try to load the basic file without styles
1350
			file = family.gsub(' ', '') + '.rb';
1351
  		font_file_name = getfontpath(file)
1352
		end
1353
    if font_file_name.nil?
1354
			Error("Could not find font #{file}.")
1355
    end
1356
		require(getfontpath(file))
1357
		font_desc = TCPDFFontDescriptor.font(file)
1358

    
1359
		if (font_desc[:name].nil? and @@fpdf_charwidths.nil?)
1360
			Error('Could not include font definition file');
1361
		end
1362

    
1363
		i = @fonts.length+1;
1364
		if (@is_unicode)
1365
			@fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg], 'cMap' => font_desc[:cMap], 'registry' => font_desc[:registry]}
1366
			@@fpdf_charwidths[fontkey] = font_desc[:cw];
1367
		else
1368
			@fonts[fontkey]={'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]}
1369
			@@fpdf_charwidths[fontkey] = font_desc[:cw];
1370
		end
1371

    
1372
		if (!font_desc[:diff].nil? and (!font_desc[:diff].empty?))
1373
			#Search existing encodings
1374
			d=0;
1375
			nb=@diffs.length;
1376
			1.upto(nb) do |i|
1377
				if (@diffs[i]== font_desc[:diff])
1378
					d = i;
1379
					break;
1380
				end
1381
			end
1382
			if (d==0)
1383
				d = nb+1;
1384
				@diffs[d] = font_desc[:diff];
1385
			end
1386
			@fonts[fontkey]['diff'] = d;
1387
		end
1388
		if (font_desc[:file] and font_desc[:file].length > 0)
1389
			if (font_desc[:type] == "TrueType") or (font_desc[:type] == "TrueTypeUnicode")
1390
				@font_files[font_desc[:file]] = {'length1' => font_desc[:originalsize]}
1391
			else
1392
				@font_files[font_desc[:file]] = {'length1' => font_desc[:size1], 'length2' => font_desc[:size2]}
1393
			end
1394
		end
1395
	end
1396
  alias_method :add_font, :AddFont
1397

    
1398
	#
1399
	# Sets the font used to print character strings. It is mandatory to call this method at least once before printing text or the resulting document would not be valid.
1400
	# The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
1401
	# The method can be called before the first page is created and the font is retained from page to page.
1402
	# If you just wish to change the current font size, it is simpler to call SetFontSize().
1403
	# Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the FPDF_FONTPATH constant</li></ul><br />
1404
	# Example for the last case (note the trailing slash):<br />
1405
	# <pre>
1406
	# define('FPDF_FONTPATH','/home/www/font/');
1407
	# require('tcpdf.rb');
1408
	#
1409
	# #Times regular 12
1410
	# :pdf->SetFont('Times');
1411
	# #Arial bold 14
1412
	# :pdf->SetFont('Arial','B',14);
1413
	# #Removes bold
1414
	# :pdf->SetFont('');
1415
	# #Times bold, italic and underlined 14
1416
	# :pdf->SetFont('Times','BIUD');
1417
	# </pre><br />
1418
	# If the file corresponding to the requested font is not found, the error "Could not include font metric file" is generated.
1419
	# @param string :family Family font. It can be either a name defined by AddFont() or one of the standard families (case insensitive):<ul><li>Courier (fixed-width)</li><li>Helvetica or Arial (synonymous; sans serif)</li><li>Times (serif)</li><li>Symbol (symbolic)</li><li>ZapfDingbats (symbolic)</li></ul>It is also possible to pass an empty string. In that case, the current family is retained.
1420
	# @param string :style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li></ul>or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats
1421
	# @param float :size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
1422
	# @since 1.0
1423
	# @see AddFont(), SetFontSize(), Cell(), MultiCell(), Write()
1424
	#
1425
	def SetFont(family, style='', size=0)
1426
		# save previous values
1427
		@prevfont_family = @font_family;
1428
		@prevfont_style = @font_style;
1429

    
1430
		family=family.downcase;
1431
		if (family=='')
1432
			family=@font_family;
1433
		end
1434
		if ((!@is_unicode) and (family == 'arial'))
1435
			family = 'helvetica';
1436
		elsif ((family=="symbol") or (family=="zapfdingbats"))
1437
			style='';
1438
		end
1439
		
1440
		style=style.upcase;
1441

    
1442
		if (style.include?('U'))
1443
			@underline=true;
1444
			style= style.gsub('U','');
1445
		else
1446
			@underline=false;
1447
		end
1448
		if (style.include?('D'))
1449
			@deleted=true;
1450
			style= style.gsub('D','');
1451
		else
1452
			@deleted=false;
1453
		end
1454
		if (style=='IB')
1455
			style='BI';
1456
		end
1457
		if (size==0)
1458
			size=@font_size_pt;
1459
		end
1460

    
1461
		# try to add font (if not already added)
1462
		AddFont(family, style);
1463
		
1464
		#Test if font is already selected
1465
		if ((@font_family == family) and (@font_style == style) and (@font_size_pt == size))
1466
			return;
1467
		end
1468
		
1469
		fontkey = family + style;
1470
		style = '' if (@fonts[fontkey].nil? and !@fonts[family].nil?)
1471
    
1472
		#Test if used for the first time
1473
		if (@fonts[fontkey].nil?)
1474
			#Check if one of the standard fonts
1475
			if (!@core_fonts[fontkey].nil?)
1476
				if @@fpdf_charwidths[fontkey].nil?
1477
					#Load metric file
1478
					file = family;
1479
					if ((family!='symbol') and (family!='zapfdingbats'))
1480
						file += style.downcase;
1481
					end
1482
					if (getfontpath(file + '.rb').nil?)
1483
						# try to load the basic file without styles
1484
						file = family;
1485
						fontkey = family;
1486
					end
1487
					require(getfontpath(file + '.rb'));
1488
      		font_desc = TCPDFFontDescriptor.font(file)
1489
					if ((@is_unicode and ctg.nil?) or ((!@is_unicode) and (@@fpdf_charwidths[fontkey].nil?)) )
1490
						Error("Could not include font metric file [" + fontkey + "]: " + getfontpath(file + ".rb"));
1491
					end
1492
				end
1493
				i = @fonts.length + 1;
1494

    
1495
				if (@is_unicode)
1496
					@fonts[fontkey] = {'i' => i, 'type' => font_desc[:type], 'name' => font_desc[:name], 'desc' => font_desc[:desc], 'up' => font_desc[:up], 'ut' => font_desc[:ut], 'cw' => font_desc[:cw], 'enc' => font_desc[:enc], 'file' => font_desc[:file], 'ctg' => font_desc[:ctg]}
1497
					@@fpdf_charwidths[fontkey] = font_desc[:cw];
1498
				else
1499
					@fonts[fontkey] = {'i' => i, 'type'=>'core', 'name'=>@core_fonts[fontkey], 'up'=>-100, 'ut'=>50, 'cw' => font_desc[:cw]}
1500
					@@fpdf_charwidths[fontkey] = font_desc[:cw];
1501
				end
1502
			else
1503
				Error('Undefined font: ' + family + ' ' + style);
1504
			end
1505
		end
1506
		#Select it
1507
		@font_family = family;
1508
		@font_style = style;
1509
		@font_size_pt = size;
1510
		@font_size = size / @k;
1511
		@current_font = @fonts[fontkey]; # was & may need deep copy?
1512
		if (@page>0)
1513
			out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt));
1514
		end
1515
	end
1516
  alias_method :set_font, :SetFont
1517

    
1518
	#
1519
	# Defines the size of the current font.
1520
	# @param float :size The size (in points)
1521
	# @since 1.0
1522
	# @see SetFont()
1523
	#
1524
	def SetFontSize(size)
1525
		#Set font size in points
1526
		if (@font_size_pt== size)
1527
			return;
1528
		end
1529
		@font_size_pt = size;
1530
		@font_size = size.to_f / @k;
1531
		if (@page > 0)
1532
			out(sprintf('BT /F%d %.2f Tf ET', @current_font['i'], @font_size_pt));
1533
		end
1534
	end
1535
  alias_method :set_font_size, :SetFontSize
1536

    
1537
	#
1538
	# Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
1539
	# The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
1540
	# @since 1.5
1541
	# @see Cell(), Write(), Image(), Link(), SetLink()
1542
	#
1543
	def AddLink()
1544
		#Create a new internal link
1545
		n=@links.length+1;
1546
		@links[n]=[0,0];
1547
		return n;
1548
	end
1549
  alias_method :add_link, :AddLink
1550

    
1551
	#
1552
	# Defines the page and position a link points to
1553
	# @param int :link The link identifier returned by AddLink()
1554
	# @param float :y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
1555
	# @param int :page Number of target page; -1 indicates the current page. This is the default value
1556
	# @since 1.5
1557
	# @see AddLink()
1558
	#
1559
	def SetLink(link, y=0, page=-1)
1560
		#Set destination of internal link
1561
		if (y==-1)
1562
			y=@y;
1563
		end
1564
		if (page==-1)
1565
			page=@page;
1566
		end
1567
		@links[link] = [page, y]
1568
	end
1569
  alias_method :set_link, :SetLink
1570

    
1571
	#
1572
	# Puts a link on a rectangular area of the page. Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
1573
	# @param float :x Abscissa of the upper-left corner of the rectangle
1574
	# @param float :y Ordinate of the upper-left corner of the rectangle
1575
	# @param float :w Width of the rectangle
1576
	# @param float :h Height of the rectangle
1577
	# @param mixed :link URL or identifier returned by AddLink()
1578
	# @since 1.5
1579
	# @see AddLink(), Cell(), Write(), Image()
1580
	#
1581
	def Link(x, y, w, h, link)
1582
		#Put a link on the page
1583
    @page_links ||= Array.new
1584
    @page_links[@page] ||= Array.new
1585
    @page_links[@page].push([x * @k, @h_pt - y * @k, w * @k, h*@k, link]);
1586
	end
1587
  alias_method :link, :Link
1588

    
1589
	#
1590
	# Prints a character string. The origin is on the left of the first charcter, on the baseline. This method allows to place a string precisely on the page, but it is usually easier to use Cell(), MultiCell() or Write() which are the standard methods to print text.
1591
	# @param float :x Abscissa of the origin
1592
	# @param float :y Ordinate of the origin
1593
	# @param string :txt String to print
1594
	# @since 1.0
1595
	# @see SetFont(), SetTextColor(), Cell(), MultiCell(), Write()
1596
	#
1597
	def Text(x, y, txt)
1598
		#Output a string
1599
		s=sprintf('BT %.2f %.2f Td (%s) Tj ET', x * @k, (@h-y) * @k, escapetext(txt));
1600
		if (@underline and (txt!=''))
1601
			s += ' ' + dolinetxt(x, y, txt);
1602
		end
1603
		if (@color_flag)
1604
			s='q ' + @text_color + ' ' + s + ' Q';
1605
		end
1606
		out(s);
1607
	end
1608
  alias_method :text, :Text
1609

    
1610
	#
1611
	# Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
1612
	# This method is called automatically and should not be called directly by the application.<br />
1613
	# <b>Example:</b><br />
1614
	# The method is overriden in an inherited class in order to obtain a 3 column layout:<br />
1615
	# <pre>
1616
	# class PDF extends TCPDF {
1617
	# 	var :col=0;
1618
	#
1619
	# 	def SetCol(col)
1620
	# 		#Move position to a column
1621
	# 		@col = col;
1622
	# 		:x=10+:col*65;
1623
	# 		SetLeftMargin(x);
1624
	# 		SetX(x);
1625
	# 	end
1626
	#
1627
	# 	def AcceptPageBreak()
1628
	# 		if (@col<2)
1629
	# 			#Go to next column
1630
	# 			SetCol(@col+1);
1631
	# 			SetY(10);
1632
	# 			return false;
1633
	# 		end
1634
	# 		else
1635
	# 			#Go back to first column and issue page break
1636
	# 			SetCol(0);
1637
	# 			return true;
1638
	# 		end
1639
	# 	end
1640
	# }
1641
	#
1642
	# :pdf=new PDF();
1643
	# :pdf->Open();
1644
	# :pdf->AddPage();
1645
	# :pdf->SetFont('Arial','',12);
1646
	# for(i=1;:i<=300;:i++)
1647
	#     :pdf->Cell(0,5,"Line :i",0,1);
1648
	# }
1649
	# :pdf->Output();
1650
	# </pre>
1651
	# @return boolean
1652
	# @since 1.4
1653
	# @see SetAutoPageBreak()
1654
	#
1655
	def AcceptPageBreak()
1656
		#Accept automatic page break or not
1657
		return @auto_page_break;
1658
	end
1659
  alias_method :accept_page_break, :AcceptPageBreak
1660

    
1661
  def BreakThePage?(h)
1662
		if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
1663
      true
1664
    else
1665
      false
1666
    end
1667
  end
1668
  alias_method :break_the_page?, :BreakThePage?
1669
	#
1670
	# Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
1671
	# If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
1672
	# @param float :w Cell width. If 0, the cell extends up to the right margin.
1673
	# @param float :h Cell height. Default value: 0.
1674
	# @param string :txt String to print. Default value: empty string.
1675
	# @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
1676
	# @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
1677
	# Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
1678
	# @param string :align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li></ul>
1679
	# @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
1680
	# @param mixed :link URL or identifier returned by AddLink().
1681
	# @since 1.0
1682
	# @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
1683
	#
1684
	def Cell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link=nil)
1685
		#Output a cell
1686
		k=@k;
1687
		if ((@y + h) > @page_break_trigger and !@in_footer and AcceptPageBreak())
1688
			#Automatic page break
1689
			if @pages[@page+1].nil?
1690
				x = @x;
1691
				ws = @ws;
1692
				if (ws > 0)
1693
					@ws = 0;
1694
					out('0 Tw');
1695
				end
1696
				AddPage(@cur_orientation);
1697
				@x = x;
1698
				if (ws > 0)
1699
					@ws = ws;
1700
					out(sprintf('%.3f Tw', ws * k));
1701
				end
1702
			else
1703
				@page += 1;
1704
				@y=@t_margin;
1705
			end
1706
		end
1707

    
1708
		if (w == 0)
1709
			w = @w - @r_margin - @x;
1710
		end
1711
		s = '';
1712
		if ((fill.to_i == 1) or (border.to_i == 1))
1713
			if (fill.to_i == 1)
1714
				op = (border.to_i == 1) ? 'B' : 'f';
1715
			else
1716
				op = 'S';
1717
			end
1718
			s = sprintf('%.2f %.2f %.2f %.2f re %s ', @x * k, (@h - @y) * k, w * k, -h * k, op);
1719
		end
1720
		if (border.is_a?(String))
1721
			x=@x;
1722
			y=@y;
1723
			if (border.include?('L'))
1724
				s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-y)*k, x*k,(@h-(y+h))*k);
1725
			end
1726
			if (border.include?('T'))
1727
				s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-y)*k,(x+w)*k,(@h-y)*k);
1728
			end
1729
			if (border.include?('R'))
1730
				s<<sprintf('%.2f %.2f m %.2f %.2f l S ',(x+w)*k,(@h-y)*k,(x+w)*k,(@h-(y+h))*k);
1731
			end
1732
			if (border.include?('B'))
1733
				s<<sprintf('%.2f %.2f m %.2f %.2f l S ', x*k,(@h-(y+h))*k,(x+w)*k,(@h-(y+h))*k);
1734
			end
1735
		end
1736
		if (txt != '')
1737
			width = GetStringWidth(txt);
1738
			if (align == 'R' || align == 'right')
1739
				dx = w - @c_margin - width;
1740
			elsif (align=='C' || align == 'center')
1741
				dx = (w - width)/2;
1742
			else
1743
				dx = @c_margin;
1744
			end
1745
			if (@color_flag)
1746
				s << 'q ' + @text_color + ' ';
1747
			end
1748
			txt2 = escapetext(txt);
1749
			s<<sprintf('BT %.2f %.2f Td (%s) Tj ET', (@x + dx) * k, (@h - (@y + 0.5 * h + 0.3 * @font_size)) * k, txt2);
1750
			if (@underline)
1751
				s<<' ' + dolinetxt(@x + dx, @y + 0.5 * h + 0.3 * @font_size, txt);
1752
			end
1753
			if (@deleted)
1754
				s<<' ' + dolinetxt(@x + dx, @y + 0.3 * h + 0.2 * @font_size, txt);
1755
			end
1756
			if (@color_flag)
1757
				s<<' Q';
1758
			end
1759
			if link && !link.empty?
1760
				Link(@x + dx, @y + 0.5 * h - 0.5 * @font_size, width, @font_size, link);
1761
			end
1762
		end
1763
		if (s)
1764
			out(s);
1765
		end
1766
		@lasth = h;
1767
		if (ln.to_i>0)
1768
			# Go to next line
1769
			@y += h;
1770
			if (ln == 1)
1771
				@x = @l_margin;
1772
			end
1773
		else
1774
			@x += w;
1775
		end
1776
	end
1777
  alias_method :cell, :Cell
1778

    
1779
	#
1780
	# This method allows printing text with line breaks. They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
1781
	# Text can be aligned, centered or justified. The cell block can be framed and the background painted.
1782
	# @param float :w Width of cells. If 0, they extend up to the right margin of the page.
1783
	# @param float :h Height of cells.
1784
	# @param string :txt String to print
1785
	# @param mixed :border Indicates if borders must be drawn around the cell block. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
1786
	# @param string :align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value)</li></ul>
1787
	# @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
1788
	# @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
1789
	# @since 1.3
1790
	# @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
1791
	#
1792
	def MultiCell(w, h, txt, border=0, align='J', fill=0, ln=1)
1793
		
1794
		# save current position
1795
		prevx = @x;
1796
		prevy = @y;
1797
		prevpage = @page;
1798
		 
1799
		#Output text with automatic or explicit line breaks
1800

    
1801
		if (w == 0)
1802
			w = @w - @r_margin - @x;
1803
		end
1804

    
1805
		wmax = (w - 3 * @c_margin);
1806

    
1807
		s = txt.gsub("\r", ''); # remove carriage returns
1808
		nb = s.length;
1809

    
1810
		b=0;
1811
		if (border)
1812
			if (border==1)
1813
				border='LTRB';
1814
				b='LRT';
1815
				b2='LR';
1816
			elsif border.is_a?(String)
1817
				b2='';
1818
				if (border.include?('L'))
1819
					b2<<'L';
1820
				end
1821
				if (border.include?('R'))
1822
					b2<<'R';
1823
				end
1824
				b=(border.include?('T')) ? b2 + 'T' : b2;
1825
			end
1826
		end
1827
		sep=-1;
1828
		to_index=0;
1829
		from_j=0;
1830
		l=0;
1831
		ns=0;
1832
		nl=1;
1833
		
1834
		while to_index < nb
1835
			#Get next character
1836
			c = s[to_index];
1837
			if c == "\n"[0]
1838
				#Explicit line break
1839
				if @ws > 0
1840
					@ws = 0
1841
					out('0 Tw')
1842
				end
1843
        #Ed Moss - change begin            
1844
        end_i = to_index == 0 ? 0 : to_index - 1
1845
        # Changed from s[from_j..to_index] to fix bug reported by Hans Allis.
1846
        from_j = to_index == 0 ? 1 : from_j 
1847
        Cell(w, h, s[from_j..end_i], b, 2, align, fill) 
1848
        #change end
1849
				to_index += 1
1850
				sep=-1
1851
        from_j=to_index
1852
				l=0
1853
				ns=0
1854
				nl += 1
1855
				b = b2 if border and nl==2
1856
				next
1857
			end
1858
			if (c == " "[0])
1859
				sep = to_index;
1860
				ls = l;
1861
				ns += 1;
1862
			end
1863

    
1864
			l = GetStringWidth(s[from_j, to_index - from_j]);
1865

    
1866
			if (l > wmax)
1867
				#Automatic line break
1868
				if (sep == -1)
1869
					if (to_index == from_j)
1870
						to_index += 1;
1871
					end
1872
					if (@ws > 0)
1873
						@ws = 0;
1874
						out('0 Tw');
1875
					end
1876
          Cell(w, h, s[from_j..to_index-1], b, 2, align, fill) # my FPDF version
1877
				else
1878
					if (align=='J' || align=='justify' || align=='justified')
1879
						@ws = (ns>1) ? (wmax-ls)/(ns-1) : 0;
1880
						out(sprintf('%.3f Tw', @ws * @k));
1881
					end
1882
					Cell(w, h, s[from_j..sep], b, 2, align, fill);
1883
					to_index = sep + 1;
1884
				end
1885
				sep=-1;
1886
				from_j = to_index;
1887
				l=0;
1888
				ns=0;
1889
				nl += 1;
1890
				if (border and (nl==2))
1891
					b = b2;
1892
				end
1893
			else
1894
				to_index += 1;
1895
			end
1896
		end
1897
		#Last chunk
1898
		if (@ws>0)
1899
			@ws=0;
1900
			out('0 Tw');
1901
		end
1902
		if (border.is_a?(String) and border.include?('B'))
1903
			b<<'B';
1904
		end
1905
		Cell(w, h, s[from_j, to_index-from_j], b, 2, align, fill);
1906
		
1907
		# move cursor to specified position
1908
		# since 2007-03-03
1909
		if (ln == 1)
1910
			# go to the beginning of the next line
1911
			@x = @l_margin;
1912
		elsif (ln == 0)
1913
			# go to the top-right of the cell
1914
			@page = prevpage;
1915
			@y = prevy;
1916
			@x = prevx + w;
1917
		elsif (ln == 2)
1918
			# go to the bottom-left of the cell
1919
			@x = prevx;
1920
		end
1921
	end
1922
  alias_method :multi_cell, :MultiCell
1923

    
1924
	#
1925
	# This method prints text from the current position. When the right margin is reached (or the \n character is met) a line break occurs and text continues from the left margin. Upon method exit, the current position is left just at the end of the text. It is possible to put a link on the text.<br />
1926
	# <b>Example:</b><br />
1927
	# <pre>
1928
	# #Begin with regular font
1929
	# :pdf->SetFont('Arial','',14);
1930
	# :pdf->Write(5,'Visit ');
1931
	# #Then put a blue underlined link
1932
	# :pdf->SetTextColor(0,0,255);
1933
	# :pdf->SetFont('','U');
1934
	# :pdf->Write(5,'www.tecnick.com','http://www.tecnick.com');
1935
	# </pre>
1936
	# @param float :h Line height
1937
	# @param string :txt String to print
1938
	# @param mixed :link URL or identifier returned by AddLink()
1939
	# @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
1940
	# @since 1.5
1941
	# @see SetFont(), SetTextColor(), AddLink(), MultiCell(), SetAutoPageBreak()
1942
	#
1943
	def Write(h, txt, link=nil, fill=0)
1944

    
1945
		#Output text in flowing mode
1946
		w = @w - @r_margin - @x;
1947
		wmax = (w - 3 * @c_margin);
1948
    
1949
		s = txt.gsub("\r", '');
1950
		nb = s.length;
1951

    
1952
		# handle single space character
1953
		if ((nb==1) and (s == " "))
1954
			@x += GetStringWidth(s);
1955
			return;
1956
		end
1957

    
1958
		sep=-1;
1959
		i=0;
1960
		j=0;
1961
		l=0;
1962
		nl=1;
1963
		while(i<nb)
1964
			#Get next character
1965
			c = s[i];
1966
			if (c == "\n"[0])
1967
				#Explicit line break
1968
				Cell(w, h, s[j,i-j], 0, 2, '', fill, link);
1969
				i += 1;
1970
				sep = -1;
1971
				j = i;
1972
				l = 0;
1973
				if (nl == 1)
1974
					@x = @l_margin;
1975
					w = @w - @r_margin - @x;
1976
					wmax = (w - 3 * @c_margin);
1977
				end
1978
				nl += 1;
1979
				next
1980
			end
1981
			if (c == " "[0])
1982
				sep= i;
1983
			end
1984
			l = GetStringWidth(s[j, i - j]);
1985
			if (l > wmax)
1986
				#Automatic line break (word wrapping)
1987
				if (sep == -1)
1988
					if (@x > @l_margin)
1989
						#Move to next line
1990
						@x = @l_margin;
1991
						@y += h;
1992
						w=@w - @r_margin - @x;
1993
						wmax=(w - 3 * @c_margin);
1994
						i += 1
1995
						nl += 1
1996
						next
1997
					end
1998
					if (i == j)
1999
						i += 1
2000
					end
2001
					Cell(w, h, s[j, (i-1)], 0, 2, '', fill, link);
2002
				else
2003
					Cell(w, h, s[j, (sep-j)], 0, 2, '', fill, link);
2004
					i = sep+1;
2005
				end
2006
				sep = -1;
2007
				j = i;
2008
				l = 0;
2009
				if (nl==1)
2010
					@x = @l_margin;
2011
					w = @w - @r_margin - @x;
2012
					wmax = (w - 3 * @c_margin);
2013
				end
2014
				nl += 1;
2015
			else
2016
				i += 1;
2017
			end
2018
		end
2019
		#Last chunk
2020
		if (i != j)
2021
			Cell(GetStringWidth(s[j..i]), h, s[j..i], 0, 0, '', fill, link);
2022
		end
2023
	end
2024
  alias_method :write, :Write
2025

    
2026
	#
2027
	# Puts an image in the page. The upper-left corner must be given. The dimensions can be specified in different ways:<ul><li>explicit width and height (expressed in user unit)</li><li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li><li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
2028
	# Supported formats are JPEG and PNG.
2029
	# For JPEG, all flavors are allowed:<ul><li>gray scales</li><li>true colors (24 bits)</li><li>CMYK (32 bits)</li></ul>
2030
	# For PNG, are allowed:<ul><li>gray scales on at most 8 bits (256 levels)</li><li>indexed colors</li><li>true colors (24 bits)</li></ul>
2031
	# but are not supported:<ul><li>Interlacing</li><li>Alpha channel</li></ul>
2032
	# If a transparent color is defined, it will be taken into account (but will be only interpreted by Acrobat 4 and above).<br />
2033
	# The format can be specified explicitly or inferred from the file extension.<br />
2034
	# It is possible to put a link on the image.<br />
2035
	# Remark: if an image is used several times, only one copy will be embedded in the file.<br />
2036
	# @param string :file Name of the file containing the image.
2037
	# @param float :x Abscissa of the upper-left corner.
2038
	# @param float :y Ordinate of the upper-left corner.
2039
	# @param float :w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
2040
	# @param float :h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
2041
	# @param string :type Image format. Possible values are (case insensitive): JPG, JPEG, PNG. If not specified, the type is inferred from the file extension.
2042
	# @param mixed :link URL or identifier returned by AddLink().
2043
	# @since 1.1
2044
	# @see AddLink()
2045
	#
2046
	def Image(file, x, y, w=0, h=0, type='', link=nil)
2047
		#Put an image on the page
2048
		if (@images[file].nil?)
2049
			#First use of image, get info
2050
			if (type == '')
2051
				pos = File::basename(file).rindex('.');
2052
				if (pos.nil? or pos == 0)
2053
					Error('Image file has no extension and no type was specified: ' + file);
2054
				end
2055
				pos = file.rindex('.');
2056
				type = file[pos+1..-1];
2057
			end
2058
			type.downcase!
2059
			if (type == 'jpg' or type == 'jpeg')
2060
				info=parsejpg(file);
2061
			elsif (type == 'png')
2062
				info=parsepng(file);
2063
			elsif (type == 'gif')
2064
			  tmpFile = imageToPNG(file);
2065
				info=parsepng(tmpFile.path);
2066
				tmpFile.delete
2067
			else
2068
				#Allow for additional formats
2069
				mtd='parse' + type;
2070
				if (!self.respond_to?(mtd))
2071
					Error('Unsupported image type: ' + type);
2072
				end
2073
				info=send(mtd, file);
2074
			end
2075
			info['i']=@images.length+1;
2076
			@images[file] = info;
2077
		else
2078
			info=@images[file];
2079
		end
2080
		#Automatic width and height calculation if needed
2081
		if ((w == 0) and (h == 0))
2082
			rescale_x = (@w - @r_margin - x) / (info['w'] / (@img_scale * @k)) 
2083
			rescale_x = 1 if rescale_x >= 1 
2084
			if (y + info['h'] * rescale_x / (@img_scale * @k) > @page_break_trigger and !@in_footer and AcceptPageBreak())
2085
				#Automatic page break
2086
				if @pages[@page+1].nil?
2087
					ws = @ws;
2088
					if (ws > 0)
2089
						@ws = 0;
2090
						out('0 Tw');
2091
					end
2092
					AddPage(@cur_orientation);
2093
					if (ws > 0)
2094
						@ws = ws;
2095
						out(sprintf('%.3f Tw', ws * @k));
2096
					end
2097
				else
2098
					@page += 1;
2099
				end
2100
				y=@t_margin;
2101
			end
2102
			rescale_y = (@page_break_trigger - y) / (info['h'] / (@img_scale * @k)) 
2103
			rescale_y = 1 if rescale_y >= 1 
2104
			rescale = rescale_y >= rescale_x ? rescale_x : rescale_y
2105

    
2106
			#Put image at 72 dpi
2107
			# 2004-06-14 :: Nicola Asuni, scale factor where added
2108
			w = info['w'] * rescale / (@img_scale * @k);
2109
			h = info['h'] * rescale / (@img_scale * @k);
2110
		elsif (w == 0)
2111
			w = h * info['w'] / info['h'];
2112
		elsif (h == 0)
2113
			h = w * info['h'] / info['w'];
2114
		end
2115
		out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q', w*@k, h*@k, x*@k, (@h-(y+h))*@k, info['i']));
2116
		if (link)
2117
			Link(x, y, w, h, link);
2118
		end
2119

    
2120
		#2002-07-31 - Nicola Asuni
2121
		# set right-bottom corner coordinates
2122
		@img_rb_x = x + w;
2123
		@img_rb_y = y + h;
2124
	end
2125
  alias_method :image, :Image
2126

    
2127
	#
2128
	# Performs a line break. The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
2129
	# @param float :h The height of the break. By default, the value equals the height of the last printed cell.
2130
	# @since 1.0
2131
	# @see Cell()
2132
	#
2133
	def Ln(h='')
2134
		#Line feed; default value is last cell height
2135
		@x=@l_margin;
2136
		if (h.is_a?(String))
2137
			@y += @lasth;
2138
		else
2139
			@y += h;
2140
		end
2141

    
2142
		k=@k;
2143
		if (@y > @page_break_trigger and !@in_footer and AcceptPageBreak())
2144
			#Automatic page break
2145
			if @pages[@page+1].nil?
2146
				x = @x;
2147
				ws = @ws;
2148
				if (ws > 0)
2149
					@ws = 0;
2150
					out('0 Tw');
2151
				end
2152
				AddPage(@cur_orientation);
2153
				@x = x;
2154
				if (ws > 0)
2155
					@ws = ws;
2156
					out(sprintf('%.3f Tw', ws * k));
2157
				end
2158
			else
2159
				@page += 1;
2160
				@y=@t_margin;
2161
			end
2162
		end
2163

    
2164
	end
2165
  alias_method :ln, :Ln
2166

    
2167
	#
2168
	# Returns the abscissa of the current position.
2169
	# @return float
2170
	# @since 1.2
2171
	# @see SetX(), GetY(), SetY()
2172
	#
2173
	def GetX()
2174
		#Get x position
2175
		return @x;
2176
	end
2177
  alias_method :get_x, :GetX
2178

    
2179
	#
2180
	# Defines the abscissa of the current position. If the passed value is negative, it is relative to the right of the page.
2181
	# @param float :x The value of the abscissa.
2182
	# @since 1.2
2183
	# @see GetX(), GetY(), SetY(), SetXY()
2184
	#
2185
	def SetX(x)
2186
		#Set x position
2187
		if (x>=0)
2188
			@x = x;
2189
		else
2190
			@x=@w+x;
2191
		end
2192
	end
2193
  alias_method :set_x, :SetX
2194

    
2195
	#
2196
	# Returns the ordinate of the current position.
2197
	# @return float
2198
	# @since 1.0
2199
	# @see SetY(), GetX(), SetX()
2200
	#
2201
	def GetY()
2202
		#Get y position
2203
		return @y;
2204
	end
2205
  alias_method :get_y, :GetY
2206

    
2207
	#
2208
	# Moves the current abscissa back to the left margin and sets the ordinate. If the passed value is negative, it is relative to the bottom of the page.
2209
	# @param float :y The value of the ordinate.
2210
	# @since 1.0
2211
	# @see GetX(), GetY(), SetY(), SetXY()
2212
	#
2213
	def SetY(y)
2214
		#Set y position and reset x
2215
		@x=@l_margin;
2216
		if (y>=0)
2217
			@y = y;
2218
		else
2219
			@y=@h+y;
2220
		end
2221
	end
2222
  alias_method :set_y, :SetY
2223

    
2224
	#
2225
	# Defines the abscissa and ordinate of the current position. If the passed values are negative, they are relative respectively to the right and bottom of the page.
2226
	# @param float :x The value of the abscissa
2227
	# @param float :y The value of the ordinate
2228
	# @since 1.2
2229
	# @see SetX(), SetY()
2230
	#
2231
	def SetXY(x, y)
2232
		#Set x and y positions
2233
		SetY(y);
2234
		SetX(x);
2235
	end
2236
  alias_method :set_xy, :SetXY
2237

    
2238
	#
2239
	# Send the document to a given destination: string, local file or browser. In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
2240
	# The method first calls Close() if necessary to terminate the document.
2241
	# @param string :name The name of the file. If not given, the document will be sent to the browser (destination I) with the name doc.pdf.
2242
	# @param string :dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local file with the name given by name.</li><li>S: return the document as a string. name is ignored.</li></ul>If the parameter is not specified but a name is given, destination is F. If no parameter is specified at all, destination is I.<br />
2243
	# @since 1.0
2244
	# @see Close()
2245
	#
2246
	def Output(name='', dest='')
2247
		#Output PDF to some destination
2248
		#Finish document if necessary
2249
		if (@state < 3)
2250
			Close();
2251
		end
2252
		#Normalize parameters
2253
		# Boolean no longer supported
2254
		# if (dest.is_a?(Boolean))
2255
		# 	dest = dest ? 'D' : 'F';
2256
		# end
2257
		dest = dest.upcase
2258
		if (dest=='')
2259
			if (name=='')
2260
				name='doc.pdf';
2261
				dest='I';
2262
			else
2263
				dest='F';
2264
			end
2265
		end
2266
		case (dest)
2267
			when 'I'
2268
			  # This is PHP specific code
2269
				##Send to standard output
2270
				# if (ob_get_contents())
2271
				# 	Error('Some data has already been output, can\'t send PDF file');
2272
				# end
2273
				# if (php_sapi_name()!='cli')
2274
				# 	#We send to a browser
2275
				# 	header('Content-Type: application/pdf');
2276
				# 	if (headers_sent())
2277
				# 		Error('Some data has already been output to browser, can\'t send PDF file');
2278
				# 	end
2279
				# 	header('Content-Length: ' + @buffer.length);
2280
				# 	header('Content-disposition: inline; filename="' + name + '"');
2281
				# end
2282
				return @buffer;
2283
				
2284
			when 'D'
2285
			  # PHP specific
2286
				#Download file
2287
				# if (ob_get_contents())
2288
				# 	Error('Some data has already been output, can\'t send PDF file');
2289
				# end
2290
				# if (!_SERVER['HTTP_USER_AGENT'].nil? && SERVER['HTTP_USER_AGENT'].include?('MSIE'))
2291
				# 	header('Content-Type: application/force-download');
2292
				# else
2293
				# 	header('Content-Type: application/octet-stream');
2294
				# end
2295
				# if (headers_sent())
2296
				# 	Error('Some data has already been output to browser, can\'t send PDF file');
2297
				# end
2298
				# header('Content-Length: '+ @buffer.length);
2299
				# header('Content-disposition: attachment; filename="' + name + '"');
2300
				return @buffer;
2301
				
2302
			when 'F'
2303
        open(name,'wb') do |f|
2304
          f.write(@buffer)
2305
        end
2306
			   # PHP code
2307
			   # 				#Save to local file
2308
			   # 				f=open(name,'wb');
2309
			   # 				if (!f)
2310
			   # 					Error('Unable to create output file: ' + name);
2311
			   # 				end
2312
			   # 				fwrite(f,@buffer,@buffer.length);
2313
			   # 				f.close
2314
				
2315
			when 'S'
2316
				#Return as a string
2317
				return @buffer;
2318
			else
2319
				Error('Incorrect output destination: ' + dest);
2320
			
2321
		end
2322
		return '';
2323
	end
2324
  alias_method :output, :Output
2325

    
2326
	# Protected methods
2327

    
2328
	#
2329
	# Check for locale-related bug
2330
	# @access protected
2331
	#
2332
	def dochecks()
2333
		#Check for locale-related bug
2334
		if (1.1==1)
2335
			Error('Don\'t alter the locale before including class file');
2336
		end
2337
		#Check for decimal separator
2338
		if (sprintf('%.1f',1.0)!='1.0')
2339
			setlocale(LC_NUMERIC,'C');
2340
		end
2341
	end
2342

    
2343
	#
2344
	# Return fonts path
2345
	# @access protected
2346
	#
2347
	def getfontpath(file)
2348
    # Is it in the @@font_path?
2349
    if @@font_path
2350
  		fpath = File.join @@font_path, file
2351
  	  if File.exists?(fpath)
2352
  	    return fpath
2353
      end
2354
    end
2355
    # Is it in this plugin's font folder?
2356
		fpath = File.join File.dirname(__FILE__), 'fonts', file
2357
	  if File.exists?(fpath)
2358
	    return fpath
2359
    end
2360
    # Could not find it.
2361
    nil
2362
	end
2363

    
2364
	#
2365
	# Start document
2366
	# @access protected
2367
	#
2368
	def begindoc()
2369
		#Start document
2370
		@state=1;
2371
		out('%PDF-1.3');
2372
	end
2373

    
2374
	#
2375
	# putpages
2376
	# @access protected
2377
	#
2378
	def putpages()
2379
		nb = @page;
2380
		if (@alias_nb_pages)
2381
			nbstr = UTF8ToUTF16BE(nb.to_s, false);
2382
			#Replace number of pages
2383
			1.upto(nb) do |n|
2384
				@pages[n].gsub!(@alias_nb_pages, nbstr)
2385
			end
2386
		end
2387
		if @def_orientation=='P'
2388
			w_pt=@fw_pt
2389
			h_pt=@fh_pt
2390
		else
2391
			w_pt=@fh_pt
2392
			h_pt=@fw_pt
2393
		end
2394
		filter=(@compress) ? '/Filter /FlateDecode ' : ''
2395
		1.upto(nb) do |n|
2396
			#Page
2397
			newobj
2398
			out('<</Type /Page')
2399
			out('/Parent 1 0 R')
2400
			unless @orientation_changes[n].nil?
2401
				out(sprintf('/MediaBox [0 0 %.2f %.2f]', h_pt, w_pt))
2402
			end
2403
			out('/Resources 2 0 R')
2404
			if @page_links[n]
2405
				#Links
2406
				annots='/Annots ['
2407
				@page_links[n].each do |pl|
2408
					rect=sprintf('%.2f %.2f %.2f %.2f', pl[0], pl[1], pl[0]+pl[2], pl[1]-pl[3]);
2409
					annots<<'<</Type /Annot /Subtype /Link /Rect [' + rect + '] /Border [0 0 0] ';
2410
					if (pl[4].is_a?(String))
2411
						annots<<'/A <</S /URI /URI (' + escape(pl[4]) + ')>>>>';
2412
					else
2413
						l=@links[pl[4]];
2414
						h=!@orientation_changes[l[0]].nil? ? w_pt : h_pt;
2415
						annots<<sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]>>',1+2*l[0], h-l[1]*@k);
2416
					end
2417
				end
2418
				out(annots + ']');
2419
			end
2420
			out('/Contents ' + (@n+1).to_s + ' 0 R>>');
2421
			out('endobj');
2422
			#Page content
2423
			p=(@compress) ? gzcompress(@pages[n]) : @pages[n];
2424
			newobj();
2425
			out('<<' + filter + '/Length '+ p.length.to_s + '>>');
2426
			putstream(p);
2427
			out('endobj');
2428
		end
2429
		#Pages root
2430
		@offsets[1]=@buffer.length;
2431
		out('1 0 obj');
2432
		out('<</Type /Pages');
2433
		kids='/Kids [';
2434
		0.upto(nb) do |i|
2435
			kids<<(3+2*i).to_s + ' 0 R ';
2436
		end
2437
		out(kids + ']');
2438
		out('/Count ' + nb.to_s);
2439
		out(sprintf('/MediaBox [0 0 %.2f %.2f]', w_pt, h_pt));
2440
		out('>>');
2441
		out('endobj');
2442
	end
2443

    
2444
	#
2445
	# Adds fonts
2446
	# putfonts
2447
	# @access protected
2448
	#
2449
	def putfonts()
2450
		nf=@n;
2451
		@diffs.each do |diff|
2452
			#Encodings
2453
			newobj();
2454
			out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' + diff + ']>>');
2455
			out('endobj');
2456
		end
2457
		@font_files.each do |file, info|
2458
			#Font file embedding
2459
			newobj();
2460
			@font_files[file]['n']=@n;
2461
			font='';
2462
			open(getfontpath(file),'rb') do |f|
2463
				font = f.read();
2464
			end
2465
			compressed=(file[-2,2]=='.z');
2466
			if (!compressed && !info['length2'].nil?)
2467
				header=((font[0][0])==128);
2468
				if (header)
2469
					#Strip first binary header
2470
					font=font[6];
2471
				end
2472
				if header && (font[info['length1']][0] == 128)
2473
					#Strip second binary header
2474
					font=font[0..info['length1']] + font[info['length1']+6];
2475
				end
2476
			end
2477
			out('<</Length '+ font.length.to_s);
2478
			if (compressed)
2479
				out('/Filter /FlateDecode');
2480
			end
2481
			out('/Length1 ' + info['length1'].to_s);
2482
			if (!info['length2'].nil?)
2483
				out('/Length2 ' + info['length2'].to_s + ' /Length3 0');
2484
			end
2485
			out('>>');
2486
			open(getfontpath(file),'rb') do |f|
2487
        putstream(font)
2488
      end
2489
			out('endobj');
2490
		end
2491
		@fonts.each do |k, font|
2492
			#Font objects
2493
			@fonts[k]['n']=@n+1;
2494
			type = font['type'];
2495
			name = font['name'];
2496
			if (type=='core')
2497
				#Standard font
2498
				newobj();
2499
				out('<</Type /Font');
2500
				out('/BaseFont /' + name);
2501
				out('/Subtype /Type1');
2502
				if (name!='Symbol' && name!='ZapfDingbats')
2503
					out('/Encoding /WinAnsiEncoding');
2504
				end
2505
				out('>>');
2506
				out('endobj');
2507
    	elsif type == 'Type0'
2508
    		putType0(font)
2509
			elsif (type=='Type1' || type=='TrueType')
2510
				#Additional Type1 or TrueType font
2511
				newobj();
2512
				out('<</Type /Font');
2513
				out('/BaseFont /' + name);
2514
				out('/Subtype /' + type);
2515
				out('/FirstChar 32 /LastChar 255');
2516
				out('/Widths ' + (@n+1).to_s + ' 0 R');
2517
				out('/FontDescriptor ' + (@n+2).to_s + ' 0 R');
2518
				if (font['enc'])
2519
					if (!font['diff'].nil?)
2520
						out('/Encoding ' + (nf+font['diff']).to_s + ' 0 R');
2521
					else
2522
						out('/Encoding /WinAnsiEncoding');
2523
					end
2524
				end
2525
				out('>>');
2526
				out('endobj');
2527
				#Widths
2528
				newobj();
2529
				cw=font['cw']; # &
2530
				s='[';
2531
				32.upto(255) do |i|
2532
					s << cw[i.chr] + ' ';
2533
				end
2534
				out(s + ']');
2535
				out('endobj');
2536
				#Descriptor
2537
				newobj();
2538
				s='<</Type /FontDescriptor /FontName /' + name;
2539
				font['desc'].each do |k, v|
2540
					s<<' /' + k + ' ' + v;
2541
				end
2542
				file = font['file'];
2543
				if (file)
2544
					s<<' /FontFile' + (type=='Type1' ? '' : '2') + ' ' + @font_files[file]['n'] + ' 0 R';
2545
				end
2546
				out(s + '>>');
2547
				out('endobj');
2548
			else
2549
				#Allow for additional types
2550
				mtd='put' + type.downcase;
2551
				if (!self.respond_to?(mtd))
2552
					Error('Unsupported font type: ' + type)
2553
				else
2554
  				self.send(mtd,font)
2555
				end
2556
			end
2557
		end
2558
	end
2559

    
2560
  def putType0(font)
2561
  	#Type0
2562
		newobj();
2563
		out('<</Type /Font')
2564
  	out('/Subtype /Type0')
2565
  	out('/BaseFont /'+font['name']+'-'+font['cMap'])
2566
  	out('/Encoding /'+font['cMap'])
2567
  	out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
2568
  	out('>>')
2569
  	out('endobj')
2570
  	#CIDFont
2571
  	newobj()
2572
  	out('<</Type /Font')
2573
  	out('/Subtype /CIDFontType0')
2574
  	out('/BaseFont /'+font['name'])
2575
  	out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
2576
  	out('/FontDescriptor '+(@n+1).to_s+' 0 R')
2577
  	w='/W [1 ['
2578
		font['cw'].keys.sort.each {|key|
2579
		  w+=font['cw'][key].to_s + " "
2580
# ActionController::Base::logger.debug key.to_s
2581
# ActionController::Base::logger.debug font['cw'][key].to_s
2582
		}
2583
  	out(w+'] 231 325 500 631 [500] 326 389 500]')
2584
  	out('>>')
2585
  	out('endobj')
2586
  	#Font descriptor
2587
  	newobj()
2588
  	out('<</Type /FontDescriptor')
2589
  	out('/FontName /'+font['name'])
2590
  	out('/Flags 6')
2591
  	out('/FontBBox [0 -200 1000 900]')
2592
  	out('/ItalicAngle 0')
2593
  	out('/Ascent 800')
2594
  	out('/Descent -200')
2595
  	out('/CapHeight 800')
2596
  	out('/StemV 60')
2597
  	out('>>')
2598
  	out('endobj')
2599
  end
2600

    
2601
	#
2602
	# putimages
2603
	# @access protected
2604
	#
2605
	def putimages()
2606
		filter=(@compress) ? '/Filter /FlateDecode ' : '';
2607
		@images.each do |file, info| # was while(list(file, info)=each(@images))
2608
			newobj();
2609
			@images[file]['n']=@n;
2610
			out('<</Type /XObject');
2611
			out('/Subtype /Image');
2612
			out('/Width ' + info['w'].to_s);
2613
			out('/Height ' + info['h'].to_s);
2614
			if (info['cs']=='Indexed')
2615
				out('/ColorSpace [/Indexed /DeviceRGB ' + (info['pal'].length/3-1).to_s + ' ' + (@n+1).to_s + ' 0 R]');
2616
			else
2617
				out('/ColorSpace /' + info['cs']);
2618
				if (info['cs']=='DeviceCMYK')
2619
					out('/Decode [1 0 1 0 1 0 1 0]');
2620
				end
2621
			end
2622
			out('/BitsPerComponent ' + info['bpc'].to_s);
2623
			if (!info['f'].nil?)
2624
				out('/Filter /' + info['f']);
2625
			end
2626
			if (!info['parms'].nil?)
2627
				out(info['parms']);
2628
			end
2629
			if (!info['trns'].nil? and info['trns'].kind_of?(Array))
2630
				trns='';
2631
				0.upto(info['trns'].length) do |i|
2632
					trns << ("#{info['trns'][i]} " * 2);
2633
				end
2634
				out('/Mask [' + trns + ']');
2635
			end
2636
			out('/Length ' + info['data'].length.to_s + '>>');
2637
			putstream(info['data']);
2638
      @images[file]['data']=nil
2639
			out('endobj');
2640
			#Palette
2641
			if (info['cs']=='Indexed')
2642
				newobj();
2643
				pal=(@compress) ? gzcompress(info['pal']) : info['pal'];
2644
				out('<<' + filter + '/Length ' + pal.length.to_s + '>>');
2645
				putstream(pal);
2646
				out('endobj');
2647
			end
2648
		end
2649
	end
2650

    
2651
	#
2652
	# putxobjectdict
2653
	# @access protected
2654
	#
2655
	def putxobjectdict()
2656
		@images.each_value do |image|
2657
			out('/I' + image['i'].to_s + ' ' + image['n'].to_s + ' 0 R');
2658
		end
2659
	end
2660

    
2661
	#
2662
	# putresourcedict
2663
	# @access protected
2664
	#
2665
	def putresourcedict()
2666
		out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
2667
		out('/Font <<');
2668
		@fonts.each_value do |font|
2669
			out('/F' + font['i'].to_s + ' ' + font['n'].to_s + ' 0 R');
2670
		end
2671
		out('>>');
2672
		out('/XObject <<');
2673
		putxobjectdict();
2674
		out('>>');
2675
	end
2676

    
2677
	#
2678
	# putresources
2679
	# @access protected
2680
	#
2681
	def putresources()
2682
		putfonts();
2683
		putimages();
2684
		#Resource dictionary
2685
		@offsets[2]=@buffer.length;
2686
		out('2 0 obj');
2687
		out('<<');
2688
		putresourcedict();
2689
		out('>>');
2690
		out('endobj');
2691
	end
2692
	
2693
	#
2694
	# putinfo
2695
	# @access protected
2696
	#
2697
	def putinfo()
2698
		out('/Producer ' + textstring(PDF_PRODUCER));
2699
		if (!@title.nil?)
2700
			out('/Title ' + textstring(@title));
2701
		end
2702
		if (!@subject.nil?)
2703
			out('/Subject ' + textstring(@subject));
2704
		end
2705
		if (!@author.nil?)
2706
			out('/Author ' + textstring(@author));
2707
		end
2708
		if (!@keywords.nil?)
2709
			out('/Keywords ' + textstring(@keywords));
2710
		end
2711
		if (!@creator.nil?)
2712
			out('/Creator ' + textstring(@creator));
2713
		end
2714
		out('/CreationDate ' + textstring('D:' + Time.now.strftime('%Y%m%d%H%M%S')));
2715
	end
2716

    
2717
	#
2718
	# putcatalog
2719
	# @access protected
2720
	#
2721
	def putcatalog()
2722
		out('/Type /Catalog');
2723
		out('/Pages 1 0 R');
2724
		if (@zoom_mode=='fullpage')
2725
			out('/OpenAction [3 0 R /Fit]');
2726
		elsif (@zoom_mode=='fullwidth')
2727
			out('/OpenAction [3 0 R /FitH null]');
2728
		elsif (@zoom_mode=='real')
2729
			out('/OpenAction [3 0 R /XYZ null null 1]');
2730
		elsif (!@zoom_mode.is_a?(String))
2731
			out('/OpenAction [3 0 R /XYZ null null ' + (@zoom_mode/100) + ']');
2732
		end
2733
		if (@layout_mode=='single')
2734
			out('/PageLayout /SinglePage');
2735
		elsif (@layout_mode=='continuous')
2736
			out('/PageLayout /OneColumn');
2737
		elsif (@layout_mode=='two')
2738
			out('/PageLayout /TwoColumnLeft');
2739
		end
2740
	end
2741

    
2742
	#
2743
	# puttrailer
2744
	# @access protected
2745
	#
2746
	def puttrailer()
2747
		out('/Size ' + (@n+1).to_s);
2748
		out('/Root ' + @n.to_s + ' 0 R');
2749
		out('/Info ' + (@n-1).to_s + ' 0 R');
2750
	end
2751

    
2752
	#
2753
	# putheader
2754
	# @access protected
2755
	#
2756
	def putheader()
2757
		out('%PDF-' + @pdf_version);
2758
	end
2759

    
2760
	#
2761
	# enddoc
2762
	# @access protected
2763
	#
2764
	def enddoc()
2765
		putheader();
2766
		putpages();
2767
		putresources();
2768
		#Info
2769
		newobj();
2770
		out('<<');
2771
		putinfo();
2772
		out('>>');
2773
		out('endobj');
2774
		#Catalog
2775
		newobj();
2776
		out('<<');
2777
		putcatalog();
2778
		out('>>');
2779
		out('endobj');
2780
		#Cross-ref
2781
		o=@buffer.length;
2782
		out('xref');
2783
		out('0 ' + (@n+1).to_s);
2784
		out('0000000000 65535 f ');
2785
		1.upto(@n) do |i|
2786
			out(sprintf('%010d 00000 n ',@offsets[i]));
2787
		end
2788
		#Trailer
2789
		out('trailer');
2790
		out('<<');
2791
		puttrailer();
2792
		out('>>');
2793
		out('startxref');
2794
		out(o);
2795
		out('%%EOF');
2796
		@state=3;
2797
	end
2798

    
2799
	#
2800
	# beginpage
2801
	# @access protected
2802
	#
2803
	def beginpage(orientation)
2804
		@page += 1;
2805
		@pages[@page]='';
2806
		@state=2;
2807
		@x=@l_margin;
2808
		@y=@t_margin;
2809
		@font_family='';
2810
		#Page orientation
2811
		if (orientation.empty?)
2812
			orientation=@def_orientation;
2813
		else
2814
			orientation.upcase!
2815
			if (orientation!=@def_orientation)
2816
				@orientation_changes[@page]=true;
2817
			end
2818
		end
2819
		if (orientation!=@cur_orientation)
2820
			#Change orientation
2821
			if (orientation=='P')
2822
				@w_pt=@fw_pt;
2823
				@h_pt=@fh_pt;
2824
				@w=@fw;
2825
				@h=@fh;
2826
			else
2827
				@w_pt=@fh_pt;
2828
				@h_pt=@fw_pt;
2829
				@w=@fh;
2830
				@h=@fw;
2831
			end
2832
			@page_break_trigger=@h-@b_margin;
2833
			@cur_orientation = orientation;
2834
		end
2835
	end
2836

    
2837
	#
2838
	# End of page contents
2839
	# @access protected
2840
	#
2841
	def endpage()
2842
		@state=1;
2843
	end
2844

    
2845
	#
2846
	# Begin a new object
2847
	# @access protected
2848
	#
2849
	def newobj()
2850
		@n += 1;
2851
		@offsets[@n]=@buffer.length;
2852
		out(@n.to_s + ' 0 obj');
2853
	end
2854

    
2855
	#
2856
	# Underline and Deleted text
2857
	# @access protected
2858
	#
2859
	def dolinetxt(x, y, txt)
2860
		up = @current_font['up'];
2861
		ut = @current_font['ut'];
2862
		w = GetStringWidth(txt) + @ws * txt.count(' ');
2863
		sprintf('%.2f %.2f %.2f %.2f re f', x * @k, (@h - (y - up / 1000.0 * @font_size)) * @k, w * @k, -ut / 1000.0 * @font_size_pt);
2864
	end
2865

    
2866
	#
2867
	# Extract info from a JPEG file
2868
	# @access protected
2869
	#
2870
	def parsejpg(file)
2871
		a=getimagesize(file);
2872
		if (a.empty?)
2873
			Error('Missing or incorrect image file: ' + file);
2874
		end
2875
		if (!a[2].nil? and a[2]!='JPEG')
2876
			Error('Not a JPEG file: ' + file);
2877
		end
2878
		if (a['channels'].nil? or a['channels']==3)
2879
			colspace='DeviceRGB';
2880
		elsif (a['channels']==4)
2881
			colspace='DeviceCMYK';
2882
		else
2883
			colspace='DeviceGray';
2884
		end
2885
		bpc=!a['bits'].nil? ? a['bits'] : 8;
2886
		#Read whole file
2887
		data='';
2888

    
2889
		open(file,'rb') do |f|
2890
			data<<f.read();
2891
		end
2892

    
2893
		return {'w' => a[0],'h' => a[1],'cs' => colspace,'bpc' => bpc,'f'=>'DCTDecode','data' => data}
2894
	end
2895

    
2896
	def imageToPNG(file)
2897
		return unless Object.const_defined?(:Magick)
2898

    
2899
		img = Magick::ImageList.new(file)
2900
		img.format = 'PNG'	 # convert to PNG from gif 
2901
		img.opacity = 0			 # PNG alpha channel delete
2902

    
2903
		#use a temporary file....
2904
		tmpFile = Tempfile.new(['', '_' + File::basename(file) + '.png'], @@k_path_cache);
2905
		tmpFile.binmode
2906
		tmpFile.print img.to_blob
2907
		tmpFile
2908
	ensure
2909
		tmpFile.close
2910
	end
2911

    
2912
	#
2913
	# Extract info from a PNG file
2914
	# @access protected
2915
	#
2916
	def parsepng(file)
2917
		f=open(file,'rb');
2918
		#Check signature
2919
		if (f.read(8)!=137.chr + 'PNG' + 13.chr + 10.chr + 26.chr + 10.chr)
2920
			Error('Not a PNG file: ' + file);
2921
		end
2922
		#Read header chunk
2923
		f.read(4);
2924
		if (f.read(4)!='IHDR')
2925
			Error('Incorrect PNG file: ' + file);
2926
		end
2927
		w=freadint(f);
2928
		h=freadint(f);
2929
		bpc=f.read(1).unpack('C')[0];
2930
		if (bpc>8)
2931
			Error('16-bit depth not supported: ' + file);
2932
		end
2933
		ct=f.read(1).unpack('C')[0];
2934
		if (ct==0)
2935
			colspace='DeviceGray';
2936
		elsif (ct==2)
2937
			colspace='DeviceRGB';
2938
		elsif (ct==3)
2939
			colspace='Indexed';
2940
		else
2941
			Error('Alpha channel not supported: ' + file);
2942
		end
2943
		if (f.read(1).unpack('C')[0] != 0)
2944
			Error('Unknown compression method: ' + file);
2945
		end
2946
		if (f.read(1).unpack('C')[0] != 0)
2947
			Error('Unknown filter method: ' + file);
2948
		end
2949
		if (f.read(1).unpack('C')[0] != 0)
2950
			Error('Interlacing not supported: ' + file);
2951
		end
2952
		f.read(4);
2953
		parms='/DecodeParms <</Predictor 15 /Colors ' + (ct==2 ? 3 : 1).to_s + ' /BitsPerComponent ' + bpc.to_s + ' /Columns ' + w.to_s + '>>';
2954
		#Scan chunks looking for palette, transparency and image data
2955
		pal='';
2956
		trns='';
2957
		data='';
2958
		begin
2959
			n=freadint(f);
2960
			type=f.read(4);
2961
			if (type=='PLTE')
2962
				#Read palette
2963
				pal=f.read( n);
2964
				f.read(4);
2965
			elsif (type=='tRNS')
2966
				#Read transparency info
2967
				t=f.read( n);
2968
				if (ct==0)
2969
					trns = t[1].unpack('C')[0]
2970
				elsif (ct==2)
2971
					trns = t[[1].unpack('C')[0], t[3].unpack('C')[0], t[5].unpack('C')[0]]
2972
				else
2973
					pos=t.index(0.chr);
2974
					unless (pos.nil?)
2975
						trns = [pos]
2976
					end
2977
				end
2978
				f.read(4);
2979
			elsif (type=='IDAT')
2980
				#Read image data block
2981
				data<<f.read( n);
2982
				f.read(4);
2983
			elsif (type=='IEND')
2984
				break;
2985
			else
2986
				f.read( n+4);
2987
			end
2988
		end while(n)
2989
		if (colspace=='Indexed' and pal.empty?)
2990
			Error('Missing palette in ' + file);
2991
		end
2992
		return {'w' => w, 'h' => h, 'cs' => colspace, 'bpc' => bpc, 'f'=>'FlateDecode', 'parms' => parms, 'pal' => pal, 'trns' => trns, 'data' => data}
2993
	ensure
2994
		f.close
2995
	end
2996

    
2997
	#
2998
	# Read a 4-byte integer from file
2999
	# @access protected
3000
	#
3001
	def freadint(f)
3002
    # Read a 4-byte integer from file
3003
    a = f.read(4).unpack('N')
3004
    return a[0]
3005
	end
3006

    
3007
	#
3008
	# Format a text string
3009
	# @access protected
3010
	#
3011
	def textstring(s)
3012
		if (@is_unicode)
3013
			#Convert string to UTF-16BE
3014
			s = UTF8ToUTF16BE(s, true);
3015
		end
3016
		return '(' +  escape(s) + ')';
3017
	end
3018

    
3019
	#
3020
	# Format a text string
3021
	# @access protected
3022
	#
3023
	def escapetext(s)
3024
		if (@is_unicode)
3025
			#Convert string to UTF-16BE
3026
			s = UTF8ToUTF16BE(s, false);
3027
		end
3028
		return escape(s);
3029
	end
3030

    
3031
	#
3032
	# Add \ before \, ( and )
3033
	# @access protected
3034
	#
3035
	def escape(s)
3036
    # Add \ before \, ( and )
3037
    s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)').gsub(13.chr, '\r')
3038
	end
3039

    
3040
	#
3041
	#
3042
	# @access protected
3043
	#
3044
	def putstream(s)
3045
		out('stream');
3046
		out(s);
3047
		out('endstream');
3048
	end
3049

    
3050
	#
3051
	# Add a line to the document
3052
	# @access protected
3053
	#
3054
	def out(s)
3055
		if (@state==2)
3056
			@pages[@page] << s.to_s + "\n";
3057
		else
3058
			@buffer << s.to_s + "\n";
3059
		end
3060
	end
3061

    
3062
	#
3063
	# Adds unicode fonts.<br>
3064
	# Based on PDF Reference 1.3 (section 5)
3065
	# @access protected
3066
	# @author Nicola Asuni
3067
	# @since 1.52.0.TC005 (2005-01-05)
3068
	#
3069
	def puttruetypeunicode(font)
3070
		# Type0 Font
3071
		# A composite font composed of other fonts, organized hierarchically
3072
		newobj();
3073
		out('<</Type /Font');
3074
		out('/Subtype /Type0');
3075
		out('/BaseFont /' + font['name'] + '');
3076
		out('/Encoding /Identity-H'); #The horizontal identity mapping for 2-byte CIDs; may be used with CIDFonts using any Registry, Ordering, and Supplement values.
3077
		out('/DescendantFonts [' + (@n + 1).to_s + ' 0 R]');
3078
		out('/ToUnicode ' + (@n + 2).to_s + ' 0 R');
3079
		out('>>');
3080
		out('endobj');
3081
		
3082
		# CIDFontType2
3083
		# A CIDFont whose glyph descriptions are based on TrueType font technology
3084
		newobj();
3085
		out('<</Type /Font');
3086
		out('/Subtype /CIDFontType2');
3087
		out('/BaseFont /' + font['name'] + '');
3088
		out('/CIDSystemInfo ' + (@n + 2).to_s + ' 0 R'); 
3089
		out('/FontDescriptor ' + (@n + 3).to_s + ' 0 R');
3090
		if (!font['desc']['MissingWidth'].nil?)
3091
			out('/DW ' + font['desc']['MissingWidth'].to_s + ''); # The default width for glyphs in the CIDFont MissingWidth
3092
		end
3093
		w = "";
3094
		font['cw'].each do |cid, width|
3095
			w << '' + cid.to_s + ' [' + width.to_s + '] '; # define a specific width for each individual CID
3096
		end
3097
		out('/W [' + w + ']'); # A description of the widths for the glyphs in the CIDFont
3098
		out('/CIDToGIDMap ' + (@n + 4).to_s + ' 0 R');
3099
		out('>>');
3100
		out('endobj');
3101
		
3102
		# ToUnicode
3103
		# is a stream object that contains the definition of the CMap
3104
		# (PDF Reference 1.3 chap. 5.9)
3105
		newobj();
3106
		out('<</Length 383>>');
3107
		out('stream');
3108
		out('/CIDInit /ProcSet findresource begin');
3109
		out('12 dict begin');
3110
		out('begincmap');
3111
		out('/CIDSystemInfo');
3112
		out('<</Registry (Adobe)');
3113
		out('/Ordering (UCS)');
3114
		out('/Supplement 0');
3115
		out('>> def');
3116
		out('/CMapName /Adobe-Identity-UCS def');
3117
		out('/CMapType 2 def');
3118
		out('1 begincodespacerange');
3119
		out('<0000> <FFFF>');
3120
		out('endcodespacerange');
3121
		out('1 beginbfrange');
3122
		out('<0000> <FFFF> <0000>');
3123
		out('endbfrange');
3124
		out('endcmap');
3125
		out('CMapName currentdict /CMap defineresource pop');
3126
		out('end');
3127
		out('end');
3128
		out('endstream');
3129
		out('endobj');
3130
		
3131
		# CIDSystemInfo dictionary
3132
		# A dictionary containing entries that define the character collection of the CIDFont.
3133
		newobj();
3134
		out('<</Registry (Adobe)'); # A string identifying an issuer of character collections
3135
		out('/Ordering (UCS)'); # A string that uniquely names a character collection issued by a specific registry
3136
		out('/Supplement 0'); # The supplement number of the character collection.
3137
		out('>>');
3138
		out('endobj');
3139
		
3140
		# Font descriptor
3141
		# A font descriptor describing the CIDFont default metrics other than its glyph widths
3142
		newobj();
3143
		out('<</Type /FontDescriptor');
3144
		out('/FontName /' + font['name']);
3145
		font['desc'].each do |key, value|
3146
			out('/' + key.to_s + ' ' + value.to_s);
3147
		end
3148
		if (font['file'])
3149
			# A stream containing a TrueType font program
3150
			out('/FontFile2 ' + @font_files[font['file']]['n'].to_s + ' 0 R');
3151
		end
3152
		out('>>');
3153
		out('endobj');
3154

    
3155
		# Embed CIDToGIDMap
3156
		# A specification of the mapping from CIDs to glyph indices
3157
		newobj();
3158
		ctgfile = getfontpath(font['ctg'])
3159
		if (!ctgfile)
3160
			Error('Font file not found: ' + ctgfile);
3161
		end
3162
		size = File.size(ctgfile);
3163
		out('<</Length ' + size.to_s + '');
3164
		if (ctgfile[-2,2] == '.z') # check file extension
3165
			# Decompresses data encoded using the public-domain 
3166
			# zlib/deflate compression method, reproducing the 
3167
			# original text or binary data#
3168
			out('/Filter /FlateDecode');
3169
		end
3170
		out('>>');
3171
    open(ctgfile, "rb") do |f|
3172
      putstream(f.read())
3173
    end
3174
		out('endobj');
3175
	end
3176

    
3177
	 #
3178
	# Converts UTF-8 strings to codepoints array.<br>
3179
	# Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
3180
	# Based on: http://www.faqs.org/rfcs/rfc3629.html
3181
	# <pre>
3182
	# 	  Char. number range  |        UTF-8 octet sequence
3183
	#       (hexadecimal)    |              (binary)
3184
	#    --------------------+-----------------------------------------------
3185
	#    0000 0000-0000 007F | 0xxxxxxx
3186
	#    0000 0080-0000 07FF | 110xxxxx 10xxxxxx
3187
	#    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
3188
	#    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
3189
	#    ---------------------------------------------------------------------
3190
	#
3191
	#   ABFN notation:
3192
	#   ---------------------------------------------------------------------
3193
	#   UTF8-octets =#( UTF8-char )
3194
	#   UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
3195
	#   UTF8-1      = %x00-7F
3196
	#   UTF8-2      = %xC2-DF UTF8-tail
3197
	#
3198
	#   UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
3199
	#                 %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
3200
	#   UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
3201
	#                 %xF4 %x80-8F 2( UTF8-tail )
3202
	#   UTF8-tail   = %x80-BF
3203
	#   ---------------------------------------------------------------------
3204
	# </pre>
3205
	# @param string :str string to process.
3206
	# @return array containing codepoints (UTF-8 characters values)
3207
	# @access protected
3208
	# @author Nicola Asuni
3209
	# @since 1.53.0.TC005 (2005-01-05)
3210
	#
3211
	def UTF8StringToArray(str)
3212
		if (!@is_unicode)
3213
			return str; # string is not in unicode
3214
		end
3215

    
3216
		unicode = [] # array containing unicode values
3217
		bytes  = [] # array containing single character byte sequences
3218
		numbytes  = 1; # number of octetc needed to represent the UTF-8 character
3219

    
3220
		str = str.to_s; # force :str to be a string
3221
		
3222
		str.each_byte do |char|
3223
			if (bytes.length == 0) # get starting octect
3224
				if (char <= 0x7F)
3225
					unicode << char # use the character "as is" because is ASCII
3226
					numbytes = 1
3227
				elsif ((char >> 0x05) == 0x06) # 2 bytes character (0x06 = 110 BIN)
3228
					bytes << ((char - 0xC0) << 0x06) 
3229
					numbytes = 2
3230
				elsif ((char >> 0x04) == 0x0E) # 3 bytes character (0x0E = 1110 BIN)
3231
					bytes << ((char - 0xE0) << 0x0C) 
3232
					numbytes = 3
3233
				elsif ((char >> 0x03) == 0x1E) # 4 bytes character (0x1E = 11110 BIN)
3234
					bytes << ((char - 0xF0) << 0x12)
3235
					numbytes = 4
3236
				else
3237
					# use replacement character for other invalid sequences
3238
					unicode << 0xFFFD
3239
					bytes = []
3240
					numbytes = 1
3241
				end
3242
			elsif ((char >> 0x06) == 0x02) # bytes 2, 3 and 4 must start with 0x02 = 10 BIN
3243
				bytes << (char - 0x80)
3244
				if (bytes.length == numbytes)
3245
					# compose UTF-8 bytes to a single unicode value
3246
					char = bytes[0]
3247
					1.upto(numbytes-1) do |j|
3248
						char += (bytes[j] << ((numbytes - j - 1) * 0x06))
3249
					end
3250
					if (((char >= 0xD800) and (char <= 0xDFFF)) or (char >= 0x10FFFF))
3251
						# The definition of UTF-8 prohibits encoding character numbers between
3252
						# U+D800 and U+DFFF, which are reserved for use with the UTF-16
3253
						# encoding form (as surrogate pairs) and do not directly represent
3254
						# characters
3255
						unicode << 0xFFFD; # use replacement character
3256
  				else
3257
  					unicode << char; # add char to array
3258
					end
3259
  				# reset data for next char
3260
  				bytes = []
3261
  				numbytes = 1;
3262
				end
3263
			else
3264
				# use replacement character for other invalid sequences
3265
				unicode << 0xFFFD;
3266
				bytes = []
3267
				numbytes = 1;
3268
			end
3269
		end
3270
		return unicode;
3271
	end
3272
	
3273
	#
3274
	# Converts UTF-8 strings to UTF16-BE.<br>
3275
	# Based on: http://www.faqs.org/rfcs/rfc2781.html
3276
 	# <pre>
3277
	#   Encoding UTF-16:
3278
	# 
3279
		#   Encoding of a single character from an ISO 10646 character value to
3280
	#    UTF-16 proceeds as follows. Let U be the character number, no greater
3281
	#    than 0x10FFFF.
3282
	# 
3283
	#    1) If U < 0x10000, encode U as a 16-bit unsigned integer and
3284
	#       terminate.
3285
	# 
3286
	#    2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
3287
	#       U' must be less than or equal to 0xFFFFF. That is, U' can be
3288
	#       represented in 20 bits.
3289
	# 
3290
	#    3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
3291
	#       0xDC00, respectively. These integers each have 10 bits free to
3292
	#       encode the character value, for a total of 20 bits.
3293
	# 
3294
	#    4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
3295
	#       bits of W1 and the 10 low-order bits of U' to the 10 low-order
3296
	#       bits of W2. Terminate.
3297
	# 
3298
	#    Graphically, steps 2 through 4 look like:
3299
	#    U' = yyyyyyyyyyxxxxxxxxxx
3300
	#    W1 = 110110yyyyyyyyyy
3301
	#    W2 = 110111xxxxxxxxxx
3302
	# </pre>
3303
	# @param string :str string to process.
3304
	# @param boolean :setbom if true set the Byte Order Mark (BOM = 0xFEFF)
3305
	# @return string
3306
	# @access protected
3307
	# @author Nicola Asuni
3308
	# @since 1.53.0.TC005 (2005-01-05)
3309
	# @uses UTF8StringToArray
3310
	#
3311
	def UTF8ToUTF16BE(str, setbom=true)
3312
		if (!@is_unicode)
3313
			return str; # string is not in unicode
3314
		end
3315
		outstr = ""; # string to be returned
3316
		unicode = UTF8StringToArray(str); # array containing UTF-8 unicode values
3317
		numitems = unicode.length;
3318
		
3319
		if (setbom)
3320
			outstr << "\xFE\xFF"; # Byte Order Mark (BOM)
3321
		end
3322
		unicode.each do |char|
3323
			if (char == 0xFFFD)
3324
				outstr << "\xFF\xFD"; # replacement character
3325
			elsif (char < 0x10000)
3326
				outstr << (char >> 0x08).chr;
3327
				outstr << (char & 0xFF).chr;
3328
			else
3329
				char -= 0x10000;
3330
				w1 = 0xD800 | (char >> 0x10);
3331
				w2 = 0xDC00 | (char & 0x3FF);	
3332
				outstr << (w1 >> 0x08).chr;
3333
				outstr << (w1 & 0xFF).chr;
3334
				outstr << (w2 >> 0x08).chr;
3335
				outstr << (w2 & 0xFF).chr;
3336
			end
3337
		end
3338
		return outstr;
3339
	end
3340
	
3341
	# ====================================================
3342
	
3343
	#
3344
 	# Set header font.
3345
	# @param array :font font
3346
	# @since 1.1
3347
	#
3348
	def SetHeaderFont(font)
3349
		@header_font = font;
3350
	end
3351
	alias_method :set_header_font, :SetHeaderFont
3352
	
3353
	#
3354
 	# Set footer font.
3355
	# @param array :font font
3356
	# @since 1.1
3357
	#
3358
	def SetFooterFont(font)
3359
		@footer_font = font;
3360
	end
3361
	alias_method :set_footer_font, :SetFooterFont
3362
	
3363
	#
3364
 	# Set language array.
3365
	# @param array :language
3366
	# @since 1.1
3367
	#
3368
	def SetLanguageArray(language)
3369
		@l = language;
3370
	end
3371
	alias_method :set_language_array, :SetLanguageArray
3372
	#
3373
 	# Set document barcode.
3374
	# @param string :bc barcode
3375
	#
3376
	def SetBarcode(bc="")
3377
		@barcode = bc;
3378
	end
3379
	
3380
	#
3381
 	# Print Barcode.
3382
	# @param int :x x position in user units
3383
	# @param int :y y position in user units
3384
	# @param int :w width in user units
3385
	# @param int :h height position in user units
3386
	# @param string :type type of barcode (I25, C128A, C128B, C128C, C39)
3387
	# @param string :style barcode style
3388
	# @param string :font font for text
3389
	# @param int :xres x resolution
3390
	# @param string :code code to print
3391
	#
3392
	def writeBarcode(x, y, w, h, type, style, font, xres, code)
3393
		require(File.dirname(__FILE__) + "/barcode/barcode.rb");
3394
		require(File.dirname(__FILE__) + "/barcode/i25object.rb");
3395
		require(File.dirname(__FILE__) + "/barcode/c39object.rb");
3396
		require(File.dirname(__FILE__) + "/barcode/c128aobject.rb");
3397
		require(File.dirname(__FILE__) + "/barcode/c128bobject.rb");
3398
		require(File.dirname(__FILE__) + "/barcode/c128cobject.rb");
3399
		
3400
		if (code.empty?)
3401
			return;
3402
		end
3403
		
3404
		if (style.empty?)
3405
			style  = BCS_ALIGN_LEFT;
3406
			style |= BCS_IMAGE_PNG;
3407
			style |= BCS_TRANSPARENT;
3408
			#:style |= BCS_BORDER;
3409
			#:style |= BCS_DRAW_TEXT;
3410
			#:style |= BCS_STRETCH_TEXT;
3411
			#:style |= BCS_REVERSE_COLOR;
3412
		end
3413
		if (font.empty?) then font = BCD_DEFAULT_FONT; end
3414
		if (xres.empty?) then xres = BCD_DEFAULT_XRES; end
3415
		
3416
		scale_factor = 1.5 * xres * @k;
3417
		bc_w = (w * scale_factor).round #width in points
3418
		bc_h = (h * scale_factor).round #height in points
3419
		
3420
		case (type.upcase)
3421
			when "I25"
3422
				obj = I25Object.new(bc_w, bc_h, style, code);
3423
			when "C128A"
3424
				obj = C128AObject.new(bc_w, bc_h, style, code);
3425
			when "C128B"
3426
				obj = C128BObject.new(bc_w, bc_h, style, code);
3427
			when "C128C"
3428
				obj = C128CObject.new(bc_w, bc_h, style, code);
3429
			when "C39"
3430
				obj = C39Object.new(bc_w, bc_h, style, code);
3431
		end
3432
		
3433
		obj.SetFont(font);   
3434
		obj.DrawObject(xres);
3435
		
3436
		#use a temporary file....
3437
		tmpName = tempnam(@@k_path_cache,'img');
3438
		imagepng(obj.getImage(), tmpName);
3439
		Image(tmpName, x, y, w, h, 'png');
3440
		obj.DestroyObject();
3441
		obj = nil
3442
		unlink(tmpName);
3443
	end
3444
	
3445
	#
3446
 	# Returns the PDF data.
3447
	#
3448
	def GetPDFData()
3449
		if (@state < 3)
3450
			Close();
3451
		end
3452
		return @buffer;
3453
	end
3454
	
3455
	# --- HTML PARSER FUNCTIONS ---
3456
	
3457
	#
3458
	# Allows to preserve some HTML formatting.<br />
3459
	# Supports: h1, h2, h3, h4, h5, h6, b, u, i, a, img, p, br, strong, em, ins, del, font, blockquote, li, ul, ol, hr, td, th, tr, table, sup, sub, small
3460
	# @param string :html text to display
3461
	# @param boolean :ln if true add a new line after text (default = true)
3462
	# @param int :fill Indicates if the background must be painted (1) or transparent (0). Default value: 0.
3463
	#
3464
	def writeHTML(html, ln=true, fill=0, h=0)
3465
    
3466
    @lasth = h if h > 0
3467
		if (@lasth == 0)
3468
			#set row height
3469
			@lasth = @font_size * @@k_cell_height_ratio; 
3470
		end
3471
		
3472
    @href = nil
3473
    @style = "";
3474
    @t_cells =  [[]];
3475
    @table_id = 0;
3476

    
3477
    # pre calculate
3478
    html.split(/(<[^>]+>)/).each do |element|
3479
      if "<" == element[0,1]
3480
        #Tag
3481
        if (element[1, 1] == '/')
3482
					closedHTMLTagCalc(element[2..-2].downcase);
3483
        else
3484
					#Extract attributes
3485
					# get tag name
3486
					tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
3487
					tag = tag[0].to_s.downcase;
3488
					
3489
					# get attributes
3490
					attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
3491
          attrs = {}
3492
          attr_array.each do |name, value|
3493
    			  attrs[name.downcase] = value;
3494
    		  end
3495
					openHTMLTagCalc(tag, attrs);
3496
				end
3497
			end
3498
		end
3499
		@table_id = 0;
3500
				
3501
    html.split(/(<[A-Za-z!?\/][^>]*?>)/).each do |element|
3502
      if "<" == element[0,1]
3503
        #Tag
3504
        if (element[1, 1] == '/')
3505
					closedHTMLTagHandler(element[2..-2].downcase);
3506
        else
3507
					#Extract attributes
3508
					# get tag name
3509
					tag = element.scan(/([a-zA-Z0-9]*)/).flatten.delete_if {|x| x.length == 0}
3510
					tag = tag[0].to_s.downcase;
3511
					
3512
					# get attributes
3513
					attr_array = element.scan(/([^=\s]*)=["\']?([^"\']*)["\']?/)
3514
          attrs = {}
3515
          attr_array.each do |name, value|
3516
    			  attrs[name.downcase] = value;
3517
    		  end
3518
					openHTMLTagHandler(tag, attrs, fill);
3519
				end
3520
				
3521
      else
3522
        #Text
3523
				if (@tdbegin)
3524
					element.gsub!(/[\t\r\n\f]/, "");
3525
					@tdtext << element.gsub(/&nbsp;/, " ");
3526
				elsif (@href)
3527
					element.gsub!(/[\t\r\n\f]/, "");
3528
					addHtmlLink(@href, element, fill);
3529
				elsif (@pre_state == true and element.length > 0)
3530
					Write(@lasth, unhtmlentities(element), '', fill);
3531
				elsif (element.strip.length > 0)
3532
					element.gsub!(/[\t\r\n\f]/, "");
3533
					element.gsub!(/&nbsp;/, " ");
3534
					Write(@lasth, unhtmlentities(element), '', fill);
3535
				end
3536
      end
3537
    end
3538
    
3539
		if (ln)
3540
			Ln(@lasth);
3541
		end
3542
	end
3543
  alias_method :write_html, :writeHTML
3544

    
3545
	#
3546
	# Prints a cell (rectangular area) with optional borders, background color and html text string. The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
3547
	# If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
3548
	# @param float :w Cell width. If 0, the cell extends up to the right margin.
3549
	# @param float :h Cell minimum height. The cell extends automatically if needed.
3550
	# @param float :x upper-left corner X coordinate
3551
	# @param float :y upper-left corner Y coordinate
3552
	# @param string :html html text to print. Default value: empty string.
3553
	# @param mixed :border Indicates if borders must be drawn around the cell. The value can be either a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
3554
	# @param int :ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
3555
# Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
3556
	# @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3557
	# @see Cell()
3558
	#
3559
	def writeHTMLCell(w, h, x, y, html='', border=0, ln=1, fill=0)
3560
		
3561
		if (@lasth == 0)
3562
			#set row height
3563
			@lasth = @font_size * @@k_cell_height_ratio; 
3564
		end
3565
		
3566
		if (x == 0)
3567
			x = GetX();
3568
		end
3569
		if (y == 0)
3570
			y = GetY();
3571
		end
3572
		
3573
		# get current page number
3574
		pagenum = @page;
3575
		
3576
		SetX(x);
3577
		SetY(y);
3578
				
3579
		if (w == 0)
3580
			w = @fw - x - @r_margin;
3581
		end
3582
		
3583
		b=0;
3584
		if (border)
3585
			if (border==1)
3586
				border='LTRB';
3587
				b='LRT';
3588
				b2='LR';
3589
			elsif border.is_a?(String)
3590
				b2='';
3591
				if (border.include?('L'))
3592
					b2<<'L';
3593
				end
3594
				if (border.include?('R'))
3595
					b2<<'R';
3596
				end
3597
				b=(border.include?('T')) ? b2 + 'T' : b2;
3598
			end
3599
		end
3600
		
3601
		# store original margin values
3602
		l_margin = @l_margin;
3603
		r_margin = @r_margin;
3604
		
3605
		# set new margin values
3606
		SetLeftMargin(x);
3607
		SetRightMargin(@fw - x - w);
3608
				
3609
		# calculate remaining vertical space on page
3610
		restspace = GetPageHeight() - GetY() - GetBreakMargin();
3611
		
3612
		writeHTML(html, true, fill); # write html text
3613
    SetX(x)
3614
		
3615
		currentY =  GetY();
3616
		@auto_page_break = false;
3617
		# check if a new page has been created
3618
		if (@page > pagenum)
3619
			# design a cell around the text on first page
3620
			currentpage = @page;
3621
			@page = pagenum;
3622
			SetY(GetPageHeight() - restspace - GetBreakMargin());
3623
      SetX(x)
3624
			Cell(w, restspace - 1, "", b, 0, 'L', 0);
3625
			b = b2;
3626
			@page += 1;
3627
			while @page < currentpage
3628
				SetY(@t_margin); # put cursor at the beginning of text
3629
        SetX(x)
3630
				Cell(w, @page_break_trigger - @t_margin, "", b, 0, 'L', 0);
3631
				@page += 1;
3632
			end
3633
			if (border.is_a?(String) and border.include?('B'))
3634
				b<<'B';
3635
			end
3636
			# design a cell around the text on last page
3637
			SetY(@t_margin); # put cursor at the beginning of text
3638
      SetX(x)
3639
			Cell(w, currentY - @t_margin, "", b, 0, 'L', 0);
3640
		else
3641
			SetY(y); # put cursor at the beginning of text
3642
			# design a cell around the text
3643
      SetX(x)
3644
			Cell(w, [h, (currentY - y)].max, "", border, 0, 'L', 0);
3645
		end
3646
		@auto_page_break = true;
3647
		
3648
		# restore original margin values
3649
		SetLeftMargin(l_margin);
3650
		SetRightMargin(r_margin);
3651
		
3652
		@lasth = h
3653

    
3654
		# move cursor to specified position
3655
		if (ln == 0)
3656
			# go to the top-right of the cell
3657
			@x = x + w;
3658
			@y = y;
3659
		elsif (ln == 1)
3660
			# go to the beginning of the next line
3661
			@x = @l_margin;
3662
			@y = currentY;
3663
		elsif (ln == 2)
3664
			# go to the bottom-left of the cell (below)
3665
			@x = x;
3666
			@y = currentY;
3667
		end
3668
	end
3669
  alias_method :write_html_cell, :writeHTMLCell
3670

    
3671
	#
3672
	# Check html table tag position.
3673
	#
3674
	# @param array :table potision array
3675
	# @param int :current tr tag id number
3676
	# @param int :current td tag id number
3677
	# @access private
3678
	# @return int : next td_id position.
3679
	#               value 0 mean that can use position.
3680
	#
3681
	def checkTableBlockingCellPosition(table, tr_id, td_id )
3682
		0.upto(tr_id) do |j|
3683
			0.upto(@t_cells[table][j].size - 1) do |i|
3684
				if @t_cells[table][j][i]['i0'] <= td_id and td_id <= @t_cells[table][j][i]['i1']
3685
					if @t_cells[table][j][i]['j0'] <= tr_id and tr_id <= @t_cells[table][j][i]['j1']
3686
						return @t_cells[table][j][i]['i1'] - td_id + 1;
3687
					end
3688
				end
3689
			end
3690
		end
3691
		return 0;
3692
	end
3693

    
3694
	#
3695
	# Calculate opening tags.
3696
	#
3697
	# html table cell array : @t_cells
3698
	#
3699
	#  i0: table cell start position
3700
	#  i1: table cell end position
3701
	#  j0: table row start position
3702
	#  j1: table row end position
3703
	#
3704
	#  +------+
3705
	#  |i0,j0 |
3706
	#  | i1,j1|
3707
	#  +------+
3708
	#
3709
	#  example html:
3710
	#  <table>
3711
	#    <tr><td></td><td></td><td></td></tr>
3712
	#    <tr><td colspan=2></td><td></td></tr>
3713
	#    <tr><td rowspan=2></td><td></td><td></td></tr>
3714
	#    <tr><td></td><td></td></tr>
3715
	#  </table>
3716
	#
3717
	#   i: 0    1    2
3718
	#  j+----+----+----+
3719
	#  :|0,0 |1,0 |2,0 |
3720
	#  0| 0,0| 1,0| 2,0|
3721
	#   +----+----+----+
3722
	#   |0,1      |2,1 |
3723
	#  1|      1,1| 2,1|
3724
	#   +----+----+----+
3725
	#   |0,2 |1,2 |2,2 |
3726
	#  2|    | 1,2| 2,2|
3727
	#   +    +----+----+
3728
	#   |    |1,3 |2,3 |
3729
	#  3| 0,3| 1,3| 2,3|
3730
	#   +----+----+----+
3731
	#
3732
	#  html table cell array :
3733
	#  [[[i0=>0,j0=>0,i1=>0,j1=>0],[i0=>1,j0=>0,i1=>1,j1=>0],[i0=>2,j0=>0,i1=>2,j1=>0]],
3734
	#   [[i0=>0,j0=>1,i1=>1,j1=>1],[i0=>2,j0=>1,i1=>2,j1=>1]],
3735
	#   [[i0=>0,j0=>2,i1=>0,j1=>3],[i0=>1,j0=>2,i1=>1,j1=>2],[i0=>2,j0=>2,i1=>2,j1=>2]]
3736
	#   [[i0=>1,j0=>3,i1=>1,j1=>3],[i0=>2,j0=>3,i1=>2,j1=>3]]]
3737
	#
3738
	# @param string :tag tag name (in upcase)
3739
	# @param string :attr tag attribute (in upcase)
3740
	# @access private
3741
	#
3742
	def openHTMLTagCalc(tag, attrs)
3743
		#Opening tag
3744
		case (tag)
3745
			when 'table'
3746
				@max_table_columns[@table_id] = 0;
3747
				@t_columns = 0;
3748
				@tr_id = -1;
3749
			when 'tr'
3750
				if @max_table_columns[@table_id] < @t_columns
3751
					@max_table_columns[@table_id] = @t_columns;
3752
				end
3753
				@t_columns = 0;
3754
				@tr_id +=  1;
3755
				@td_id =  -1;
3756
				@t_cells[@table_id].push []
3757
			when 'td', 'th'
3758
				@td_id +=  1;
3759
				if attrs['colspan'].nil? or attrs['colspan'] == ''
3760
					colspan = 1;
3761
				else
3762
					colspan = attrs['colspan'].to_i;
3763
				end
3764
				if attrs['rowspan'].nil? or attrs['rowspan'] == ''
3765
					rowspan = 1;
3766
				else
3767
					rowspan = attrs['rowspan'].to_i;
3768
				end
3769

    
3770
				i = 0;
3771
				while true
3772
					next_i_distance = checkTableBlockingCellPosition(@table_id, @tr_id, @td_id + i);
3773
					if next_i_distance == 0
3774
						@t_cells[@table_id][@tr_id].push "i0"=>@td_id + i, "j0"=>@tr_id, "i1"=>(@td_id + i + colspan - 1), "j1"=>@tr_id + rowspan - 1
3775
						break;
3776
					end
3777
					i += next_i_distance;
3778
				end
3779

    
3780
				@t_columns += colspan;
3781
		end
3782
	end
3783

    
3784
	#
3785
	# Calculate closing tags.
3786
	# @param string :tag tag name (in upcase)
3787
	# @access private
3788
	#
3789
	def closedHTMLTagCalc(tag)
3790
		#Closing tag
3791
		case (tag)
3792
			when 'table'
3793
				if @max_table_columns[@table_id] < @t_columns
3794
					@max_table_columns[@table_id] = @t_columns;
3795
				end
3796
				@table_id += 1;
3797
				@t_cells.push []
3798
		end
3799
	end
3800

    
3801
	#
3802
	# Convert to accessible file path
3803
	# @param string :attrname image file name
3804
	#
3805
	def getImageFilename( attrname )
3806
		nil
3807
	end
3808

    
3809
	#
3810
	# Process opening tags.
3811
	# @param string :tag tag name (in upcase)
3812
	# @param string :attr tag attribute (in upcase)
3813
	# @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
3814
	# @access private
3815
	#
3816
	def openHTMLTagHandler(tag, attrs, fill=0)
3817
		#Opening tag
3818
		case (tag)
3819
			when 'pre'
3820
				@pre_state = true;
3821
				@l_margin += 5;	
3822
				@r_margin += 5;	
3823
				@x += 5;	
3824

    
3825
			when 'table'
3826
				Ln();
3827
				if @default_table_columns < @max_table_columns[@table_id]
3828
					@table_columns = @max_table_columns[@table_id];
3829
				else
3830
					@table_columns = @default_table_columns;
3831
				end
3832
				@l_margin += 5;	
3833
				@r_margin += 5;	
3834
				@x += 5;	
3835

    
3836
				if attrs['border'].nil? or attrs['border'] == ''
3837
					@tableborder = 0;
3838
				else
3839
					@tableborder = attrs['border'];
3840
				end
3841
				@tr_id        = -1;
3842
				@max_td_page[0] = @page;
3843
				@max_td_y[0]    = @y;
3844

    
3845
			when 'tr', 'td', 'th'
3846
				if tag == 'th'
3847
					SetStyle('b', true);
3848
					@tdalign = "C";
3849
				end
3850
				if ((!attrs['width'].nil?) and (attrs['width'] != ''))
3851
					@tdwidth = (attrs['width'].to_i/4);
3852
				else
3853
					@tdwidth = ((@w - @l_margin - @r_margin) / @table_columns);
3854
				end
3855

    
3856
				if tag == 'tr'
3857
					@tr_id +=  1;
3858
					@td_id  = -1;
3859
				else
3860
					@td_id +=  1;
3861
					@x =  @l_margin + @tdwidth * @t_cells[@table_id][@tr_id][@td_id]['i0'];
3862
				end
3863

    
3864
				if attrs['colspan'].nil? or attrs['border'] == ''
3865
					@colspan = 1;
3866
				else
3867
					@colspan = attrs['colspan'].to_i;
3868
				end
3869
				@tdwidth *= @colspan;
3870
				if ((!attrs['height'].nil?) and (attrs['height'] != ''))
3871
					@tdheight=(attrs['height'].to_i / @k);
3872
				else
3873
					@tdheight = @lasth;
3874
				end
3875
				if ((!attrs['align'].nil?) and (attrs['align'] != ''))
3876
					case (attrs['align'])
3877
						when 'center'
3878
							@tdalign = "C";
3879
						when 'right'
3880
							@tdalign = "R";
3881
						when 'left'
3882
							@tdalign = "L";
3883
					end
3884
				end
3885
				if ((!attrs['bgcolor'].nil?) and (attrs['bgcolor'] != ''))
3886
					coul = convertColorHexToDec(attrs['bgcolor']);
3887
					SetFillColor(coul['R'], coul['G'], coul['B']);
3888
					@tdfill=1;
3889
				end
3890
				@tdbegin=true;
3891
				
3892
			when 'hr'
3893
				margin = 1;
3894
				if ((!attrs['width'].nil?) and (attrs['width'] != ''))
3895
					hrWidth = attrs['width'];
3896
				else
3897
					hrWidth = @w - @l_margin - @r_margin - margin;
3898
				end
3899
				SetLineWidth(0.2);
3900
				Line(@x + margin, @y, @x + hrWidth, @y);
3901
				Ln();
3902
				
3903
			when 'strong'
3904
				SetStyle('b', true);
3905
				
3906
			when 'em'
3907
				SetStyle('i', true);
3908
				
3909
			when 'ins'
3910
				SetStyle('u', true);
3911
				
3912
			when 'del'
3913
				SetStyle('d', true);
3914
				
3915
			when 'b', 'i', 'u'
3916
				SetStyle(tag, true);
3917
				
3918
			when 'a'
3919
				@href = attrs['href'];
3920
				
3921
			when 'img'
3922
				if (!attrs['src'].nil?)
3923
					# Don't generates image inside table tag
3924
					if (@tdbegin)
3925
						@tdtext << attrs['src'];
3926
						return
3927
					end
3928
					# Only generates image include a pdf if RMagick is avalaible
3929
					unless Object.const_defined?(:Magick)
3930
						Write(@lasth, attrs['src'], '', fill);
3931
						return
3932
					end
3933
					file = getImageFilename(attrs['src'])
3934
					if (file.nil?)
3935
						Write(@lasth, attrs['src'], '', fill);
3936
						return
3937
					end
3938

    
3939
					if (attrs['width'].nil?)
3940
						attrs['width'] = 0;
3941
					end
3942
					if (attrs['height'].nil?)
3943
						attrs['height'] = 0;
3944
					end
3945
					
3946
					begin
3947
						Image(file, GetX(),GetY(), pixelsToMillimeters(attrs['width']), pixelsToMillimeters(attrs['height']));
3948
						#SetX(@img_rb_x);
3949
						SetY(@img_rb_y);
3950
					rescue => err
3951
						logger.error "pdf: Image: error: #{err.message}"
3952
						Write(@lasth, attrs['src'], '', fill);
3953
					end
3954
				end
3955
				
3956
			when 'ul', 'ol'
3957
				if @li_count == 0
3958
					Ln() if @prevquote_count == @quote_count; # insert Ln for keeping quote lines
3959
					@prevquote_count = @quote_count;
3960
				end
3961
				if @li_state == true
3962
					Ln();
3963
			  	@li_state = false;
3964
				end
3965
				if tag == 'ul'
3966
					@list_ordered[@li_count] = false;
3967
				else
3968
					@list_ordered[@li_count] = true;
3969
				end
3970
				@list_count[@li_count] = 0;
3971
				@li_count += 1
3972
				
3973
			when 'li'
3974
				Ln() if @li_state == true
3975
				if (@list_ordered[@li_count - 1])
3976
					@list_count[@li_count - 1] += 1;
3977
					@li_spacer = "    " * @li_count + (@list_count[@li_count - 1]).to_s + ". ";
3978
				else
3979
					#unordered list simbol
3980
					@li_spacer = "    " * @li_count + "-  ";
3981
				end
3982
				Write(@lasth, @spacer + @li_spacer, '', fill);
3983
				@li_state = true;
3984

    
3985
			when 'blockquote'
3986
				if (@quote_count == 0)
3987
					SetStyle('i', true);
3988
					@l_margin += 5;	
3989
				else
3990
					@l_margin += 5 / 2;	
3991
				end
3992
				@x = @l_margin;	
3993
				@quote_top[@quote_count]  = @y;
3994
				@quote_page[@quote_count] = @page;
3995
				@quote_count += 1
3996
			when 'br'
3997
				if @tdbegin
3998
					@tdtext << "\n"
3999
					return
4000
				end
4001
				Ln();
4002

    
4003
				if (@li_spacer.length > 0)
4004
					@x += GetStringWidth(@li_spacer);
4005
				end
4006
				
4007
			when 'p'
4008
				Ln();
4009
				0.upto(@quote_count - 1) do |i|
4010
			  	if @quote_page[i] == @page;
4011
			  		if @quote_top[i] == @y - @lasth; # fix start line
4012
			  			@quote_top[i] = @y;
4013
						end
4014
					else
4015
			  		if @quote_page[i] == @page - 1;
4016
			  			@quote_page[i] = @page; # fix start line
4017
			  			@quote_top[i] = @t_margin;
4018
						end
4019
					end
4020
				end
4021
				
4022
			when 'sup'
4023
				currentfont_size = @font_size;
4024
				@tempfontsize = @font_size_pt;
4025
				SetFontSize(@font_size_pt * @@k_small_ratio);
4026
				SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio)));
4027
				
4028
			when 'sub'
4029
				currentfont_size = @font_size;
4030
				@tempfontsize = @font_size_pt;
4031
				SetFontSize(@font_size_pt * @@k_small_ratio);
4032
				SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio)));
4033
				
4034
			when 'small'
4035
				currentfont_size = @font_size;
4036
				@tempfontsize = @font_size_pt;
4037
				SetFontSize(@font_size_pt * @@k_small_ratio);
4038
				SetXY(GetX(), GetY() + ((currentfont_size - @font_size)/3));
4039
				
4040
			when 'font'
4041
				if (!attrs['color'].nil? and attrs['color']!='')
4042
					coul = convertColorHexToDec(attrs['color']);
4043
					SetTextColor(coul['R'], coul['G'], coul['B']);
4044
					@issetcolor=true;
4045
				end
4046
				if (!attrs['face'].nil? and @fontlist.include?(attrs['face'].downcase))
4047
					SetFont(attrs['face'].downcase);
4048
					@issetfont=true;
4049
				end
4050
				if (!attrs['size'].nil?)
4051
					headsize = attrs['size'].to_i;
4052
				else
4053
					headsize = 0;
4054
				end
4055
				currentfont_size = @font_size;
4056
				@tempfontsize = @font_size_pt;
4057
				SetFontSize(@font_size_pt + headsize);
4058
				@lasth = @font_size * @@k_cell_height_ratio;
4059
				
4060
			when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
4061
				Ln();
4062
				headsize = (4 - tag[1,1].to_f) * 2
4063
				@tempfontsize = @font_size_pt;
4064
				SetFontSize(@font_size_pt + headsize);
4065
				SetStyle('b', true);
4066
				@lasth = @font_size * @@k_cell_height_ratio;
4067
				
4068
		end
4069
	end
4070
  
4071
	#
4072
	# Process closing tags.
4073
	# @param string :tag tag name (in upcase)
4074
	# @access private
4075
	#
4076
	def closedHTMLTagHandler(tag)
4077
		#Closing tag
4078
		case (tag)
4079
			when 'pre'
4080
				@pre_state = false;
4081
				@l_margin -= 5;
4082
				@r_margin -= 5;
4083
				@x = @l_margin;
4084
				Ln();
4085

    
4086
			when 'td','th'
4087
				base_page = @page;
4088
				base_x = @x;
4089
				base_y = @y;
4090

    
4091
				MultiCell(@tdwidth, @tdheight, unhtmlentities(@tdtext.strip), @tableborder, @tdalign, @tdfill, 1);
4092
				tr_end = @t_cells[@table_id][@tr_id][@td_id]['j1'] + 1;
4093
				if @max_td_page[tr_end].nil?  or (@max_td_page[tr_end] < @page)
4094
					@max_td_page[tr_end] = @page
4095
					@max_td_y[tr_end] = @y
4096
				elsif (@max_td_page[tr_end] == @page)
4097
					@max_td_y[tr_end] = @y if @max_td_y[tr_end].nil? or (@max_td_y[tr_end] < @y) 
4098
				end
4099

    
4100
				@page = base_page;
4101
				@x = base_x + @tdwidth;
4102
				@y = base_y;
4103
				@tdtext = '';
4104
				@tdbegin = false;
4105
				@tdwidth = 0;
4106
				@tdheight = 0;
4107
				@tdalign = "L";
4108
				SetStyle('b', false);
4109
				@tdfill = 0;
4110
				SetFillColor(@prevfill_color[0], @prevfill_color[1], @prevfill_color[2]);
4111
				
4112
			when 'tr'
4113
				@y = @max_td_y[@tr_id + 1];
4114
				@x = @l_margin;
4115
				@page = @max_td_page[@tr_id + 1];
4116
				
4117
			when 'table'
4118
				# Write Table Line
4119
				width = (@w - @l_margin - @r_margin) / @table_columns;
4120
				0.upto(@t_cells[@table_id].size - 1) do |j|
4121
					0.upto(@t_cells[@table_id][j].size - 1) do |i|
4122
						@page = @max_td_page[j]
4123
						i0=@t_cells[@table_id][j][i]['i0'];
4124
						j0=@t_cells[@table_id][j][i]['j0'];
4125
						i1=@t_cells[@table_id][j][i]['i1'];
4126
						j1=@t_cells[@table_id][j][i]['j1'];
4127

    
4128
						Line(@l_margin + width * i0,     @max_td_y[j0],   @l_margin + width * (i1+1), @max_td_y[j0])   # top
4129
						if ( @page == @max_td_page[j1 + 1])
4130
							Line(@l_margin + width * i0,     @max_td_y[j0],   @l_margin + width * i0,     @max_td_y[j1+1]) # left
4131
							Line(@l_margin + width * (i1+1), @max_td_y[j0],   @l_margin + width * (i1+1), @max_td_y[j1+1]) # right
4132
						else
4133
							Line(@l_margin + width * i0,     @max_td_y[j0],   @l_margin + width * i0,     @page_break_trigger) # left
4134
							Line(@l_margin + width * (i1+1), @max_td_y[j0],   @l_margin + width * (i1+1), @page_break_trigger) # right
4135
							@page += 1;
4136
							while @page < @max_td_page[j1 + 1]
4137
								Line(@l_margin + width * i0,     @t_margin,   @l_margin + width * i0,     @page_break_trigger) # left
4138
								Line(@l_margin + width * (i1+1), @t_margin,   @l_margin + width * (i1+1), @page_break_trigger) # right
4139
								@page += 1;
4140
							end
4141
							Line(@l_margin + width * i0,     @t_margin,   @l_margin + width * i0,     @max_td_y[j1+1]) # left
4142
							Line(@l_margin + width * (i1+1), @t_margin,   @l_margin + width * (i1+1), @max_td_y[j1+1]) # right
4143
						end
4144
						Line(@l_margin + width * i0,     @max_td_y[j1+1], @l_margin + width * (i1+1), @max_td_y[j1+1]) # bottom
4145
					end
4146
				end
4147

    
4148
				@l_margin -= 5;
4149
				@r_margin -= 5;
4150
				@tableborder=0;
4151
				@table_id += 1;
4152
				
4153
			when 'strong'
4154
				SetStyle('b', false);
4155
				
4156
			when 'em'
4157
				SetStyle('i', false);
4158
				
4159
			when 'ins'
4160
				SetStyle('u', false);
4161
				
4162
			when 'del'
4163
				SetStyle('d', false);
4164
				
4165
			when 'b', 'i', 'u'
4166
				SetStyle(tag, false);
4167
				
4168
			when 'a'
4169
				@href = nil;
4170
				
4171
			when 'p'
4172
				Ln();
4173
				
4174
			when 'sup'
4175
				currentfont_size = @font_size;
4176
				SetFontSize(@tempfontsize);
4177
				@tempfontsize = @font_size_pt;
4178
				SetXY(GetX(), GetY() - ((currentfont_size - @font_size)*(@@k_small_ratio)));
4179
				
4180
			when 'sub'
4181
				currentfont_size = @font_size;
4182
				SetFontSize(@tempfontsize);
4183
				@tempfontsize = @font_size_pt;
4184
				SetXY(GetX(), GetY() + ((currentfont_size - @font_size)*(@@k_small_ratio)));
4185
				
4186
			when 'small'
4187
				currentfont_size = @font_size;
4188
				SetFontSize(@tempfontsize);
4189
				@tempfontsize = @font_size_pt;
4190
				SetXY(GetX(), GetY() - ((@font_size - currentfont_size)/3));
4191
				
4192
			when 'font'
4193
				if (@issetcolor == true)
4194
					SetTextColor(@prevtext_color[0], @prevtext_color[1], @prevtext_color[2]);
4195
				end
4196
				if (@issetfont)
4197
					@font_family = @prevfont_family;
4198
					@font_style = @prevfont_style;
4199
					SetFont(@font_family);
4200
					@issetfont = false;
4201
				end
4202
				currentfont_size = @font_size;
4203
				SetFontSize(@tempfontsize);
4204
				@tempfontsize = @font_size_pt;
4205
				#@text_color = @prevtext_color;
4206
				@lasth = @font_size * @@k_cell_height_ratio;
4207
				
4208
			when 'blockquote'
4209
			  @quote_count -= 1
4210
				if (@quote_page[@quote_count] == @page)
4211
					Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @y)  # quoto line
4212
				else
4213
					cur_page = @page;
4214
					cur_y = @y;
4215
					@page = @quote_page[@quote_count];
4216
					if (@quote_top[@quote_count] < @page_break_trigger)
4217
						Line(@l_margin - 1, @quote_top[@quote_count], @l_margin - 1, @page_break_trigger)  # quoto line
4218
					end
4219
					@page += 1;
4220
					while @page < cur_page
4221
						Line(@l_margin - 1, @t_margin, @l_margin - 1, @page_break_trigger)  # quoto line
4222
						@page += 1;
4223
					end
4224
					@y = cur_y;
4225
					Line(@l_margin - 1, @t_margin, @l_margin - 1, @y)  # quoto line
4226
				end
4227
				if (@quote_count <= 0)
4228
					SetStyle('i', false);
4229
					@l_margin -= 5;
4230
				else
4231
					@l_margin -= 5 / 2;
4232
				end
4233
				@x = @l_margin;
4234
				Ln() if @quote_count == 0
4235

    
4236
			when 'ul', 'ol'
4237
			  @li_count -= 1
4238
				if @li_state == true
4239
					Ln();
4240
			  	@li_state = false;
4241
				end
4242
				
4243
			when 'li'
4244
				@li_spacer = "";
4245
				if @li_state == true
4246
					Ln();
4247
			  	@li_state = false;
4248
				end
4249
				
4250
			when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
4251
				SetFontSize(@tempfontsize);
4252
				@tempfontsize = @font_size_pt;
4253
				SetStyle('b', false);
4254
				Ln();
4255
				@lasth = @font_size * @@k_cell_height_ratio;
4256
				
4257
				if tag == 'h1' or tag == 'h2' or tag == 'h3' or tag == 'h4'
4258
					margin = 1;
4259
					hrWidth = @w - @l_margin - @r_margin - margin;
4260
					if tag == 'h1' or tag == 'h2'
4261
						SetLineWidth(0.2);
4262
					else
4263
						SetLineWidth(0.1);
4264
					end
4265
					Line(@x + margin, @y, @x + hrWidth, @y);
4266
				end
4267
		end
4268
	end
4269
	
4270
	#
4271
	# Sets font style.
4272
	# @param string :tag tag name (in lowercase)
4273
	# @param boolean :enable
4274
	# @access private
4275
	#
4276
	def SetStyle(tag, enable)
4277
		#Modify style and select corresponding font
4278
		['b', 'i', 'u', 'd'].each do |s|
4279
			if tag.downcase == s
4280
				if enable
4281
					@style << s if ! @style.include?(s)
4282
				else
4283
					@style = @style.gsub(s,'')
4284
				end
4285
			end
4286
		end
4287
		SetFont('', @style);
4288
	end
4289
	
4290
	#
4291
	# Output anchor link.
4292
	# @param string :url link URL
4293
	# @param string :name link name
4294
	# @param int :fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
4295
	# @access public
4296
	#
4297
	def addHtmlLink(url, name, fill=0)
4298
		#Put a hyperlink
4299
		SetTextColor(0, 0, 255);
4300
		SetStyle('u', true);
4301
		Write(@lasth, name, url, fill);
4302
		SetStyle('u', false);
4303
		SetTextColor(0);
4304
	end
4305
	
4306
	#
4307
	# Returns an associative array (keys: R,G,B) from 
4308
	# a hex html code (e.g. #3FE5AA).
4309
	# @param string :color hexadecimal html color [#rrggbb]
4310
	# @return array
4311
	# @access private
4312
	#
4313
	def convertColorHexToDec(color = "#000000")
4314
		tbl_color = {}
4315
		tbl_color['R'] = color[1,2].hex.to_i;
4316
		tbl_color['G'] = color[3,2].hex.to_i;
4317
		tbl_color['B'] = color[5,2].hex.to_i;
4318
		return tbl_color;
4319
	end
4320
	
4321
	#
4322
	# Converts pixels to millimeters in 72 dpi.
4323
	# @param int :px pixels
4324
	# @return float millimeters
4325
	# @access private
4326
	#
4327
	def pixelsToMillimeters(px)
4328
		return px.to_f * 25.4 / 72;
4329
	end
4330
		
4331
	#
4332
	# Reverse function for htmlentities.
4333
	# Convert entities in UTF-8.
4334
	#
4335
	# @param :text_to_convert Text to convert.
4336
	# @return string converted
4337
	#
4338
	def unhtmlentities(string)
4339
      CGI.unescapeHTML(string)
4340
  end
4341
  
4342
end # END OF CLASS
4343

    
4344
#TODO 2007-05-25 (EJM) Level=0 - 
4345
#Handle special IE contype request
4346
# if (!_SERVER['HTTP_USER_AGENT'].nil? and (_SERVER['HTTP_USER_AGENT']=='contype'))
4347
# 	header('Content-Type: application/pdf');
4348
# 	exit;
4349
# }