##// END OF EJS Templates
PDF: fix 0x5c(backslash) escape processing in FPDF (#61)....
Toshi MARUYAMA -
r5445:f62605c636c2
parent child
Show More
@@ -1,404 +1,402
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2009 Jean-Philippe Lang
4 # Copyright (C) 2006-2009 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'iconv'
20 require 'iconv'
21 require 'rfpdf/fpdf'
21 require 'rfpdf/fpdf'
22 require 'fpdf/chinese'
22 require 'fpdf/chinese'
23 require 'fpdf/japanese'
23 require 'fpdf/japanese'
24 require 'fpdf/korean'
24 require 'fpdf/korean'
25
25
26 module Redmine
26 module Redmine
27 module Export
27 module Export
28 module PDF
28 module PDF
29 include ActionView::Helpers::TextHelper
29 include ActionView::Helpers::TextHelper
30 include ActionView::Helpers::NumberHelper
30 include ActionView::Helpers::NumberHelper
31
31
32 class ITCPDF < TCPDF
32 class ITCPDF < TCPDF
33 include Redmine::I18n
33 include Redmine::I18n
34 attr_accessor :footer_date
34 attr_accessor :footer_date
35
35
36 def initialize(lang)
36 def initialize(lang)
37 super()
37 super()
38 set_language_if_valid lang
38 set_language_if_valid lang
39 @font_for_content = 'FreeSans'
39 @font_for_content = 'FreeSans'
40 @font_for_footer = 'FreeSans'
40 @font_for_footer = 'FreeSans'
41 SetCreator(Redmine::Info.app_name)
41 SetCreator(Redmine::Info.app_name)
42 SetFont(@font_for_content)
42 SetFont(@font_for_content)
43 end
43 end
44
44
45 def SetFontStyle(style, size)
45 def SetFontStyle(style, size)
46 SetFont(@font_for_content, style, size)
46 SetFont(@font_for_content, style, size)
47 end
47 end
48
48
49 def SetTitle(txt)
49 def SetTitle(txt)
50 txt = begin
50 txt = begin
51 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
51 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
52 hextxt = "<FEFF" # FEFF is BOM
52 hextxt = "<FEFF" # FEFF is BOM
53 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
53 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
54 hextxt << ">"
54 hextxt << ">"
55 rescue
55 rescue
56 txt
56 txt
57 end || ''
57 end || ''
58 super(txt)
58 super(txt)
59 end
59 end
60
60
61 def textstring(s)
61 def textstring(s)
62 # Format a text string
62 # Format a text string
63 if s =~ /^</ # This means the string is hex-dumped.
63 if s =~ /^</ # This means the string is hex-dumped.
64 return s
64 return s
65 else
65 else
66 return '('+escape(s)+')'
66 return '('+escape(s)+')'
67 end
67 end
68 end
68 end
69
69
70 alias RDMCell Cell
70 alias RDMCell Cell
71 alias RDMMultiCell MultiCell
71 alias RDMMultiCell MultiCell
72
72
73 def Footer
73 def Footer
74 SetFont(@font_for_footer, 'I', 8)
74 SetFont(@font_for_footer, 'I', 8)
75 SetY(-15)
75 SetY(-15)
76 SetX(15)
76 SetX(15)
77 RDMCell(0, 5, @footer_date, 0, 0, 'L')
77 RDMCell(0, 5, @footer_date, 0, 0, 'L')
78 SetY(-15)
78 SetY(-15)
79 SetX(-30)
79 SetX(-30)
80 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
80 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
81 end
81 end
82 end
82 end
83
83
84 class IFPDF < FPDF
84 class IFPDF < FPDF
85 include Redmine::I18n
85 include Redmine::I18n
86 attr_accessor :footer_date
86 attr_accessor :footer_date
87
87
88 def initialize(lang)
88 def initialize(lang)
89 super()
89 super()
90 set_language_if_valid lang
90 set_language_if_valid lang
91 case l(:general_pdf_encoding).upcase
91 case l(:general_pdf_encoding).upcase
92 when 'CP949'
92 when 'CP949'
93 extend(PDF_Korean)
93 extend(PDF_Korean)
94 AddUHCFont()
94 AddUHCFont()
95 @font_for_content = 'UHC'
95 @font_for_content = 'UHC'
96 @font_for_footer = 'UHC'
96 @font_for_footer = 'UHC'
97 when 'CP932'
97 when 'CP932'
98 extend(PDF_Japanese)
98 extend(PDF_Japanese)
99 AddSJISFont()
99 AddSJISFont()
100 @font_for_content = 'SJIS'
100 @font_for_content = 'SJIS'
101 @font_for_footer = 'SJIS'
101 @font_for_footer = 'SJIS'
102 when 'GB18030'
102 when 'GB18030'
103 extend(PDF_Chinese)
103 extend(PDF_Chinese)
104 AddGBFont()
104 AddGBFont()
105 @font_for_content = 'GB'
105 @font_for_content = 'GB'
106 @font_for_footer = 'GB'
106 @font_for_footer = 'GB'
107 when 'BIG5'
107 when 'BIG5'
108 extend(PDF_Chinese)
108 extend(PDF_Chinese)
109 AddBig5Font()
109 AddBig5Font()
110 @font_for_content = 'Big5'
110 @font_for_content = 'Big5'
111 @font_for_footer = 'Big5'
111 @font_for_footer = 'Big5'
112 else
112 else
113 @font_for_content = 'Arial'
113 @font_for_content = 'Arial'
114 @font_for_footer = 'Helvetica'
114 @font_for_footer = 'Helvetica'
115 end
115 end
116 SetCreator(Redmine::Info.app_name)
116 SetCreator(Redmine::Info.app_name)
117 SetFont(@font_for_content)
117 SetFont(@font_for_content)
118 end
118 end
119
119
120 def SetFontStyle(style, size)
120 def SetFontStyle(style, size)
121 SetFont(@font_for_content, style, size)
121 SetFont(@font_for_content, style, size)
122 end
122 end
123
123
124 def SetTitle(txt)
124 def SetTitle(txt)
125 txt = begin
125 txt = begin
126 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
126 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
127 hextxt = "<FEFF" # FEFF is BOM
127 hextxt = "<FEFF" # FEFF is BOM
128 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
128 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
129 hextxt << ">"
129 hextxt << ">"
130 rescue
130 rescue
131 txt
131 txt
132 end || ''
132 end || ''
133 super(txt)
133 super(txt)
134 end
134 end
135
135
136 def textstring(s)
136 def textstring(s)
137 # Format a text string
137 # Format a text string
138 if s =~ /^</ # This means the string is hex-dumped.
138 if s =~ /^</ # This means the string is hex-dumped.
139 return s
139 return s
140 else
140 else
141 return '('+escape(s)+')'
141 return '('+escape(s)+')'
142 end
142 end
143 end
143 end
144
144
145 def fix_text_encoding(txt)
145 def fix_text_encoding(txt)
146 txt ||= ''
146 txt ||= ''
147 if txt.respond_to?(:force_encoding)
147 if txt.respond_to?(:force_encoding)
148 txt.force_encoding('UTF-8')
148 txt.force_encoding('UTF-8')
149 txt = txt.encode(l(:general_pdf_encoding), :invalid => :replace,
149 txt = txt.encode(l(:general_pdf_encoding), :invalid => :replace,
150 :undef => :replace, :replace => '?')
150 :undef => :replace, :replace => '?')
151 txt.force_encoding('ASCII-8BIT')
151 txt.force_encoding('ASCII-8BIT')
152 else
152 else
153 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
153 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
154 txtar = ""
154 txtar = ""
155 begin
155 begin
156 txtar += @ic.iconv(txt)
156 txtar += @ic.iconv(txt)
157 rescue Iconv::IllegalSequence
157 rescue Iconv::IllegalSequence
158 txtar += $!.success
158 txtar += $!.success
159 txt = '?' + $!.failed[1,$!.failed.length]
159 txt = '?' + $!.failed[1,$!.failed.length]
160 retry
160 retry
161 rescue
161 rescue
162 txtar += $!.success
162 txtar += $!.success
163 end
163 end
164 txt = txtar
164 txt = txtar
165 end
165 end
166 # 0x5c char handling
167 txt.gsub(/\\/, "\\\\\\\\")
168 end
166 end
169
167
170 def RDMCell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
168 def RDMCell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
171 Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
169 Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
172 end
170 end
173
171
174 def RDMMultiCell(w,h=0,txt='',border=0,align='',fill=0)
172 def RDMMultiCell(w,h=0,txt='',border=0,align='',fill=0)
175 MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
173 MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
176 end
174 end
177
175
178 def Footer
176 def Footer
179 SetFont(@font_for_footer, 'I', 8)
177 SetFont(@font_for_footer, 'I', 8)
180 SetY(-15)
178 SetY(-15)
181 SetX(15)
179 SetX(15)
182 RDMCell(0, 5, @footer_date, 0, 0, 'L')
180 RDMCell(0, 5, @footer_date, 0, 0, 'L')
183 SetY(-15)
181 SetY(-15)
184 SetX(-30)
182 SetX(-30)
185 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
183 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
186 end
184 end
187 alias alias_nb_pages AliasNbPages
185 alias alias_nb_pages AliasNbPages
188 end
186 end
189
187
190 # Returns a PDF string of a list of issues
188 # Returns a PDF string of a list of issues
191 def issues_to_pdf(issues, project, query)
189 def issues_to_pdf(issues, project, query)
192 if l(:general_pdf_encoding).upcase != 'UTF-8'
190 if l(:general_pdf_encoding).upcase != 'UTF-8'
193 pdf = IFPDF.new(current_language)
191 pdf = IFPDF.new(current_language)
194 else
192 else
195 pdf = ITCPDF.new(current_language)
193 pdf = ITCPDF.new(current_language)
196 end
194 end
197 title = query.new_record? ? l(:label_issue_plural) : query.name
195 title = query.new_record? ? l(:label_issue_plural) : query.name
198 title = "#{project} - #{title}" if project
196 title = "#{project} - #{title}" if project
199 pdf.SetTitle(title)
197 pdf.SetTitle(title)
200 pdf.alias_nb_pages
198 pdf.alias_nb_pages
201 pdf.footer_date = format_date(Date.today)
199 pdf.footer_date = format_date(Date.today)
202 pdf.AddPage("L")
200 pdf.AddPage("L")
203
201
204 row_height = 6
202 row_height = 6
205 col_width = []
203 col_width = []
206 unless query.columns.empty?
204 unless query.columns.empty?
207 col_width = query.columns.collect {|column| column.name == :subject ? 4.0 : 1.0 }
205 col_width = query.columns.collect {|column| column.name == :subject ? 4.0 : 1.0 }
208 ratio = 262.0 / col_width.inject(0) {|s,w| s += w}
206 ratio = 262.0 / col_width.inject(0) {|s,w| s += w}
209 col_width = col_width.collect {|w| w * ratio}
207 col_width = col_width.collect {|w| w * ratio}
210 end
208 end
211
209
212 # title
210 # title
213 pdf.SetFontStyle('B',11)
211 pdf.SetFontStyle('B',11)
214 pdf.RDMCell(190,10, title)
212 pdf.RDMCell(190,10, title)
215 pdf.Ln
213 pdf.Ln
216
214
217 # headers
215 # headers
218 pdf.SetFontStyle('B',8)
216 pdf.SetFontStyle('B',8)
219 pdf.SetFillColor(230, 230, 230)
217 pdf.SetFillColor(230, 230, 230)
220 pdf.RDMCell(15, row_height, "#", 1, 0, 'L', 1)
218 pdf.RDMCell(15, row_height, "#", 1, 0, 'L', 1)
221 query.columns.each_with_index do |column, i|
219 query.columns.each_with_index do |column, i|
222 pdf.RDMCell(col_width[i], row_height, column.caption, 1, 0, 'L', 1)
220 pdf.RDMCell(col_width[i], row_height, column.caption, 1, 0, 'L', 1)
223 end
221 end
224 pdf.Ln
222 pdf.Ln
225
223
226 # rows
224 # rows
227 pdf.SetFontStyle('',8)
225 pdf.SetFontStyle('',8)
228 pdf.SetFillColor(255, 255, 255)
226 pdf.SetFillColor(255, 255, 255)
229 previous_group = false
227 previous_group = false
230 issues.each do |issue|
228 issues.each do |issue|
231 if query.grouped? &&
229 if query.grouped? &&
232 (group = query.group_by_column.value(issue)) != previous_group
230 (group = query.group_by_column.value(issue)) != previous_group
233 pdf.SetFontStyle('B',9)
231 pdf.SetFontStyle('B',9)
234 pdf.RDMCell(277, row_height,
232 pdf.RDMCell(277, row_height,
235 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
233 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
236 1, 1, 'L')
234 1, 1, 'L')
237 pdf.SetFontStyle('',8)
235 pdf.SetFontStyle('',8)
238 previous_group = group
236 previous_group = group
239 end
237 end
240 pdf.RDMCell(15, row_height, issue.id.to_s, 1, 0, 'L', 1)
238 pdf.RDMCell(15, row_height, issue.id.to_s, 1, 0, 'L', 1)
241 query.columns.each_with_index do |column, i|
239 query.columns.each_with_index do |column, i|
242 s = if column.is_a?(QueryCustomFieldColumn)
240 s = if column.is_a?(QueryCustomFieldColumn)
243 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
241 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
244 show_value(cv)
242 show_value(cv)
245 else
243 else
246 value = issue.send(column.name)
244 value = issue.send(column.name)
247 if value.is_a?(Date)
245 if value.is_a?(Date)
248 format_date(value)
246 format_date(value)
249 elsif value.is_a?(Time)
247 elsif value.is_a?(Time)
250 format_time(value)
248 format_time(value)
251 else
249 else
252 value
250 value
253 end
251 end
254 end
252 end
255 pdf.RDMCell(col_width[i], row_height, s.to_s, 1, 0, 'L', 1)
253 pdf.RDMCell(col_width[i], row_height, s.to_s, 1, 0, 'L', 1)
256 end
254 end
257 pdf.Ln
255 pdf.Ln
258 end
256 end
259 if issues.size == Setting.issues_export_limit.to_i
257 if issues.size == Setting.issues_export_limit.to_i
260 pdf.SetFontStyle('B',10)
258 pdf.SetFontStyle('B',10)
261 pdf.RDMCell(0, row_height, '...')
259 pdf.RDMCell(0, row_height, '...')
262 end
260 end
263 pdf.Output
261 pdf.Output
264 end
262 end
265
263
266 # Returns a PDF string of a single issue
264 # Returns a PDF string of a single issue
267 def issue_to_pdf(issue)
265 def issue_to_pdf(issue)
268 if l(:general_pdf_encoding).upcase != 'UTF-8'
266 if l(:general_pdf_encoding).upcase != 'UTF-8'
269 pdf = IFPDF.new(current_language)
267 pdf = IFPDF.new(current_language)
270 else
268 else
271 pdf = ITCPDF.new(current_language)
269 pdf = ITCPDF.new(current_language)
272 end
270 end
273 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
271 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
274 pdf.alias_nb_pages
272 pdf.alias_nb_pages
275 pdf.footer_date = format_date(Date.today)
273 pdf.footer_date = format_date(Date.today)
276 pdf.AddPage
274 pdf.AddPage
277
275
278 pdf.SetFontStyle('B',11)
276 pdf.SetFontStyle('B',11)
279 pdf.RDMCell(190,10,
277 pdf.RDMCell(190,10,
280 "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
278 "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
281 pdf.Ln
279 pdf.Ln
282
280
283 y0 = pdf.GetY
281 y0 = pdf.GetY
284
282
285 pdf.SetFontStyle('B',9)
283 pdf.SetFontStyle('B',9)
286 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
284 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
287 pdf.SetFontStyle('',9)
285 pdf.SetFontStyle('',9)
288 pdf.RDMCell(60,5, issue.status.to_s,"RT")
286 pdf.RDMCell(60,5, issue.status.to_s,"RT")
289 pdf.SetFontStyle('B',9)
287 pdf.SetFontStyle('B',9)
290 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
288 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
291 pdf.SetFontStyle('',9)
289 pdf.SetFontStyle('',9)
292 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
290 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
293 pdf.Ln
291 pdf.Ln
294
292
295 pdf.SetFontStyle('B',9)
293 pdf.SetFontStyle('B',9)
296 pdf.RDMCell(35,5, l(:field_author) + ":","L")
294 pdf.RDMCell(35,5, l(:field_author) + ":","L")
297 pdf.SetFontStyle('',9)
295 pdf.SetFontStyle('',9)
298 pdf.RDMCell(60,5, issue.author.to_s,"R")
296 pdf.RDMCell(60,5, issue.author.to_s,"R")
299 pdf.SetFontStyle('B',9)
297 pdf.SetFontStyle('B',9)
300 pdf.RDMCell(35,5, l(:field_category) + ":","L")
298 pdf.RDMCell(35,5, l(:field_category) + ":","L")
301 pdf.SetFontStyle('',9)
299 pdf.SetFontStyle('',9)
302 pdf.RDMCell(60,5, issue.category.to_s,"R")
300 pdf.RDMCell(60,5, issue.category.to_s,"R")
303 pdf.Ln
301 pdf.Ln
304
302
305 pdf.SetFontStyle('B',9)
303 pdf.SetFontStyle('B',9)
306 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
304 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
307 pdf.SetFontStyle('',9)
305 pdf.SetFontStyle('',9)
308 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
306 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
309 pdf.SetFontStyle('B',9)
307 pdf.SetFontStyle('B',9)
310 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
308 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
311 pdf.SetFontStyle('',9)
309 pdf.SetFontStyle('',9)
312 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
310 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
313 pdf.Ln
311 pdf.Ln
314
312
315 pdf.SetFontStyle('B',9)
313 pdf.SetFontStyle('B',9)
316 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
314 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
317 pdf.SetFontStyle('',9)
315 pdf.SetFontStyle('',9)
318 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
316 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
319 pdf.SetFontStyle('B',9)
317 pdf.SetFontStyle('B',9)
320 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
318 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
321 pdf.SetFontStyle('',9)
319 pdf.SetFontStyle('',9)
322 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
320 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
323 pdf.Ln
321 pdf.Ln
324
322
325 for custom_value in issue.custom_field_values
323 for custom_value in issue.custom_field_values
326 pdf.SetFontStyle('B',9)
324 pdf.SetFontStyle('B',9)
327 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
325 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
328 pdf.SetFontStyle('',9)
326 pdf.SetFontStyle('',9)
329 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
327 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
330 end
328 end
331
329
332 pdf.SetFontStyle('B',9)
330 pdf.SetFontStyle('B',9)
333 pdf.RDMCell(35,5, l(:field_subject) + ":","LTB")
331 pdf.RDMCell(35,5, l(:field_subject) + ":","LTB")
334 pdf.SetFontStyle('',9)
332 pdf.SetFontStyle('',9)
335 pdf.RDMCell(155,5, issue.subject,"RTB")
333 pdf.RDMCell(155,5, issue.subject,"RTB")
336 pdf.Ln
334 pdf.Ln
337
335
338 pdf.SetFontStyle('B',9)
336 pdf.SetFontStyle('B',9)
339 pdf.RDMCell(35,5, l(:field_description) + ":")
337 pdf.RDMCell(35,5, l(:field_description) + ":")
340 pdf.SetFontStyle('',9)
338 pdf.SetFontStyle('',9)
341 pdf.RDMMultiCell(155,5, issue.description.to_s,"BR")
339 pdf.RDMMultiCell(155,5, issue.description.to_s,"BR")
342
340
343 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
341 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
344 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
342 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
345 pdf.Ln
343 pdf.Ln
346
344
347 if issue.changesets.any? &&
345 if issue.changesets.any? &&
348 User.current.allowed_to?(:view_changesets, issue.project)
346 User.current.allowed_to?(:view_changesets, issue.project)
349 pdf.SetFontStyle('B',9)
347 pdf.SetFontStyle('B',9)
350 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
348 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
351 pdf.Ln
349 pdf.Ln
352 for changeset in issue.changesets
350 for changeset in issue.changesets
353 pdf.SetFontStyle('B',8)
351 pdf.SetFontStyle('B',8)
354 pdf.RDMCell(190,5,
352 pdf.RDMCell(190,5,
355 format_time(changeset.committed_on) + " - " + changeset.author.to_s)
353 format_time(changeset.committed_on) + " - " + changeset.author.to_s)
356 pdf.Ln
354 pdf.Ln
357 unless changeset.comments.blank?
355 unless changeset.comments.blank?
358 pdf.SetFontStyle('',8)
356 pdf.SetFontStyle('',8)
359 pdf.RDMMultiCell(190,5, changeset.comments.to_s)
357 pdf.RDMMultiCell(190,5, changeset.comments.to_s)
360 end
358 end
361 pdf.Ln
359 pdf.Ln
362 end
360 end
363 end
361 end
364
362
365 pdf.SetFontStyle('B',9)
363 pdf.SetFontStyle('B',9)
366 pdf.RDMCell(190,5, l(:label_history), "B")
364 pdf.RDMCell(190,5, l(:label_history), "B")
367 pdf.Ln
365 pdf.Ln
368 for journal in issue.journals.find(
366 for journal in issue.journals.find(
369 :all, :include => [:user, :details],
367 :all, :include => [:user, :details],
370 :order => "#{Journal.table_name}.created_on ASC")
368 :order => "#{Journal.table_name}.created_on ASC")
371 pdf.SetFontStyle('B',8)
369 pdf.SetFontStyle('B',8)
372 pdf.RDMCell(190,5,
370 pdf.RDMCell(190,5,
373 format_time(journal.created_on) + " - " + journal.user.name)
371 format_time(journal.created_on) + " - " + journal.user.name)
374 pdf.Ln
372 pdf.Ln
375 pdf.SetFontStyle('I',8)
373 pdf.SetFontStyle('I',8)
376 for detail in journal.details
374 for detail in journal.details
377 pdf.RDMCell(190,5, "- " + show_detail(detail, true))
375 pdf.RDMCell(190,5, "- " + show_detail(detail, true))
378 pdf.Ln
376 pdf.Ln
379 end
377 end
380 if journal.notes?
378 if journal.notes?
381 pdf.SetFontStyle('',8)
379 pdf.SetFontStyle('',8)
382 pdf.RDMMultiCell(190,5, journal.notes.to_s)
380 pdf.RDMMultiCell(190,5, journal.notes.to_s)
383 end
381 end
384 pdf.Ln
382 pdf.Ln
385 end
383 end
386
384
387 if issue.attachments.any?
385 if issue.attachments.any?
388 pdf.SetFontStyle('B',9)
386 pdf.SetFontStyle('B',9)
389 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
387 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
390 pdf.Ln
388 pdf.Ln
391 for attachment in issue.attachments
389 for attachment in issue.attachments
392 pdf.SetFontStyle('',8)
390 pdf.SetFontStyle('',8)
393 pdf.RDMCell(80,5, attachment.filename)
391 pdf.RDMCell(80,5, attachment.filename)
394 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
392 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
395 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
393 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
396 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
394 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
397 pdf.Ln
395 pdf.Ln
398 end
396 end
399 end
397 end
400 pdf.Output
398 pdf.Output
401 end
399 end
402 end
400 end
403 end
401 end
404 end
402 end
@@ -1,92 +1,52
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../test_helper', __FILE__)
19
19
20 class PdfTest < ActiveSupport::TestCase
20 class PdfTest < ActiveSupport::TestCase
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 def test_fix_text_encoding_nil
23 def test_fix_text_encoding_nil
24 set_language_if_valid 'ja'
24 set_language_if_valid 'ja'
25 pdf = Redmine::Export::PDF::IFPDF.new('ja')
25 pdf = Redmine::Export::PDF::IFPDF.new('ja')
26 assert pdf
26 assert pdf
27 assert_equal '', pdf.fix_text_encoding(nil)
27 assert_equal '', pdf.fix_text_encoding(nil)
28 end
28 end
29
29
30 def test_fix_text_encoding_backslash_ascii
31 set_language_if_valid 'ja'
32 pdf = Redmine::Export::PDF::IFPDF.new('ja')
33 assert pdf
34 assert_equal '\\\\abcd', pdf.fix_text_encoding('\\abcd')
35 assert_equal 'abcd\\\\', pdf.fix_text_encoding('abcd\\')
36 assert_equal 'ab\\\\cd', pdf.fix_text_encoding('ab\\cd')
37 assert_equal '\\\\abcd\\\\', pdf.fix_text_encoding('\\abcd\\')
38 assert_equal '\\\\abcd\\\\abcd\\\\',
39 pdf.fix_text_encoding('\\abcd\\abcd\\')
40 end
41
42 def test_fix_text_encoding_double_backslash_ascii
43 set_language_if_valid 'ja'
44 pdf = Redmine::Export::PDF::IFPDF.new('ja')
45 assert pdf
46 assert_equal '\\\\\\\\abcd', pdf.fix_text_encoding('\\\\abcd')
47 assert_equal 'abcd\\\\\\\\', pdf.fix_text_encoding('abcd\\\\')
48 assert_equal 'ab\\\\\\\\cd', pdf.fix_text_encoding('ab\\\\cd')
49 assert_equal 'ab\\\\\\\\cd\\\\de', pdf.fix_text_encoding('ab\\\\cd\\de')
50 assert_equal '\\\\\\\\abcd\\\\\\\\', pdf.fix_text_encoding('\\\\abcd\\\\')
51 assert_equal '\\\\\\\\abcd\\\\\\\\abcd\\\\\\\\',
52 pdf.fix_text_encoding('\\\\abcd\\\\abcd\\\\')
53 end
54
55 def test_fix_text_encoding_backslash_ja_cp932
56 pdf = Redmine::Export::PDF::IFPDF.new('ja')
57 assert pdf
58 assert_equal "\x83\\\\\x98A",
59 pdf.fix_text_encoding("\xe3\x82\xbd\xe9\x80\xa3")
60 assert_equal "\x83\\\\\x98A\x91\xe3\x95\\\\",
61 pdf.fix_text_encoding("\xe3\x82\xbd\xe9\x80\xa3\xe4\xbb\xa3\xe8\xa1\xa8")
62 assert_equal "\x91\xe3\x95\\\\\\\\",
63 pdf.fix_text_encoding("\xe4\xbb\xa3\xe8\xa1\xa8\\")
64 assert_equal "\x91\xe3\x95\\\\\\\\\\\\",
65 pdf.fix_text_encoding("\xe4\xbb\xa3\xe8\xa1\xa8\\\\")
66 assert_equal "\x91\xe3\x95\\\\a\\\\",
67 pdf.fix_text_encoding("\xe4\xbb\xa3\xe8\xa1\xa8a\\")
68 end
69
70 def test_fix_text_encoding_cannot_convert_ja_cp932
30 def test_fix_text_encoding_cannot_convert_ja_cp932
71 pdf = Redmine::Export::PDF::IFPDF.new('ja')
31 pdf = Redmine::Export::PDF::IFPDF.new('ja')
72 assert pdf
32 assert pdf
73 utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b"
33 utf8_txt_1 = "\xe7\x8b\x80\xe6\x85\x8b"
74 utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
34 utf8_txt_2 = "\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
75 utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
35 utf8_txt_3 = "\xe7\x8b\x80\xe7\x8b\x80\xe6\x85\x8b\xe7\x8b\x80"
76 if utf8_txt_1.respond_to?(:force_encoding)
36 if utf8_txt_1.respond_to?(:force_encoding)
77 assert_equal "?\x91\xd4",
37 assert_equal "?\x91\xd4",
78 pdf.fix_text_encoding(utf8_txt_1)
38 pdf.fix_text_encoding(utf8_txt_1)
79 assert_equal "?\x91\xd4?",
39 assert_equal "?\x91\xd4?",
80 pdf.fix_text_encoding(utf8_txt_2)
40 pdf.fix_text_encoding(utf8_txt_2)
81 assert_equal "??\x91\xd4?",
41 assert_equal "??\x91\xd4?",
82 pdf.fix_text_encoding(utf8_txt_3)
42 pdf.fix_text_encoding(utf8_txt_3)
83 else
43 else
84 assert_equal "???\x91\xd4",
44 assert_equal "???\x91\xd4",
85 pdf.fix_text_encoding(utf8_txt_1)
45 pdf.fix_text_encoding(utf8_txt_1)
86 assert_equal "???\x91\xd4???",
46 assert_equal "???\x91\xd4???",
87 pdf.fix_text_encoding(utf8_txt_2)
47 pdf.fix_text_encoding(utf8_txt_2)
88 assert_equal "??????\x91\xd4???",
48 assert_equal "??????\x91\xd4???",
89 pdf.fix_text_encoding(utf8_txt_3)
49 pdf.fix_text_encoding(utf8_txt_3)
90 end
50 end
91 end
51 end
92 end
52 end
@@ -1,1638 +1,1632
1 # Ruby FPDF 1.53d
1 # Ruby FPDF 1.53d
2 # FPDF 1.53 by Olivier Plathey ported to Ruby by Brian Ollenberger
2 # FPDF 1.53 by Olivier Plathey ported to Ruby by Brian Ollenberger
3 # Copyright 2005 Brian Ollenberger
3 # Copyright 2005 Brian Ollenberger
4 # Please retain this entire copyright notice. If you distribute any
4 # Please retain this entire copyright notice. If you distribute any
5 # modifications, place an additional comment here that clearly indicates
5 # modifications, place an additional comment here that clearly indicates
6 # that it was modified. You may (but are not send any useful modifications that you make
6 # that it was modified. You may (but are not send any useful modifications that you make
7 # back to me at http://zeropluszero.com/software/fpdf/
7 # back to me at http://zeropluszero.com/software/fpdf/
8
8
9 # Bug fixes, examples, external fonts, JPEG support, and upgrade to version
9 # Bug fixes, examples, external fonts, JPEG support, and upgrade to version
10 # 1.53 contributed by Kim Shrier.
10 # 1.53 contributed by Kim Shrier.
11 #
11 #
12 # Bookmark support contributed by Sylvain Lafleur.
12 # Bookmark support contributed by Sylvain Lafleur.
13 #
13 #
14 # EPS support contributed by Thiago Jackiw, ported from the PHP version by Valentin Schmidt.
14 # EPS support contributed by Thiago Jackiw, ported from the PHP version by Valentin Schmidt.
15 #
15 #
16 # Bookmarks contributed by Sylvain Lafleur.
16 # Bookmarks contributed by Sylvain Lafleur.
17 #
17 #
18 # 1.53 contributed by Ed Moss
18 # 1.53 contributed by Ed Moss
19 # Make sure all \n references are inside double quotes - Fix some multicell bugs
19 # Make sure all \n references are inside double quotes - Fix some multicell bugs
20 # Handle "\n" at the beginning of a string
20 # Handle "\n" at the beginning of a string
21 # Bookmarks contributed by Sylvain Lafleur.
21 # Bookmarks contributed by Sylvain Lafleur.
22
22
23 require 'date'
23 require 'date'
24 require 'zlib'
24 require 'zlib'
25
25
26 class FPDF
26 class FPDF
27 include RFPDF
27 include RFPDF
28
28
29 attr_accessor :default_font
29 attr_accessor :default_font
30
30
31 FPDF_VERSION = '1.53d'
31 FPDF_VERSION = '1.53d'
32
32
33 Charwidths = {
33 Charwidths = {
34 'courier'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
34 'courier'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
35
35
36 'courierB'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
36 'courierB'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
37
37
38 'courierI'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
38 'courierI'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
39
39
40 'courierBI'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
40 'courierBI'=>[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],
41
41
42 'helvetica'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 350, 556, 350, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500],
42 'helvetica'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 350, 556, 350, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500],
43
43
44 'helveticaB'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 350, 556, 350, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556],
44 'helveticaB'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 350, 556, 350, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556],
45
45
46 'helveticaI'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 350, 556, 350, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500],
46 'helveticaI'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 350, 556, 350, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 222, 222, 333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 500],
47
47
48 'helveticaBI'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 350, 556, 350, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556],
48 'helveticaBI'=>[278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 350, 556, 350, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 350, 611, 350, 350, 278, 278, 500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 350, 500, 667, 278, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333, 333, 611, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 584, 611, 611, 611, 611, 611, 556, 611, 556],
49
49
50 'times'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 408, 500, 500, 833, 778, 180, 333, 333, 500, 564, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 564, 564, 564, 444, 921, 722, 667, 667, 722, 611, 556, 722, 722, 333, 389, 722, 611, 889, 722, 722, 556, 722, 667, 556, 611, 722, 722, 944, 722, 722, 611, 333, 278, 333, 469, 500, 333, 444, 500, 444, 500, 444, 333, 500, 500, 278, 278, 500, 278, 778, 500, 500, 500, 500, 333, 389, 278, 500, 500, 722, 500, 500, 444, 480, 200, 480, 541, 350, 500, 350, 333, 500, 444, 1000, 500, 500, 333, 1000, 556, 333, 889, 350, 611, 350, 350, 333, 333, 444, 444, 350, 500, 1000, 333, 980, 389, 333, 722, 350, 444, 722, 250, 333, 500, 500, 500, 500, 200, 500, 333, 760, 276, 500, 564, 333, 760, 333, 400, 564, 300, 300, 333, 500, 453, 250, 333, 300, 310, 500, 750, 750, 750, 444, 722, 722, 722, 722, 722, 722, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 722, 722, 722, 722, 722, 722, 564, 722, 722, 722, 722, 722, 722, 556, 500, 444, 444, 444, 444, 444, 444, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 564, 500, 500, 500, 500, 500, 500, 500, 500],
50 'times'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 408, 500, 500, 833, 778, 180, 333, 333, 500, 564, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 564, 564, 564, 444, 921, 722, 667, 667, 722, 611, 556, 722, 722, 333, 389, 722, 611, 889, 722, 722, 556, 722, 667, 556, 611, 722, 722, 944, 722, 722, 611, 333, 278, 333, 469, 500, 333, 444, 500, 444, 500, 444, 333, 500, 500, 278, 278, 500, 278, 778, 500, 500, 500, 500, 333, 389, 278, 500, 500, 722, 500, 500, 444, 480, 200, 480, 541, 350, 500, 350, 333, 500, 444, 1000, 500, 500, 333, 1000, 556, 333, 889, 350, 611, 350, 350, 333, 333, 444, 444, 350, 500, 1000, 333, 980, 389, 333, 722, 350, 444, 722, 250, 333, 500, 500, 500, 500, 200, 500, 333, 760, 276, 500, 564, 333, 760, 333, 400, 564, 300, 300, 333, 500, 453, 250, 333, 300, 310, 500, 750, 750, 750, 444, 722, 722, 722, 722, 722, 722, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 722, 722, 722, 722, 722, 722, 564, 722, 722, 722, 722, 722, 722, 556, 500, 444, 444, 444, 444, 444, 444, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 564, 500, 500, 500, 500, 500, 500, 500, 500],
51
51
52 'timesB'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 555, 500, 500, 1000, 833, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 930, 722, 667, 722, 722, 667, 611, 778, 778, 389, 500, 778, 667, 944, 722, 778, 611, 778, 722, 556, 667, 722, 722, 1000, 722, 722, 667, 333, 278, 333, 581, 500, 333, 500, 556, 444, 556, 444, 333, 500, 556, 278, 333, 556, 278, 833, 556, 500, 556, 556, 444, 389, 333, 556, 500, 722, 500, 500, 444, 394, 220, 394, 520, 350, 500, 350, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 1000, 350, 667, 350, 350, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 350, 444, 722, 250, 333, 500, 500, 500, 500, 220, 500, 333, 747, 300, 500, 570, 333, 747, 333, 400, 570, 300, 300, 333, 556, 540, 250, 333, 300, 330, 500, 750, 750, 750, 500, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 778, 778, 778, 778, 778, 570, 778, 722, 722, 722, 722, 722, 611, 556, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 500, 556, 500],
52 'timesB'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 555, 500, 500, 1000, 833, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 930, 722, 667, 722, 722, 667, 611, 778, 778, 389, 500, 778, 667, 944, 722, 778, 611, 778, 722, 556, 667, 722, 722, 1000, 722, 722, 667, 333, 278, 333, 581, 500, 333, 500, 556, 444, 556, 444, 333, 500, 556, 278, 333, 556, 278, 833, 556, 500, 556, 556, 444, 389, 333, 556, 500, 722, 500, 500, 444, 394, 220, 394, 520, 350, 500, 350, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 1000, 350, 667, 350, 350, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 350, 444, 722, 250, 333, 500, 500, 500, 500, 220, 500, 333, 747, 300, 500, 570, 333, 747, 333, 400, 570, 300, 300, 333, 556, 540, 250, 333, 300, 330, 500, 750, 750, 750, 500, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 778, 778, 778, 778, 778, 570, 778, 722, 722, 722, 722, 722, 611, 556, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 500, 556, 500],
53
53
54 'timesI'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 420, 500, 500, 833, 778, 214, 333, 333, 500, 675, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 675, 675, 675, 500, 920, 611, 611, 667, 722, 611, 611, 722, 722, 333, 444, 667, 556, 833, 667, 722, 611, 722, 611, 500, 556, 722, 611, 833, 611, 556, 556, 389, 278, 389, 422, 500, 333, 500, 500, 444, 500, 444, 278, 500, 500, 278, 278, 444, 278, 722, 500, 500, 500, 500, 389, 389, 278, 500, 444, 667, 444, 444, 389, 400, 275, 400, 541, 350, 500, 350, 333, 500, 556, 889, 500, 500, 333, 1000, 500, 333, 944, 350, 556, 350, 350, 333, 333, 556, 556, 350, 500, 889, 333, 980, 389, 333, 667, 350, 389, 556, 250, 389, 500, 500, 500, 500, 275, 500, 333, 760, 276, 500, 675, 333, 760, 333, 400, 675, 300, 300, 333, 500, 523, 250, 333, 300, 310, 500, 750, 750, 750, 500, 611, 611, 611, 611, 611, 611, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 667, 722, 722, 722, 722, 722, 675, 722, 722, 722, 722, 722, 556, 611, 500, 500, 500, 500, 500, 500, 500, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 675, 500, 500, 500, 500, 500, 444, 500, 444],
54 'timesI'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 420, 500, 500, 833, 778, 214, 333, 333, 500, 675, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 675, 675, 675, 500, 920, 611, 611, 667, 722, 611, 611, 722, 722, 333, 444, 667, 556, 833, 667, 722, 611, 722, 611, 500, 556, 722, 611, 833, 611, 556, 556, 389, 278, 389, 422, 500, 333, 500, 500, 444, 500, 444, 278, 500, 500, 278, 278, 444, 278, 722, 500, 500, 500, 500, 389, 389, 278, 500, 444, 667, 444, 444, 389, 400, 275, 400, 541, 350, 500, 350, 333, 500, 556, 889, 500, 500, 333, 1000, 500, 333, 944, 350, 556, 350, 350, 333, 333, 556, 556, 350, 500, 889, 333, 980, 389, 333, 667, 350, 389, 556, 250, 389, 500, 500, 500, 500, 275, 500, 333, 760, 276, 500, 675, 333, 760, 333, 400, 675, 300, 300, 333, 500, 523, 250, 333, 300, 310, 500, 750, 750, 750, 500, 611, 611, 611, 611, 611, 611, 889, 667, 611, 611, 611, 611, 333, 333, 333, 333, 722, 667, 722, 722, 722, 722, 722, 675, 722, 722, 722, 722, 722, 556, 611, 500, 500, 500, 500, 500, 500, 500, 667, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 500, 500, 500, 500, 500, 500, 675, 500, 500, 500, 500, 500, 444, 500, 444],
55
55
56 'timesBI'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 389, 555, 500, 500, 833, 778, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 832, 667, 667, 667, 722, 667, 667, 722, 778, 389, 500, 667, 611, 889, 722, 722, 611, 722, 667, 556, 611, 722, 667, 889, 667, 611, 611, 333, 278, 333, 570, 500, 333, 500, 500, 444, 500, 444, 333, 500, 556, 278, 278, 500, 278, 778, 556, 500, 500, 500, 389, 389, 278, 556, 444, 667, 500, 444, 389, 348, 220, 348, 570, 350, 500, 350, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 944, 350, 611, 350, 350, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 350, 389, 611, 250, 389, 500, 500, 500, 500, 220, 500, 333, 747, 266, 500, 606, 333, 747, 333, 400, 570, 300, 300, 333, 576, 500, 250, 333, 300, 300, 500, 750, 750, 750, 500, 667, 667, 667, 667, 667, 667, 944, 667, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 722, 722, 722, 722, 722, 570, 722, 722, 722, 722, 722, 611, 611, 500, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 444, 500, 444],
56 'timesBI'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 389, 555, 500, 500, 833, 778, 278, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 832, 667, 667, 667, 722, 667, 667, 722, 778, 389, 500, 667, 611, 889, 722, 722, 611, 722, 667, 556, 611, 722, 667, 889, 667, 611, 611, 333, 278, 333, 570, 500, 333, 500, 500, 444, 500, 444, 333, 500, 556, 278, 278, 500, 278, 778, 556, 500, 500, 500, 389, 389, 278, 556, 444, 667, 500, 444, 389, 348, 220, 348, 570, 350, 500, 350, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 944, 350, 611, 350, 350, 333, 333, 500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 350, 389, 611, 250, 389, 500, 500, 500, 500, 220, 500, 333, 747, 266, 500, 606, 333, 747, 333, 400, 570, 300, 300, 333, 576, 500, 250, 333, 300, 300, 500, 750, 750, 750, 500, 667, 667, 667, 667, 667, 667, 944, 667, 667, 667, 667, 667, 389, 389, 389, 389, 722, 722, 722, 722, 722, 722, 722, 570, 722, 722, 722, 722, 722, 611, 611, 500, 500, 500, 500, 500, 500, 500, 722, 444, 444, 444, 444, 444, 278, 278, 278, 278, 500, 556, 500, 500, 500, 500, 500, 570, 500, 556, 556, 556, 556, 444, 500, 444],
57
57
58 'symbol'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444, 549, 722, 667, 722, 612, 611, 763, 603, 722, 333, 631, 722, 686, 889, 722, 722, 768, 741, 556, 592, 611, 690, 439, 768, 645, 795, 611, 333, 863, 333, 658, 500, 500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549, 549, 576, 521, 549, 549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603, 400, 549, 411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658, 823, 686, 795, 987, 768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713, 768, 713, 790, 790, 890, 823, 549, 250, 713, 603, 603, 1042, 987, 603, 987, 603, 494, 329, 790, 790, 786, 713, 384, 384, 384, 384, 384, 384, 494, 494, 494, 494, 0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494, 494, 494, 0],
58 'symbol'=>[250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444, 549, 722, 667, 722, 612, 611, 763, 603, 722, 333, 631, 722, 686, 889, 722, 722, 768, 741, 556, 592, 611, 690, 439, 768, 645, 795, 611, 333, 863, 333, 658, 500, 500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549, 549, 576, 521, 549, 549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603, 400, 549, 411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658, 823, 686, 795, 987, 768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713, 768, 713, 790, 790, 890, 823, 549, 250, 713, 603, 603, 1042, 987, 603, 987, 603, 494, 329, 790, 790, 786, 713, 384, 384, 384, 384, 384, 384, 494, 494, 494, 494, 0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494, 494, 494, 0],
59
59
60 'zapfdingbats'=>[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 278, 974, 961, 974, 980, 719, 789, 790, 791, 690, 960, 939, 549, 855, 911, 933, 911, 945, 974, 755, 846, 762, 761, 571, 677, 763, 760, 759, 754, 494, 552, 537, 577, 692, 786, 788, 788, 790, 793, 794, 816, 823, 789, 841, 823, 833, 816, 831, 923, 744, 723, 749, 790, 792, 695, 776, 768, 792, 759, 707, 708, 682, 701, 826, 815, 789, 789, 707, 687, 696, 689, 786, 787, 713, 791, 785, 791, 873, 761, 762, 762, 759, 759, 892, 892, 788, 784, 438, 138, 277, 415, 392, 392, 668, 668, 0, 390, 390, 317, 317, 276, 276, 509, 509, 410, 410, 234, 234, 334, 334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 732, 544, 544, 910, 667, 760, 760, 776, 595, 694, 626, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 894, 838, 1016, 458, 748, 924, 748, 918, 927, 928, 928, 834, 873, 828, 924, 924, 917, 930, 931, 463, 883, 836, 836, 867, 867, 696, 696, 874, 0, 874, 760, 946, 771, 865, 771, 888, 967, 888, 831, 873, 927, 970, 918, 0]
60 'zapfdingbats'=>[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 278, 974, 961, 974, 980, 719, 789, 790, 791, 690, 960, 939, 549, 855, 911, 933, 911, 945, 974, 755, 846, 762, 761, 571, 677, 763, 760, 759, 754, 494, 552, 537, 577, 692, 786, 788, 788, 790, 793, 794, 816, 823, 789, 841, 823, 833, 816, 831, 923, 744, 723, 749, 790, 792, 695, 776, 768, 792, 759, 707, 708, 682, 701, 826, 815, 789, 789, 707, 687, 696, 689, 786, 787, 713, 791, 785, 791, 873, 761, 762, 762, 759, 759, 892, 892, 788, 784, 438, 138, 277, 415, 392, 392, 668, 668, 0, 390, 390, 317, 317, 276, 276, 509, 509, 410, 410, 234, 234, 334, 334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 732, 544, 544, 910, 667, 760, 760, 776, 595, 694, 626, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 894, 838, 1016, 458, 748, 924, 748, 918, 927, 928, 928, 834, 873, 828, 924, 924, 917, 930, 931, 463, 883, 836, 836, 867, 867, 696, 696, 874, 0, 874, 760, 946, 771, 865, 771, 888, 967, 888, 831, 873, 927, 970, 918, 0]
61 }
61 }
62
62
63 def initialize(orientation='P', unit='mm', format='A4')
63 def initialize(orientation='P', unit='mm', format='A4')
64 # Initialization of properties
64 # Initialization of properties
65 @page=0
65 @page=0
66 @n=2
66 @n=2
67 @buffer=''
67 @buffer=''
68 @pages=[]
68 @pages=[]
69 @OrientationChanges=[]
69 @OrientationChanges=[]
70 @state=0
70 @state=0
71 @default_font = "arial"
71 @default_font = "arial"
72 @fonts={}
72 @fonts={}
73 @FontFiles={}
73 @FontFiles={}
74 @diffs=[]
74 @diffs=[]
75 @images={}
75 @images={}
76 @links=[]
76 @links=[]
77 @PageLinks={}
77 @PageLinks={}
78 @InFooter=false
78 @InFooter=false
79 @FontFamily=''
79 @FontFamily=''
80 @FontStyle=''
80 @FontStyle=''
81 @FontSizePt=12
81 @FontSizePt=12
82 @underline= false
82 @underline= false
83 @DrawColor='0 G'
83 @DrawColor='0 G'
84 @FillColor='0 g'
84 @FillColor='0 g'
85 @TextColor='0 g'
85 @TextColor='0 g'
86 @ColorFlag=false
86 @ColorFlag=false
87 @ws=0
87 @ws=0
88 @offsets=[]
88 @offsets=[]
89
89
90 # Standard fonts
90 # Standard fonts
91 @CoreFonts={}
91 @CoreFonts={}
92 @CoreFonts['courier']='Courier'
92 @CoreFonts['courier']='Courier'
93 @CoreFonts['courierB']='Courier-Bold'
93 @CoreFonts['courierB']='Courier-Bold'
94 @CoreFonts['courierI']='Courier-Oblique'
94 @CoreFonts['courierI']='Courier-Oblique'
95 @CoreFonts['courierBI']='Courier-BoldOblique'
95 @CoreFonts['courierBI']='Courier-BoldOblique'
96 @CoreFonts['helvetica']='Helvetica'
96 @CoreFonts['helvetica']='Helvetica'
97 @CoreFonts['helveticaB']='Helvetica-Bold'
97 @CoreFonts['helveticaB']='Helvetica-Bold'
98 @CoreFonts['helveticaI']='Helvetica-Oblique'
98 @CoreFonts['helveticaI']='Helvetica-Oblique'
99 @CoreFonts['helveticaBI']='Helvetica-BoldOblique'
99 @CoreFonts['helveticaBI']='Helvetica-BoldOblique'
100 @CoreFonts['times']='Times-Roman'
100 @CoreFonts['times']='Times-Roman'
101 @CoreFonts['timesB']='Times-Bold'
101 @CoreFonts['timesB']='Times-Bold'
102 @CoreFonts['timesI']='Times-Italic'
102 @CoreFonts['timesI']='Times-Italic'
103 @CoreFonts['timesBI']='Times-BoldItalic'
103 @CoreFonts['timesBI']='Times-BoldItalic'
104 @CoreFonts['symbol']='Symbol'
104 @CoreFonts['symbol']='Symbol'
105 @CoreFonts['zapfdingbats']='ZapfDingbats'
105 @CoreFonts['zapfdingbats']='ZapfDingbats'
106
106
107 # Scale factor
107 # Scale factor
108 if unit=='pt'
108 if unit=='pt'
109 @k=1
109 @k=1
110 elsif unit=='mm'
110 elsif unit=='mm'
111 @k=72/25.4
111 @k=72/25.4
112 elsif unit=='cm'
112 elsif unit=='cm'
113 @k=72/2.54;
113 @k=72/2.54;
114 elsif unit=='in'
114 elsif unit=='in'
115 @k=72
115 @k=72
116 else
116 else
117 raise 'Incorrect unit: '+unit
117 raise 'Incorrect unit: '+unit
118 end
118 end
119
119
120 # Page format
120 # Page format
121 if format.is_a? String
121 if format.is_a? String
122 format.downcase!
122 format.downcase!
123 if format=='a3'
123 if format=='a3'
124 format=[841.89,1190.55]
124 format=[841.89,1190.55]
125 elsif format=='a4'
125 elsif format=='a4'
126 format=[595.28,841.89]
126 format=[595.28,841.89]
127 elsif format=='a5'
127 elsif format=='a5'
128 format=[420.94,595.28]
128 format=[420.94,595.28]
129 elsif format=='letter'
129 elsif format=='letter'
130 format=[612,792]
130 format=[612,792]
131 elsif format=='legal'
131 elsif format=='legal'
132 format=[612,1008]
132 format=[612,1008]
133 else
133 else
134 raise 'Unknown page format: '+format
134 raise 'Unknown page format: '+format
135 end
135 end
136 @fwPt,@fhPt=format
136 @fwPt,@fhPt=format
137 else
137 else
138 @fwPt=format[0]*@k
138 @fwPt=format[0]*@k
139 @fhPt=format[1]*@k
139 @fhPt=format[1]*@k
140 end
140 end
141 @fw=@fwPt/@k;
141 @fw=@fwPt/@k;
142 @fh=@fhPt/@k;
142 @fh=@fhPt/@k;
143
143
144 # Page orientation
144 # Page orientation
145 orientation.downcase!
145 orientation.downcase!
146 if orientation=='p' or orientation=='portrait'
146 if orientation=='p' or orientation=='portrait'
147 @DefOrientation='P'
147 @DefOrientation='P'
148 @wPt=@fwPt
148 @wPt=@fwPt
149 @hPt=@fhPt
149 @hPt=@fhPt
150 elsif orientation=='l' or orientation=='landscape'
150 elsif orientation=='l' or orientation=='landscape'
151 @DefOrientation='L'
151 @DefOrientation='L'
152 @wPt=@fhPt
152 @wPt=@fhPt
153 @hPt=@fwPt
153 @hPt=@fwPt
154 else
154 else
155 raise 'Incorrect orientation: '+orientation
155 raise 'Incorrect orientation: '+orientation
156 end
156 end
157 @CurOrientation=@DefOrientation
157 @CurOrientation=@DefOrientation
158 @w=@wPt/@k
158 @w=@wPt/@k
159 @h=@hPt/@k
159 @h=@hPt/@k
160
160
161 # Page margins (1 cm)
161 # Page margins (1 cm)
162 margin=28.35/@k
162 margin=28.35/@k
163 SetMargins(margin,margin)
163 SetMargins(margin,margin)
164 # Interior cell margin (1 mm)
164 # Interior cell margin (1 mm)
165 @cMargin=margin/10
165 @cMargin=margin/10
166 # Line width (0.2 mm)
166 # Line width (0.2 mm)
167 @LineWidth=0.567/@k
167 @LineWidth=0.567/@k
168 # Automatic page break
168 # Automatic page break
169 SetAutoPageBreak(true,2*margin)
169 SetAutoPageBreak(true,2*margin)
170 # Full width display mode
170 # Full width display mode
171 SetDisplayMode('fullwidth')
171 SetDisplayMode('fullwidth')
172 # Enable compression
172 # Enable compression
173 SetCompression(true)
173 SetCompression(true)
174 # Set default PDF version number
174 # Set default PDF version number
175 @PDFVersion='1.3'
175 @PDFVersion='1.3'
176 end
176 end
177
177
178 def GetMargins()
178 def GetMargins()
179 return @lMargin, @tMargin, @rMargin
179 return @lMargin, @tMargin, @rMargin
180 end
180 end
181
181
182 def SetMargins(left, top, right=-1)
182 def SetMargins(left, top, right=-1)
183 # Set left, top and right margins
183 # Set left, top and right margins
184 @lMargin=left
184 @lMargin=left
185 @tMargin=top
185 @tMargin=top
186 right=left if right==-1
186 right=left if right==-1
187 @rMargin=right
187 @rMargin=right
188 end
188 end
189
189
190 def SetLeftMargin(margin)
190 def SetLeftMargin(margin)
191 # Set left margin
191 # Set left margin
192 @lMargin=margin
192 @lMargin=margin
193 @x=margin if @page>0 and @x<margin
193 @x=margin if @page>0 and @x<margin
194 end
194 end
195
195
196 def SetTopMargin(margin)
196 def SetTopMargin(margin)
197 # Set top margin
197 # Set top margin
198 @tMargin=margin
198 @tMargin=margin
199 end
199 end
200
200
201 def SetRightMargin(margin)
201 def SetRightMargin(margin)
202 #Set right margin
202 #Set right margin
203 @rMargin=margin
203 @rMargin=margin
204 end
204 end
205
205
206 def SetAutoPageBreak(auto, margin=0)
206 def SetAutoPageBreak(auto, margin=0)
207 # Set auto page break mode and triggering margin
207 # Set auto page break mode and triggering margin
208 @AutoPageBreak=auto
208 @AutoPageBreak=auto
209 @bMargin=margin
209 @bMargin=margin
210 @PageBreakTrigger=@h-margin
210 @PageBreakTrigger=@h-margin
211 end
211 end
212
212
213 def SetDisplayMode(zoom, layout='continuous')
213 def SetDisplayMode(zoom, layout='continuous')
214 # Set display mode in viewer
214 # Set display mode in viewer
215 if zoom=='fullpage' or zoom=='fullwidth' or zoom=='real' or
215 if zoom=='fullpage' or zoom=='fullwidth' or zoom=='real' or
216 zoom=='default' or not zoom.kind_of? String
216 zoom=='default' or not zoom.kind_of? String
217
217
218 @ZoomMode=zoom;
218 @ZoomMode=zoom;
219 elsif zoom=='zoom'
219 elsif zoom=='zoom'
220 @ZoomMode=layout
220 @ZoomMode=layout
221 else
221 else
222 raise 'Incorrect zoom display mode: '+zoom
222 raise 'Incorrect zoom display mode: '+zoom
223 end
223 end
224 if layout=='single' or layout=='continuous' or layout=='two' or
224 if layout=='single' or layout=='continuous' or layout=='two' or
225 layout=='default'
225 layout=='default'
226
226
227 @LayoutMode=layout
227 @LayoutMode=layout
228 elsif zoom!='zoom'
228 elsif zoom!='zoom'
229 raise 'Incorrect layout display mode: '+layout
229 raise 'Incorrect layout display mode: '+layout
230 end
230 end
231 end
231 end
232
232
233 def SetCompression(compress)
233 def SetCompression(compress)
234 # Set page compression
234 # Set page compression
235 @compress = compress
235 @compress = compress
236 end
236 end
237
237
238 def SetTitle(title)
238 def SetTitle(title)
239 # Title of document
239 # Title of document
240 @title=title
240 @title=title
241 end
241 end
242
242
243 def SetSubject(subject)
243 def SetSubject(subject)
244 # Subject of document
244 # Subject of document
245 @subject=subject
245 @subject=subject
246 end
246 end
247
247
248 def SetAuthor(author)
248 def SetAuthor(author)
249 # Author of document
249 # Author of document
250 @author=author
250 @author=author
251 end
251 end
252
252
253 def SetKeywords(keywords)
253 def SetKeywords(keywords)
254 # Keywords of document
254 # Keywords of document
255 @keywords=keywords
255 @keywords=keywords
256 end
256 end
257
257
258 def SetCreator(creator)
258 def SetCreator(creator)
259 # Creator of document
259 # Creator of document
260 @creator=creator
260 @creator=creator
261 end
261 end
262
262
263 def AliasNbPages(aliasnb='{nb}')
263 def AliasNbPages(aliasnb='{nb}')
264 # Define an alias for total number of pages
264 # Define an alias for total number of pages
265 @AliasNbPages=aliasnb
265 @AliasNbPages=aliasnb
266 end
266 end
267
267
268 def Error(msg)
268 def Error(msg)
269 raise 'FPDF error: '+msg
269 raise 'FPDF error: '+msg
270 end
270 end
271
271
272 def Open
272 def Open
273 # Begin document
273 # Begin document
274 @state=1
274 @state=1
275 end
275 end
276
276
277 def Close
277 def Close
278 # Terminate document
278 # Terminate document
279 return if @state==3
279 return if @state==3
280 self.AddPage if @page==0
280 self.AddPage if @page==0
281 # Page footer
281 # Page footer
282 @InFooter=true
282 @InFooter=true
283 self.Footer
283 self.Footer
284 @InFooter=false
284 @InFooter=false
285 # Close page
285 # Close page
286 endpage
286 endpage
287 # Close document
287 # Close document
288 enddoc
288 enddoc
289 end
289 end
290
290
291 def AddPage(orientation='')
291 def AddPage(orientation='')
292 # Start a new page
292 # Start a new page
293 self.Open if @state==0
293 self.Open if @state==0
294 family=@FontFamily
294 family=@FontFamily
295 style=@FontStyle+(@underline ? 'U' : '')
295 style=@FontStyle+(@underline ? 'U' : '')
296 size=@FontSizePt
296 size=@FontSizePt
297 lw=@LineWidth
297 lw=@LineWidth
298 dc=@DrawColor
298 dc=@DrawColor
299 fc=@FillColor
299 fc=@FillColor
300 tc=@TextColor
300 tc=@TextColor
301 cf=@ColorFlag
301 cf=@ColorFlag
302 if @page>0
302 if @page>0
303 # Page footer
303 # Page footer
304 @InFooter=true
304 @InFooter=true
305 self.Footer
305 self.Footer
306 @InFooter=false
306 @InFooter=false
307 # Close page
307 # Close page
308 endpage
308 endpage
309 end
309 end
310 # Start new page
310 # Start new page
311 beginpage(orientation)
311 beginpage(orientation)
312 # Set line cap style to square
312 # Set line cap style to square
313 out('2 J')
313 out('2 J')
314 # Set line width
314 # Set line width
315 @LineWidth=lw
315 @LineWidth=lw
316 out(sprintf('%.2f w',lw*@k))
316 out(sprintf('%.2f w',lw*@k))
317 # Set font
317 # Set font
318 SetFont(family,style,size) if family
318 SetFont(family,style,size) if family
319 # Set colors
319 # Set colors
320 @DrawColor=dc
320 @DrawColor=dc
321 out(dc) if dc!='0 G'
321 out(dc) if dc!='0 G'
322 @FillColor=fc
322 @FillColor=fc
323 out(fc) if fc!='0 g'
323 out(fc) if fc!='0 g'
324 @TextColor=tc
324 @TextColor=tc
325 @ColorFlag=cf
325 @ColorFlag=cf
326 # Page header
326 # Page header
327 self.Header
327 self.Header
328 # Restore line width
328 # Restore line width
329 if @LineWidth!=lw
329 if @LineWidth!=lw
330 @LineWidth=lw
330 @LineWidth=lw
331 out(sprintf('%.2f w',lw*@k))
331 out(sprintf('%.2f w',lw*@k))
332 end
332 end
333 # Restore font
333 # Restore font
334 self.SetFont(family,style,size) if family
334 self.SetFont(family,style,size) if family
335 # Restore colors
335 # Restore colors
336 if @DrawColor!=dc
336 if @DrawColor!=dc
337 @DrawColor=dc
337 @DrawColor=dc
338 out(dc)
338 out(dc)
339 end
339 end
340 if @FillColor!=fc
340 if @FillColor!=fc
341 @FillColor=fc
341 @FillColor=fc
342 out(fc)
342 out(fc)
343 end
343 end
344 @TextColor=tc
344 @TextColor=tc
345 @ColorFlag=cf
345 @ColorFlag=cf
346 end
346 end
347 alias_method :add_page, :AddPage
347 alias_method :add_page, :AddPage
348
348
349 def Header
349 def Header
350 # To be implemented in your inherited class
350 # To be implemented in your inherited class
351 end
351 end
352
352
353 def Footer
353 def Footer
354 # To be implemented in your inherited class
354 # To be implemented in your inherited class
355 end
355 end
356
356
357 def PageNo
357 def PageNo
358 # Get current page number
358 # Get current page number
359 @page
359 @page
360 end
360 end
361
361
362 def SetDrawColor(r,g=-1,b=-1)
362 def SetDrawColor(r,g=-1,b=-1)
363 # Set color for all stroking operations
363 # Set color for all stroking operations
364 if (r==0 and g==0 and b==0) or g==-1
364 if (r==0 and g==0 and b==0) or g==-1
365 @DrawColor=sprintf('%.3f G',r/255.0)
365 @DrawColor=sprintf('%.3f G',r/255.0)
366 else
366 else
367 @DrawColor=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0)
367 @DrawColor=sprintf('%.3f %.3f %.3f RG',r/255.0,g/255.0,b/255.0)
368 end
368 end
369 out(@DrawColor) if(@page>0)
369 out(@DrawColor) if(@page>0)
370 end
370 end
371
371
372 def SetFillColor(r,g=-1,b=-1)
372 def SetFillColor(r,g=-1,b=-1)
373 # Set color for all filling operations
373 # Set color for all filling operations
374 if (r==0 and g==0 and b==0) or g==-1
374 if (r==0 and g==0 and b==0) or g==-1
375 @FillColor=sprintf('%.3f g',r/255.0)
375 @FillColor=sprintf('%.3f g',r/255.0)
376 else
376 else
377 @FillColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
377 @FillColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
378 end
378 end
379 @ColorFlag=(@FillColor!=@TextColor)
379 @ColorFlag=(@FillColor!=@TextColor)
380 out(@FillColor) if(@page>0)
380 out(@FillColor) if(@page>0)
381 end
381 end
382
382
383 def SetTextColor(r,g=-1,b=-1)
383 def SetTextColor(r,g=-1,b=-1)
384 # Set color for text
384 # Set color for text
385 if (r==0 and g==0 and b==0) or g==-1
385 if (r==0 and g==0 and b==0) or g==-1
386 @TextColor=sprintf('%.3f g',r/255.0)
386 @TextColor=sprintf('%.3f g',r/255.0)
387 else
387 else
388 @TextColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
388 @TextColor=sprintf('%.3f %.3f %.3f rg',r/255.0,g/255.0,b/255.0)
389 end
389 end
390 @ColorFlag=(@FillColor!=@TextColor)
390 @ColorFlag=(@FillColor!=@TextColor)
391 end
391 end
392
392
393 def GetCharWidth(widths, index)
393 def GetCharWidth(widths, index)
394 if index.is_a?(String)
394 if index.is_a?(String)
395 widths[index.ord]
395 widths[index.ord]
396 else
396 else
397 widths[index]
397 widths[index]
398 end
398 end
399 end
399 end
400
400
401 def GetStringWidth(s)
401 def GetStringWidth(s)
402 # Get width of a string in the current font
402 # Get width of a string in the current font
403 cw=@CurrentFont['cw']
403 cw=@CurrentFont['cw']
404 w=0
404 w=0
405 s.each_byte do |c|
405 s.each_byte do |c|
406 w=w+GetCharWidth(cw, c)
406 w=w+GetCharWidth(cw, c)
407 end
407 end
408 w*@FontSize/1000.0
408 w*@FontSize/1000.0
409 end
409 end
410
410
411 def SetLineWidth(width)
411 def SetLineWidth(width)
412 # Set line width
412 # Set line width
413 @LineWidth=width
413 @LineWidth=width
414 out(sprintf('%.2f w',width*@k)) if @page>0
414 out(sprintf('%.2f w',width*@k)) if @page>0
415 end
415 end
416
416
417 def Circle(mid_x, mid_y, radius, style='')
417 def Circle(mid_x, mid_y, radius, style='')
418 mid_y = (@h-mid_y)*@k
418 mid_y = (@h-mid_y)*@k
419 out(sprintf("q\n")) # postscript content in pdf
419 out(sprintf("q\n")) # postscript content in pdf
420 # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc.
420 # init line type etc. with /GSD gs G g (grey) RG rg (RGB) w=line witdh etc.
421 out(sprintf("1 j\n")) # line join
421 out(sprintf("1 j\n")) # line join
422 # translate ("move") circle to mid_y, mid_y
422 # translate ("move") circle to mid_y, mid_y
423 out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y))
423 out(sprintf("1 0 0 1 %f %f cm", mid_x, mid_y))
424 kappa = 0.5522847498307933984022516322796
424 kappa = 0.5522847498307933984022516322796
425 # Quadrant 1
425 # Quadrant 1
426 x_s = 0.0 # 12 o'clock
426 x_s = 0.0 # 12 o'clock
427 y_s = 0.0 + radius
427 y_s = 0.0 + radius
428 x_e = 0.0 + radius # 3 o'clock
428 x_e = 0.0 + radius # 3 o'clock
429 y_e = 0.0
429 y_e = 0.0
430 out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock
430 out(sprintf("%f %f m\n", x_s, y_s)) # move to 12 o'clock
431 # cubic bezier control point 1, start height and kappa * radius to the right
431 # cubic bezier control point 1, start height and kappa * radius to the right
432 bx_e1 = x_s + (radius * kappa)
432 bx_e1 = x_s + (radius * kappa)
433 by_e1 = y_s
433 by_e1 = y_s
434 # cubic bezier control point 2, end and kappa * radius above
434 # cubic bezier control point 2, end and kappa * radius above
435 bx_e2 = x_e
435 bx_e2 = x_e
436 by_e2 = y_e + (radius * kappa)
436 by_e2 = y_e + (radius * kappa)
437 # 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
437 # 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
438 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
438 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
439 # Quadrant 2
439 # Quadrant 2
440 x_s = x_e
440 x_s = x_e
441 y_s = y_e # 3 o'clock
441 y_s = y_e # 3 o'clock
442 x_e = 0.0
442 x_e = 0.0
443 y_e = 0.0 - radius # 6 o'clock
443 y_e = 0.0 - radius # 6 o'clock
444 bx_e1 = x_s # cubic bezier point 1
444 bx_e1 = x_s # cubic bezier point 1
445 by_e1 = y_s - (radius * kappa)
445 by_e1 = y_s - (radius * kappa)
446 bx_e2 = x_e + (radius * kappa) # cubic bezier point 2
446 bx_e2 = x_e + (radius * kappa) # cubic bezier point 2
447 by_e2 = y_e
447 by_e2 = y_e
448 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
448 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
449 # Quadrant 3
449 # Quadrant 3
450 x_s = x_e
450 x_s = x_e
451 y_s = y_e # 6 o'clock
451 y_s = y_e # 6 o'clock
452 x_e = 0.0 - radius
452 x_e = 0.0 - radius
453 y_e = 0.0 # 9 o'clock
453 y_e = 0.0 # 9 o'clock
454 bx_e1 = x_s - (radius * kappa) # cubic bezier point 1
454 bx_e1 = x_s - (radius * kappa) # cubic bezier point 1
455 by_e1 = y_s
455 by_e1 = y_s
456 bx_e2 = x_e # cubic bezier point 2
456 bx_e2 = x_e # cubic bezier point 2
457 by_e2 = y_e - (radius * kappa)
457 by_e2 = y_e - (radius * kappa)
458 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
458 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
459 # Quadrant 4
459 # Quadrant 4
460 x_s = x_e
460 x_s = x_e
461 y_s = y_e # 9 o'clock
461 y_s = y_e # 9 o'clock
462 x_e = 0.0
462 x_e = 0.0
463 y_e = 0.0 + radius # 12 o'clock
463 y_e = 0.0 + radius # 12 o'clock
464 bx_e1 = x_s # cubic bezier point 1
464 bx_e1 = x_s # cubic bezier point 1
465 by_e1 = y_s + (radius * kappa)
465 by_e1 = y_s + (radius * kappa)
466 bx_e2 = x_e - (radius * kappa) # cubic bezier point 2
466 bx_e2 = x_e - (radius * kappa) # cubic bezier point 2
467 by_e2 = y_e
467 by_e2 = y_e
468 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
468 out(sprintf("%f %f %f %f %f %f c\n", bx_e1, by_e1, bx_e2, by_e2, x_e, y_e))
469 if style=='F'
469 if style=='F'
470 op='f'
470 op='f'
471 elsif style=='FD' or style=='DF'
471 elsif style=='FD' or style=='DF'
472 op='b'
472 op='b'
473 else
473 else
474 op='s'
474 op='s'
475 end
475 end
476 out(sprintf("#{op}\n")) # stroke circle, do not fill and close path
476 out(sprintf("#{op}\n")) # stroke circle, do not fill and close path
477 # for filling etc. b, b*, f, f*
477 # for filling etc. b, b*, f, f*
478 out(sprintf("Q\n")) # finish postscript in PDF
478 out(sprintf("Q\n")) # finish postscript in PDF
479 end
479 end
480
480
481 def Line(x1, y1, x2, y2)
481 def Line(x1, y1, x2, y2)
482 # Draw a line
482 # Draw a line
483 out(sprintf('%.2f %.2f m %.2f %.2f l S',
483 out(sprintf('%.2f %.2f m %.2f %.2f l S',
484 x1*@k,(@h-y1)*@k,x2*@k,(@h-y2)*@k))
484 x1*@k,(@h-y1)*@k,x2*@k,(@h-y2)*@k))
485 end
485 end
486
486
487 def Rect(x, y, w, h, style='')
487 def Rect(x, y, w, h, style='')
488 # Draw a rectangle
488 # Draw a rectangle
489 if style=='F'
489 if style=='F'
490 op='f'
490 op='f'
491 elsif style=='FD' or style=='DF'
491 elsif style=='FD' or style=='DF'
492 op='B'
492 op='B'
493 else
493 else
494 op='S'
494 op='S'
495 end
495 end
496 # x y width height re
496 # x y width height re
497 out(sprintf('%.2f %.2f %.2f %.2f re %s', x*@k,(@h-y)*@k,w*@k,-h*@k,op))
497 out(sprintf('%.2f %.2f %.2f %.2f re %s', x*@k,(@h-y)*@k,w*@k,-h*@k,op))
498 end
498 end
499
499
500 def AddFont(family, style='', file='')
500 def AddFont(family, style='', file='')
501 # Add a TrueType or Type1 font
501 # Add a TrueType or Type1 font
502 family = family.downcase
502 family = family.downcase
503 family = 'helvetica' if family == 'arial'
503 family = 'helvetica' if family == 'arial'
504
504
505 style = style.upcase
505 style = style.upcase
506 style = 'BI' if style == 'IB'
506 style = 'BI' if style == 'IB'
507
507
508 fontkey = family + style
508 fontkey = family + style
509
509
510 if @fonts.has_key?(fontkey)
510 if @fonts.has_key?(fontkey)
511 self.Error("Font already added: #{family} #{style}")
511 self.Error("Font already added: #{family} #{style}")
512 end
512 end
513
513
514 file = family.gsub(' ', '') + style.downcase + '.rb' if file == ''
514 file = family.gsub(' ', '') + style.downcase + '.rb' if file == ''
515
515
516 if self.class.const_defined? 'FPDF_FONTPATH'
516 if self.class.const_defined? 'FPDF_FONTPATH'
517 if FPDF_FONTPATH[-1,1] == '/'
517 if FPDF_FONTPATH[-1,1] == '/'
518 file = FPDF_FONTPATH + file
518 file = FPDF_FONTPATH + file
519 else
519 else
520 file = FPDF_FONTPATH + '/' + file
520 file = FPDF_FONTPATH + '/' + file
521 end
521 end
522 end
522 end
523
523
524 # Changed from "require file" to fix bug reported by Hans Allis.
524 # Changed from "require file" to fix bug reported by Hans Allis.
525 load file
525 load file
526
526
527 if FontDef.desc.nil?
527 if FontDef.desc.nil?
528 self.Error("Could not include font definition file #{file}")
528 self.Error("Could not include font definition file #{file}")
529 end
529 end
530
530
531 i = @fonts.length + 1
531 i = @fonts.length + 1
532
532
533 @fonts[fontkey] = {'i' => i,
533 @fonts[fontkey] = {'i' => i,
534 'type' => FontDef.type,
534 'type' => FontDef.type,
535 'name' => FontDef.name,
535 'name' => FontDef.name,
536 'desc' => FontDef.desc,
536 'desc' => FontDef.desc,
537 'up' => FontDef.up,
537 'up' => FontDef.up,
538 'ut' => FontDef.ut,
538 'ut' => FontDef.ut,
539 'cw' => FontDef.cw,
539 'cw' => FontDef.cw,
540 'enc' => FontDef.enc,
540 'enc' => FontDef.enc,
541 'file' => FontDef.file
541 'file' => FontDef.file
542 }
542 }
543
543
544 if FontDef.diff
544 if FontDef.diff
545 # Search existing encodings
545 # Search existing encodings
546 unless @diffs.include?(FontDef.diff)
546 unless @diffs.include?(FontDef.diff)
547 @diffs.push(FontDef.diff)
547 @diffs.push(FontDef.diff)
548 @fonts[fontkey]['diff'] = @diffs.length - 1
548 @fonts[fontkey]['diff'] = @diffs.length - 1
549 end
549 end
550 end
550 end
551
551
552 if FontDef.file
552 if FontDef.file
553 if FontDef.type == 'TrueType'
553 if FontDef.type == 'TrueType'
554 @FontFiles[FontDef.file] = {'length1' => FontDef.originalsize}
554 @FontFiles[FontDef.file] = {'length1' => FontDef.originalsize}
555 else
555 else
556 @FontFiles[FontDef.file] = {'length1' => FontDef.size1, 'length2' => FontDef.size2}
556 @FontFiles[FontDef.file] = {'length1' => FontDef.size1, 'length2' => FontDef.size2}
557 end
557 end
558 end
558 end
559
559
560 return self
560 return self
561 end
561 end
562
562
563 def SetFont(family, style='', size=0)
563 def SetFont(family, style='', size=0)
564 # Select a font; size given in points
564 # Select a font; size given in points
565 family.downcase!
565 family.downcase!
566 family=@FontFamily if family==''
566 family=@FontFamily if family==''
567 if family=='arial'
567 if family=='arial'
568 family='helvetica'
568 family='helvetica'
569 elsif family=='symbol' or family=='zapfdingbats'
569 elsif family=='symbol' or family=='zapfdingbats'
570 style=''
570 style=''
571 end
571 end
572 style.upcase!
572 style.upcase!
573 unless style.index('U').nil?
573 unless style.index('U').nil?
574 @underline=true
574 @underline=true
575 style.gsub!('U','')
575 style.gsub!('U','')
576 else
576 else
577 @underline=false;
577 @underline=false;
578 end
578 end
579 style='BI' if style=='IB'
579 style='BI' if style=='IB'
580 size=@FontSizePt if size==0
580 size=@FontSizePt if size==0
581 # Test if font is already selected
581 # Test if font is already selected
582 return if @FontFamily==family and
582 return if @FontFamily==family and
583 @FontStyle==style and @FontSizePt==size
583 @FontStyle==style and @FontSizePt==size
584 # Test if used for the first time
584 # Test if used for the first time
585 fontkey=family+style
585 fontkey=family+style
586 unless @fonts.has_key?(fontkey)
586 unless @fonts.has_key?(fontkey)
587 if @CoreFonts.has_key?(fontkey)
587 if @CoreFonts.has_key?(fontkey)
588 unless Charwidths.has_key?(fontkey)
588 unless Charwidths.has_key?(fontkey)
589 raise 'Font unavailable'
589 raise 'Font unavailable'
590 end
590 end
591 @fonts[fontkey]={
591 @fonts[fontkey]={
592 'i'=>@fonts.size,
592 'i'=>@fonts.size,
593 'type'=>'core',
593 'type'=>'core',
594 'name'=>@CoreFonts[fontkey],
594 'name'=>@CoreFonts[fontkey],
595 'up'=>-100,
595 'up'=>-100,
596 'ut'=>50,
596 'ut'=>50,
597 'cw'=>Charwidths[fontkey]}
597 'cw'=>Charwidths[fontkey]}
598 else
598 else
599 raise 'Font unavailable'
599 raise 'Font unavailable'
600 end
600 end
601 end
601 end
602
602
603 #Select it
603 #Select it
604 @FontFamily=family
604 @FontFamily=family
605 @FontStyle=style;
605 @FontStyle=style;
606 @FontSizePt=size
606 @FontSizePt=size
607 @FontSize=size/@k;
607 @FontSize=size/@k;
608 @CurrentFont=@fonts[fontkey]
608 @CurrentFont=@fonts[fontkey]
609 if @page>0
609 if @page>0
610 out(sprintf('BT /F%d %.2f Tf ET', @CurrentFont['i'], @FontSizePt))
610 out(sprintf('BT /F%d %.2f Tf ET', @CurrentFont['i'], @FontSizePt))
611 end
611 end
612 end
612 end
613
613
614 def SetFontSize(size)
614 def SetFontSize(size)
615 # Set font size in points
615 # Set font size in points
616 return if @FontSizePt==size
616 return if @FontSizePt==size
617 @FontSizePt=size
617 @FontSizePt=size
618 @FontSize=size/@k
618 @FontSize=size/@k
619 if @page>0
619 if @page>0
620 out(sprintf('BT /F%d %.2f Tf ET',@CurrentFont['i'],@FontSizePt))
620 out(sprintf('BT /F%d %.2f Tf ET',@CurrentFont['i'],@FontSizePt))
621 end
621 end
622 end
622 end
623
623
624 def AddLink
624 def AddLink
625 # Create a new internal link
625 # Create a new internal link
626 @links.push([0, 0])
626 @links.push([0, 0])
627 @links.size
627 @links.size
628 end
628 end
629
629
630 def SetLink(link, y=0, page=-1)
630 def SetLink(link, y=0, page=-1)
631 # Set destination of internal link
631 # Set destination of internal link
632 y=@y if y==-1
632 y=@y if y==-1
633 page=@page if page==-1
633 page=@page if page==-1
634 @links[link]=[page, y]
634 @links[link]=[page, y]
635 end
635 end
636
636
637 def Link(x, y, w, h, link)
637 def Link(x, y, w, h, link)
638 # Put a link on the page
638 # Put a link on the page
639 @PageLinks[@page]=Array.new unless @PageLinks.has_key?(@Page)
639 @PageLinks[@page]=Array.new unless @PageLinks.has_key?(@Page)
640 @PageLinks[@page].push([x*@k,@hPt-y*@k,w*@k,h*@k,link])
640 @PageLinks[@page].push([x*@k,@hPt-y*@k,w*@k,h*@k,link])
641 end
641 end
642
642
643 def Text(x, y, txt)
643 def Text(x, y, txt)
644 # Output a string
644 # Output a string
645 txt.gsub!(')', '\\)')
645 s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*@k,(@h-y)*@k, escape(txt));
646 txt.gsub!('(', '\\(')
647 txt.gsub!('\\', '\\\\')
648 s=sprintf('BT %.2f %.2f Td (%s) Tj ET',x*@k,(@h-y)*@k,txt);
649 s=s+' '+dounderline(x,y,txt) if @underline and txt!=''
646 s=s+' '+dounderline(x,y,txt) if @underline and txt!=''
650 s='q '+@TextColor+' '+s+' Q' if @ColorFlag
647 s='q '+@TextColor+' '+s+' Q' if @ColorFlag
651 out(s)
648 out(s)
652 end
649 end
653
650
654 def AcceptPageBreak
651 def AcceptPageBreak
655 # Accept automatic page break or not
652 # Accept automatic page break or not
656 @AutoPageBreak
653 @AutoPageBreak
657 end
654 end
658
655
659 def BreakThePage?(h)
656 def BreakThePage?(h)
660 if (@y + h) > @PageBreakTrigger and !@InFooter and self.AcceptPageBreak
657 if (@y + h) > @PageBreakTrigger and !@InFooter and self.AcceptPageBreak
661 true
658 true
662 else
659 else
663 false
660 false
664 end
661 end
665 end
662 end
666
663
667 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
664 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
668 # Output a cell
665 # Output a cell
669 if self.BreakThePage?(h)
666 if self.BreakThePage?(h)
670 # Automatic page break
667 # Automatic page break
671 x=@x
668 x=@x
672 ws=@ws
669 ws=@ws
673 if ws>0
670 if ws>0
674 @ws=0
671 @ws=0
675 out('0 Tw')
672 out('0 Tw')
676 end
673 end
677 self.AddPage(@CurOrientation)
674 self.AddPage(@CurOrientation)
678 @x=x
675 @x=x
679 if ws>0
676 if ws>0
680 @ws=ws
677 @ws=ws
681 out(sprintf('%.3f Tw',ws*@k))
678 out(sprintf('%.3f Tw',ws*@k))
682 end
679 end
683 end
680 end
684 w=@w-@rMargin-@x if w==0
681 w=@w-@rMargin-@x if w==0
685 s=''
682 s=''
686 if fill==1 or border==1
683 if fill==1 or border==1
687 if fill==1
684 if fill==1
688 op=(border==1) ? 'B' : 'f'
685 op=(border==1) ? 'B' : 'f'
689 else
686 else
690 op='S'
687 op='S'
691 end
688 end
692 s=sprintf('%.2f %.2f %.2f %.2f re %s ',@x*@k,(@h-@y)*@k,w*@k,-h*@k,op)
689 s=sprintf('%.2f %.2f %.2f %.2f re %s ',@x*@k,(@h-@y)*@k,w*@k,-h*@k,op)
693 end
690 end
694 if border.is_a? String
691 if border.is_a? String
695 x=@x
692 x=@x
696 y=@y
693 y=@y
697 unless border.index('L').nil?
694 unless border.index('L').nil?
698 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
695 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
699 x*@k,(@h-y)*@k,x*@k,(@h-(y+h))*@k)
696 x*@k,(@h-y)*@k,x*@k,(@h-(y+h))*@k)
700 end
697 end
701 unless border.index('T').nil?
698 unless border.index('T').nil?
702 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
699 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
703 x*@k,(@h-y)*@k,(x+w)*@k,(@h-y)*@k)
700 x*@k,(@h-y)*@k,(x+w)*@k,(@h-y)*@k)
704 end
701 end
705 unless border.index('R').nil?
702 unless border.index('R').nil?
706 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
703 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
707 (x+w)*@k,(@h-y)*@k,(x+w)*@k,(@h-(y+h))*@k)
704 (x+w)*@k,(@h-y)*@k,(x+w)*@k,(@h-(y+h))*@k)
708 end
705 end
709 unless border.index('B').nil?
706 unless border.index('B').nil?
710 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
707 s=s+sprintf('%.2f %.2f m %.2f %.2f l S ',
711 x*@k,(@h-(y+h))*@k,(x+w)*@k,(@h-(y+h))*@k)
708 x*@k,(@h-(y+h))*@k,(x+w)*@k,(@h-(y+h))*@k)
712 end
709 end
713 end
710 end
714 if txt!=''
711 if txt!=''
715 if align=='R'
712 if align=='R'
716 dx=w-@cMargin-self.GetStringWidth(txt)
713 dx=w-@cMargin-self.GetStringWidth(txt)
717 elsif align=='C'
714 elsif align=='C'
718 dx=(w-self.GetStringWidth(txt))/2
715 dx=(w-self.GetStringWidth(txt))/2
719 else
716 else
720 dx=@cMargin
717 dx=@cMargin
721 end
718 end
722 txt = txt.gsub(')', '\\)')
723 txt.gsub!('(', '\\(')
724 txt.gsub!('\\', '\\\\')
725 if @ColorFlag
719 if @ColorFlag
726 s=s+'q '+@TextColor+' '
720 s=s+'q '+@TextColor+' '
727 end
721 end
728 s=s+sprintf('BT %.2f %.2f Td (%s) Tj ET',
722 s=s+sprintf('BT %.2f %.2f Td (%s) Tj ET',
729 (@x+dx)*@k,(@h-(@y+0.5*h+0.3*@FontSize))*@k,txt)
723 (@x+dx)*@k,(@h-(@y+0.5*h+0.3*@FontSize))*@k,escape(txt))
730 s=s+' '+dounderline(@x+dx,@y+0.5*h+0.3*@FontSize,txt) if @underline
724 s=s+' '+dounderline(@x+dx,@y+0.5*h+0.3*@FontSize,txt) if @underline
731 s=s+' Q' if @ColorFlag
725 s=s+' Q' if @ColorFlag
732 if link and link != ''
726 if link and link != ''
733 Link(@x+dx,@y+0.5*h-0.5*@FontSize,GetStringWidth(txt),@FontSize,link)
727 Link(@x+dx,@y+0.5*h-0.5*@FontSize,GetStringWidth(txt),@FontSize,link)
734 end
728 end
735 end
729 end
736 out(s) if s
730 out(s) if s
737 @lasth=h
731 @lasth=h
738 if ln>0
732 if ln>0
739 # Go to next line
733 # Go to next line
740 @y=@y+h
734 @y=@y+h
741 @x=@lMargin if ln==1
735 @x=@lMargin if ln==1
742 else
736 else
743 @x=@x+w
737 @x=@x+w
744 end
738 end
745 end
739 end
746
740
747 def MultiCell(w,h,txt,border=0,align='J',fill=0)
741 def MultiCell(w,h,txt,border=0,align='J',fill=0)
748 # Output text with automatic or explicit line breaks
742 # Output text with automatic or explicit line breaks
749 cw=@CurrentFont['cw']
743 cw=@CurrentFont['cw']
750 w=@w-@rMargin-@x if w==0
744 w=@w-@rMargin-@x if w==0
751 wmax=(w-2*@cMargin)*1000/@FontSize
745 wmax=(w-2*@cMargin)*1000/@FontSize
752 s=txt.gsub("\r",'')
746 s=txt.gsub("\r",'')
753 nb=s.length
747 nb=s.length
754 nb=nb-1 if nb>0 and s[nb-1].chr=="\n"
748 nb=nb-1 if nb>0 and s[nb-1].chr=="\n"
755 b=0
749 b=0
756 if border!=0
750 if border!=0
757 if border==1
751 if border==1
758 border='LTRB'
752 border='LTRB'
759 b='LRT'
753 b='LRT'
760 b2='LR'
754 b2='LR'
761 else
755 else
762 b2=''
756 b2=''
763 b2='L' unless border.index('L').nil?
757 b2='L' unless border.index('L').nil?
764 b2=b2+'R' unless border.index('R').nil?
758 b2=b2+'R' unless border.index('R').nil?
765 b=(not border.index('T').nil?) ? (b2+'T') : b2
759 b=(not border.index('T').nil?) ? (b2+'T') : b2
766 end
760 end
767 end
761 end
768 sep=-1
762 sep=-1
769 to_index=0
763 to_index=0
770 from_j=0
764 from_j=0
771 l=0
765 l=0
772 ns=0
766 ns=0
773 nl=1
767 nl=1
774 while to_index<nb
768 while to_index<nb
775 # Get next character
769 # Get next character
776 char=s[to_index]
770 char=s[to_index]
777 if char=="\n"[0]
771 if char=="\n"[0]
778 # Explicit line break
772 # Explicit line break
779 if @ws>0
773 if @ws>0
780 @ws=0
774 @ws=0
781 out('0 Tw')
775 out('0 Tw')
782 end
776 end
783 #Ed Moss
777 #Ed Moss
784 end_i = to_index == 0 ? 0 : to_index - 1
778 end_i = to_index == 0 ? 0 : to_index - 1
785 # Changed from s[from_j..to_index] to fix bug reported by Hans Allis.
779 # Changed from s[from_j..to_index] to fix bug reported by Hans Allis.
786 self.Cell(w,h,s[from_j..end_i],b,2,align,fill)
780 self.Cell(w,h,s[from_j..end_i],b,2,align,fill)
787 #
781 #
788 to_index=to_index+1
782 to_index=to_index+1
789 sep=-1
783 sep=-1
790 from_j=to_index
784 from_j=to_index
791 l=0
785 l=0
792 ns=0
786 ns=0
793 nl=nl+1
787 nl=nl+1
794 b=b2 if border and nl==2
788 b=b2 if border and nl==2
795 else
789 else
796 if char==' '[0]
790 if char==' '[0]
797 sep=to_index
791 sep=to_index
798 ls=l
792 ls=l
799 ns=ns+1
793 ns=ns+1
800 end
794 end
801 l=l+GetCharWidth(cw, char)
795 l=l+GetCharWidth(cw, char)
802 if l>wmax
796 if l>wmax
803 # Automatic line break
797 # Automatic line break
804 if sep==-1
798 if sep==-1
805 to_index=to_index+1 if to_index==from_j
799 to_index=to_index+1 if to_index==from_j
806 if @ws>0
800 if @ws>0
807 @ws=0
801 @ws=0
808 out('0 Tw')
802 out('0 Tw')
809 end
803 end
810 #Ed Moss
804 #Ed Moss
811 self.Cell(w,h,s[from_j..to_index-1],b,2,align,fill)
805 self.Cell(w,h,s[from_j..to_index-1],b,2,align,fill)
812 #
806 #
813 else
807 else
814 if align=='J'
808 if align=='J'
815 @ws=(ns>1) ? (wmax-ls)/1000.0*@FontSize/(ns-1) : 0
809 @ws=(ns>1) ? (wmax-ls)/1000.0*@FontSize/(ns-1) : 0
816 out(sprintf('%.3f Tw',@ws*@k))
810 out(sprintf('%.3f Tw',@ws*@k))
817 end
811 end
818 self.Cell(w,h,s[from_j..sep],b,2,align,fill)
812 self.Cell(w,h,s[from_j..sep],b,2,align,fill)
819 to_index=sep+1
813 to_index=sep+1
820 end
814 end
821 sep=-1
815 sep=-1
822 from_j=to_index
816 from_j=to_index
823 l=0
817 l=0
824 ns=0
818 ns=0
825 nl=nl+1
819 nl=nl+1
826 b=b2 if border and nl==2
820 b=b2 if border and nl==2
827 else
821 else
828 to_index=to_index+1
822 to_index=to_index+1
829 end
823 end
830 end
824 end
831 end
825 end
832
826
833 # Last chunk
827 # Last chunk
834 if @ws>0
828 if @ws>0
835 @ws=0
829 @ws=0
836 out('0 Tw')
830 out('0 Tw')
837 end
831 end
838 b=b+'B' if border!=0 and not border.index('B').nil?
832 b=b+'B' if border!=0 and not border.index('B').nil?
839 self.Cell(w,h,s[from_j..to_index],b,2,align,fill)
833 self.Cell(w,h,s[from_j..to_index],b,2,align,fill)
840 @x=@lMargin
834 @x=@lMargin
841 end
835 end
842
836
843 def Write(h,txt,link='')
837 def Write(h,txt,link='')
844 # Output text in flowing mode
838 # Output text in flowing mode
845 cw=@CurrentFont['cw']
839 cw=@CurrentFont['cw']
846 w=@w-@rMargin-@x
840 w=@w-@rMargin-@x
847 wmax=(w-2*@cMargin)*1000/@FontSize
841 wmax=(w-2*@cMargin)*1000/@FontSize
848 s=txt.gsub("\r",'')
842 s=txt.gsub("\r",'')
849 nb=s.length
843 nb=s.length
850 sep=-1
844 sep=-1
851 i=0
845 i=0
852 j=0
846 j=0
853 l=0
847 l=0
854 nl=1
848 nl=1
855 while i<nb
849 while i<nb
856 # Get next character
850 # Get next character
857 c=s[i]
851 c=s[i]
858 if c=="\n"[0]
852 if c=="\n"[0]
859 # Explicit line break
853 # Explicit line break
860 self.Cell(w,h,s[j,i-j],0,2,'',0,link)
854 self.Cell(w,h,s[j,i-j],0,2,'',0,link)
861 i=i+1
855 i=i+1
862 sep=-1
856 sep=-1
863 j=i
857 j=i
864 l=0
858 l=0
865 if nl==1
859 if nl==1
866 @x=@lMargin
860 @x=@lMargin
867 w=@w-@rMargin-@x
861 w=@w-@rMargin-@x
868 wmax=(w-2*@cMargin)*1000/@FontSize
862 wmax=(w-2*@cMargin)*1000/@FontSize
869 end
863 end
870 nl=nl+1
864 nl=nl+1
871 next
865 next
872 end
866 end
873 if c==' '[0]
867 if c==' '[0]
874 sep=i
868 sep=i
875 ls=l
869 ls=l
876 end
870 end
877 l=l+GetCharWidth(cw, c);
871 l=l+GetCharWidth(cw, c);
878 if l>wmax
872 if l>wmax
879 # Automatic line break
873 # Automatic line break
880 if sep==-1
874 if sep==-1
881 if @x>@lMargin
875 if @x>@lMargin
882 # Move to next line
876 # Move to next line
883 @x=@lMargin
877 @x=@lMargin
884 @y=@y+h
878 @y=@y+h
885 w=@w-@rMargin-@x
879 w=@w-@rMargin-@x
886 wmax=(w-2*@cMargin)*1000/@FontSize
880 wmax=(w-2*@cMargin)*1000/@FontSize
887 i=i+1
881 i=i+1
888 nl=nl+1
882 nl=nl+1
889 next
883 next
890 end
884 end
891 i=i+1 if i==j
885 i=i+1 if i==j
892 self.Cell(w,h,s[j,i-j],0,2,'',0,link)
886 self.Cell(w,h,s[j,i-j],0,2,'',0,link)
893 else
887 else
894 self.Cell(w,h,s[j,sep-j],0,2,'',0,link)
888 self.Cell(w,h,s[j,sep-j],0,2,'',0,link)
895 i=sep+1
889 i=sep+1
896 end
890 end
897 sep=-1
891 sep=-1
898 j=i
892 j=i
899 l=0
893 l=0
900 if nl==1
894 if nl==1
901 @x=@lMargin
895 @x=@lMargin
902 w=@w-@rMargin-@x
896 w=@w-@rMargin-@x
903 wmax=(w-2*@cMargin)*1000/@FontSize
897 wmax=(w-2*@cMargin)*1000/@FontSize
904 end
898 end
905 nl=nl+1
899 nl=nl+1
906 else
900 else
907 i=i+1
901 i=i+1
908 end
902 end
909 end
903 end
910 # Last chunk
904 # Last chunk
911 self.Cell(l/1000.0*@FontSize,h,s[j,i],0,0,'',0,link) if i!=j
905 self.Cell(l/1000.0*@FontSize,h,s[j,i],0,0,'',0,link) if i!=j
912 end
906 end
913
907
914 def Image(file,x,y,w=0,h=0,type='',link='')
908 def Image(file,x,y,w=0,h=0,type='',link='')
915 # Put an image on the page
909 # Put an image on the page
916 unless @images.has_key?(file)
910 unless @images.has_key?(file)
917 # First use of image, get info
911 # First use of image, get info
918 if type==''
912 if type==''
919 pos=file.rindex('.')
913 pos=file.rindex('.')
920 if pos.nil?
914 if pos.nil?
921 self.Error('Image file has no extension and no type was '+
915 self.Error('Image file has no extension and no type was '+
922 'specified: '+file)
916 'specified: '+file)
923 end
917 end
924 type=file[pos+1..-1]
918 type=file[pos+1..-1]
925 end
919 end
926 type.downcase!
920 type.downcase!
927 if type=='jpg' or type=='jpeg'
921 if type=='jpg' or type=='jpeg'
928 info=parsejpg(file)
922 info=parsejpg(file)
929 elsif type=='png'
923 elsif type=='png'
930 info=parsepng(file)
924 info=parsepng(file)
931 else
925 else
932 self.Error('Unsupported image file type: '+type)
926 self.Error('Unsupported image file type: '+type)
933 end
927 end
934 info['i']=@images.length+1
928 info['i']=@images.length+1
935 @images[file]=info
929 @images[file]=info
936 else
930 else
937 info=@images[file]
931 info=@images[file]
938 end
932 end
939 #Ed Moss
933 #Ed Moss
940 if(w==0 && h==0)
934 if(w==0 && h==0)
941 #Put image at 72 dpi
935 #Put image at 72 dpi
942 w=info['w']/@k;
936 w=info['w']/@k;
943 h=info['h']/@k;
937 h=info['h']/@k;
944 end
938 end
945 #
939 #
946 # Automatic width or height calculation
940 # Automatic width or height calculation
947 w=h*info['w']/info['h'] if w==0
941 w=h*info['w']/info['h'] if w==0
948 h=w*info['h']/info['w'] if h==0
942 h=w*info['h']/info['w'] if h==0
949 out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',
943 out(sprintf('q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q',
950 w*@k,h*@k,x*@k,(@h-(y+h))*@k,info['i']))
944 w*@k,h*@k,x*@k,(@h-(y+h))*@k,info['i']))
951 Link(x,y,w,h,link) if link and link != ''
945 Link(x,y,w,h,link) if link and link != ''
952 end
946 end
953
947
954 def Ln(h='')
948 def Ln(h='')
955 # Line feed; default value is last cell height
949 # Line feed; default value is last cell height
956 @x=@lMargin
950 @x=@lMargin
957 if h.kind_of?(String)
951 if h.kind_of?(String)
958 @y=@y+@lasth
952 @y=@y+@lasth
959 else
953 else
960 @y=@y+h
954 @y=@y+h
961 end
955 end
962 end
956 end
963
957
964 def GetX
958 def GetX
965 # Get x position
959 # Get x position
966 @x
960 @x
967 end
961 end
968
962
969 def SetX(x)
963 def SetX(x)
970 # Set x position
964 # Set x position
971 if x>=0
965 if x>=0
972 @x=x
966 @x=x
973 else
967 else
974 @x=@w+x
968 @x=@w+x
975 end
969 end
976 end
970 end
977
971
978 def GetY
972 def GetY
979 # Get y position
973 # Get y position
980 @y
974 @y
981 end
975 end
982
976
983 def SetY(y)
977 def SetY(y)
984 # Set y position and reset x
978 # Set y position and reset x
985 @x=@lMargin
979 @x=@lMargin
986 if y>=0
980 if y>=0
987 @y=y
981 @y=y
988 else
982 else
989 @y=@h+y
983 @y=@h+y
990 end
984 end
991 end
985 end
992
986
993 def SetXY(x,y)
987 def SetXY(x,y)
994 # Set x and y positions
988 # Set x and y positions
995 SetY(y)
989 SetY(y)
996 SetX(x)
990 SetX(x)
997 end
991 end
998
992
999 def Output(file=nil)
993 def Output(file=nil)
1000 # Output PDF to file or return as a string
994 # Output PDF to file or return as a string
1001
995
1002 # Finish document if necessary
996 # Finish document if necessary
1003 self.Close if(@state<3)
997 self.Close if(@state<3)
1004
998
1005 if file.nil?
999 if file.nil?
1006 # Return as a string
1000 # Return as a string
1007 return @buffer
1001 return @buffer
1008 else
1002 else
1009 # Save file locally
1003 # Save file locally
1010 open(file,'wb') do |f|
1004 open(file,'wb') do |f|
1011 f.write(@buffer)
1005 f.write(@buffer)
1012 end
1006 end
1013 end
1007 end
1014 end
1008 end
1015
1009
1016 private
1010 private
1017
1011
1018 def putpages
1012 def putpages
1019 nb=@page
1013 nb=@page
1020 unless @AliasNbPages.nil? or @AliasNbPages==''
1014 unless @AliasNbPages.nil? or @AliasNbPages==''
1021 # Replace number of pages
1015 # Replace number of pages
1022 1.upto(nb) do |n|
1016 1.upto(nb) do |n|
1023 @pages[n].gsub!(@AliasNbPages,nb.to_s)
1017 @pages[n].gsub!(@AliasNbPages,nb.to_s)
1024 end
1018 end
1025 end
1019 end
1026 if @DefOrientation=='P'
1020 if @DefOrientation=='P'
1027 wPt=@fwPt
1021 wPt=@fwPt
1028 hPt=@fhPt
1022 hPt=@fhPt
1029 else
1023 else
1030 wPt=@fhPt
1024 wPt=@fhPt
1031 hPt=@fwPt
1025 hPt=@fwPt
1032 end
1026 end
1033 filter=(@compress) ? '/Filter /FlateDecode ' : ''
1027 filter=(@compress) ? '/Filter /FlateDecode ' : ''
1034 1.upto(nb) do |n|
1028 1.upto(nb) do |n|
1035 # Page
1029 # Page
1036 newobj
1030 newobj
1037 out('<</Type /Page')
1031 out('<</Type /Page')
1038 out('/Parent 1 0 R')
1032 out('/Parent 1 0 R')
1039 unless @OrientationChanges[n].nil?
1033 unless @OrientationChanges[n].nil?
1040 out(sprintf('/MediaBox [0 0 %.2f %.2f]',hPt,wPt))
1034 out(sprintf('/MediaBox [0 0 %.2f %.2f]',hPt,wPt))
1041 end
1035 end
1042 out('/Resources 2 0 R')
1036 out('/Resources 2 0 R')
1043 if @PageLinks[n]
1037 if @PageLinks[n]
1044 # Links
1038 # Links
1045 annots='/Annots ['
1039 annots='/Annots ['
1046 @PageLinks[n].each do |pl|
1040 @PageLinks[n].each do |pl|
1047 rect=sprintf('%.2f %.2f %.2f %.2f',
1041 rect=sprintf('%.2f %.2f %.2f %.2f',
1048 pl[0],pl[1],pl[0]+pl[2],pl[1]-pl[3])
1042 pl[0],pl[1],pl[0]+pl[2],pl[1]-pl[3])
1049 annots=annots+'<</Type /Annot /Subtype /Link /Rect ['+rect+
1043 annots=annots+'<</Type /Annot /Subtype /Link /Rect ['+rect+
1050 '] /Border [0 0 0] '
1044 '] /Border [0 0 0] '
1051 if pl[4].kind_of?(String)
1045 if pl[4].kind_of?(String)
1052 annots=annots+'/A <</S /URI /URI '+textstring(pl[4])+
1046 annots=annots+'/A <</S /URI /URI '+textstring(pl[4])+
1053 '>>>>'
1047 '>>>>'
1054 else
1048 else
1055 l=@links[pl[4]]
1049 l=@links[pl[4]]
1056 h=@OrientationChanges[l[0]].nil? ? hPt : wPt
1050 h=@OrientationChanges[l[0]].nil? ? hPt : wPt
1057 annots=annots+sprintf(
1051 annots=annots+sprintf(
1058 '/Dest [%d 0 R /XYZ 0 %.2f null]>>',
1052 '/Dest [%d 0 R /XYZ 0 %.2f null]>>',
1059 1+2*l[0],h-l[1]*@k)
1053 1+2*l[0],h-l[1]*@k)
1060 end
1054 end
1061 end
1055 end
1062 out(annots+']')
1056 out(annots+']')
1063 end
1057 end
1064 out('/Contents '+(@n+1).to_s+' 0 R>>')
1058 out('/Contents '+(@n+1).to_s+' 0 R>>')
1065 out('endobj')
1059 out('endobj')
1066 # Page content
1060 # Page content
1067 p=(@compress) ? Zlib::Deflate.deflate(@pages[n]) : @pages[n]
1061 p=(@compress) ? Zlib::Deflate.deflate(@pages[n]) : @pages[n]
1068 newobj
1062 newobj
1069 out('<<'+filter+'/Length '+p.length.to_s+'>>')
1063 out('<<'+filter+'/Length '+p.length.to_s+'>>')
1070 putstream(p)
1064 putstream(p)
1071 out('endobj')
1065 out('endobj')
1072 end
1066 end
1073 # Pages root
1067 # Pages root
1074 @offsets[1]=@buffer.length
1068 @offsets[1]=@buffer.length
1075 out('1 0 obj')
1069 out('1 0 obj')
1076 out('<</Type /Pages')
1070 out('<</Type /Pages')
1077 kids='/Kids ['
1071 kids='/Kids ['
1078 nb.times do |i|
1072 nb.times do |i|
1079 kids=kids+(3+2*i).to_s+' 0 R '
1073 kids=kids+(3+2*i).to_s+' 0 R '
1080 end
1074 end
1081 out(kids+']')
1075 out(kids+']')
1082 out('/Count '+nb.to_s)
1076 out('/Count '+nb.to_s)
1083 out(sprintf('/MediaBox [0 0 %.2f %.2f]',wPt,hPt))
1077 out(sprintf('/MediaBox [0 0 %.2f %.2f]',wPt,hPt))
1084 out('>>')
1078 out('>>')
1085 out('endobj')
1079 out('endobj')
1086 end
1080 end
1087
1081
1088 def putfonts
1082 def putfonts
1089 nf=@n
1083 nf=@n
1090 @diffs.each do |diff|
1084 @diffs.each do |diff|
1091 # Encodings
1085 # Encodings
1092 newobj
1086 newobj
1093 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences '+
1087 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences '+
1094 '['+diff+']>>')
1088 '['+diff+']>>')
1095 out('endobj')
1089 out('endobj')
1096 end
1090 end
1097
1091
1098 @FontFiles.each do |file, info|
1092 @FontFiles.each do |file, info|
1099 # Font file embedding
1093 # Font file embedding
1100 newobj
1094 newobj
1101 @FontFiles[file]['n'] = @n
1095 @FontFiles[file]['n'] = @n
1102
1096
1103 if self.class.const_defined? 'FPDF_FONTPATH' then
1097 if self.class.const_defined? 'FPDF_FONTPATH' then
1104 if FPDF_FONTPATH[-1,1] == '/' then
1098 if FPDF_FONTPATH[-1,1] == '/' then
1105 file = FPDF_FONTPATH + file
1099 file = FPDF_FONTPATH + file
1106 else
1100 else
1107 file = FPDF_FONTPATH + '/' + file
1101 file = FPDF_FONTPATH + '/' + file
1108 end
1102 end
1109 end
1103 end
1110
1104
1111 size = File.size(file)
1105 size = File.size(file)
1112 unless File.exists?(file)
1106 unless File.exists?(file)
1113 Error('Font file not found')
1107 Error('Font file not found')
1114 end
1108 end
1115
1109
1116 out('<</Length ' + size.to_s)
1110 out('<</Length ' + size.to_s)
1117
1111
1118 if file[-2, 2] == '.z' then
1112 if file[-2, 2] == '.z' then
1119 out('/Filter /FlateDecode')
1113 out('/Filter /FlateDecode')
1120 end
1114 end
1121 out('/Length1 ' + info['length1'])
1115 out('/Length1 ' + info['length1'])
1122 out('/Length2 ' + info['length2'] + ' /Length3 0') if info['length2']
1116 out('/Length2 ' + info['length2'] + ' /Length3 0') if info['length2']
1123 out('>>')
1117 out('>>')
1124 open(file, 'rb') do |f|
1118 open(file, 'rb') do |f|
1125 putstream(f.read())
1119 putstream(f.read())
1126 end
1120 end
1127 out('endobj')
1121 out('endobj')
1128 end
1122 end
1129
1123
1130 file = 0
1124 file = 0
1131 @fonts.each do |k, font|
1125 @fonts.each do |k, font|
1132 # Font objects
1126 # Font objects
1133 @fonts[k]['n']=@n+1
1127 @fonts[k]['n']=@n+1
1134 type=font['type']
1128 type=font['type']
1135 name=font['name']
1129 name=font['name']
1136 if type=='core'
1130 if type=='core'
1137 # Standard font
1131 # Standard font
1138 newobj
1132 newobj
1139 out('<</Type /Font')
1133 out('<</Type /Font')
1140 out('/BaseFont /'+name)
1134 out('/BaseFont /'+name)
1141 out('/Subtype /Type1')
1135 out('/Subtype /Type1')
1142 if name!='Symbol' and name!='ZapfDingbats'
1136 if name!='Symbol' and name!='ZapfDingbats'
1143 out('/Encoding /WinAnsiEncoding')
1137 out('/Encoding /WinAnsiEncoding')
1144 end
1138 end
1145 out('>>')
1139 out('>>')
1146 out('endobj')
1140 out('endobj')
1147 elsif type=='Type1' or type=='TrueType'
1141 elsif type=='Type1' or type=='TrueType'
1148 # Additional Type1 or TrueType font
1142 # Additional Type1 or TrueType font
1149 newobj
1143 newobj
1150 out('<</Type /Font')
1144 out('<</Type /Font')
1151 out('/BaseFont /'+name)
1145 out('/BaseFont /'+name)
1152 out('/Subtype /'+type)
1146 out('/Subtype /'+type)
1153 out('/FirstChar 32 /LastChar 255')
1147 out('/FirstChar 32 /LastChar 255')
1154 out('/Widths '+(@n+1).to_s+' 0 R')
1148 out('/Widths '+(@n+1).to_s+' 0 R')
1155 out('/FontDescriptor '+(@n+2).to_s+' 0 R')
1149 out('/FontDescriptor '+(@n+2).to_s+' 0 R')
1156 if font['enc'] and font['enc'] != ''
1150 if font['enc'] and font['enc'] != ''
1157 unless font['diff'].nil?
1151 unless font['diff'].nil?
1158 out('/Encoding '+(nf+font['diff']).to_s+' 0 R')
1152 out('/Encoding '+(nf+font['diff']).to_s+' 0 R')
1159 else
1153 else
1160 out('/Encoding /WinAnsiEncoding')
1154 out('/Encoding /WinAnsiEncoding')
1161 end
1155 end
1162 end
1156 end
1163 out('>>')
1157 out('>>')
1164 out('endobj')
1158 out('endobj')
1165 # Widths
1159 # Widths
1166 newobj
1160 newobj
1167 cw=font['cw']
1161 cw=font['cw']
1168 s='['
1162 s='['
1169 32.upto(255) do |i|
1163 32.upto(255) do |i|
1170 s << GetCharWidth(cw, i).to_s + ' '
1164 s << GetCharWidth(cw, i).to_s + ' '
1171 end
1165 end
1172 out(s+']')
1166 out(s+']')
1173 out('endobj')
1167 out('endobj')
1174 # Descriptor
1168 # Descriptor
1175 newobj
1169 newobj
1176 s='<</Type /FontDescriptor /FontName /'+name
1170 s='<</Type /FontDescriptor /FontName /'+name
1177 font['desc'].each do |k, v|
1171 font['desc'].each do |k, v|
1178 s << ' /'+k+' '+v
1172 s << ' /'+k+' '+v
1179 end
1173 end
1180 file=font['file']
1174 file=font['file']
1181 if file
1175 if file
1182 s << ' /FontFile'+(type=='Type1' ? '' : '2')+' '+
1176 s << ' /FontFile'+(type=='Type1' ? '' : '2')+' '+
1183 @FontFiles[file]['n'].to_s+' 0 R'
1177 @FontFiles[file]['n'].to_s+' 0 R'
1184 end
1178 end
1185 out(s+'>>')
1179 out(s+'>>')
1186 out('endobj')
1180 out('endobj')
1187 else
1181 else
1188 # Allow for additional types
1182 # Allow for additional types
1189 mtd='put'+type.downcase
1183 mtd='put'+type.downcase
1190 unless self.respond_to?(mtd)
1184 unless self.respond_to?(mtd)
1191 self.Error('Unsupported font type: '+type)
1185 self.Error('Unsupported font type: '+type)
1192 end
1186 end
1193 self.send(mtd, font)
1187 self.send(mtd, font)
1194 end
1188 end
1195 end
1189 end
1196 end
1190 end
1197
1191
1198 def putimages
1192 def putimages
1199 filter=(@compress) ? '/Filter /FlateDecode ' : ''
1193 filter=(@compress) ? '/Filter /FlateDecode ' : ''
1200 @images.each do |file, info|
1194 @images.each do |file, info|
1201 newobj
1195 newobj
1202 @images[file]['n']=@n
1196 @images[file]['n']=@n
1203 out('<</Type /XObject')
1197 out('<</Type /XObject')
1204 out('/Subtype /Image')
1198 out('/Subtype /Image')
1205 out('/Width '+info['w'].to_s)
1199 out('/Width '+info['w'].to_s)
1206 out('/Height '+info['h'].to_s)
1200 out('/Height '+info['h'].to_s)
1207 if info['cs']=='Indexed'
1201 if info['cs']=='Indexed'
1208 out("/ColorSpace [/Indexed /DeviceRGB #{info['pal'].length/3-1} #{(@n+1)} 0 R]")
1202 out("/ColorSpace [/Indexed /DeviceRGB #{info['pal'].length/3-1} #{(@n+1)} 0 R]")
1209 else
1203 else
1210 out('/ColorSpace /'+info['cs'])
1204 out('/ColorSpace /'+info['cs'])
1211 if info['cs']=='DeviceCMYK'
1205 if info['cs']=='DeviceCMYK'
1212 out('/Decode [1 0 1 0 1 0 1 0]')
1206 out('/Decode [1 0 1 0 1 0 1 0]')
1213 end
1207 end
1214 end
1208 end
1215 out('/BitsPerComponent '+info['bpc'].to_s)
1209 out('/BitsPerComponent '+info['bpc'].to_s)
1216 out('/Filter /'+info['f']) if info['f']
1210 out('/Filter /'+info['f']) if info['f']
1217 unless info['parms'].nil?
1211 unless info['parms'].nil?
1218 out(info['parms'])
1212 out(info['parms'])
1219 end
1213 end
1220 if info['trns'] and info['trns'].kind_of?(Array)
1214 if info['trns'] and info['trns'].kind_of?(Array)
1221 trns=''
1215 trns=''
1222 info['trns'].length.times do |i|
1216 info['trns'].length.times do |i|
1223 trns=trns+info['trns'][i].to_s+' '+info['trns'][i].to_s+' '
1217 trns=trns+info['trns'][i].to_s+' '+info['trns'][i].to_s+' '
1224 end
1218 end
1225 out('/Mask ['+trns+']')
1219 out('/Mask ['+trns+']')
1226 end
1220 end
1227 out('/Length '+info['data'].length.to_s+'>>')
1221 out('/Length '+info['data'].length.to_s+'>>')
1228 putstream(info['data'])
1222 putstream(info['data'])
1229 @images[file]['data']=nil
1223 @images[file]['data']=nil
1230 out('endobj')
1224 out('endobj')
1231 # Palette
1225 # Palette
1232 if info['cs']=='Indexed'
1226 if info['cs']=='Indexed'
1233 newobj
1227 newobj
1234 pal=(@compress) ? Zlib::Deflate.deflate(info['pal']) : info['pal']
1228 pal=(@compress) ? Zlib::Deflate.deflate(info['pal']) : info['pal']
1235 out('<<'+filter+'/Length '+pal.length.to_s+'>>')
1229 out('<<'+filter+'/Length '+pal.length.to_s+'>>')
1236 putstream(pal)
1230 putstream(pal)
1237 out('endobj')
1231 out('endobj')
1238 end
1232 end
1239 end
1233 end
1240 end
1234 end
1241
1235
1242 def putxobjectdict
1236 def putxobjectdict
1243 @images.each_value do |image|
1237 @images.each_value do |image|
1244 out('/I'+image['i'].to_s+' '+image['n'].to_s+' 0 R')
1238 out('/I'+image['i'].to_s+' '+image['n'].to_s+' 0 R')
1245 end
1239 end
1246 end
1240 end
1247
1241
1248 def putresourcedict
1242 def putresourcedict
1249 out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]')
1243 out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]')
1250 out('/Font <<')
1244 out('/Font <<')
1251 @fonts.each_value do |font|
1245 @fonts.each_value do |font|
1252 out('/F'+font['i'].to_s+' '+font['n'].to_s+' 0 R')
1246 out('/F'+font['i'].to_s+' '+font['n'].to_s+' 0 R')
1253 end
1247 end
1254 out('>>')
1248 out('>>')
1255 out('/XObject <<')
1249 out('/XObject <<')
1256 putxobjectdict
1250 putxobjectdict
1257 out('>>')
1251 out('>>')
1258 end
1252 end
1259
1253
1260 def putresources
1254 def putresources
1261 putfonts
1255 putfonts
1262 putimages
1256 putimages
1263 # Resource dictionary
1257 # Resource dictionary
1264 @offsets[2]=@buffer.length
1258 @offsets[2]=@buffer.length
1265 out('2 0 obj')
1259 out('2 0 obj')
1266 out('<<')
1260 out('<<')
1267 putresourcedict
1261 putresourcedict
1268 out('>>')
1262 out('>>')
1269 out('endobj')
1263 out('endobj')
1270 end
1264 end
1271
1265
1272 def putinfo
1266 def putinfo
1273 out('/Producer '+textstring('Ruby FPDF '+FPDF_VERSION));
1267 out('/Producer '+textstring('Ruby FPDF '+FPDF_VERSION));
1274 unless @title.nil?
1268 unless @title.nil?
1275 out('/Title '+textstring(@title))
1269 out('/Title '+textstring(@title))
1276 end
1270 end
1277 unless @subject.nil?
1271 unless @subject.nil?
1278 out('/Subject '+textstring(@subject))
1272 out('/Subject '+textstring(@subject))
1279 end
1273 end
1280 unless @author.nil?
1274 unless @author.nil?
1281 out('/Author '+textstring(@author))
1275 out('/Author '+textstring(@author))
1282 end
1276 end
1283 unless @keywords.nil?
1277 unless @keywords.nil?
1284 out('/Keywords '+textstring(@keywords))
1278 out('/Keywords '+textstring(@keywords))
1285 end
1279 end
1286 unless @creator.nil?
1280 unless @creator.nil?
1287 out('/Creator '+textstring(@creator))
1281 out('/Creator '+textstring(@creator))
1288 end
1282 end
1289 out('/CreationDate '+textstring('D: '+DateTime.now.to_s))
1283 out('/CreationDate '+textstring('D: '+DateTime.now.to_s))
1290 end
1284 end
1291
1285
1292 def putcatalog
1286 def putcatalog
1293 out('/Type /Catalog')
1287 out('/Type /Catalog')
1294 out('/Pages 1 0 R')
1288 out('/Pages 1 0 R')
1295 if @ZoomMode=='fullpage'
1289 if @ZoomMode=='fullpage'
1296 out('/OpenAction [3 0 R /Fit]')
1290 out('/OpenAction [3 0 R /Fit]')
1297 elsif @ZoomMode=='fullwidth'
1291 elsif @ZoomMode=='fullwidth'
1298 out('/OpenAction [3 0 R /FitH null]')
1292 out('/OpenAction [3 0 R /FitH null]')
1299 elsif @ZoomMode=='real'
1293 elsif @ZoomMode=='real'
1300 out('/OpenAction [3 0 R /XYZ null null 1]')
1294 out('/OpenAction [3 0 R /XYZ null null 1]')
1301 elsif not @ZoomMode.kind_of?(String)
1295 elsif not @ZoomMode.kind_of?(String)
1302 out('/OpenAction [3 0 R /XYZ null null '+(@ZoomMode/100)+']')
1296 out('/OpenAction [3 0 R /XYZ null null '+(@ZoomMode/100)+']')
1303 end
1297 end
1304
1298
1305 if @LayoutMode=='single'
1299 if @LayoutMode=='single'
1306 out('/PageLayout /SinglePage')
1300 out('/PageLayout /SinglePage')
1307 elsif @LayoutMode=='continuous'
1301 elsif @LayoutMode=='continuous'
1308 out('/PageLayout /OneColumn')
1302 out('/PageLayout /OneColumn')
1309 elsif @LayoutMode=='two'
1303 elsif @LayoutMode=='two'
1310 out('/PageLayout /TwoColumnLeft')
1304 out('/PageLayout /TwoColumnLeft')
1311 end
1305 end
1312 end
1306 end
1313
1307
1314 def putheader
1308 def putheader
1315 out('%PDF-'+@PDFVersion)
1309 out('%PDF-'+@PDFVersion)
1316 end
1310 end
1317
1311
1318 def puttrailer
1312 def puttrailer
1319 out('/Size '+(@n+1).to_s)
1313 out('/Size '+(@n+1).to_s)
1320 out('/Root '+@n.to_s+' 0 R')
1314 out('/Root '+@n.to_s+' 0 R')
1321 out('/Info '+(@n-1).to_s+' 0 R')
1315 out('/Info '+(@n-1).to_s+' 0 R')
1322 end
1316 end
1323
1317
1324 def enddoc
1318 def enddoc
1325 putheader
1319 putheader
1326 putpages
1320 putpages
1327 putresources
1321 putresources
1328 # Info
1322 # Info
1329 newobj
1323 newobj
1330 out('<<')
1324 out('<<')
1331 putinfo
1325 putinfo
1332 out('>>')
1326 out('>>')
1333 out('endobj')
1327 out('endobj')
1334 # Catalog
1328 # Catalog
1335 newobj
1329 newobj
1336 out('<<')
1330 out('<<')
1337 putcatalog
1331 putcatalog
1338 out('>>')
1332 out('>>')
1339 out('endobj')
1333 out('endobj')
1340 # Cross-ref
1334 # Cross-ref
1341 o=@buffer.length
1335 o=@buffer.length
1342 out('xref')
1336 out('xref')
1343 out('0 '+(@n+1).to_s)
1337 out('0 '+(@n+1).to_s)
1344 out('0000000000 65535 f ')
1338 out('0000000000 65535 f ')
1345 1.upto(@n) do |i|
1339 1.upto(@n) do |i|
1346 out(sprintf('%010d 00000 n ',@offsets[i]))
1340 out(sprintf('%010d 00000 n ',@offsets[i]))
1347 end
1341 end
1348 # Trailer
1342 # Trailer
1349 out('trailer')
1343 out('trailer')
1350 out('<<')
1344 out('<<')
1351 puttrailer
1345 puttrailer
1352 out('>>')
1346 out('>>')
1353 out('startxref')
1347 out('startxref')
1354 out(o)
1348 out(o)
1355 out('%%EOF')
1349 out('%%EOF')
1356 @state=3
1350 @state=3
1357 end
1351 end
1358
1352
1359 def beginpage(orientation)
1353 def beginpage(orientation)
1360 @page=@page+1
1354 @page=@page+1
1361 @pages[@page]=''
1355 @pages[@page]=''
1362 @state=2
1356 @state=2
1363 @x=@lMargin
1357 @x=@lMargin
1364 @y=@tMargin
1358 @y=@tMargin
1365 @lasth=0
1359 @lasth=0
1366 @FontFamily=''
1360 @FontFamily=''
1367 # Page orientation
1361 # Page orientation
1368 if orientation==''
1362 if orientation==''
1369 orientation=@DefOrientation
1363 orientation=@DefOrientation
1370 else
1364 else
1371 orientation=orientation[0].chr.upcase
1365 orientation=orientation[0].chr.upcase
1372 if orientation!=@DefOrientation
1366 if orientation!=@DefOrientation
1373 @OrientationChanges[@page]=true
1367 @OrientationChanges[@page]=true
1374 end
1368 end
1375 end
1369 end
1376 if orientation!=@CurOrientation
1370 if orientation!=@CurOrientation
1377 # Change orientation
1371 # Change orientation
1378 if orientation=='P'
1372 if orientation=='P'
1379 @wPt=@fwPt
1373 @wPt=@fwPt
1380 @hPt=@fhPt
1374 @hPt=@fhPt
1381 @w=@fw
1375 @w=@fw
1382 @h=@fh
1376 @h=@fh
1383 else
1377 else
1384 @wPt=@fhPt
1378 @wPt=@fhPt
1385 @hPt=@fwPt
1379 @hPt=@fwPt
1386 @w=@fh
1380 @w=@fh
1387 @h=@fw
1381 @h=@fw
1388 end
1382 end
1389 @PageBreakTrigger=@h-@bMargin
1383 @PageBreakTrigger=@h-@bMargin
1390 @CurOrientation=orientation
1384 @CurOrientation=orientation
1391 end
1385 end
1392 end
1386 end
1393
1387
1394 def endpage
1388 def endpage
1395 # End of page contents
1389 # End of page contents
1396 @state=1
1390 @state=1
1397 end
1391 end
1398
1392
1399 def newobj
1393 def newobj
1400 # Begin a new object
1394 # Begin a new object
1401 @n=@n+1
1395 @n=@n+1
1402 @offsets[@n]=@buffer.length
1396 @offsets[@n]=@buffer.length
1403 out(@n.to_s+' 0 obj')
1397 out(@n.to_s+' 0 obj')
1404 end
1398 end
1405
1399
1406 def dounderline(x,y,txt)
1400 def dounderline(x,y,txt)
1407 # Underline text
1401 # Underline text
1408 up=@CurrentFont['up']
1402 up=@CurrentFont['up']
1409 ut=@CurrentFont['ut']
1403 ut=@CurrentFont['ut']
1410 w=GetStringWidth(txt)+@ws*txt.count(' ')
1404 w=GetStringWidth(txt)+@ws*txt.count(' ')
1411 sprintf('%.2f %.2f %.2f %.2f re f',
1405 sprintf('%.2f %.2f %.2f %.2f re f',
1412 x*@k,(@h-(y-up/1000.0*@FontSize))*@k,w*@k,-ut/1000.0*@FontSizePt)
1406 x*@k,(@h-(y-up/1000.0*@FontSize))*@k,w*@k,-ut/1000.0*@FontSizePt)
1413 end
1407 end
1414
1408
1415 def parsejpg(file)
1409 def parsejpg(file)
1416 # Extract info from a JPEG file
1410 # Extract info from a JPEG file
1417 a=extractjpginfo(file)
1411 a=extractjpginfo(file)
1418 raise "Missing or incorrect JPEG file: #{file}" if a.nil?
1412 raise "Missing or incorrect JPEG file: #{file}" if a.nil?
1419
1413
1420 if a['channels'].nil? || a['channels']==3 then
1414 if a['channels'].nil? || a['channels']==3 then
1421 colspace='DeviceRGB'
1415 colspace='DeviceRGB'
1422 elsif a['channels']==4 then
1416 elsif a['channels']==4 then
1423 colspace='DeviceCMYK'
1417 colspace='DeviceCMYK'
1424 else
1418 else
1425 colspace='DeviceGray'
1419 colspace='DeviceGray'
1426 end
1420 end
1427 bpc= a['bits'] ? a['bits'].to_i : 8
1421 bpc= a['bits'] ? a['bits'].to_i : 8
1428
1422
1429 # Read whole file
1423 # Read whole file
1430 data = nil
1424 data = nil
1431 open(file, 'rb') do |f|
1425 open(file, 'rb') do |f|
1432 data = f.read
1426 data = f.read
1433 end
1427 end
1434 return {'w'=>a['width'],'h'=>a['height'],'cs'=>colspace,'bpc'=>bpc,'f'=>'DCTDecode','data'=>data}
1428 return {'w'=>a['width'],'h'=>a['height'],'cs'=>colspace,'bpc'=>bpc,'f'=>'DCTDecode','data'=>data}
1435 end
1429 end
1436
1430
1437 def parsepng(file)
1431 def parsepng(file)
1438 # Extract info from a PNG file
1432 # Extract info from a PNG file
1439 f=open(file,'rb')
1433 f=open(file,'rb')
1440 # Check signature
1434 # Check signature
1441 unless f.read(8)==137.chr+'PNG'+13.chr+10.chr+26.chr+10.chr
1435 unless f.read(8)==137.chr+'PNG'+13.chr+10.chr+26.chr+10.chr
1442 self.Error('Not a PNG file: '+file)
1436 self.Error('Not a PNG file: '+file)
1443 end
1437 end
1444 # Read header chunk
1438 # Read header chunk
1445 f.read(4)
1439 f.read(4)
1446 if f.read(4)!='IHDR'
1440 if f.read(4)!='IHDR'
1447 self.Error('Incorrect PNG file: '+file)
1441 self.Error('Incorrect PNG file: '+file)
1448 end
1442 end
1449 w=freadint(f)
1443 w=freadint(f)
1450 h=freadint(f)
1444 h=freadint(f)
1451 bpc=f.read(1)[0]
1445 bpc=f.read(1)[0]
1452 if bpc>8
1446 if bpc>8
1453 self.Error('16-bit depth not supported: '+file)
1447 self.Error('16-bit depth not supported: '+file)
1454 end
1448 end
1455 ct=f.read(1)[0]
1449 ct=f.read(1)[0]
1456 if ct==0
1450 if ct==0
1457 colspace='DeviceGray'
1451 colspace='DeviceGray'
1458 elsif ct==2
1452 elsif ct==2
1459 colspace='DeviceRGB'
1453 colspace='DeviceRGB'
1460 elsif ct==3
1454 elsif ct==3
1461 colspace='Indexed'
1455 colspace='Indexed'
1462 else
1456 else
1463 self.Error('Alpha channel not supported: '+file)
1457 self.Error('Alpha channel not supported: '+file)
1464 end
1458 end
1465 if f.read(1)[0]!=0
1459 if f.read(1)[0]!=0
1466 self.Error('Unknown compression method: '+file)
1460 self.Error('Unknown compression method: '+file)
1467 end
1461 end
1468 if f.read(1)[0]!=0
1462 if f.read(1)[0]!=0
1469 self.Error('Unknown filter method: '+file)
1463 self.Error('Unknown filter method: '+file)
1470 end
1464 end
1471 if f.read(1)[0]!=0
1465 if f.read(1)[0]!=0
1472 self.Error('Interlacing not supported: '+file)
1466 self.Error('Interlacing not supported: '+file)
1473 end
1467 end
1474 f.read(4)
1468 f.read(4)
1475 parms='/DecodeParms <</Predictor 15 /Colors '+(ct==2 ? '3' : '1')+
1469 parms='/DecodeParms <</Predictor 15 /Colors '+(ct==2 ? '3' : '1')+
1476 ' /BitsPerComponent '+bpc.to_s+' /Columns '+w.to_s+'>>'
1470 ' /BitsPerComponent '+bpc.to_s+' /Columns '+w.to_s+'>>'
1477 # Scan chunks looking for palette, transparency and image data
1471 # Scan chunks looking for palette, transparency and image data
1478 pal=''
1472 pal=''
1479 trns=''
1473 trns=''
1480 data=''
1474 data=''
1481 begin
1475 begin
1482 n=freadint(f)
1476 n=freadint(f)
1483 type=f.read(4)
1477 type=f.read(4)
1484 if type=='PLTE'
1478 if type=='PLTE'
1485 # Read palette
1479 # Read palette
1486 pal=f.read(n)
1480 pal=f.read(n)
1487 f.read(4)
1481 f.read(4)
1488 elsif type=='tRNS'
1482 elsif type=='tRNS'
1489 # Read transparency info
1483 # Read transparency info
1490 t=f.read(n)
1484 t=f.read(n)
1491 if ct==0
1485 if ct==0
1492 trns=[t[1]]
1486 trns=[t[1]]
1493 elsif ct==2
1487 elsif ct==2
1494 trns=[t[1],t[3],t[5]]
1488 trns=[t[1],t[3],t[5]]
1495 else
1489 else
1496 pos=t.index(0)
1490 pos=t.index(0)
1497 trns=[pos] unless pos.nil?
1491 trns=[pos] unless pos.nil?
1498 end
1492 end
1499 f.read(4)
1493 f.read(4)
1500 elsif type=='IDAT'
1494 elsif type=='IDAT'
1501 # Read image data block
1495 # Read image data block
1502 data << f.read(n)
1496 data << f.read(n)
1503 f.read(4)
1497 f.read(4)
1504 elsif type=='IEND'
1498 elsif type=='IEND'
1505 break
1499 break
1506 else
1500 else
1507 f.read(n+4)
1501 f.read(n+4)
1508 end
1502 end
1509 end while n
1503 end while n
1510 if colspace=='Indexed' and pal==''
1504 if colspace=='Indexed' and pal==''
1511 self.Error('Missing palette in '+file)
1505 self.Error('Missing palette in '+file)
1512 end
1506 end
1513 f.close
1507 f.close
1514 {'w'=>w,'h'=>h,'cs'=>colspace,'bpc'=>bpc,'f'=>'FlateDecode',
1508 {'w'=>w,'h'=>h,'cs'=>colspace,'bpc'=>bpc,'f'=>'FlateDecode',
1515 'parms'=>parms,'pal'=>pal,'trns'=>trns,'data'=>data}
1509 'parms'=>parms,'pal'=>pal,'trns'=>trns,'data'=>data}
1516 end
1510 end
1517
1511
1518 def freadint(f)
1512 def freadint(f)
1519 # Read a 4-byte integer from file
1513 # Read a 4-byte integer from file
1520 a = f.read(4).unpack('N')
1514 a = f.read(4).unpack('N')
1521 return a[0]
1515 return a[0]
1522 end
1516 end
1523
1517
1524 def freadshort(f)
1518 def freadshort(f)
1525 a = f.read(2).unpack('n')
1519 a = f.read(2).unpack('n')
1526 return a[0]
1520 return a[0]
1527 end
1521 end
1528
1522
1529 def freadbyte(f)
1523 def freadbyte(f)
1530 a = f.read(1).unpack('C')
1524 a = f.read(1).unpack('C')
1531 return a[0]
1525 return a[0]
1532 end
1526 end
1533
1527
1534 def textstring(s)
1528 def textstring(s)
1535 # Format a text string
1529 # Format a text string
1536 '('+escape(s)+')'
1530 '('+escape(s)+')'
1537 end
1531 end
1538
1532
1539 def escape(s)
1533 def escape(s)
1540 # Add \ before \, ( and )
1534 # Add \ before \, ( and )
1541 s.gsub('\\','\\\\').gsub('(','\\(').gsub(')','\\)')
1535 s.gsub('\\','\\\\\\').gsub('(','\\(').gsub(')','\\)')
1542 end
1536 end
1543
1537
1544 def putstream(s)
1538 def putstream(s)
1545 out('stream')
1539 out('stream')
1546 out(s)
1540 out(s)
1547 out('endstream')
1541 out('endstream')
1548 end
1542 end
1549
1543
1550 def out(s)
1544 def out(s)
1551 # Add a line to the document
1545 # Add a line to the document
1552 if @state==2
1546 if @state==2
1553 @pages[@page]=@pages[@page]+s+"\n"
1547 @pages[@page]=@pages[@page]+s+"\n"
1554 else
1548 else
1555 @buffer=@buffer+s.to_s+"\n"
1549 @buffer=@buffer+s.to_s+"\n"
1556 end
1550 end
1557 end
1551 end
1558
1552
1559 # jpeg marker codes
1553 # jpeg marker codes
1560
1554
1561 M_SOF0 = 0xc0
1555 M_SOF0 = 0xc0
1562 M_SOF1 = 0xc1
1556 M_SOF1 = 0xc1
1563 M_SOF2 = 0xc2
1557 M_SOF2 = 0xc2
1564 M_SOF3 = 0xc3
1558 M_SOF3 = 0xc3
1565
1559
1566 M_SOF5 = 0xc5
1560 M_SOF5 = 0xc5
1567 M_SOF6 = 0xc6
1561 M_SOF6 = 0xc6
1568 M_SOF7 = 0xc7
1562 M_SOF7 = 0xc7
1569
1563
1570 M_SOF9 = 0xc9
1564 M_SOF9 = 0xc9
1571 M_SOF10 = 0xca
1565 M_SOF10 = 0xca
1572 M_SOF11 = 0xcb
1566 M_SOF11 = 0xcb
1573
1567
1574 M_SOF13 = 0xcd
1568 M_SOF13 = 0xcd
1575 M_SOF14 = 0xce
1569 M_SOF14 = 0xce
1576 M_SOF15 = 0xcf
1570 M_SOF15 = 0xcf
1577
1571
1578 M_SOI = 0xd8
1572 M_SOI = 0xd8
1579 M_EOI = 0xd9
1573 M_EOI = 0xd9
1580 M_SOS = 0xda
1574 M_SOS = 0xda
1581
1575
1582 def extractjpginfo(file)
1576 def extractjpginfo(file)
1583 result = nil
1577 result = nil
1584
1578
1585 open(file, "rb") do |f|
1579 open(file, "rb") do |f|
1586 marker = jpegnextmarker(f)
1580 marker = jpegnextmarker(f)
1587
1581
1588 if marker != M_SOI
1582 if marker != M_SOI
1589 return nil
1583 return nil
1590 end
1584 end
1591
1585
1592 while true
1586 while true
1593 marker = jpegnextmarker(f)
1587 marker = jpegnextmarker(f)
1594
1588
1595 case marker
1589 case marker
1596 when M_SOF0, M_SOF1, M_SOF2, M_SOF3,
1590 when M_SOF0, M_SOF1, M_SOF2, M_SOF3,
1597 M_SOF5, M_SOF6, M_SOF7, M_SOF9,
1591 M_SOF5, M_SOF6, M_SOF7, M_SOF9,
1598 M_SOF10, M_SOF11, M_SOF13, M_SOF14,
1592 M_SOF10, M_SOF11, M_SOF13, M_SOF14,
1599 M_SOF15 then
1593 M_SOF15 then
1600
1594
1601 length = freadshort(f)
1595 length = freadshort(f)
1602
1596
1603 if result.nil?
1597 if result.nil?
1604 result = {}
1598 result = {}
1605
1599
1606 result['bits'] = freadbyte(f)
1600 result['bits'] = freadbyte(f)
1607 result['height'] = freadshort(f)
1601 result['height'] = freadshort(f)
1608 result['width'] = freadshort(f)
1602 result['width'] = freadshort(f)
1609 result['channels'] = freadbyte(f)
1603 result['channels'] = freadbyte(f)
1610
1604
1611 f.seek(length - 8, IO::SEEK_CUR)
1605 f.seek(length - 8, IO::SEEK_CUR)
1612 else
1606 else
1613 f.seek(length - 2, IO::SEEK_CUR)
1607 f.seek(length - 2, IO::SEEK_CUR)
1614 end
1608 end
1615 when M_SOS, M_EOI then
1609 when M_SOS, M_EOI then
1616 return result
1610 return result
1617 else
1611 else
1618 length = freadshort(f)
1612 length = freadshort(f)
1619 f.seek(length - 2, IO::SEEK_CUR)
1613 f.seek(length - 2, IO::SEEK_CUR)
1620 end
1614 end
1621 end
1615 end
1622 end
1616 end
1623 end
1617 end
1624
1618
1625 def jpegnextmarker(f)
1619 def jpegnextmarker(f)
1626 while true
1620 while true
1627 # look for 0xff
1621 # look for 0xff
1628 while (c = freadbyte(f)) != 0xff
1622 while (c = freadbyte(f)) != 0xff
1629 end
1623 end
1630
1624
1631 c = freadbyte(f)
1625 c = freadbyte(f)
1632
1626
1633 if c != 0
1627 if c != 0
1634 return c
1628 return c
1635 end
1629 end
1636 end
1630 end
1637 end
1631 end
1638 end
1632 end
General Comments 0
You need to be logged in to leave comments. Login now