##// END OF EJS Templates
PDF: import CJK patches and all languages use TCPDF (#8312)....
Toshi MARUYAMA -
r5600:29f6dd2a9e77
parent child
Show More
@@ -1,526 +1,518
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 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 if RUBY_VERSION < '1.9'
37 if RUBY_VERSION < '1.9'
38 @ic = Iconv.new(l(:general_pdf_encoding), 'UTF-8')
38 @ic = Iconv.new(l(:general_pdf_encoding), 'UTF-8')
39 end
39 end
40 pdf_encoding = l(:general_pdf_encoding).upcase
40 pdf_encoding = l(:general_pdf_encoding).upcase
41 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
41 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
42 set_language_if_valid lang
42 set_language_if_valid lang
43 case pdf_encoding
43 case pdf_encoding
44 when 'UTF-8'
44 when 'UTF-8'
45 @font_for_content = 'FreeSans'
45 @font_for_content = 'FreeSans'
46 @font_for_footer = 'FreeSans'
46 @font_for_footer = 'FreeSans'
47 when 'CP949'
47 when 'CP949'
48 extend(PDF_Korean)
48 extend(PDF_Korean)
49 AddUHCFont()
49 AddUHCFont()
50 @font_for_content = 'UHC'
50 @font_for_content = 'UHC'
51 @font_for_footer = 'UHC'
51 @font_for_footer = 'UHC'
52 when 'CP932'
52 when 'CP932'
53 extend(PDF_Japanese)
53 extend(PDF_Japanese)
54 AddSJISFont()
54 AddSJISFont()
55 @font_for_content = 'SJIS'
55 @font_for_content = 'SJIS'
56 @font_for_footer = 'SJIS'
56 @font_for_footer = 'SJIS'
57 when 'GB18030'
57 when 'GB18030'
58 extend(PDF_Chinese)
58 extend(PDF_Chinese)
59 AddGBFont()
59 AddGBFont()
60 @font_for_content = 'GB'
60 @font_for_content = 'GB'
61 @font_for_footer = 'GB'
61 @font_for_footer = 'GB'
62 when 'BIG5'
62 when 'BIG5'
63 extend(PDF_Chinese)
63 extend(PDF_Chinese)
64 AddBig5Font()
64 AddBig5Font()
65 @font_for_content = 'Big5'
65 @font_for_content = 'Big5'
66 @font_for_footer = 'Big5'
66 @font_for_footer = 'Big5'
67 else
67 else
68 @font_for_content = 'Arial'
68 @font_for_content = 'Arial'
69 @font_for_footer = 'Helvetica'
69 @font_for_footer = 'Helvetica'
70 end
70 end
71 SetCreator(Redmine::Info.app_name)
71 SetCreator(Redmine::Info.app_name)
72 SetFont(@font_for_content)
72 SetFont(@font_for_content)
73 end
73 end
74
74
75 def SetFontStyle(style, size)
75 def SetFontStyle(style, size)
76 SetFont(@font_for_content, style, size)
76 SetFont(@font_for_content, style, size)
77 end
77 end
78
78
79 def SetTitle(txt)
79 def SetTitle(txt)
80 txt = begin
80 txt = begin
81 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
81 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
82 hextxt = "<FEFF" # FEFF is BOM
82 hextxt = "<FEFF" # FEFF is BOM
83 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
83 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
84 hextxt << ">"
84 hextxt << ">"
85 rescue
85 rescue
86 txt
86 txt
87 end || ''
87 end || ''
88 super(txt)
88 super(txt)
89 end
89 end
90
90
91 def textstring(s)
91 def textstring(s)
92 # Format a text string
92 # Format a text string
93 if s =~ /^</ # This means the string is hex-dumped.
93 if s =~ /^</ # This means the string is hex-dumped.
94 return s
94 return s
95 else
95 else
96 return '('+escape(s)+')'
96 return '('+escape(s)+')'
97 end
97 end
98 end
98 end
99
99
100 def fix_text_encoding(txt)
100 def fix_text_encoding(txt)
101 RDMPdfEncoding::rdm_pdf_iconv(@ic, txt)
101 RDMPdfEncoding::rdm_pdf_iconv(@ic, txt)
102 end
102 end
103
103
104 def RDMCell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
104 def RDMCell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
105 Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
105 Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
106 end
106 end
107
107
108 def RDMMultiCell(w,h=0,txt='',border=0,align='',fill=0)
108 def RDMMultiCell(w,h=0,txt='',border=0,align='',fill=0)
109 MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
109 MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
110 end
110 end
111
111
112 def Footer
112 def Footer
113 SetFont(@font_for_footer, 'I', 8)
113 SetFont(@font_for_footer, 'I', 8)
114 SetY(-15)
114 SetY(-15)
115 SetX(15)
115 SetX(15)
116 RDMCell(0, 5, @footer_date, 0, 0, 'L')
116 RDMCell(0, 5, @footer_date, 0, 0, 'L')
117 SetY(-15)
117 SetY(-15)
118 SetX(-30)
118 SetX(-30)
119 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
119 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
120 end
120 end
121 end
121 end
122
122
123 class IFPDF < FPDF
123 class IFPDF < FPDF
124 include Redmine::I18n
124 include Redmine::I18n
125 attr_accessor :footer_date
125 attr_accessor :footer_date
126
126
127 def initialize(lang)
127 def initialize(lang)
128 super()
128 super()
129 if RUBY_VERSION < '1.9'
129 if RUBY_VERSION < '1.9'
130 @ic = Iconv.new(l(:general_pdf_encoding), 'UTF-8')
130 @ic = Iconv.new(l(:general_pdf_encoding), 'UTF-8')
131 end
131 end
132 set_language_if_valid lang
132 set_language_if_valid lang
133 case l(:general_pdf_encoding).upcase
133 case l(:general_pdf_encoding).upcase
134 when 'CP949'
134 when 'CP949'
135 extend(PDF_Korean)
135 extend(PDF_Korean)
136 AddUHCFont()
136 AddUHCFont()
137 @font_for_content = 'UHC'
137 @font_for_content = 'UHC'
138 @font_for_footer = 'UHC'
138 @font_for_footer = 'UHC'
139 when 'CP932'
139 when 'CP932'
140 extend(PDF_Japanese)
140 extend(PDF_Japanese)
141 AddSJISFont()
141 AddSJISFont()
142 @font_for_content = 'SJIS'
142 @font_for_content = 'SJIS'
143 @font_for_footer = 'SJIS'
143 @font_for_footer = 'SJIS'
144 when 'GB18030'
144 when 'GB18030'
145 extend(PDF_Chinese)
145 extend(PDF_Chinese)
146 AddGBFont()
146 AddGBFont()
147 @font_for_content = 'GB'
147 @font_for_content = 'GB'
148 @font_for_footer = 'GB'
148 @font_for_footer = 'GB'
149 when 'BIG5'
149 when 'BIG5'
150 extend(PDF_Chinese)
150 extend(PDF_Chinese)
151 AddBig5Font()
151 AddBig5Font()
152 @font_for_content = 'Big5'
152 @font_for_content = 'Big5'
153 @font_for_footer = 'Big5'
153 @font_for_footer = 'Big5'
154 else
154 else
155 @font_for_content = 'Arial'
155 @font_for_content = 'Arial'
156 @font_for_footer = 'Helvetica'
156 @font_for_footer = 'Helvetica'
157 end
157 end
158 SetCreator(Redmine::Info.app_name)
158 SetCreator(Redmine::Info.app_name)
159 SetFont(@font_for_content)
159 SetFont(@font_for_content)
160 end
160 end
161
161
162 def SetFontStyle(style, size)
162 def SetFontStyle(style, size)
163 SetFont(@font_for_content, style, size)
163 SetFont(@font_for_content, style, size)
164 end
164 end
165
165
166 def SetTitle(txt)
166 def SetTitle(txt)
167 txt = begin
167 txt = begin
168 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
168 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
169 hextxt = "<FEFF" # FEFF is BOM
169 hextxt = "<FEFF" # FEFF is BOM
170 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
170 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
171 hextxt << ">"
171 hextxt << ">"
172 rescue
172 rescue
173 txt
173 txt
174 end || ''
174 end || ''
175 super(txt)
175 super(txt)
176 end
176 end
177
177
178 def textstring(s)
178 def textstring(s)
179 # Format a text string
179 # Format a text string
180 if s =~ /^</ # This means the string is hex-dumped.
180 if s =~ /^</ # This means the string is hex-dumped.
181 return s
181 return s
182 else
182 else
183 return '('+escape(s)+')'
183 return '('+escape(s)+')'
184 end
184 end
185 end
185 end
186
186
187 def fix_text_encoding(txt)
187 def fix_text_encoding(txt)
188 RDMPdfEncoding::rdm_pdf_iconv(@ic, txt)
188 RDMPdfEncoding::rdm_pdf_iconv(@ic, txt)
189 end
189 end
190
190
191 def RDMCell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
191 def RDMCell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
192 Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
192 Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
193 end
193 end
194
194
195 def RDMMultiCell(w,h=0,txt='',border=0,align='',fill=0)
195 def RDMMultiCell(w,h=0,txt='',border=0,align='',fill=0)
196 MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
196 MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
197 end
197 end
198
198
199 def Footer
199 def Footer
200 SetFont(@font_for_footer, 'I', 8)
200 SetFont(@font_for_footer, 'I', 8)
201 SetY(-15)
201 SetY(-15)
202 SetX(15)
202 SetX(15)
203 RDMCell(0, 5, @footer_date, 0, 0, 'L')
203 RDMCell(0, 5, @footer_date, 0, 0, 'L')
204 SetY(-15)
204 SetY(-15)
205 SetX(-30)
205 SetX(-30)
206 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
206 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
207 end
207 end
208 alias alias_nb_pages AliasNbPages
208 alias alias_nb_pages AliasNbPages
209 end
209 end
210
210
211 # Returns a PDF string of a list of issues
211 # Returns a PDF string of a list of issues
212 def issues_to_pdf(issues, project, query)
212 def issues_to_pdf(issues, project, query)
213 if l(:general_pdf_encoding).upcase != 'UTF-8'
213 pdf = ITCPDF.new(current_language)
214 pdf = IFPDF.new(current_language)
215 else
216 pdf = ITCPDF.new(current_language)
217 end
218 title = query.new_record? ? l(:label_issue_plural) : query.name
214 title = query.new_record? ? l(:label_issue_plural) : query.name
219 title = "#{project} - #{title}" if project
215 title = "#{project} - #{title}" if project
220 pdf.SetTitle(title)
216 pdf.SetTitle(title)
221 pdf.alias_nb_pages
217 pdf.alias_nb_pages
222 pdf.footer_date = format_date(Date.today)
218 pdf.footer_date = format_date(Date.today)
223 pdf.SetAutoPageBreak(false)
219 pdf.SetAutoPageBreak(false)
224 pdf.AddPage("L")
220 pdf.AddPage("L")
225
221
226 # Landscape A4 = 210 x 297 mm
222 # Landscape A4 = 210 x 297 mm
227 page_height = 210
223 page_height = 210
228 page_width = 297
224 page_width = 297
229 right_margin = 10
225 right_margin = 10
230 bottom_margin = 20
226 bottom_margin = 20
231 col_id_width = 10
227 col_id_width = 10
232 row_height = 5
228 row_height = 5
233
229
234 # column widths
230 # column widths
235 table_width = page_width - right_margin - 10 # fixed left margin
231 table_width = page_width - right_margin - 10 # fixed left margin
236 col_width = []
232 col_width = []
237 unless query.columns.empty?
233 unless query.columns.empty?
238 col_width = query.columns.collect do |c|
234 col_width = query.columns.collect do |c|
239 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) && ['string', 'text'].include?(c.custom_field.field_format)))? 4.0 : 1.0
235 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) && ['string', 'text'].include?(c.custom_field.field_format)))? 4.0 : 1.0
240 end
236 end
241 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
237 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w}
242 col_width = col_width.collect {|w| w * ratio}
238 col_width = col_width.collect {|w| w * ratio}
243 end
239 end
244
240
245 # title
241 # title
246 pdf.SetFontStyle('B',11)
242 pdf.SetFontStyle('B',11)
247 pdf.RDMCell(190,10, title)
243 pdf.RDMCell(190,10, title)
248 pdf.Ln
244 pdf.Ln
249
245
250 # headers
246 # headers
251 pdf.SetFontStyle('B',8)
247 pdf.SetFontStyle('B',8)
252 pdf.SetFillColor(230, 230, 230)
248 pdf.SetFillColor(230, 230, 230)
253
249
254 # render it background to find the max height used
250 # render it background to find the max height used
255 base_x = pdf.GetX
251 base_x = pdf.GetX
256 base_y = pdf.GetY
252 base_y = pdf.GetY
257 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
253 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
258 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
254 pdf.Rect(base_x, base_y, table_width, max_height, 'FD');
259 pdf.SetXY(base_x, base_y);
255 pdf.SetXY(base_x, base_y);
260
256
261 # write the cells on page
257 # write the cells on page
262 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
258 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
263 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
259 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
264 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
260 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
265 pdf.SetY(base_y + max_height);
261 pdf.SetY(base_y + max_height);
266
262
267 # rows
263 # rows
268 pdf.SetFontStyle('',8)
264 pdf.SetFontStyle('',8)
269 pdf.SetFillColor(255, 255, 255)
265 pdf.SetFillColor(255, 255, 255)
270 previous_group = false
266 previous_group = false
271 issues.each do |issue|
267 issues.each do |issue|
272 if query.grouped? &&
268 if query.grouped? &&
273 (group = query.group_by_column.value(issue)) != previous_group
269 (group = query.group_by_column.value(issue)) != previous_group
274 pdf.SetFontStyle('B',9)
270 pdf.SetFontStyle('B',9)
275 pdf.RDMCell(277, row_height,
271 pdf.RDMCell(277, row_height,
276 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
272 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
277 1, 1, 'L')
273 1, 1, 'L')
278 pdf.SetFontStyle('',8)
274 pdf.SetFontStyle('',8)
279 previous_group = group
275 previous_group = group
280 end
276 end
281 # fetch all the row values
277 # fetch all the row values
282 col_values = query.columns.collect do |column|
278 col_values = query.columns.collect do |column|
283 s = if column.is_a?(QueryCustomFieldColumn)
279 s = if column.is_a?(QueryCustomFieldColumn)
284 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
280 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
285 show_value(cv)
281 show_value(cv)
286 else
282 else
287 value = issue.send(column.name)
283 value = issue.send(column.name)
288 if value.is_a?(Date)
284 if value.is_a?(Date)
289 format_date(value)
285 format_date(value)
290 elsif value.is_a?(Time)
286 elsif value.is_a?(Time)
291 format_time(value)
287 format_time(value)
292 else
288 else
293 value
289 value
294 end
290 end
295 end
291 end
296 s.to_s
292 s.to_s
297 end
293 end
298
294
299 # render it off-page to find the max height used
295 # render it off-page to find the max height used
300 base_x = pdf.GetX
296 base_x = pdf.GetX
301 base_y = pdf.GetY
297 base_y = pdf.GetY
302 pdf.SetY(2 * page_height)
298 pdf.SetY(2 * page_height)
303 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
299 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
304 pdf.SetXY(base_x, base_y)
300 pdf.SetXY(base_x, base_y)
305
301
306 # make new page if it doesn't fit on the current one
302 # make new page if it doesn't fit on the current one
307 space_left = page_height - base_y - bottom_margin
303 space_left = page_height - base_y - bottom_margin
308 if max_height > space_left
304 if max_height > space_left
309 pdf.AddPage("L")
305 pdf.AddPage("L")
310 base_x = pdf.GetX
306 base_x = pdf.GetX
311 base_y = pdf.GetY
307 base_y = pdf.GetY
312 end
308 end
313
309
314 # write the cells on page
310 # write the cells on page
315 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
311 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
316 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
312 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
317 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
313 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
318 pdf.SetY(base_y + max_height);
314 pdf.SetY(base_y + max_height);
319 end
315 end
320
316
321 if issues.size == Setting.issues_export_limit.to_i
317 if issues.size == Setting.issues_export_limit.to_i
322 pdf.SetFontStyle('B',10)
318 pdf.SetFontStyle('B',10)
323 pdf.RDMCell(0, row_height, '...')
319 pdf.RDMCell(0, row_height, '...')
324 end
320 end
325 pdf.Output
321 pdf.Output
326 end
322 end
327
323
328 # Renders MultiCells and returns the maximum height used
324 # Renders MultiCells and returns the maximum height used
329 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
325 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
330 row_height, head=false)
326 row_height, head=false)
331 base_y = pdf.GetY
327 base_y = pdf.GetY
332 max_height = row_height
328 max_height = row_height
333 col_values.each_with_index do |column, i|
329 col_values.each_with_index do |column, i|
334 col_x = pdf.GetX
330 col_x = pdf.GetX
335 if head == true
331 if head == true
336 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
332 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
337 else
333 else
338 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
334 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
339 end
335 end
340 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
336 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
341 pdf.SetXY(col_x + col_widths[i], base_y);
337 pdf.SetXY(col_x + col_widths[i], base_y);
342 end
338 end
343 return max_height
339 return max_height
344 end
340 end
345
341
346 # Draw lines to close the row (MultiCell border drawing in not uniform)
342 # Draw lines to close the row (MultiCell border drawing in not uniform)
347 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
343 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
348 id_width, col_widths)
344 id_width, col_widths)
349 col_x = top_x + id_width
345 col_x = top_x + id_width
350 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
346 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
351 col_widths.each do |width|
347 col_widths.each do |width|
352 col_x += width
348 col_x += width
353 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
349 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
354 end
350 end
355 pdf.Line(top_x, top_y, top_x, lower_y) # left border
351 pdf.Line(top_x, top_y, top_x, lower_y) # left border
356 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
352 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
357 end
353 end
358
354
359 # Returns a PDF string of a single issue
355 # Returns a PDF string of a single issue
360 def issue_to_pdf(issue)
356 def issue_to_pdf(issue)
361 if l(:general_pdf_encoding).upcase != 'UTF-8'
357 pdf = ITCPDF.new(current_language)
362 pdf = IFPDF.new(current_language)
363 else
364 pdf = ITCPDF.new(current_language)
365 end
366 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
358 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
367 pdf.alias_nb_pages
359 pdf.alias_nb_pages
368 pdf.footer_date = format_date(Date.today)
360 pdf.footer_date = format_date(Date.today)
369 pdf.AddPage
361 pdf.AddPage
370 pdf.SetFontStyle('B',11)
362 pdf.SetFontStyle('B',11)
371 pdf.RDMMultiCell(190,5,
363 pdf.RDMMultiCell(190,5,
372 "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
364 "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
373 pdf.Ln
365 pdf.Ln
374
366
375 y0 = pdf.GetY
367 y0 = pdf.GetY
376
368
377 pdf.SetFontStyle('B',9)
369 pdf.SetFontStyle('B',9)
378 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
370 pdf.RDMCell(35,5, l(:field_status) + ":","LT")
379 pdf.SetFontStyle('',9)
371 pdf.SetFontStyle('',9)
380 pdf.RDMCell(60,5, issue.status.to_s,"RT")
372 pdf.RDMCell(60,5, issue.status.to_s,"RT")
381 pdf.SetFontStyle('B',9)
373 pdf.SetFontStyle('B',9)
382 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
374 pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
383 pdf.SetFontStyle('',9)
375 pdf.SetFontStyle('',9)
384 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
376 pdf.RDMCell(60,5, issue.priority.to_s,"RT")
385 pdf.Ln
377 pdf.Ln
386
378
387 pdf.SetFontStyle('B',9)
379 pdf.SetFontStyle('B',9)
388 pdf.RDMCell(35,5, l(:field_author) + ":","L")
380 pdf.RDMCell(35,5, l(:field_author) + ":","L")
389 pdf.SetFontStyle('',9)
381 pdf.SetFontStyle('',9)
390 pdf.RDMCell(60,5, issue.author.to_s,"R")
382 pdf.RDMCell(60,5, issue.author.to_s,"R")
391 pdf.SetFontStyle('B',9)
383 pdf.SetFontStyle('B',9)
392 pdf.RDMCell(35,5, l(:field_category) + ":","L")
384 pdf.RDMCell(35,5, l(:field_category) + ":","L")
393 pdf.SetFontStyle('',9)
385 pdf.SetFontStyle('',9)
394 pdf.RDMCell(60,5, issue.category.to_s,"R")
386 pdf.RDMCell(60,5, issue.category.to_s,"R")
395 pdf.Ln
387 pdf.Ln
396
388
397 pdf.SetFontStyle('B',9)
389 pdf.SetFontStyle('B',9)
398 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
390 pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
399 pdf.SetFontStyle('',9)
391 pdf.SetFontStyle('',9)
400 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
392 pdf.RDMCell(60,5, format_date(issue.created_on),"R")
401 pdf.SetFontStyle('B',9)
393 pdf.SetFontStyle('B',9)
402 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
394 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
403 pdf.SetFontStyle('',9)
395 pdf.SetFontStyle('',9)
404 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
396 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
405 pdf.Ln
397 pdf.Ln
406
398
407 pdf.SetFontStyle('B',9)
399 pdf.SetFontStyle('B',9)
408 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
400 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
409 pdf.SetFontStyle('',9)
401 pdf.SetFontStyle('',9)
410 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
402 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
411 pdf.SetFontStyle('B',9)
403 pdf.SetFontStyle('B',9)
412 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
404 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
413 pdf.SetFontStyle('',9)
405 pdf.SetFontStyle('',9)
414 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
406 pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
415 pdf.Ln
407 pdf.Ln
416
408
417 for custom_value in issue.custom_field_values
409 for custom_value in issue.custom_field_values
418 pdf.SetFontStyle('B',9)
410 pdf.SetFontStyle('B',9)
419 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
411 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
420 pdf.SetFontStyle('',9)
412 pdf.SetFontStyle('',9)
421 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
413 pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
422 end
414 end
423
415
424 pdf.SetFontStyle('B',9)
416 pdf.SetFontStyle('B',9)
425 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
417 pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
426 pdf.SetFontStyle('',9)
418 pdf.SetFontStyle('',9)
427 pdf.RDMMultiCell(155,5, issue.subject,"RT")
419 pdf.RDMMultiCell(155,5, issue.subject,"RT")
428
420
429 pdf.SetFontStyle('B',9)
421 pdf.SetFontStyle('B',9)
430 pdf.RDMCell(35,5, l(:field_description) + ":","LT")
422 pdf.RDMCell(35,5, l(:field_description) + ":","LT")
431 pdf.SetFontStyle('',9)
423 pdf.SetFontStyle('',9)
432 pdf.RDMMultiCell(155,5, issue.description.to_s,"RT")
424 pdf.RDMMultiCell(155,5, issue.description.to_s,"RT")
433
425
434 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
426 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
435 pdf.Line(pdf.GetX, pdf.GetY, pdf.GetX + 190, pdf.GetY)
427 pdf.Line(pdf.GetX, pdf.GetY, pdf.GetX + 190, pdf.GetY)
436 pdf.Ln
428 pdf.Ln
437
429
438 if issue.changesets.any? &&
430 if issue.changesets.any? &&
439 User.current.allowed_to?(:view_changesets, issue.project)
431 User.current.allowed_to?(:view_changesets, issue.project)
440 pdf.SetFontStyle('B',9)
432 pdf.SetFontStyle('B',9)
441 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
433 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
442 pdf.Ln
434 pdf.Ln
443 for changeset in issue.changesets
435 for changeset in issue.changesets
444 pdf.SetFontStyle('B',8)
436 pdf.SetFontStyle('B',8)
445 pdf.RDMCell(190,5,
437 pdf.RDMCell(190,5,
446 format_time(changeset.committed_on) + " - " + changeset.author.to_s)
438 format_time(changeset.committed_on) + " - " + changeset.author.to_s)
447 pdf.Ln
439 pdf.Ln
448 unless changeset.comments.blank?
440 unless changeset.comments.blank?
449 pdf.SetFontStyle('',8)
441 pdf.SetFontStyle('',8)
450 pdf.RDMMultiCell(190,5, changeset.comments.to_s)
442 pdf.RDMMultiCell(190,5, changeset.comments.to_s)
451 end
443 end
452 pdf.Ln
444 pdf.Ln
453 end
445 end
454 end
446 end
455
447
456 pdf.SetFontStyle('B',9)
448 pdf.SetFontStyle('B',9)
457 pdf.RDMCell(190,5, l(:label_history), "B")
449 pdf.RDMCell(190,5, l(:label_history), "B")
458 pdf.Ln
450 pdf.Ln
459 for journal in issue.journals.find(
451 for journal in issue.journals.find(
460 :all, :include => [:user, :details],
452 :all, :include => [:user, :details],
461 :order => "#{Journal.table_name}.created_on ASC")
453 :order => "#{Journal.table_name}.created_on ASC")
462 pdf.SetFontStyle('B',8)
454 pdf.SetFontStyle('B',8)
463 pdf.RDMCell(190,5,
455 pdf.RDMCell(190,5,
464 format_time(journal.created_on) + " - " + journal.user.name)
456 format_time(journal.created_on) + " - " + journal.user.name)
465 pdf.Ln
457 pdf.Ln
466 pdf.SetFontStyle('I',8)
458 pdf.SetFontStyle('I',8)
467 for detail in journal.details
459 for detail in journal.details
468 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
460 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true))
469 end
461 end
470 if journal.notes?
462 if journal.notes?
471 pdf.Ln unless journal.details.empty?
463 pdf.Ln unless journal.details.empty?
472 pdf.SetFontStyle('',8)
464 pdf.SetFontStyle('',8)
473 pdf.RDMMultiCell(190,5, journal.notes.to_s)
465 pdf.RDMMultiCell(190,5, journal.notes.to_s)
474 end
466 end
475 pdf.Ln
467 pdf.Ln
476 end
468 end
477
469
478 if issue.attachments.any?
470 if issue.attachments.any?
479 pdf.SetFontStyle('B',9)
471 pdf.SetFontStyle('B',9)
480 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
472 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
481 pdf.Ln
473 pdf.Ln
482 for attachment in issue.attachments
474 for attachment in issue.attachments
483 pdf.SetFontStyle('',8)
475 pdf.SetFontStyle('',8)
484 pdf.RDMCell(80,5, attachment.filename)
476 pdf.RDMCell(80,5, attachment.filename)
485 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
477 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
486 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
478 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
487 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
479 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
488 pdf.Ln
480 pdf.Ln
489 end
481 end
490 end
482 end
491 pdf.Output
483 pdf.Output
492 end
484 end
493
485
494 class RDMPdfEncoding
486 class RDMPdfEncoding
495 include Redmine::I18n
487 include Redmine::I18n
496 def self.rdm_pdf_iconv(ic, txt)
488 def self.rdm_pdf_iconv(ic, txt)
497 txt ||= ''
489 txt ||= ''
498 if txt.respond_to?(:force_encoding)
490 if txt.respond_to?(:force_encoding)
499 txt.force_encoding('UTF-8')
491 txt.force_encoding('UTF-8')
500 if l(:general_pdf_encoding).upcase != 'UTF-8'
492 if l(:general_pdf_encoding).upcase != 'UTF-8'
501 txt = txt.encode(l(:general_pdf_encoding), :invalid => :replace,
493 txt = txt.encode(l(:general_pdf_encoding), :invalid => :replace,
502 :undef => :replace, :replace => '?')
494 :undef => :replace, :replace => '?')
503 else
495 else
504 txt = Redmine::CodesetUtil.replace_invalid_utf8(txt)
496 txt = Redmine::CodesetUtil.replace_invalid_utf8(txt)
505 end
497 end
506 txt.force_encoding('ASCII-8BIT')
498 txt.force_encoding('ASCII-8BIT')
507 else
499 else
508 ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
500 ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
509 txtar = ""
501 txtar = ""
510 begin
502 begin
511 txtar += ic.iconv(txt)
503 txtar += ic.iconv(txt)
512 rescue Iconv::IllegalSequence
504 rescue Iconv::IllegalSequence
513 txtar += $!.success
505 txtar += $!.success
514 txt = '?' + $!.failed[1,$!.failed.length]
506 txt = '?' + $!.failed[1,$!.failed.length]
515 retry
507 retry
516 rescue
508 rescue
517 txtar += $!.success
509 txtar += $!.success
518 end
510 end
519 txt = txtar
511 txt = txtar
520 end
512 end
521 txt
513 txt
522 end
514 end
523 end
515 end
524 end
516 end
525 end
517 end
526 end
518 end
@@ -1,864 +1,860
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 module Redmine
18 module Redmine
19 module Helpers
19 module Helpers
20 # Simple class to handle gantt chart data
20 # Simple class to handle gantt chart data
21 class Gantt
21 class Gantt
22 include ERB::Util
22 include ERB::Util
23 include Redmine::I18n
23 include Redmine::I18n
24
24
25 # :nodoc:
25 # :nodoc:
26 # Some utility methods for the PDF export
26 # Some utility methods for the PDF export
27 class PDF
27 class PDF
28 MaxCharactorsForSubject = 45
28 MaxCharactorsForSubject = 45
29 TotalWidth = 280
29 TotalWidth = 280
30 LeftPaneWidth = 100
30 LeftPaneWidth = 100
31
31
32 def self.right_pane_width
32 def self.right_pane_width
33 TotalWidth - LeftPaneWidth
33 TotalWidth - LeftPaneWidth
34 end
34 end
35 end
35 end
36
36
37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
38 attr_accessor :query
38 attr_accessor :query
39 attr_accessor :project
39 attr_accessor :project
40 attr_accessor :view
40 attr_accessor :view
41
41
42 def initialize(options={})
42 def initialize(options={})
43 options = options.dup
43 options = options.dup
44
44
45 if options[:year] && options[:year].to_i >0
45 if options[:year] && options[:year].to_i >0
46 @year_from = options[:year].to_i
46 @year_from = options[:year].to_i
47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
48 @month_from = options[:month].to_i
48 @month_from = options[:month].to_i
49 else
49 else
50 @month_from = 1
50 @month_from = 1
51 end
51 end
52 else
52 else
53 @month_from ||= Date.today.month
53 @month_from ||= Date.today.month
54 @year_from ||= Date.today.year
54 @year_from ||= Date.today.year
55 end
55 end
56
56
57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
59 months = (options[:months] || User.current.pref[:gantt_months]).to_i
59 months = (options[:months] || User.current.pref[:gantt_months]).to_i
60 @months = (months > 0 && months < 25) ? months : 6
60 @months = (months > 0 && months < 25) ? months : 6
61
61
62 # Save gantt parameters as user preference (zoom and months count)
62 # Save gantt parameters as user preference (zoom and months count)
63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
65 User.current.preference.save
65 User.current.preference.save
66 end
66 end
67
67
68 @date_from = Date.civil(@year_from, @month_from, 1)
68 @date_from = Date.civil(@year_from, @month_from, 1)
69 @date_to = (@date_from >> @months) - 1
69 @date_to = (@date_from >> @months) - 1
70
70
71 @subjects = ''
71 @subjects = ''
72 @lines = ''
72 @lines = ''
73 @number_of_rows = nil
73 @number_of_rows = nil
74
74
75 @issue_ancestors = []
75 @issue_ancestors = []
76
76
77 @truncated = false
77 @truncated = false
78 if options.has_key?(:max_rows)
78 if options.has_key?(:max_rows)
79 @max_rows = options[:max_rows]
79 @max_rows = options[:max_rows]
80 else
80 else
81 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
81 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
82 end
82 end
83 end
83 end
84
84
85 def common_params
85 def common_params
86 { :controller => 'gantts', :action => 'show', :project_id => @project }
86 { :controller => 'gantts', :action => 'show', :project_id => @project }
87 end
87 end
88
88
89 def params
89 def params
90 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
90 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
91 end
91 end
92
92
93 def params_previous
93 def params_previous
94 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
94 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
95 end
95 end
96
96
97 def params_next
97 def params_next
98 common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
98 common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
99 end
99 end
100
100
101 # Returns the number of rows that will be rendered on the Gantt chart
101 # Returns the number of rows that will be rendered on the Gantt chart
102 def number_of_rows
102 def number_of_rows
103 return @number_of_rows if @number_of_rows
103 return @number_of_rows if @number_of_rows
104
104
105 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
105 rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
106 rows > @max_rows ? @max_rows : rows
106 rows > @max_rows ? @max_rows : rows
107 end
107 end
108
108
109 # Returns the number of rows that will be used to list a project on
109 # Returns the number of rows that will be used to list a project on
110 # the Gantt chart. This will recurse for each subproject.
110 # the Gantt chart. This will recurse for each subproject.
111 def number_of_rows_on_project(project)
111 def number_of_rows_on_project(project)
112 return 0 unless projects.include?(project)
112 return 0 unless projects.include?(project)
113
113
114 count = 1
114 count = 1
115 count += project_issues(project).size
115 count += project_issues(project).size
116 count += project_versions(project).size
116 count += project_versions(project).size
117 count
117 count
118 end
118 end
119
119
120 # Renders the subjects of the Gantt chart, the left side.
120 # Renders the subjects of the Gantt chart, the left side.
121 def subjects(options={})
121 def subjects(options={})
122 render(options.merge(:only => :subjects)) unless @subjects_rendered
122 render(options.merge(:only => :subjects)) unless @subjects_rendered
123 @subjects
123 @subjects
124 end
124 end
125
125
126 # Renders the lines of the Gantt chart, the right side
126 # Renders the lines of the Gantt chart, the right side
127 def lines(options={})
127 def lines(options={})
128 render(options.merge(:only => :lines)) unless @lines_rendered
128 render(options.merge(:only => :lines)) unless @lines_rendered
129 @lines
129 @lines
130 end
130 end
131
131
132 # Returns issues that will be rendered
132 # Returns issues that will be rendered
133 def issues
133 def issues
134 @issues ||= @query.issues(
134 @issues ||= @query.issues(
135 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
135 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
136 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
136 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
137 :limit => @max_rows
137 :limit => @max_rows
138 )
138 )
139 end
139 end
140
140
141 # Return all the project nodes that will be displayed
141 # Return all the project nodes that will be displayed
142 def projects
142 def projects
143 return @projects if @projects
143 return @projects if @projects
144
144
145 ids = issues.collect(&:project).uniq.collect(&:id)
145 ids = issues.collect(&:project).uniq.collect(&:id)
146 if ids.any?
146 if ids.any?
147 # All issues projects and their visible ancestors
147 # All issues projects and their visible ancestors
148 @projects = Project.visible.all(
148 @projects = Project.visible.all(
149 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
149 :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
150 :conditions => ["child.id IN (?)", ids],
150 :conditions => ["child.id IN (?)", ids],
151 :order => "#{Project.table_name}.lft ASC"
151 :order => "#{Project.table_name}.lft ASC"
152 ).uniq
152 ).uniq
153 else
153 else
154 @projects = []
154 @projects = []
155 end
155 end
156 end
156 end
157
157
158 # Returns the issues that belong to +project+
158 # Returns the issues that belong to +project+
159 def project_issues(project)
159 def project_issues(project)
160 @issues_by_project ||= issues.group_by(&:project)
160 @issues_by_project ||= issues.group_by(&:project)
161 @issues_by_project[project] || []
161 @issues_by_project[project] || []
162 end
162 end
163
163
164 # Returns the distinct versions of the issues that belong to +project+
164 # Returns the distinct versions of the issues that belong to +project+
165 def project_versions(project)
165 def project_versions(project)
166 project_issues(project).collect(&:fixed_version).compact.uniq
166 project_issues(project).collect(&:fixed_version).compact.uniq
167 end
167 end
168
168
169 # Returns the issues that belong to +project+ and are assigned to +version+
169 # Returns the issues that belong to +project+ and are assigned to +version+
170 def version_issues(project, version)
170 def version_issues(project, version)
171 project_issues(project).select {|issue| issue.fixed_version == version}
171 project_issues(project).select {|issue| issue.fixed_version == version}
172 end
172 end
173
173
174 def render(options={})
174 def render(options={})
175 options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
175 options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
176 indent = options[:indent] || 4
176 indent = options[:indent] || 4
177
177
178 @subjects = '' unless options[:only] == :lines
178 @subjects = '' unless options[:only] == :lines
179 @lines = '' unless options[:only] == :subjects
179 @lines = '' unless options[:only] == :subjects
180 @number_of_rows = 0
180 @number_of_rows = 0
181
181
182 Project.project_tree(projects) do |project, level|
182 Project.project_tree(projects) do |project, level|
183 options[:indent] = indent + level * options[:indent_increment]
183 options[:indent] = indent + level * options[:indent_increment]
184 render_project(project, options)
184 render_project(project, options)
185 break if abort?
185 break if abort?
186 end
186 end
187
187
188 @subjects_rendered = true unless options[:only] == :lines
188 @subjects_rendered = true unless options[:only] == :lines
189 @lines_rendered = true unless options[:only] == :subjects
189 @lines_rendered = true unless options[:only] == :subjects
190
190
191 render_end(options)
191 render_end(options)
192 end
192 end
193
193
194 def render_project(project, options={})
194 def render_project(project, options={})
195 subject_for_project(project, options) unless options[:only] == :lines
195 subject_for_project(project, options) unless options[:only] == :lines
196 line_for_project(project, options) unless options[:only] == :subjects
196 line_for_project(project, options) unless options[:only] == :subjects
197
197
198 options[:top] += options[:top_increment]
198 options[:top] += options[:top_increment]
199 options[:indent] += options[:indent_increment]
199 options[:indent] += options[:indent_increment]
200 @number_of_rows += 1
200 @number_of_rows += 1
201 return if abort?
201 return if abort?
202
202
203 issues = project_issues(project).select {|i| i.fixed_version.nil?}
203 issues = project_issues(project).select {|i| i.fixed_version.nil?}
204 sort_issues!(issues)
204 sort_issues!(issues)
205 if issues
205 if issues
206 render_issues(issues, options)
206 render_issues(issues, options)
207 return if abort?
207 return if abort?
208 end
208 end
209
209
210 versions = project_versions(project)
210 versions = project_versions(project)
211 versions.each do |version|
211 versions.each do |version|
212 render_version(project, version, options)
212 render_version(project, version, options)
213 end
213 end
214
214
215 # Remove indent to hit the next sibling
215 # Remove indent to hit the next sibling
216 options[:indent] -= options[:indent_increment]
216 options[:indent] -= options[:indent_increment]
217 end
217 end
218
218
219 def render_issues(issues, options={})
219 def render_issues(issues, options={})
220 @issue_ancestors = []
220 @issue_ancestors = []
221
221
222 issues.each do |i|
222 issues.each do |i|
223 subject_for_issue(i, options) unless options[:only] == :lines
223 subject_for_issue(i, options) unless options[:only] == :lines
224 line_for_issue(i, options) unless options[:only] == :subjects
224 line_for_issue(i, options) unless options[:only] == :subjects
225
225
226 options[:top] += options[:top_increment]
226 options[:top] += options[:top_increment]
227 @number_of_rows += 1
227 @number_of_rows += 1
228 break if abort?
228 break if abort?
229 end
229 end
230
230
231 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
231 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
232 end
232 end
233
233
234 def render_version(project, version, options={})
234 def render_version(project, version, options={})
235 # Version header
235 # Version header
236 subject_for_version(version, options) unless options[:only] == :lines
236 subject_for_version(version, options) unless options[:only] == :lines
237 line_for_version(version, options) unless options[:only] == :subjects
237 line_for_version(version, options) unless options[:only] == :subjects
238
238
239 options[:top] += options[:top_increment]
239 options[:top] += options[:top_increment]
240 @number_of_rows += 1
240 @number_of_rows += 1
241 return if abort?
241 return if abort?
242
242
243 issues = version_issues(project, version)
243 issues = version_issues(project, version)
244 if issues
244 if issues
245 sort_issues!(issues)
245 sort_issues!(issues)
246 # Indent issues
246 # Indent issues
247 options[:indent] += options[:indent_increment]
247 options[:indent] += options[:indent_increment]
248 render_issues(issues, options)
248 render_issues(issues, options)
249 options[:indent] -= options[:indent_increment]
249 options[:indent] -= options[:indent_increment]
250 end
250 end
251 end
251 end
252
252
253 def render_end(options={})
253 def render_end(options={})
254 case options[:format]
254 case options[:format]
255 when :pdf
255 when :pdf
256 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
256 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
257 end
257 end
258 end
258 end
259
259
260 def subject_for_project(project, options)
260 def subject_for_project(project, options)
261 case options[:format]
261 case options[:format]
262 when :html
262 when :html
263 subject = "<span class='icon icon-projects #{project.overdue? ? 'project-overdue' : ''}'>"
263 subject = "<span class='icon icon-projects #{project.overdue? ? 'project-overdue' : ''}'>"
264 subject << view.link_to_project(project)
264 subject << view.link_to_project(project)
265 subject << '</span>'
265 subject << '</span>'
266 html_subject(options, subject, :css => "project-name")
266 html_subject(options, subject, :css => "project-name")
267 when :image
267 when :image
268 image_subject(options, project.name)
268 image_subject(options, project.name)
269 when :pdf
269 when :pdf
270 pdf_new_page?(options)
270 pdf_new_page?(options)
271 pdf_subject(options, project.name)
271 pdf_subject(options, project.name)
272 end
272 end
273 end
273 end
274
274
275 def line_for_project(project, options)
275 def line_for_project(project, options)
276 # Skip versions that don't have a start_date or due date
276 # Skip versions that don't have a start_date or due date
277 if project.is_a?(Project) && project.start_date && project.due_date
277 if project.is_a?(Project) && project.start_date && project.due_date
278 options[:zoom] ||= 1
278 options[:zoom] ||= 1
279 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
279 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
280
280
281 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
281 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
282 label = h(project)
282 label = h(project)
283
283
284 case options[:format]
284 case options[:format]
285 when :html
285 when :html
286 html_task(options, coords, :css => "project task", :label => label, :markers => true)
286 html_task(options, coords, :css => "project task", :label => label, :markers => true)
287 when :image
287 when :image
288 image_task(options, coords, :label => label, :markers => true, :height => 3)
288 image_task(options, coords, :label => label, :markers => true, :height => 3)
289 when :pdf
289 when :pdf
290 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
290 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
291 end
291 end
292 else
292 else
293 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
293 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
294 ''
294 ''
295 end
295 end
296 end
296 end
297
297
298 def subject_for_version(version, options)
298 def subject_for_version(version, options)
299 case options[:format]
299 case options[:format]
300 when :html
300 when :html
301 subject = "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
301 subject = "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
302 subject << view.link_to_version(version)
302 subject << view.link_to_version(version)
303 subject << '</span>'
303 subject << '</span>'
304 html_subject(options, subject, :css => "version-name")
304 html_subject(options, subject, :css => "version-name")
305 when :image
305 when :image
306 image_subject(options, version.to_s_with_project)
306 image_subject(options, version.to_s_with_project)
307 when :pdf
307 when :pdf
308 pdf_new_page?(options)
308 pdf_new_page?(options)
309 pdf_subject(options, version.to_s_with_project)
309 pdf_subject(options, version.to_s_with_project)
310 end
310 end
311 end
311 end
312
312
313 def line_for_version(version, options)
313 def line_for_version(version, options)
314 # Skip versions that don't have a start_date
314 # Skip versions that don't have a start_date
315 if version.is_a?(Version) && version.start_date && version.due_date
315 if version.is_a?(Version) && version.start_date && version.due_date
316 options[:zoom] ||= 1
316 options[:zoom] ||= 1
317 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
317 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
318
318
319 coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
319 coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
320 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
320 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
321 label = h("#{version.project} -") + label unless @project && @project == version.project
321 label = h("#{version.project} -") + label unless @project && @project == version.project
322
322
323 case options[:format]
323 case options[:format]
324 when :html
324 when :html
325 html_task(options, coords, :css => "version task", :label => label, :markers => true)
325 html_task(options, coords, :css => "version task", :label => label, :markers => true)
326 when :image
326 when :image
327 image_task(options, coords, :label => label, :markers => true, :height => 3)
327 image_task(options, coords, :label => label, :markers => true, :height => 3)
328 when :pdf
328 when :pdf
329 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
329 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
330 end
330 end
331 else
331 else
332 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
332 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
333 ''
333 ''
334 end
334 end
335 end
335 end
336
336
337 def subject_for_issue(issue, options)
337 def subject_for_issue(issue, options)
338 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
338 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
339 @issue_ancestors.pop
339 @issue_ancestors.pop
340 options[:indent] -= options[:indent_increment]
340 options[:indent] -= options[:indent_increment]
341 end
341 end
342
342
343 output = case options[:format]
343 output = case options[:format]
344 when :html
344 when :html
345 css_classes = ''
345 css_classes = ''
346 css_classes << ' issue-overdue' if issue.overdue?
346 css_classes << ' issue-overdue' if issue.overdue?
347 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
347 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
348 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
348 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
349
349
350 subject = "<span class='#{css_classes}'>"
350 subject = "<span class='#{css_classes}'>"
351 if issue.assigned_to.present?
351 if issue.assigned_to.present?
352 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
352 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
353 subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string).to_s
353 subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string).to_s
354 end
354 end
355 subject << view.link_to_issue(issue)
355 subject << view.link_to_issue(issue)
356 subject << '</span>'
356 subject << '</span>'
357 html_subject(options, subject, :css => "issue-subject", :title => issue.subject) + "\n"
357 html_subject(options, subject, :css => "issue-subject", :title => issue.subject) + "\n"
358 when :image
358 when :image
359 image_subject(options, issue.subject)
359 image_subject(options, issue.subject)
360 when :pdf
360 when :pdf
361 pdf_new_page?(options)
361 pdf_new_page?(options)
362 pdf_subject(options, issue.subject)
362 pdf_subject(options, issue.subject)
363 end
363 end
364
364
365 unless issue.leaf?
365 unless issue.leaf?
366 @issue_ancestors << issue
366 @issue_ancestors << issue
367 options[:indent] += options[:indent_increment]
367 options[:indent] += options[:indent_increment]
368 end
368 end
369
369
370 output
370 output
371 end
371 end
372
372
373 def line_for_issue(issue, options)
373 def line_for_issue(issue, options)
374 # Skip issues that don't have a due_before (due_date or version's due_date)
374 # Skip issues that don't have a due_before (due_date or version's due_date)
375 if issue.is_a?(Issue) && issue.due_before
375 if issue.is_a?(Issue) && issue.due_before
376 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
376 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
377 label = "#{ issue.status.name } #{ issue.done_ratio }%"
377 label = "#{ issue.status.name } #{ issue.done_ratio }%"
378
378
379 case options[:format]
379 case options[:format]
380 when :html
380 when :html
381 html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
381 html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
382 when :image
382 when :image
383 image_task(options, coords, :label => label)
383 image_task(options, coords, :label => label)
384 when :pdf
384 when :pdf
385 pdf_task(options, coords, :label => label)
385 pdf_task(options, coords, :label => label)
386 end
386 end
387 else
387 else
388 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
388 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
389 ''
389 ''
390 end
390 end
391 end
391 end
392
392
393 # Generates a gantt image
393 # Generates a gantt image
394 # Only defined if RMagick is avalaible
394 # Only defined if RMagick is avalaible
395 def to_image(format='PNG')
395 def to_image(format='PNG')
396 date_to = (@date_from >> @months)-1
396 date_to = (@date_from >> @months)-1
397 show_weeks = @zoom > 1
397 show_weeks = @zoom > 1
398 show_days = @zoom > 2
398 show_days = @zoom > 2
399
399
400 subject_width = 400
400 subject_width = 400
401 header_height = 18
401 header_height = 18
402 # width of one day in pixels
402 # width of one day in pixels
403 zoom = @zoom*2
403 zoom = @zoom*2
404 g_width = (@date_to - @date_from + 1)*zoom
404 g_width = (@date_to - @date_from + 1)*zoom
405 g_height = 20 * number_of_rows + 30
405 g_height = 20 * number_of_rows + 30
406 headers_height = (show_weeks ? 2*header_height : header_height)
406 headers_height = (show_weeks ? 2*header_height : header_height)
407 height = g_height + headers_height
407 height = g_height + headers_height
408
408
409 imgl = Magick::ImageList.new
409 imgl = Magick::ImageList.new
410 imgl.new_image(subject_width+g_width+1, height)
410 imgl.new_image(subject_width+g_width+1, height)
411 gc = Magick::Draw.new
411 gc = Magick::Draw.new
412
412
413 # Subjects
413 # Subjects
414 gc.stroke('transparent')
414 gc.stroke('transparent')
415 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
415 subjects(:image => gc, :top => (headers_height + 20), :indent => 4, :format => :image)
416
416
417 # Months headers
417 # Months headers
418 month_f = @date_from
418 month_f = @date_from
419 left = subject_width
419 left = subject_width
420 @months.times do
420 @months.times do
421 width = ((month_f >> 1) - month_f) * zoom
421 width = ((month_f >> 1) - month_f) * zoom
422 gc.fill('white')
422 gc.fill('white')
423 gc.stroke('grey')
423 gc.stroke('grey')
424 gc.stroke_width(1)
424 gc.stroke_width(1)
425 gc.rectangle(left, 0, left + width, height)
425 gc.rectangle(left, 0, left + width, height)
426 gc.fill('black')
426 gc.fill('black')
427 gc.stroke('transparent')
427 gc.stroke('transparent')
428 gc.stroke_width(1)
428 gc.stroke_width(1)
429 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
429 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
430 left = left + width
430 left = left + width
431 month_f = month_f >> 1
431 month_f = month_f >> 1
432 end
432 end
433
433
434 # Weeks headers
434 # Weeks headers
435 if show_weeks
435 if show_weeks
436 left = subject_width
436 left = subject_width
437 height = header_height
437 height = header_height
438 if @date_from.cwday == 1
438 if @date_from.cwday == 1
439 # date_from is monday
439 # date_from is monday
440 week_f = date_from
440 week_f = date_from
441 else
441 else
442 # find next monday after date_from
442 # find next monday after date_from
443 week_f = @date_from + (7 - @date_from.cwday + 1)
443 week_f = @date_from + (7 - @date_from.cwday + 1)
444 width = (7 - @date_from.cwday + 1) * zoom
444 width = (7 - @date_from.cwday + 1) * zoom
445 gc.fill('white')
445 gc.fill('white')
446 gc.stroke('grey')
446 gc.stroke('grey')
447 gc.stroke_width(1)
447 gc.stroke_width(1)
448 gc.rectangle(left, header_height, left + width, 2*header_height + g_height-1)
448 gc.rectangle(left, header_height, left + width, 2*header_height + g_height-1)
449 left = left + width
449 left = left + width
450 end
450 end
451 while week_f <= date_to
451 while week_f <= date_to
452 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
452 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
453 gc.fill('white')
453 gc.fill('white')
454 gc.stroke('grey')
454 gc.stroke('grey')
455 gc.stroke_width(1)
455 gc.stroke_width(1)
456 gc.rectangle(left.round, header_height, left.round + width, 2*header_height + g_height-1)
456 gc.rectangle(left.round, header_height, left.round + width, 2*header_height + g_height-1)
457 gc.fill('black')
457 gc.fill('black')
458 gc.stroke('transparent')
458 gc.stroke('transparent')
459 gc.stroke_width(1)
459 gc.stroke_width(1)
460 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
460 gc.text(left.round + 2, header_height + 14, week_f.cweek.to_s)
461 left = left + width
461 left = left + width
462 week_f = week_f+7
462 week_f = week_f+7
463 end
463 end
464 end
464 end
465
465
466 # Days details (week-end in grey)
466 # Days details (week-end in grey)
467 if show_days
467 if show_days
468 left = subject_width
468 left = subject_width
469 height = g_height + header_height - 1
469 height = g_height + header_height - 1
470 wday = @date_from.cwday
470 wday = @date_from.cwday
471 (date_to - @date_from + 1).to_i.times do
471 (date_to - @date_from + 1).to_i.times do
472 width = zoom
472 width = zoom
473 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
473 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
474 gc.stroke('#ddd')
474 gc.stroke('#ddd')
475 gc.stroke_width(1)
475 gc.stroke_width(1)
476 gc.rectangle(left, 2*header_height, left + width, 2*header_height + g_height-1)
476 gc.rectangle(left, 2*header_height, left + width, 2*header_height + g_height-1)
477 left = left + width
477 left = left + width
478 wday = wday + 1
478 wday = wday + 1
479 wday = 1 if wday > 7
479 wday = 1 if wday > 7
480 end
480 end
481 end
481 end
482
482
483 # border
483 # border
484 gc.fill('transparent')
484 gc.fill('transparent')
485 gc.stroke('grey')
485 gc.stroke('grey')
486 gc.stroke_width(1)
486 gc.stroke_width(1)
487 gc.rectangle(0, 0, subject_width+g_width, headers_height)
487 gc.rectangle(0, 0, subject_width+g_width, headers_height)
488 gc.stroke('black')
488 gc.stroke('black')
489 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_height-1)
489 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_height-1)
490
490
491 # content
491 # content
492 top = headers_height + 20
492 top = headers_height + 20
493
493
494 gc.stroke('transparent')
494 gc.stroke('transparent')
495 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
495 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
496
496
497 # today red line
497 # today red line
498 if Date.today >= @date_from and Date.today <= date_to
498 if Date.today >= @date_from and Date.today <= date_to
499 gc.stroke('red')
499 gc.stroke('red')
500 x = (Date.today-@date_from+1)*zoom + subject_width
500 x = (Date.today-@date_from+1)*zoom + subject_width
501 gc.line(x, headers_height, x, headers_height + g_height-1)
501 gc.line(x, headers_height, x, headers_height + g_height-1)
502 end
502 end
503
503
504 gc.draw(imgl)
504 gc.draw(imgl)
505 imgl.format = format
505 imgl.format = format
506 imgl.to_blob
506 imgl.to_blob
507 end if Object.const_defined?(:Magick)
507 end if Object.const_defined?(:Magick)
508
508
509 def to_pdf
509 def to_pdf
510 if l(:general_pdf_encoding).upcase != 'UTF-8'
510 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
511 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
512 else
513 pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
514 end
515 pdf.SetTitle("#{l(:label_gantt)} #{project}")
511 pdf.SetTitle("#{l(:label_gantt)} #{project}")
516 pdf.alias_nb_pages
512 pdf.alias_nb_pages
517 pdf.footer_date = format_date(Date.today)
513 pdf.footer_date = format_date(Date.today)
518 pdf.AddPage("L")
514 pdf.AddPage("L")
519 pdf.SetFontStyle('B',12)
515 pdf.SetFontStyle('B',12)
520 pdf.SetX(15)
516 pdf.SetX(15)
521 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
517 pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s)
522 pdf.Ln
518 pdf.Ln
523 pdf.SetFontStyle('B',9)
519 pdf.SetFontStyle('B',9)
524
520
525 subject_width = PDF::LeftPaneWidth
521 subject_width = PDF::LeftPaneWidth
526 header_height = 5
522 header_height = 5
527
523
528 headers_height = header_height
524 headers_height = header_height
529 show_weeks = false
525 show_weeks = false
530 show_days = false
526 show_days = false
531
527
532 if self.months < 7
528 if self.months < 7
533 show_weeks = true
529 show_weeks = true
534 headers_height = 2*header_height
530 headers_height = 2*header_height
535 if self.months < 3
531 if self.months < 3
536 show_days = true
532 show_days = true
537 headers_height = 3*header_height
533 headers_height = 3*header_height
538 end
534 end
539 end
535 end
540
536
541 g_width = PDF.right_pane_width
537 g_width = PDF.right_pane_width
542 zoom = (g_width) / (self.date_to - self.date_from + 1)
538 zoom = (g_width) / (self.date_to - self.date_from + 1)
543 g_height = 120
539 g_height = 120
544 t_height = g_height + headers_height
540 t_height = g_height + headers_height
545
541
546 y_start = pdf.GetY
542 y_start = pdf.GetY
547
543
548 # Months headers
544 # Months headers
549 month_f = self.date_from
545 month_f = self.date_from
550 left = subject_width
546 left = subject_width
551 height = header_height
547 height = header_height
552 self.months.times do
548 self.months.times do
553 width = ((month_f >> 1) - month_f) * zoom
549 width = ((month_f >> 1) - month_f) * zoom
554 pdf.SetY(y_start)
550 pdf.SetY(y_start)
555 pdf.SetX(left)
551 pdf.SetX(left)
556 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
552 pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
557 left = left + width
553 left = left + width
558 month_f = month_f >> 1
554 month_f = month_f >> 1
559 end
555 end
560
556
561 # Weeks headers
557 # Weeks headers
562 if show_weeks
558 if show_weeks
563 left = subject_width
559 left = subject_width
564 height = header_height
560 height = header_height
565 if self.date_from.cwday == 1
561 if self.date_from.cwday == 1
566 # self.date_from is monday
562 # self.date_from is monday
567 week_f = self.date_from
563 week_f = self.date_from
568 else
564 else
569 # find next monday after self.date_from
565 # find next monday after self.date_from
570 week_f = self.date_from + (7 - self.date_from.cwday + 1)
566 week_f = self.date_from + (7 - self.date_from.cwday + 1)
571 width = (7 - self.date_from.cwday + 1) * zoom-1
567 width = (7 - self.date_from.cwday + 1) * zoom-1
572 pdf.SetY(y_start + header_height)
568 pdf.SetY(y_start + header_height)
573 pdf.SetX(left)
569 pdf.SetX(left)
574 pdf.RDMCell(width + 1, height, "", "LTR")
570 pdf.RDMCell(width + 1, height, "", "LTR")
575 left = left + width+1
571 left = left + width+1
576 end
572 end
577 while week_f <= self.date_to
573 while week_f <= self.date_to
578 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
574 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
579 pdf.SetY(y_start + header_height)
575 pdf.SetY(y_start + header_height)
580 pdf.SetX(left)
576 pdf.SetX(left)
581 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
577 pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
582 left = left + width
578 left = left + width
583 week_f = week_f+7
579 week_f = week_f+7
584 end
580 end
585 end
581 end
586
582
587 # Days headers
583 # Days headers
588 if show_days
584 if show_days
589 left = subject_width
585 left = subject_width
590 height = header_height
586 height = header_height
591 wday = self.date_from.cwday
587 wday = self.date_from.cwday
592 pdf.SetFontStyle('B',7)
588 pdf.SetFontStyle('B',7)
593 (self.date_to - self.date_from + 1).to_i.times do
589 (self.date_to - self.date_from + 1).to_i.times do
594 width = zoom
590 width = zoom
595 pdf.SetY(y_start + 2 * header_height)
591 pdf.SetY(y_start + 2 * header_height)
596 pdf.SetX(left)
592 pdf.SetX(left)
597 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
593 pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
598 left = left + width
594 left = left + width
599 wday = wday + 1
595 wday = wday + 1
600 wday = 1 if wday > 7
596 wday = 1 if wday > 7
601 end
597 end
602 end
598 end
603
599
604 pdf.SetY(y_start)
600 pdf.SetY(y_start)
605 pdf.SetX(15)
601 pdf.SetX(15)
606 pdf.RDMCell(subject_width+g_width-15, headers_height, "", 1)
602 pdf.RDMCell(subject_width+g_width-15, headers_height, "", 1)
607
603
608 # Tasks
604 # Tasks
609 top = headers_height + y_start
605 top = headers_height + y_start
610 options = {
606 options = {
611 :top => top,
607 :top => top,
612 :zoom => zoom,
608 :zoom => zoom,
613 :subject_width => subject_width,
609 :subject_width => subject_width,
614 :g_width => g_width,
610 :g_width => g_width,
615 :indent => 0,
611 :indent => 0,
616 :indent_increment => 5,
612 :indent_increment => 5,
617 :top_increment => 5,
613 :top_increment => 5,
618 :format => :pdf,
614 :format => :pdf,
619 :pdf => pdf
615 :pdf => pdf
620 }
616 }
621 render(options)
617 render(options)
622 pdf.Output
618 pdf.Output
623 end
619 end
624
620
625 private
621 private
626
622
627 def coordinates(start_date, end_date, progress, zoom=nil)
623 def coordinates(start_date, end_date, progress, zoom=nil)
628 zoom ||= @zoom
624 zoom ||= @zoom
629
625
630 coords = {}
626 coords = {}
631 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
627 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
632 if start_date > self.date_from
628 if start_date > self.date_from
633 coords[:start] = start_date - self.date_from
629 coords[:start] = start_date - self.date_from
634 coords[:bar_start] = start_date - self.date_from
630 coords[:bar_start] = start_date - self.date_from
635 else
631 else
636 coords[:bar_start] = 0
632 coords[:bar_start] = 0
637 end
633 end
638 if end_date < self.date_to
634 if end_date < self.date_to
639 coords[:end] = end_date - self.date_from
635 coords[:end] = end_date - self.date_from
640 coords[:bar_end] = end_date - self.date_from + 1
636 coords[:bar_end] = end_date - self.date_from + 1
641 else
637 else
642 coords[:bar_end] = self.date_to - self.date_from + 1
638 coords[:bar_end] = self.date_to - self.date_from + 1
643 end
639 end
644
640
645 if progress
641 if progress
646 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
642 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0)
647 if progress_date > self.date_from && progress_date > start_date
643 if progress_date > self.date_from && progress_date > start_date
648 if progress_date < self.date_to
644 if progress_date < self.date_to
649 coords[:bar_progress_end] = progress_date - self.date_from
645 coords[:bar_progress_end] = progress_date - self.date_from
650 else
646 else
651 coords[:bar_progress_end] = self.date_to - self.date_from + 1
647 coords[:bar_progress_end] = self.date_to - self.date_from + 1
652 end
648 end
653 end
649 end
654
650
655 if progress_date < Date.today
651 if progress_date < Date.today
656 late_date = [Date.today, end_date].min
652 late_date = [Date.today, end_date].min
657 if late_date > self.date_from && late_date > start_date
653 if late_date > self.date_from && late_date > start_date
658 if late_date < self.date_to
654 if late_date < self.date_to
659 coords[:bar_late_end] = late_date - self.date_from + 1
655 coords[:bar_late_end] = late_date - self.date_from + 1
660 else
656 else
661 coords[:bar_late_end] = self.date_to - self.date_from + 1
657 coords[:bar_late_end] = self.date_to - self.date_from + 1
662 end
658 end
663 end
659 end
664 end
660 end
665 end
661 end
666 end
662 end
667
663
668 # Transforms dates into pixels witdh
664 # Transforms dates into pixels witdh
669 coords.keys.each do |key|
665 coords.keys.each do |key|
670 coords[key] = (coords[key] * zoom).floor
666 coords[key] = (coords[key] * zoom).floor
671 end
667 end
672 coords
668 coords
673 end
669 end
674
670
675 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
671 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
676 def sort_issues!(issues)
672 def sort_issues!(issues)
677 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
673 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
678 end
674 end
679
675
680 # TODO: top level issues should be sorted by start date
676 # TODO: top level issues should be sorted by start date
681 def gantt_issue_compare(x, y, issues)
677 def gantt_issue_compare(x, y, issues)
682 if x.root_id == y.root_id
678 if x.root_id == y.root_id
683 x.lft <=> y.lft
679 x.lft <=> y.lft
684 else
680 else
685 x.root_id <=> y.root_id
681 x.root_id <=> y.root_id
686 end
682 end
687 end
683 end
688
684
689 def current_limit
685 def current_limit
690 if @max_rows
686 if @max_rows
691 @max_rows - @number_of_rows
687 @max_rows - @number_of_rows
692 else
688 else
693 nil
689 nil
694 end
690 end
695 end
691 end
696
692
697 def abort?
693 def abort?
698 if @max_rows && @number_of_rows >= @max_rows
694 if @max_rows && @number_of_rows >= @max_rows
699 @truncated = true
695 @truncated = true
700 end
696 end
701 end
697 end
702
698
703 def pdf_new_page?(options)
699 def pdf_new_page?(options)
704 if options[:top] > 180
700 if options[:top] > 180
705 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
701 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
706 options[:pdf].AddPage("L")
702 options[:pdf].AddPage("L")
707 options[:top] = 15
703 options[:top] = 15
708 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
704 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
709 end
705 end
710 end
706 end
711
707
712 def html_subject(params, subject, options={})
708 def html_subject(params, subject, options={})
713 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
709 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
714 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
710 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
715
711
716 output = view.content_tag 'div', subject, :class => options[:css], :style => style, :title => options[:title]
712 output = view.content_tag 'div', subject, :class => options[:css], :style => style, :title => options[:title]
717 @subjects << output
713 @subjects << output
718 output
714 output
719 end
715 end
720
716
721 def pdf_subject(params, subject, options={})
717 def pdf_subject(params, subject, options={})
722 params[:pdf].SetY(params[:top])
718 params[:pdf].SetY(params[:top])
723 params[:pdf].SetX(15)
719 params[:pdf].SetX(15)
724
720
725 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
721 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
726 params[:pdf].RDMCell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
722 params[:pdf].RDMCell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
727
723
728 params[:pdf].SetY(params[:top])
724 params[:pdf].SetY(params[:top])
729 params[:pdf].SetX(params[:subject_width])
725 params[:pdf].SetX(params[:subject_width])
730 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
726 params[:pdf].RDMCell(params[:g_width], 5, "", "LR")
731 end
727 end
732
728
733 def image_subject(params, subject, options={})
729 def image_subject(params, subject, options={})
734 params[:image].fill('black')
730 params[:image].fill('black')
735 params[:image].stroke('transparent')
731 params[:image].stroke('transparent')
736 params[:image].stroke_width(1)
732 params[:image].stroke_width(1)
737 params[:image].text(params[:indent], params[:top] + 2, subject)
733 params[:image].text(params[:indent], params[:top] + 2, subject)
738 end
734 end
739
735
740 def html_task(params, coords, options={})
736 def html_task(params, coords, options={})
741 output = ''
737 output = ''
742 # Renders the task bar, with progress and late
738 # Renders the task bar, with progress and late
743 if coords[:bar_start] && coords[:bar_end]
739 if coords[:bar_start] && coords[:bar_end]
744 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
740 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
745
741
746 if coords[:bar_late_end]
742 if coords[:bar_late_end]
747 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
743 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
748 end
744 end
749 if coords[:bar_progress_end]
745 if coords[:bar_progress_end]
750 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>"
746 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>"
751 end
747 end
752 end
748 end
753 # Renders the markers
749 # Renders the markers
754 if options[:markers]
750 if options[:markers]
755 if coords[:start]
751 if coords[:start]
756 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
752 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
757 end
753 end
758 if coords[:end]
754 if coords[:end]
759 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
755 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
760 end
756 end
761 end
757 end
762 # Renders the label on the right
758 # Renders the label on the right
763 if options[:label]
759 if options[:label]
764 output << "<div style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>"
760 output << "<div style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>"
765 output << options[:label]
761 output << options[:label]
766 output << "</div>"
762 output << "</div>"
767 end
763 end
768 # Renders the tooltip
764 # Renders the tooltip
769 if options[:issue] && coords[:bar_start] && coords[:bar_end]
765 if options[:issue] && coords[:bar_start] && coords[:bar_end]
770 output << "<div class='tooltip' style='position: absolute;top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>"
766 output << "<div class='tooltip' style='position: absolute;top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>"
771 output << '<span class="tip">'
767 output << '<span class="tip">'
772 output << view.render_issue_tooltip(options[:issue])
768 output << view.render_issue_tooltip(options[:issue])
773 output << "</span></div>"
769 output << "</span></div>"
774 end
770 end
775 @lines << output
771 @lines << output
776 output
772 output
777 end
773 end
778
774
779 def pdf_task(params, coords, options={})
775 def pdf_task(params, coords, options={})
780 height = options[:height] || 2
776 height = options[:height] || 2
781
777
782 # Renders the task bar, with progress and late
778 # Renders the task bar, with progress and late
783 if coords[:bar_start] && coords[:bar_end]
779 if coords[:bar_start] && coords[:bar_end]
784 params[:pdf].SetY(params[:top]+1.5)
780 params[:pdf].SetY(params[:top]+1.5)
785 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
781 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
786 params[:pdf].SetFillColor(200,200,200)
782 params[:pdf].SetFillColor(200,200,200)
787 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
783 params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
788
784
789 if coords[:bar_late_end]
785 if coords[:bar_late_end]
790 params[:pdf].SetY(params[:top]+1.5)
786 params[:pdf].SetY(params[:top]+1.5)
791 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
787 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
792 params[:pdf].SetFillColor(255,100,100)
788 params[:pdf].SetFillColor(255,100,100)
793 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
789 params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
794 end
790 end
795 if coords[:bar_progress_end]
791 if coords[:bar_progress_end]
796 params[:pdf].SetY(params[:top]+1.5)
792 params[:pdf].SetY(params[:top]+1.5)
797 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
793 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
798 params[:pdf].SetFillColor(90,200,90)
794 params[:pdf].SetFillColor(90,200,90)
799 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
795 params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
800 end
796 end
801 end
797 end
802 # Renders the markers
798 # Renders the markers
803 if options[:markers]
799 if options[:markers]
804 if coords[:start]
800 if coords[:start]
805 params[:pdf].SetY(params[:top] + 1)
801 params[:pdf].SetY(params[:top] + 1)
806 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
802 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
807 params[:pdf].SetFillColor(50,50,200)
803 params[:pdf].SetFillColor(50,50,200)
808 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
804 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
809 end
805 end
810 if coords[:end]
806 if coords[:end]
811 params[:pdf].SetY(params[:top] + 1)
807 params[:pdf].SetY(params[:top] + 1)
812 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
808 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
813 params[:pdf].SetFillColor(50,50,200)
809 params[:pdf].SetFillColor(50,50,200)
814 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
810 params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
815 end
811 end
816 end
812 end
817 # Renders the label on the right
813 # Renders the label on the right
818 if options[:label]
814 if options[:label]
819 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
815 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
820 params[:pdf].RDMCell(30, 2, options[:label])
816 params[:pdf].RDMCell(30, 2, options[:label])
821 end
817 end
822 end
818 end
823
819
824 def image_task(params, coords, options={})
820 def image_task(params, coords, options={})
825 height = options[:height] || 6
821 height = options[:height] || 6
826
822
827 # Renders the task bar, with progress and late
823 # Renders the task bar, with progress and late
828 if coords[:bar_start] && coords[:bar_end]
824 if coords[:bar_start] && coords[:bar_end]
829 params[:image].fill('#aaa')
825 params[:image].fill('#aaa')
830 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height)
826 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height)
831
827
832 if coords[:bar_late_end]
828 if coords[:bar_late_end]
833 params[:image].fill('#f66')
829 params[:image].fill('#f66')
834 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height)
830 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height)
835 end
831 end
836 if coords[:bar_progress_end]
832 if coords[:bar_progress_end]
837 params[:image].fill('#00c600')
833 params[:image].fill('#00c600')
838 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_progress_end], params[:top] - height)
834 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_progress_end], params[:top] - height)
839 end
835 end
840 end
836 end
841 # Renders the markers
837 # Renders the markers
842 if options[:markers]
838 if options[:markers]
843 if coords[:start]
839 if coords[:start]
844 x = params[:subject_width] + coords[:start]
840 x = params[:subject_width] + coords[:start]
845 y = params[:top] - height / 2
841 y = params[:top] - height / 2
846 params[:image].fill('blue')
842 params[:image].fill('blue')
847 params[:image].polygon(x-4, y, x, y-4, x+4, y, x, y+4)
843 params[:image].polygon(x-4, y, x, y-4, x+4, y, x, y+4)
848 end
844 end
849 if coords[:end]
845 if coords[:end]
850 x = params[:subject_width] + coords[:end] + params[:zoom]
846 x = params[:subject_width] + coords[:end] + params[:zoom]
851 y = params[:top] - height / 2
847 y = params[:top] - height / 2
852 params[:image].fill('blue')
848 params[:image].fill('blue')
853 params[:image].polygon(x-4, y, x, y-4, x+4, y, x, y+4)
849 params[:image].polygon(x-4, y, x, y-4, x+4, y, x, y+4)
854 end
850 end
855 end
851 end
856 # Renders the label on the right
852 # Renders the label on the right
857 if options[:label]
853 if options[:label]
858 params[:image].fill('black')
854 params[:image].fill('black')
859 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,params[:top] + 1, options[:label])
855 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,params[:top] + 1, options[:label])
860 end
856 end
861 end
857 end
862 end
858 end
863 end
859 end
864 end
860 end
@@ -1,469 +1,469
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
2 # 1.12 contributed by Ed Moss.
2 # 1.12 contributed by Ed Moss.
3 #
3 #
4 # The MIT License
4 # The MIT License
5 #
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
11 # furnished to do so, subject to the following conditions:
12 #
12 #
13 # The above copyright notice and this permission notice shall be included in
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
14 # all copies or substantial portions of the Software.
15 #
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 # THE SOFTWARE.
22 # THE SOFTWARE.
23 #
23 #
24 # This is direct port of chinese.php
24 # This is direct port of chinese.php
25 #
25 #
26 # Chinese PDF support.
26 # Chinese PDF support.
27 #
27 #
28 # Usage is as follows:
28 # Usage is as follows:
29 #
29 #
30 # require 'fpdf'
30 # require 'fpdf'
31 # require 'chinese'
31 # require 'chinese'
32 # pdf = FPDF.new
32 # pdf = FPDF.new
33 # pdf.extend(PDF_Chinese)
33 # pdf.extend(PDF_Chinese)
34 #
34 #
35 # This allows it to be combined with other extensions, such as the bookmark
35 # This allows it to be combined with other extensions, such as the bookmark
36 # module.
36 # module.
37
37
38 module PDF_Chinese
38 module PDF_Chinese
39
39
40 Big5_widths={' '=>250,'!'=>250,'"'=>408,'#'=>668,'$'=>490,'%'=>875,'&'=>698,'\''=>250,
40 Big5_widths={' '=>250,'!'=>250,'"'=>408,'#'=>668,'$'=>490,'%'=>875,'&'=>698,'\''=>250,
41 '('=>240,')'=>240,'*'=>417,'+'=>667,','=>250,'-'=>313,'.'=>250,'/'=>520,'0'=>500,'1'=>500,
41 '('=>240,')'=>240,'*'=>417,'+'=>667,','=>250,'-'=>313,'.'=>250,'/'=>520,'0'=>500,'1'=>500,
42 '2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>250,';'=>250,
42 '2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>250,';'=>250,
43 '<'=>667,'='=>667,'>'=>667,'?'=>396,'@'=>921,'A'=>677,'B'=>615,'C'=>719,'D'=>760,'E'=>625,
43 '<'=>667,'='=>667,'>'=>667,'?'=>396,'@'=>921,'A'=>677,'B'=>615,'C'=>719,'D'=>760,'E'=>625,
44 'F'=>552,'G'=>771,'H'=>802,'I'=>354,'J'=>354,'K'=>781,'L'=>604,'M'=>927,'N'=>750,'O'=>823,
44 'F'=>552,'G'=>771,'H'=>802,'I'=>354,'J'=>354,'K'=>781,'L'=>604,'M'=>927,'N'=>750,'O'=>823,
45 'P'=>563,'Q'=>823,'R'=>729,'S'=>542,'T'=>698,'U'=>771,'V'=>729,'W'=>948,'X'=>771,'Y'=>677,
45 'P'=>563,'Q'=>823,'R'=>729,'S'=>542,'T'=>698,'U'=>771,'V'=>729,'W'=>948,'X'=>771,'Y'=>677,
46 'Z'=>635,'['=>344,'\\'=>520,']'=>344,'^'=>469,'_'=>500,'`'=>250,'a'=>469,'b'=>521,'c'=>427,
46 'Z'=>635,'['=>344,'\\'=>520,']'=>344,'^'=>469,'_'=>500,'`'=>250,'a'=>469,'b'=>521,'c'=>427,
47 'd'=>521,'e'=>438,'f'=>271,'g'=>469,'h'=>531,'i'=>250,'j'=>250,'k'=>458,'l'=>240,'m'=>802,
47 'd'=>521,'e'=>438,'f'=>271,'g'=>469,'h'=>531,'i'=>250,'j'=>250,'k'=>458,'l'=>240,'m'=>802,
48 'n'=>531,'o'=>500,'p'=>521,'q'=>521,'r'=>365,'s'=>333,'t'=>292,'u'=>521,'v'=>458,'w'=>677,
48 'n'=>531,'o'=>500,'p'=>521,'q'=>521,'r'=>365,'s'=>333,'t'=>292,'u'=>521,'v'=>458,'w'=>677,
49 'x'=>479,'y'=>458,'z'=>427,'{'=>480,'|'=>496,'}'=>480,'~'=>667}
49 'x'=>479,'y'=>458,'z'=>427,'{'=>480,'|'=>496,'}'=>480,'~'=>667}
50
50
51 GB_widths={' '=>207,'!'=>270,'"'=>342,'#'=>467,'$'=>462,'%'=>797,'&'=>710,'\''=>239,
51 GB_widths={' '=>207,'!'=>270,'"'=>342,'#'=>467,'$'=>462,'%'=>797,'&'=>710,'\''=>239,
52 '('=>374,')'=>374,'*'=>423,'+'=>605,','=>238,'-'=>375,'.'=>238,'/'=>334,'0'=>462,'1'=>462,
52 '('=>374,')'=>374,'*'=>423,'+'=>605,','=>238,'-'=>375,'.'=>238,'/'=>334,'0'=>462,'1'=>462,
53 '2'=>462,'3'=>462,'4'=>462,'5'=>462,'6'=>462,'7'=>462,'8'=>462,'9'=>462,':'=>238,';'=>238,
53 '2'=>462,'3'=>462,'4'=>462,'5'=>462,'6'=>462,'7'=>462,'8'=>462,'9'=>462,':'=>238,';'=>238,
54 '<'=>605,'='=>605,'>'=>605,'?'=>344,'@'=>748,'A'=>684,'B'=>560,'C'=>695,'D'=>739,'E'=>563,
54 '<'=>605,'='=>605,'>'=>605,'?'=>344,'@'=>748,'A'=>684,'B'=>560,'C'=>695,'D'=>739,'E'=>563,
55 'F'=>511,'G'=>729,'H'=>793,'I'=>318,'J'=>312,'K'=>666,'L'=>526,'M'=>896,'N'=>758,'O'=>772,
55 'F'=>511,'G'=>729,'H'=>793,'I'=>318,'J'=>312,'K'=>666,'L'=>526,'M'=>896,'N'=>758,'O'=>772,
56 'P'=>544,'Q'=>772,'R'=>628,'S'=>465,'T'=>607,'U'=>753,'V'=>711,'W'=>972,'X'=>647,'Y'=>620,
56 'P'=>544,'Q'=>772,'R'=>628,'S'=>465,'T'=>607,'U'=>753,'V'=>711,'W'=>972,'X'=>647,'Y'=>620,
57 'Z'=>607,'['=>374,'\\'=>333,']'=>374,'^'=>606,'_'=>500,'`'=>239,'a'=>417,'b'=>503,'c'=>427,
57 'Z'=>607,'['=>374,'\\'=>333,']'=>374,'^'=>606,'_'=>500,'`'=>239,'a'=>417,'b'=>503,'c'=>427,
58 'd'=>529,'e'=>415,'f'=>264,'g'=>444,'h'=>518,'i'=>241,'j'=>230,'k'=>495,'l'=>228,'m'=>793,
58 'd'=>529,'e'=>415,'f'=>264,'g'=>444,'h'=>518,'i'=>241,'j'=>230,'k'=>495,'l'=>228,'m'=>793,
59 'n'=>527,'o'=>524,'p'=>524,'q'=>504,'r'=>338,'s'=>336,'t'=>277,'u'=>517,'v'=>450,'w'=>652,
59 'n'=>527,'o'=>524,'p'=>524,'q'=>504,'r'=>338,'s'=>336,'t'=>277,'u'=>517,'v'=>450,'w'=>652,
60 'x'=>466,'y'=>452,'z'=>407,'{'=>370,'|'=>258,'}'=>370,'~'=>605}
60 'x'=>466,'y'=>452,'z'=>407,'{'=>370,'|'=>258,'}'=>370,'~'=>605}
61
61
62 def AddCIDFont(family,style,name,cw,cMap,registry)
62 def AddCIDFont(family,style,name,cw,cMap,registry)
63 #ActionController::Base::logger.debug registry.to_a.join(":").to_s
63 #ActionController::Base::logger.debug registry.to_a.join(":").to_s
64 fontkey=family.downcase+style.upcase
64 fontkey=family.downcase+style.upcase
65 unless @fonts[fontkey].nil?
65 unless @fonts[fontkey].nil?
66 Error("Font already added: family style")
66 Error("Font already added: family style")
67 end
67 end
68 i=@fonts.length+1
68 i=@fonts.length+1
69 name=name.gsub(' ','')
69 name=name.gsub(' ','')
70 @fonts[fontkey]={'i'=>i,'type'=>'Type0','name'=>name,'up'=>-130,'ut'=>40,'cw'=>cw, 'CMap'=>cMap,'registry'=>registry}
70 @fonts[fontkey]={'i'=>i,'type'=>'Type0','name'=>name,'up'=>-130,'ut'=>40,'cw'=>cw, 'CMap'=>cMap,'registry'=>registry}
71 end
71 end
72
72
73 def AddCIDFonts(family,name,cw,cMap,registry)
73 def AddCIDFonts(family,name,cw,cMap,registry)
74 AddCIDFont(family,'',name,cw,cMap,registry)
74 AddCIDFont(family,'',name,cw,cMap,registry)
75 AddCIDFont(family,'B',name+',Bold',cw,cMap,registry)
75 AddCIDFont(family,'B',name+',Bold',cw,cMap,registry)
76 AddCIDFont(family,'I',name+',Italic',cw,cMap,registry)
76 AddCIDFont(family,'I',name+',Italic',cw,cMap,registry)
77 AddCIDFont(family,'BI',name+',BoldItalic',cw,cMap,registry)
77 AddCIDFont(family,'BI',name+',BoldItalic',cw,cMap,registry)
78 end
78 end
79
79
80 def AddBig5Font(family='Big5',name='MSungStd-Light-Acro')
80 def AddBig5Font(family='Big5',name='MSungStd-Light-Acro')
81 #Add Big5 font with proportional Latin
81 #Add Big5 font with proportional Latin
82 cw=Big5_widths
82 cw=Big5_widths
83 cMap='ETenms-B5-H'
83 cMap='ETenms-B5-H'
84 registry={'ordering'=>'CNS1','supplement'=>0}
84 registry={'ordering'=>'CNS1','supplement'=>0}
85 #ActionController::Base::logger.debug registry.to_a.join(":").to_s
85 #ActionController::Base::logger.debug registry.to_a.join(":").to_s
86 AddCIDFonts(family,name,cw,cMap,registry)
86 AddCIDFonts(family,name,cw,cMap,registry)
87 end
87 end
88
88
89 def AddBig5hwFont(family='Big5-hw',name='MSungStd-Light-Acro')
89 def AddBig5hwFont(family='Big5-hw',name='MSungStd-Light-Acro')
90 #Add Big5 font with half-witdh Latin
90 #Add Big5 font with half-witdh Latin
91 cw = {}
91 cw = {}
92 32.upto(126) do |i|
92 32.upto(126) do |i|
93 cw[i.chr]=500
93 cw[i.chr]=500
94 end
94 end
95 cMap='ETen-B5-H'
95 cMap='ETen-B5-H'
96 registry={'ordering'=>'CNS1','supplement'=>0}
96 registry={'ordering'=>'CNS1','supplement'=>0}
97 AddCIDFonts(family,name,cw,cMap,registry)
97 AddCIDFonts(family,name,cw,cMap,registry)
98 end
98 end
99
99
100 def AddGBFont(family='GB',name='STSongStd-Light-Acro')
100 def AddGBFont(family='GB',name='STSongStd-Light-Acro')
101 #Add GB font with proportional Latin
101 #Add GB font with proportional Latin
102 cw=GB_widths
102 cw=GB_widths
103 cMap='GBKp-EUC-H'
103 cMap='GBKp-EUC-H'
104 registry={'ordering'=>'GB1','supplement'=>2}
104 registry={'ordering'=>'GB1','supplement'=>2}
105 AddCIDFonts(family,name,cw,cMap,registry)
105 AddCIDFonts(family,name,cw,cMap,registry)
106 end
106 end
107
107
108 def AddGBhwFont(family='GB-hw',name='STSongStd-Light-Acro')
108 def AddGBhwFont(family='GB-hw',name='STSongStd-Light-Acro')
109 #Add GB font with half-width Latin
109 #Add GB font with half-width Latin
110 32.upto(126) do |i|
110 32.upto(126) do |i|
111 cw[i.chr]=500
111 cw[i.chr]=500
112 end
112 end
113 cMap='GBK-EUC-H'
113 cMap='GBK-EUC-H'
114 registry={'ordering'=>'GB1','supplement'=>2}
114 registry={'ordering'=>'GB1','supplement'=>2}
115 AddCIDFonts(family,name,cw,cMap,registry)
115 AddCIDFonts(family,name,cw,cMap,registry)
116 end
116 end
117
117
118 def GetStringWidth(s)
118 def GetStringWidth(s)
119 if(@CurrentFont['type']=='Type0')
119 if(@current_font['type']=='Type0')
120 return GetMBStringWidth(s)
120 return GetMBStringWidth(s)
121 else
121 else
122 return super(s)
122 return super(s)
123 end
123 end
124 end
124 end
125
125
126 def GetMBStringWidth(s)
126 def GetMBStringWidth(s)
127 #Multi-byte version of GetStringWidth()
127 #Multi-byte version of GetStringWidth()
128 l=0
128 l=0
129 cw=@CurrentFont['cw']
129 cw=@current_font['cw']
130 nb=s.length
130 nb=s.length
131 i=0
131 i=0
132 while(i<nb)
132 while(i<nb)
133 c = s[i].is_a?(String) ? s[i].ord : s[i]
133 c = s[i].is_a?(String) ? s[i].ord : s[i]
134 if(c<128)
134 if(c<128)
135 l+=cw[c.chr] if cw[c.chr]
135 l+=cw[c.chr] if cw[c.chr]
136 i+=1
136 i+=1
137 else
137 else
138 l+=1000
138 l+=1000
139 i+=2
139 i+=2
140 end
140 end
141 end
141 end
142 return l*@FontSize/1000
142 return l*@font_size/1000
143 end
143 end
144
144
145 def MultiCell(w,h,txt,border=0,align='L',fill=0)
145 def MultiCell(w,h,txt,border=0,align='L',fill=0)
146 if(@CurrentFont['type']=='Type0')
146 if(@current_font['type']=='Type0')
147 MBMultiCell(w,h,txt,border,align,fill)
147 MBMultiCell(w,h,txt,border,align,fill)
148 else
148 else
149 super(w,h,txt,border,align,fill)
149 super(w,h,txt,border,align,fill)
150 end
150 end
151 end
151 end
152
152
153 def MBMultiCell(w,h,txt,border=0,align='L',fill=0)
153 def MBMultiCell(w,h,txt,border=0,align='L',fill=0)
154 #Multi-byte version of MultiCell()
154 #Multi-byte version of MultiCell()
155 cw=@CurrentFont['cw']
155 cw=@current_font['cw']
156 if(w==0)
156 if(w==0)
157 w=@w-@rMargin-@x
157 w=@w-@r_margin-@x
158 end
158 end
159 wmax=(w-2*@cMargin)*1000/@FontSize
159 wmax=(w-2*@c_margin)*1000/@font_size
160 s=txt.gsub("\r",'')
160 s=txt.gsub("\r",'')
161 nb=s.length
161 nb=s.length
162 if(nb>0 and s[nb-1]=="\n")
162 if(nb>0 and s[nb-1]=="\n")
163 nb-=1
163 nb-=1
164 end
164 end
165 b=0
165 b=0
166 if(border)
166 if(border)
167 if(border==1)
167 if(border==1)
168 border='LTRB'
168 border='LTRB'
169 b='LRT'
169 b='LRT'
170 b2='LR'
170 b2='LR'
171 else
171 else
172 b2=''
172 b2=''
173 b2='L' unless border.to_s.index('L').nil?
173 b2='L' unless border.to_s.index('L').nil?
174 b2=b2+'R' unless border.to_s.index('R').nil?
174 b2=b2+'R' unless border.to_s.index('R').nil?
175 b=(border.to_s.index('T')) ? (b2+'T') : b2
175 b=(border.to_s.index('T')) ? (b2+'T') : b2
176 end
176 end
177 end
177 end
178 sep=-1
178 sep=-1
179 i=0
179 i=0
180 j=0
180 j=0
181 l=0
181 l=0
182 nl=1
182 nl=1
183 while(i<nb)
183 while(i<nb)
184 #Get next character
184 #Get next character
185 c = s[i].is_a?(String) ? s[i].ord : s[i]
185 c = s[i].is_a?(String) ? s[i].ord : s[i]
186 #Check if ASCII or MB
186 #Check if ASCII or MB
187 ascii=(c<128)
187 ascii=(c<128)
188 if(c.chr=="\n")
188 if(c.chr=="\n")
189 #Explicit line break
189 #Explicit line break
190 Cell(w,h,s[j,i-j],b,2,align,fill)
190 Cell(w,h,s[j,i-j],b,2,align,fill)
191 i+=1
191 i+=1
192 sep=-1
192 sep=-1
193 j=i
193 j=i
194 l=0
194 l=0
195 nl+=1
195 nl+=1
196 if(border and nl==2)
196 if(border and nl==2)
197 b=b2
197 b=b2
198 end
198 end
199 next
199 next
200 end
200 end
201 if(!ascii)
201 if(!ascii)
202 sep=i
202 sep=i
203 ls=l
203 ls=l
204 elsif(c.chr==' ')
204 elsif(c.chr==' ')
205 sep=i
205 sep=i
206 ls=l
206 ls=l
207 end
207 end
208 l+=(ascii ? cw[c.chr] : 1000) || 0
208 l+=(ascii ? cw[c.chr] : 1000) || 0
209 if(l>wmax)
209 if(l>wmax)
210 #Automatic line break
210 #Automatic line break
211 if(sep==-1 or i==j)
211 if(sep==-1 or i==j)
212 if(i==j)
212 if(i==j)
213 i+=ascii ? 1 : 2
213 i+=ascii ? 1 : 2
214 end
214 end
215 Cell(w,h,s[j,i-j],b,2,align,fill)
215 Cell(w,h,s[j,i-j],b,2,align,fill)
216 else
216 else
217 Cell(w,h,s[j,sep-j],b,2,align,fill)
217 Cell(w,h,s[j,sep-j],b,2,align,fill)
218 i=(s[sep].chr==' ') ? sep+1 : sep
218 i=(s[sep].chr==' ') ? sep+1 : sep
219 end
219 end
220 sep=-1
220 sep=-1
221 j=i
221 j=i
222 l=0
222 l=0
223 nl+=1
223 nl+=1
224 if(border and nl==2)
224 if(border and nl==2)
225 b=b2
225 b=b2
226 end
226 end
227 else
227 else
228 i+=ascii ? 1 : 2
228 i+=ascii ? 1 : 2
229 end
229 end
230 end
230 end
231 #Last chunk
231 #Last chunk
232 if(border and not border.to_s.index('B').nil?)
232 if(border and not border.to_s.index('B').nil?)
233 b+='B'
233 b+='B'
234 end
234 end
235 Cell(w,h,s[j,i-j],b,2,align,fill)
235 Cell(w,h,s[j,i-j],b,2,align,fill)
236 @x=@lMargin
236 @x=@l_margin
237 end
237 end
238
238
239 def Write(h,txt,link='')
239 def Write(h,txt,link='')
240 if(@CurrentFont['type']=='Type0')
240 if(@current_font['type']=='Type0')
241 MBWrite(h,txt,link)
241 MBWrite(h,txt,link)
242 else
242 else
243 super(h,txt,link)
243 super(h,txt,link)
244 end
244 end
245 end
245 end
246
246
247 def MBWrite(h,txt,link)
247 def MBWrite(h,txt,link)
248 #Multi-byte version of Write()
248 #Multi-byte version of Write()
249 cw=@CurrentFont['cw']
249 cw=@current_font['cw']
250 w=@w-@rMargin-@x
250 w=@w-@r_margin-@x
251 wmax=(w-2*@cMargin)*1000/@FontSize
251 wmax=(w-2*@c_margin)*1000/@font_size
252 s=txt.gsub("\r",'')
252 s=txt.gsub("\r",'')
253 nb=s.length
253 nb=s.length
254 sep=-1
254 sep=-1
255 i=0
255 i=0
256 j=0
256 j=0
257 l=0
257 l=0
258 nl=1
258 nl=1
259 while(i<nb)
259 while(i<nb)
260 #Get next character
260 #Get next character
261 c = s[i].is_a?(String) ? s[i].ord : s[i]
261 c = s[i].is_a?(String) ? s[i].ord : s[i]
262 #Check if ASCII or MB
262 #Check if ASCII or MB
263 ascii=(c<128)
263 ascii=(c<128)
264 if(c.chr=="\n")
264 if(c.chr=="\n")
265 #Explicit line break
265 #Explicit line break
266 Cell(w,h,s[j,i-j],0,2,'',0,link)
266 Cell(w,h,s[j,i-j],0,2,'',0,link)
267 i+=1
267 i+=1
268 sep=-1
268 sep=-1
269 j=i
269 j=i
270 l=0
270 l=0
271 if(nl==1)
271 if(nl==1)
272 @x=@lMargin
272 @x=@l_margin
273 w=@w-@rMargin-@x
273 w=@w-@r_margin-@x
274 wmax=(w-2*@cMargin)*1000/@FontSize
274 wmax=(w-2*@c_margin)*1000/@font_size
275 end
275 end
276 nl+=1
276 nl+=1
277 next
277 next
278 end
278 end
279 if(!ascii or c.chr==' ')
279 if(!ascii or c.chr==' ')
280 sep=i
280 sep=i
281 end
281 end
282 l+=(ascii ? cw[c.chr] : 1000) || 0
282 l+=(ascii ? cw[c.chr] : 1000) || 0
283 if(l>wmax)
283 if(l>wmax)
284 #Automatic line break
284 #Automatic line break
285 if(sep==-1 or i==j)
285 if(sep==-1 or i==j)
286 if(@x>@lMargin)
286 if(@x>@l_margin)
287 #Move to next line
287 #Move to next line
288 @x=@lMargin
288 @x=@l_margin
289 @y+=h
289 @y+=h
290 w=@w-@rMargin-@x
290 w=@w-@r_margin-@x
291 wmax=(w-2*@cMargin)*1000/@FontSize
291 wmax=(w-2*@c_margin)*1000/@font_size
292 i+=1
292 i+=1
293 nl+=1
293 nl+=1
294 next
294 next
295 end
295 end
296 if(i==j)
296 if(i==j)
297 i+=ascii ? 1 : 2
297 i+=ascii ? 1 : 2
298 end
298 end
299 Cell(w,h,s[j,i-j],0,2,'',0,link)
299 Cell(w,h,s[j,i-j],0,2,'',0,link)
300 else
300 else
301 Cell(w,h,s[j,sep-j],0,2,'',0,link)
301 Cell(w,h,s[j,sep-j],0,2,'',0,link)
302 i=(s[sep].chr==' ') ? sep+1 : sep
302 i=(s[sep].chr==' ') ? sep+1 : sep
303 end
303 end
304 sep=-1
304 sep=-1
305 j=i
305 j=i
306 l=0
306 l=0
307 if(nl==1)
307 if(nl==1)
308 @x=@lMargin
308 @x=@l_margin
309 w=@w-@rMargin-@x
309 w=@w-@r_margin-@x
310 wmax=(w-2*@cMargin)*1000/@FontSize
310 wmax=(w-2*@c_margin)*1000/@font_size
311 end
311 end
312 nl+=1
312 nl+=1
313 else
313 else
314 i+=ascii ? 1 : 2
314 i+=ascii ? 1 : 2
315 end
315 end
316 end
316 end
317 #Last chunk
317 #Last chunk
318 if(i!=j)
318 if(i!=j)
319 Cell(l/1000*@FontSize,h,s[j,i-j],0,0,'',0,link)
319 Cell(l/1000*@font_size,h,s[j,i-j],0,0,'',0,link)
320 end
320 end
321 end
321 end
322
322
323 private
323 private
324
324
325 def putfonts()
325 def putfonts()
326 nf=@n
326 nf=@n
327 @diffs.each do |diff|
327 @diffs.each do |diff|
328 #Encodings
328 #Encodings
329 newobj()
329 newobj()
330 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>')
330 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>')
331 out('endobj')
331 out('endobj')
332 end
332 end
333 # mqr=get_magic_quotes_runtime()
333 # mqr=get_magic_quotes_runtime()
334 # set_magic_quotes_runtime(0)
334 # set_magic_quotes_runtime(0)
335 @FontFiles.each_pair do |file, info|
335 @font_files.each_pair do |file, info|
336 #Font file embedding
336 #Font file embedding
337 newobj()
337 newobj()
338 @FontFiles[file]['n']=@n
338 @font_files[file]['n']=@n
339 if(defined('FPDF_FONTPATH'))
339 if(defined('FPDF_FONTPATH'))
340 file=FPDF_FONTPATH+file
340 file=FPDF_FONTPATH+file
341 end
341 end
342 size=filesize(file)
342 size=filesize(file)
343 if(!size)
343 if(!size)
344 Error('Font file not found')
344 Error('Font file not found')
345 end
345 end
346 out('<</Length '+size)
346 out('<</Length '+size)
347 if(file[-2]=='.z')
347 if(file[-2]=='.z')
348 out('/Filter /FlateDecode')
348 out('/Filter /FlateDecode')
349 end
349 end
350 out('/Length1 '+info['length1'])
350 out('/Length1 '+info['length1'])
351 unless info['length2'].nil?
351 unless info['length2'].nil?
352 out('/Length2 '+info['length2']+' /Length3 0')
352 out('/Length2 '+info['length2']+' /Length3 0')
353 end
353 end
354 out('>>')
354 out('>>')
355 f=fopen(file,'rb')
355 f=fopen(file,'rb')
356 putstream(fread(f,size))
356 putstream(fread(f,size))
357 fclose(f)
357 fclose(f)
358 out('endobj')
358 out('endobj')
359 end
359 end
360 #
360 #
361 # set_magic_quotes_runtime(mqr)
361 # set_magic_quotes_runtime(mqr)
362 #
362 #
363 @fonts.each_pair do |k, font|
363 @fonts.each_pair do |k, font|
364 #Font objects
364 #Font objects
365 newobj()
365 newobj()
366 @fonts[k]['n']=@n
366 @fonts[k]['n']=@n
367 out('<</Type /Font')
367 out('<</Type /Font')
368 if(font['type']=='Type0')
368 if(font['type']=='Type0')
369 putType0(font)
369 putType0(font)
370 else
370 else
371 name=font['name']
371 name=font['name']
372 out('/BaseFont /'+name)
372 out('/BaseFont /'+name)
373 if(font['type']=='core')
373 if(font['type']=='core')
374 #Standard font
374 #Standard font
375 out('/Subtype /Type1')
375 out('/Subtype /Type1')
376 if(name!='Symbol' and name!='ZapfDingbats')
376 if(name!='Symbol' and name!='ZapfDingbats')
377 out('/Encoding /WinAnsiEncoding')
377 out('/Encoding /WinAnsiEncoding')
378 end
378 end
379 else
379 else
380 #Additional font
380 #Additional font
381 out('/Subtype /'+font['type'])
381 out('/Subtype /'+font['type'])
382 out('/FirstChar 32')
382 out('/FirstChar 32')
383 out('/LastChar 255')
383 out('/LastChar 255')
384 out('/Widths '+(@n+1)+' 0 R')
384 out('/Widths '+(@n+1)+' 0 R')
385 out('/FontDescriptor '+(@n+2)+' 0 R')
385 out('/FontDescriptor '+(@n+2)+' 0 R')
386 if(font['enc'])
386 if(font['enc'])
387 if !font['diff'].nil?
387 if !font['diff'].nil?
388 out('/Encoding '+(nf+font['diff'])+' 0 R')
388 out('/Encoding '+(nf+font['diff'])+' 0 R')
389 else
389 else
390 out('/Encoding /WinAnsiEncoding')
390 out('/Encoding /WinAnsiEncoding')
391 end
391 end
392 end
392 end
393 end
393 end
394 out('>>')
394 out('>>')
395 out('endobj')
395 out('endobj')
396 if(font['type']!='core')
396 if(font['type']!='core')
397 #Widths
397 #Widths
398 newobj()
398 newobj()
399 cw=font['cw']
399 cw=font['cw']
400 s='['
400 s='['
401 32.upto(255) do |i|
401 32.upto(255) do |i|
402 s+=cw[i.chr]+' '
402 s+=cw[i.chr]+' '
403 end
403 end
404 out(s+']')
404 out(s+']')
405 out('endobj')
405 out('endobj')
406 #Descriptor
406 #Descriptor
407 newobj()
407 newobj()
408 s='<</Type /FontDescriptor /FontName /'+name
408 s='<</Type /FontDescriptor /FontName /'+name
409 font['desc'].each_pair do |k, v|
409 font['desc'].each_pair do |k, v|
410 s+=' /'+k+' '+v
410 s+=' /'+k+' '+v
411 end
411 end
412 file=font['file']
412 file=font['file']
413 if(file)
413 if(file)
414 s+=' /FontFile'+(font['type']=='Type1' ? '' : '2')+' '+@FontFiles[file]['n']+' 0 R'
414 s+=' /FontFile'+(font['type']=='Type1' ? '' : '2')+' '+@font_files[file]['n']+' 0 R'
415 end
415 end
416 out(s+'>>')
416 out(s+'>>')
417 out('endobj')
417 out('endobj')
418 end
418 end
419 end
419 end
420 end
420 end
421 end
421 end
422
422
423 def putType0(font)
423 def putType0(font)
424 #Type0
424 #Type0
425 out('/Subtype /Type0')
425 out('/Subtype /Type0')
426 out('/BaseFont /'+font['name']+'-'+font['CMap'])
426 out('/BaseFont /'+font['name']+'-'+font['CMap'])
427 out('/Encoding /'+font['CMap'])
427 out('/Encoding /'+font['CMap'])
428 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
428 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
429 out('>>')
429 out('>>')
430 out('endobj')
430 out('endobj')
431 #CIDFont
431 #CIDFont
432 newobj()
432 newobj()
433 out('<</Type /Font')
433 out('<</Type /Font')
434 out('/Subtype /CIDFontType0')
434 out('/Subtype /CIDFontType0')
435 out('/BaseFont /'+font['name'])
435 out('/BaseFont /'+font['name'])
436 out('/CIDSystemInfo <</Registry '+textstring('Adobe')+' /Ordering '+textstring(font['registry']['ordering'])+' /Supplement '+font['registry']['supplement'].to_s+'>>')
436 out('/CIDSystemInfo <</Registry '+textstring('Adobe')+' /Ordering '+textstring(font['registry']['ordering'])+' /Supplement '+font['registry']['supplement'].to_s+'>>')
437 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
437 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
438 if(font['CMap']=='ETen-B5-H')
438 if(font['CMap']=='ETen-B5-H')
439 w='13648 13742 500'
439 w='13648 13742 500'
440 elsif(font['CMap']=='GBK-EUC-H')
440 elsif(font['CMap']=='GBK-EUC-H')
441 w='814 907 500 7716 [500]'
441 w='814 907 500 7716 [500]'
442 else
442 else
443 # ActionController::Base::logger.debug font['cw'].keys.sort.join(' ').to_s
443 # ActionController::Base::logger.debug font['cw'].keys.sort.join(' ').to_s
444 # ActionController::Base::logger.debug font['cw'].values.join(' ').to_s
444 # ActionController::Base::logger.debug font['cw'].values.join(' ').to_s
445 w='1 ['
445 w='1 ['
446 font['cw'].keys.sort.each {|key|
446 font['cw'].keys.sort.each {|key|
447 w+=font['cw'][key].to_s + " "
447 w+=font['cw'][key].to_s + " "
448 # ActionController::Base::logger.debug key.to_s
448 # ActionController::Base::logger.debug key.to_s
449 # ActionController::Base::logger.debug font['cw'][key].to_s
449 # ActionController::Base::logger.debug font['cw'][key].to_s
450 }
450 }
451 w +=']'
451 w +=']'
452 end
452 end
453 out('/W ['+w+']>>')
453 out('/W ['+w+']>>')
454 out('endobj')
454 out('endobj')
455 #Font descriptor
455 #Font descriptor
456 newobj()
456 newobj()
457 out('<</Type /FontDescriptor')
457 out('<</Type /FontDescriptor')
458 out('/FontName /'+font['name'])
458 out('/FontName /'+font['name'])
459 out('/Flags 6')
459 out('/Flags 6')
460 out('/FontBBox [0 -200 1000 900]')
460 out('/FontBBox [0 -200 1000 900]')
461 out('/ItalicAngle 0')
461 out('/ItalicAngle 0')
462 out('/Ascent 800')
462 out('/Ascent 800')
463 out('/Descent -200')
463 out('/Descent -200')
464 out('/CapHeight 800')
464 out('/CapHeight 800')
465 out('/StemV 50')
465 out('/StemV 50')
466 out('>>')
466 out('>>')
467 out('endobj')
467 out('endobj')
468 end
468 end
469 end
469 end
@@ -1,464 +1,464
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
2 # 1.12 contributed by Ed Moss.
2 # 1.12 contributed by Ed Moss.
3 #
3 #
4 # The MIT License
4 # The MIT License
5 #
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
11 # furnished to do so, subject to the following conditions:
12 #
12 #
13 # The above copyright notice and this permission notice shall be included in
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
14 # all copies or substantial portions of the Software.
15 #
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 # THE SOFTWARE.
22 # THE SOFTWARE.
23 #
23 #
24 # This is direct port of japanese.php
24 # This is direct port of japanese.php
25 #
25 #
26 # Japanese PDF support.
26 # Japanese PDF support.
27 #
27 #
28 # Usage is as follows:
28 # Usage is as follows:
29 #
29 #
30 # require 'fpdf'
30 # require 'fpdf'
31 # require 'chinese'
31 # require 'chinese'
32 # pdf = FPDF.new
32 # pdf = FPDF.new
33 # pdf.extend(PDF_Japanese)
33 # pdf.extend(PDF_Japanese)
34 #
34 #
35 # This allows it to be combined with other extensions, such as the bookmark
35 # This allows it to be combined with other extensions, such as the bookmark
36 # module.
36 # module.
37
37
38 module PDF_Japanese
38 module PDF_Japanese
39
39
40 SJIS_widths={' ' => 278, '!' => 299, '"' => 353, '#' => 614, '$' => 614, '%' => 721, '&' => 735, '\'' => 216,
40 SJIS_widths={' ' => 278, '!' => 299, '"' => 353, '#' => 614, '$' => 614, '%' => 721, '&' => 735, '\'' => 216,
41 '(' => 323, ')' => 323, '*' => 449, '+' => 529, ',' => 219, '-' => 306, '.' => 219, '/' => 453, '0' => 614, '1' => 614,
41 '(' => 323, ')' => 323, '*' => 449, '+' => 529, ',' => 219, '-' => 306, '.' => 219, '/' => 453, '0' => 614, '1' => 614,
42 '2' => 614, '3' => 614, '4' => 614, '5' => 614, '6' => 614, '7' => 614, '8' => 614, '9' => 614, ':' => 219, ';' => 219,
42 '2' => 614, '3' => 614, '4' => 614, '5' => 614, '6' => 614, '7' => 614, '8' => 614, '9' => 614, ':' => 219, ';' => 219,
43 '<' => 529, '=' => 529, '>' => 529, '?' => 486, '@' => 744, 'A' => 646, 'B' => 604, 'C' => 617, 'D' => 681, 'E' => 567,
43 '<' => 529, '=' => 529, '>' => 529, '?' => 486, '@' => 744, 'A' => 646, 'B' => 604, 'C' => 617, 'D' => 681, 'E' => 567,
44 'F' => 537, 'G' => 647, 'H' => 738, 'I' => 320, 'J' => 433, 'K' => 637, 'L' => 566, 'M' => 904, 'N' => 710, 'O' => 716,
44 'F' => 537, 'G' => 647, 'H' => 738, 'I' => 320, 'J' => 433, 'K' => 637, 'L' => 566, 'M' => 904, 'N' => 710, 'O' => 716,
45 'P' => 605, 'Q' => 716, 'R' => 623, 'S' => 517, 'T' => 601, 'U' => 690, 'V' => 668, 'W' => 990, 'X' => 681, 'Y' => 634,
45 'P' => 605, 'Q' => 716, 'R' => 623, 'S' => 517, 'T' => 601, 'U' => 690, 'V' => 668, 'W' => 990, 'X' => 681, 'Y' => 634,
46 'Z' => 578, '[' => 316, '\\' => 614, ']' => 316, '^' => 529, '_' => 500, '`' => 387, 'a' => 509, 'b' => 566, 'c' => 478,
46 'Z' => 578, '[' => 316, '\\' => 614, ']' => 316, '^' => 529, '_' => 500, '`' => 387, 'a' => 509, 'b' => 566, 'c' => 478,
47 'd' => 565, 'e' => 503, 'f' => 337, 'g' => 549, 'h' => 580, 'i' => 275, 'j' => 266, 'k' => 544, 'l' => 276, 'm' => 854,
47 'd' => 565, 'e' => 503, 'f' => 337, 'g' => 549, 'h' => 580, 'i' => 275, 'j' => 266, 'k' => 544, 'l' => 276, 'm' => 854,
48 'n' => 579, 'o' => 550, 'p' => 578, 'q' => 566, 'r' => 410, 's' => 444, 't' => 340, 'u' => 575, 'v' => 512, 'w' => 760,
48 'n' => 579, 'o' => 550, 'p' => 578, 'q' => 566, 'r' => 410, 's' => 444, 't' => 340, 'u' => 575, 'v' => 512, 'w' => 760,
49 'x' => 503, 'y' => 529, 'z' => 453, '{' => 326, '|' => 380, '}' => 326, '~' => 387}
49 'x' => 503, 'y' => 529, 'z' => 453, '{' => 326, '|' => 380, '}' => 326, '~' => 387}
50
50
51 def AddCIDFont(family,style,name,cw,cMap,registry)
51 def AddCIDFont(family,style,name,cw,cMap,registry)
52 fontkey=family.downcase+style.upcase
52 fontkey=family.downcase+style.upcase
53 unless @fonts[fontkey].nil?
53 unless @fonts[fontkey].nil?
54 Error("CID font already added: family style")
54 Error("CID font already added: family style")
55 end
55 end
56 i=@fonts.length+1
56 i=@fonts.length+1
57 @fonts[fontkey]={'i'=>i,'type'=>'Type0','name'=>name,'up'=>-120,'ut'=>40,'cw'=>cw,
57 @fonts[fontkey]={'i'=>i,'type'=>'Type0','name'=>name,'up'=>-120,'ut'=>40,'cw'=>cw,
58 'CMap'=>cMap,'registry'=>registry}
58 'CMap'=>cMap,'registry'=>registry}
59 end
59 end
60
60
61 def AddCIDFonts(family,name,cw,cMap,registry)
61 def AddCIDFonts(family,name,cw,cMap,registry)
62 AddCIDFont(family,'',name,cw,cMap,registry)
62 AddCIDFont(family,'',name,cw,cMap,registry)
63 AddCIDFont(family,'B',name+',Bold',cw,cMap,registry)
63 AddCIDFont(family,'B',name+',Bold',cw,cMap,registry)
64 AddCIDFont(family,'I',name+',Italic',cw,cMap,registry)
64 AddCIDFont(family,'I',name+',Italic',cw,cMap,registry)
65 AddCIDFont(family,'BI',name+',BoldItalic',cw,cMap,registry)
65 AddCIDFont(family,'BI',name+',BoldItalic',cw,cMap,registry)
66 end
66 end
67
67
68 def AddSJISFont(family='SJIS')
68 def AddSJISFont(family='SJIS')
69 #Add SJIS font with proportional Latin
69 #Add SJIS font with proportional Latin
70 name='KozMinPro-Regular-Acro'
70 name='KozMinPro-Regular-Acro'
71 cw=SJIS_widths
71 cw=SJIS_widths
72 cMap='90msp-RKSJ-H'
72 cMap='90msp-RKSJ-H'
73 registry={'ordering'=>'Japan1','supplement'=>2}
73 registry={'ordering'=>'Japan1','supplement'=>2}
74 AddCIDFonts(family,name,cw,cMap,registry)
74 AddCIDFonts(family,name,cw,cMap,registry)
75 end
75 end
76
76
77 def AddSJIShwFont(family='SJIS-hw')
77 def AddSJIShwFont(family='SJIS-hw')
78 #Add SJIS font with half-width Latin
78 #Add SJIS font with half-width Latin
79 name='KozMinPro-Regular-Acro'
79 name='KozMinPro-Regular-Acro'
80 32.upto(126) do |i|
80 32.upto(126) do |i|
81 cw[i.chr]=500
81 cw[i.chr]=500
82 end
82 end
83 cMap='90ms-RKSJ-H'
83 cMap='90ms-RKSJ-H'
84 registry={'ordering'=>'Japan1','supplement'=>2}
84 registry={'ordering'=>'Japan1','supplement'=>2}
85 AddCIDFonts(family,name,cw,cMap,registry)
85 AddCIDFonts(family,name,cw,cMap,registry)
86 end
86 end
87
87
88 def GetStringWidth(s)
88 def GetStringWidth(s)
89 if(@CurrentFont['type']=='Type0')
89 if(@current_font['type']=='Type0')
90 return GetSJISStringWidth(s)
90 return GetSJISStringWidth(s)
91 else
91 else
92 return super(s)
92 return super(s)
93 end
93 end
94 end
94 end
95
95
96 def GetSJISStringWidth(s)
96 def GetSJISStringWidth(s)
97 #SJIS version of GetStringWidth()
97 #SJIS version of GetStringWidth()
98 l=0
98 l=0
99 cw=@CurrentFont['cw']
99 cw=@current_font['cw']
100 nb=s.length
100 nb=s.length
101 i=0
101 i=0
102 while(i<nb)
102 while(i<nb)
103 o = s[i].is_a?(String) ? s[i].ord : s[i]
103 o = s[i].is_a?(String) ? s[i].ord : s[i]
104 if(o<128)
104 if(o<128)
105 #ASCII
105 #ASCII
106 l+=cw[o.chr] if cw[o.chr]
106 l+=cw[o.chr] if cw[o.chr]
107 i+=1
107 i+=1
108 elsif(o>=161 and o<=223)
108 elsif(o>=161 and o<=223)
109 #Half-width katakana
109 #Half-width katakana
110 l+=500
110 l+=500
111 i+=1
111 i+=1
112 else
112 else
113 #Full-width character
113 #Full-width character
114 l+=1000
114 l+=1000
115 i+=2
115 i+=2
116 end
116 end
117 end
117 end
118 return l*@FontSize/1000
118 return l*@font_size/1000
119 end
119 end
120
120
121 def MultiCell(w,h,txt,border=0,align='L',fill=0)
121 def MultiCell(w,h,txt,border=0,align='L',fill=0)
122 if(@CurrentFont['type']=='Type0')
122 if(@current_font['type']=='Type0')
123 SJISMultiCell(w,h,txt,border,align,fill)
123 SJISMultiCell(w,h,txt,border,align,fill)
124 else
124 else
125 super(w,h,txt,border,align,fill)
125 super(w,h,txt,border,align,fill)
126 end
126 end
127 end
127 end
128
128
129 def SJISMultiCell(w,h,txt,border=0,align='L',fill=0)
129 def SJISMultiCell(w,h,txt,border=0,align='L',fill=0)
130 #Output text with automatic or explicit line breaks
130 #Output text with automatic or explicit line breaks
131 cw=@CurrentFont['cw']
131 cw=@current_font['cw']
132 if(w==0)
132 if(w==0)
133 w=@w-@rMargin-@x
133 w=@w-@r_margin-@x
134 end
134 end
135 wmax=(w-2*@cMargin)*1000/@FontSize
135 wmax=(w-2*@c_margin)*1000/@font_size
136 s=txt.gsub("\r",'')
136 s=txt.gsub("\r",'')
137 nb=s.length
137 nb=s.length
138 if(nb>0 and s[nb-1]=="\n")
138 if(nb>0 and s[nb-1]=="\n")
139 nb-=1
139 nb-=1
140 end
140 end
141 b=0
141 b=0
142 if(border)
142 if(border)
143 if(border==1)
143 if(border==1)
144 border='LTRB'
144 border='LTRB'
145 b='LRT'
145 b='LRT'
146 b2='LR'
146 b2='LR'
147 else
147 else
148 b2=''
148 b2=''
149 b2='L' unless border.to_s.index('L').nil?
149 b2='L' unless border.to_s.index('L').nil?
150 b2=b2+'R' unless border.to_s.index('R').nil?
150 b2=b2+'R' unless border.to_s.index('R').nil?
151 b=(border.to_s.index('T')) ? (b2+'T') : b2
151 b=(border.to_s.index('T')) ? (b2+'T') : b2
152 end
152 end
153 end
153 end
154 sep=-1
154 sep=-1
155 i=0
155 i=0
156 j=0
156 j=0
157 l=0
157 l=0
158 nl=1
158 nl=1
159 while(i<nb)
159 while(i<nb)
160 #Get next character
160 #Get next character
161 c = s[i].is_a?(String) ? s[i].ord : s[i]
161 c = s[i].is_a?(String) ? s[i].ord : s[i]
162 o=c #o=ord(c)
162 o=c #o=ord(c)
163 if(o==10)
163 if(o==10)
164 #Explicit line break
164 #Explicit line break
165 Cell(w,h,s[j,i-j],b,2,align,fill)
165 Cell(w,h,s[j,i-j],b,2,align,fill)
166 i+=1
166 i+=1
167 sep=-1
167 sep=-1
168 j=i
168 j=i
169 l=0
169 l=0
170 nl+=1
170 nl+=1
171 if(border and nl==2)
171 if(border and nl==2)
172 b=b2
172 b=b2
173 end
173 end
174 next
174 next
175 end
175 end
176 if(o<128)
176 if(o<128)
177 #ASCII
177 #ASCII
178 l+=cw[c.chr] || 0
178 l+=cw[c.chr] || 0
179 n=1
179 n=1
180 if(o==32)
180 if(o==32)
181 sep=i
181 sep=i
182 end
182 end
183 elsif(o>=161 and o<=223)
183 elsif(o>=161 and o<=223)
184 #Half-width katakana
184 #Half-width katakana
185 l+=500
185 l+=500
186 n=1
186 n=1
187 sep=i
187 sep=i
188 else
188 else
189 #Full-width character
189 #Full-width character
190 l+=1000
190 l+=1000
191 n=2
191 n=2
192 sep=i
192 sep=i
193 end
193 end
194 if(l>wmax)
194 if(l>wmax)
195 #Automatic line break
195 #Automatic line break
196 if(sep==-1 or i==j)
196 if(sep==-1 or i==j)
197 if(i==j)
197 if(i==j)
198 i+=n
198 i+=n
199 end
199 end
200 Cell(w,h,s[j,i-j],b,2,align,fill)
200 Cell(w,h,s[j,i-j],b,2,align,fill)
201 else
201 else
202 Cell(w,h,s[j,sep-j],b,2,align,fill)
202 Cell(w,h,s[j,sep-j],b,2,align,fill)
203 i=(s[sep].chr==' ') ? sep+1 : sep
203 i=(s[sep].chr==' ') ? sep+1 : sep
204 end
204 end
205 sep=-1
205 sep=-1
206 j=i
206 j=i
207 l=0
207 l=0
208 nl+=1
208 nl+=1
209 if(border and nl==2)
209 if(border and nl==2)
210 b=b2
210 b=b2
211 end
211 end
212 else
212 else
213 i+=n
213 i+=n
214 if(o>=128)
214 if(o>=128)
215 sep=i
215 sep=i
216 end
216 end
217 end
217 end
218 end
218 end
219 #Last chunk
219 #Last chunk
220 if(border and not border.to_s.index('B').nil?)
220 if(border and not border.to_s.index('B').nil?)
221 b+='B'
221 b+='B'
222 end
222 end
223 Cell(w,h,s[j,i-j],b,2,align,fill)
223 Cell(w,h,s[j,i-j],b,2,align,fill)
224 @x=@lMargin
224 @x=@l_margin
225 end
225 end
226
226
227 def Write(h,txt,link='')
227 def Write(h,txt,link='')
228 if(@CurrentFont['type']=='Type0')
228 if(@current_font['type']=='Type0')
229 SJISWrite(h,txt,link)
229 SJISWrite(h,txt,link)
230 else
230 else
231 super(h,txt,link)
231 super(h,txt,link)
232 end
232 end
233 end
233 end
234
234
235 def SJISWrite(h,txt,link)
235 def SJISWrite(h,txt,link)
236 #SJIS version of Write()
236 #SJIS version of Write()
237 cw=@CurrentFont['cw']
237 cw=@current_font['cw']
238 w=@w-@rMargin-@x
238 w=@w-@r_margin-@x
239 wmax=(w-2*@cMargin)*1000/@FontSize
239 wmax=(w-2*@c_margin)*1000/@font_size
240 s=txt.gsub("\r",'')
240 s=txt.gsub("\r",'')
241 nb=s.length
241 nb=s.length
242 sep=-1
242 sep=-1
243 i=0
243 i=0
244 j=0
244 j=0
245 l=0
245 l=0
246 nl=1
246 nl=1
247 while(i<nb)
247 while(i<nb)
248 #Get next character
248 #Get next character
249 c = s[i].is_a?(String) ? s[i].ord : s[i]
249 c = s[i].is_a?(String) ? s[i].ord : s[i]
250 o=c
250 o=c
251 if(o==10)
251 if(o==10)
252 #Explicit line break
252 #Explicit line break
253 Cell(w,h,s[j,i-j],0,2,'',0,link)
253 Cell(w,h,s[j,i-j],0,2,'',0,link)
254 i+=1
254 i+=1
255 sep=-1
255 sep=-1
256 j=i
256 j=i
257 l=0
257 l=0
258 if(nl==1)
258 if(nl==1)
259 #Go to left margin
259 #Go to left margin
260 @x=@lMargin
260 @x=@l_margin
261 w=@w-@rMargin-@x
261 w=@w-@r_margin-@x
262 wmax=(w-2*@cMargin)*1000/@FontSize
262 wmax=(w-2*@c_margin)*1000/@font_size
263 end
263 end
264 nl+=1
264 nl+=1
265 next
265 next
266 end
266 end
267 if(o<128)
267 if(o<128)
268 #ASCII
268 #ASCII
269 l+=cw[c.chr] || 0
269 l+=cw[c.chr] || 0
270 n=1
270 n=1
271 if(o==32)
271 if(o==32)
272 sep=i
272 sep=i
273 end
273 end
274 elsif(o>=161 and o<=223)
274 elsif(o>=161 and o<=223)
275 #Half-width katakana
275 #Half-width katakana
276 l+=500
276 l+=500
277 n=1
277 n=1
278 sep=i
278 sep=i
279 else
279 else
280 #Full-width character
280 #Full-width character
281 l+=1000
281 l+=1000
282 n=2
282 n=2
283 sep=i
283 sep=i
284 end
284 end
285 if(l>wmax)
285 if(l>wmax)
286 #Automatic line break
286 #Automatic line break
287 if(sep==-1 or i==j)
287 if(sep==-1 or i==j)
288 if(@x>@lMargin)
288 if(@x>@l_margin)
289 #Move to next line
289 #Move to next line
290 @x=@lMargin
290 @x=@l_margin
291 @y+=h
291 @y+=h
292 w=@w-@rMargin-@x
292 w=@w-@r_margin-@x
293 wmax=(w-2*@cMargin)*1000/@FontSize
293 wmax=(w-2*@c_margin)*1000/@font_size
294 i+=n
294 i+=n
295 nl+=1
295 nl+=1
296 next
296 next
297 end
297 end
298 if(i==j)
298 if(i==j)
299 i+=n
299 i+=n
300 end
300 end
301 Cell(w,h,s[j,i-j],0,2,'',0,link)
301 Cell(w,h,s[j,i-j],0,2,'',0,link)
302 else
302 else
303 Cell(w,h,s[j,sep-j],0,2,'',0,link)
303 Cell(w,h,s[j,sep-j],0,2,'',0,link)
304 i=(s[sep].chr==' ') ? sep+1 : sep
304 i=(s[sep].chr==' ') ? sep+1 : sep
305 end
305 end
306 sep=-1
306 sep=-1
307 j=i
307 j=i
308 l=0
308 l=0
309 if(nl==1)
309 if(nl==1)
310 @x=@lMargin
310 @x=@l_margin
311 w=@w-@rMargin-@x
311 w=@w-@r_margin-@x
312 wmax=(w-2*@cMargin)*1000/@FontSize
312 wmax=(w-2*@c_margin)*1000/@font_size
313 end
313 end
314 nl+=1
314 nl+=1
315 else
315 else
316 i+=n
316 i+=n
317 if(o>=128)
317 if(o>=128)
318 sep=i
318 sep=i
319 end
319 end
320 end
320 end
321 end
321 end
322 #Last chunk
322 #Last chunk
323 if(i!=j)
323 if(i!=j)
324 Cell(l/1000*@FontSize,h,s[j,i-j],0,0,'',0,link)
324 Cell(l/1000*@font_size,h,s[j,i-j],0,0,'',0,link)
325 end
325 end
326 end
326 end
327
327
328 private
328 private
329
329
330 def putfonts()
330 def putfonts()
331 nf=@n
331 nf=@n
332 @diffs.each do |diff|
332 @diffs.each do |diff|
333 #Encodings
333 #Encodings
334 newobj()
334 newobj()
335 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>')
335 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>')
336 out('endobj')
336 out('endobj')
337 end
337 end
338 # mqr=get_magic_quotes_runtime()
338 # mqr=get_magic_quotes_runtime()
339 # set_magic_quotes_runtime(0)
339 # set_magic_quotes_runtime(0)
340 @FontFiles.each_pair do |file, info|
340 @font_files.each_pair do |file, info|
341 #Font file embedding
341 #Font file embedding
342 newobj()
342 newobj()
343 @FontFiles[file]['n']=@n
343 @font_files[file]['n']=@n
344 if(defined('FPDF_FONTPATH'))
344 if(defined('FPDF_FONTPATH'))
345 file=FPDF_FONTPATH+file
345 file=FPDF_FONTPATH+file
346 end
346 end
347 size=filesize(file)
347 size=filesize(file)
348 if(!size)
348 if(!size)
349 Error('Font file not found')
349 Error('Font file not found')
350 end
350 end
351 out('<</Length '+size)
351 out('<</Length '+size)
352 if(file[-2]=='.z')
352 if(file[-2]=='.z')
353 out('/Filter /FlateDecode')
353 out('/Filter /FlateDecode')
354 end
354 end
355 out('/Length1 '+info['length1'])
355 out('/Length1 '+info['length1'])
356 unless info['length2'].nil?
356 unless info['length2'].nil?
357 out('/Length2 '+info['length2']+' /Length3 0')
357 out('/Length2 '+info['length2']+' /Length3 0')
358 end
358 end
359 out('>>')
359 out('>>')
360 f=fopen(file,'rb')
360 f=fopen(file,'rb')
361 putstream(fread(f,size))
361 putstream(fread(f,size))
362 fclose(f)
362 fclose(f)
363 out('endobj')
363 out('endobj')
364 end
364 end
365 # set_magic_quotes_runtime(mqr)
365 # set_magic_quotes_runtime(mqr)
366 @fonts.each_pair do |k, font|
366 @fonts.each_pair do |k, font|
367 #Font objects
367 #Font objects
368 newobj()
368 newobj()
369 @fonts[k]['n']=@n
369 @fonts[k]['n']=@n
370 out('<</Type /Font')
370 out('<</Type /Font')
371 if(font['type']=='Type0')
371 if(font['type']=='Type0')
372 putType0(font)
372 putType0(font)
373 else
373 else
374 name=font['name']
374 name=font['name']
375 out('/BaseFont /'+name)
375 out('/BaseFont /'+name)
376 if(font['type']=='core')
376 if(font['type']=='core')
377 #Standard font
377 #Standard font
378 out('/Subtype /Type1')
378 out('/Subtype /Type1')
379 if(name!='Symbol' and name!='ZapfDingbats')
379 if(name!='Symbol' and name!='ZapfDingbats')
380 out('/Encoding /WinAnsiEncoding')
380 out('/Encoding /WinAnsiEncoding')
381 end
381 end
382 else
382 else
383 #Additional font
383 #Additional font
384 out('/Subtype /'+font['type'])
384 out('/Subtype /'+font['type'])
385 out('/FirstChar 32')
385 out('/FirstChar 32')
386 out('/LastChar 255')
386 out('/LastChar 255')
387 out('/Widths '+(@n+1)+' 0 R')
387 out('/Widths '+(@n+1)+' 0 R')
388 out('/FontDescriptor '+(@n+2)+' 0 R')
388 out('/FontDescriptor '+(@n+2)+' 0 R')
389 if(font['enc'])
389 if(font['enc'])
390 if !font['diff'].nil?
390 if !font['diff'].nil?
391 out('/Encoding '+(nf+font['diff'])+' 0 R')
391 out('/Encoding '+(nf+font['diff'])+' 0 R')
392 else
392 else
393 out('/Encoding /WinAnsiEncoding')
393 out('/Encoding /WinAnsiEncoding')
394 end
394 end
395 end
395 end
396 end
396 end
397 out('>>')
397 out('>>')
398 out('endobj')
398 out('endobj')
399 if(font['type']!='core')
399 if(font['type']!='core')
400 #Widths
400 #Widths
401 newobj()
401 newobj()
402 cw=font['cw']
402 cw=font['cw']
403 s='['
403 s='['
404 32.upto(255) do |i|
404 32.upto(255) do |i|
405 s+=cw[i.chr]+' '
405 s+=cw[i.chr]+' '
406 end
406 end
407 out(s+']')
407 out(s+']')
408 out('endobj')
408 out('endobj')
409 #Descriptor
409 #Descriptor
410 newobj()
410 newobj()
411 s='<</Type /FontDescriptor /FontName /'+name
411 s='<</Type /FontDescriptor /FontName /'+name
412 font['desc'].each_pair do |k, v|
412 font['desc'].each_pair do |k, v|
413 s+=' /'+k+' '+v
413 s+=' /'+k+' '+v
414 end
414 end
415 file=font['file']
415 file=font['file']
416 if(file)
416 if(file)
417 s+=' /FontFile'+(font['type']=='Type1' ? '' : '2')+' '+@FontFiles[file]['n']+' 0 R'
417 s+=' /FontFile'+(font['type']=='Type1' ? '' : '2')+' '+@font_files[file]['n']+' 0 R'
418 end
418 end
419 out(s+'>>')
419 out(s+'>>')
420 out('endobj')
420 out('endobj')
421 end
421 end
422 end
422 end
423 end
423 end
424 end
424 end
425
425
426 def putType0(font)
426 def putType0(font)
427 #Type0
427 #Type0
428 out('/Subtype /Type0')
428 out('/Subtype /Type0')
429 out('/BaseFont /'+font['name']+'-'+font['CMap'])
429 out('/BaseFont /'+font['name']+'-'+font['CMap'])
430 out('/Encoding /'+font['CMap'])
430 out('/Encoding /'+font['CMap'])
431 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
431 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
432 out('>>')
432 out('>>')
433 out('endobj')
433 out('endobj')
434 #CIDFont
434 #CIDFont
435 newobj()
435 newobj()
436 out('<</Type /Font')
436 out('<</Type /Font')
437 out('/Subtype /CIDFontType0')
437 out('/Subtype /CIDFontType0')
438 out('/BaseFont /'+font['name'])
438 out('/BaseFont /'+font['name'])
439 out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
439 out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
440 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
440 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
441 w='/W [1 ['
441 w='/W [1 ['
442 font['cw'].keys.sort.each {|key|
442 font['cw'].keys.sort.each {|key|
443 w+=font['cw'][key].to_s + " "
443 w+=font['cw'][key].to_s + " "
444 # ActionController::Base::logger.debug key.to_s
444 # ActionController::Base::logger.debug key.to_s
445 # ActionController::Base::logger.debug font['cw'][key].to_s
445 # ActionController::Base::logger.debug font['cw'][key].to_s
446 }
446 }
447 out(w+'] 231 325 500 631 [500] 326 389 500]')
447 out(w+'] 231 325 500 631 [500] 326 389 500]')
448 out('>>')
448 out('>>')
449 out('endobj')
449 out('endobj')
450 #Font descriptor
450 #Font descriptor
451 newobj()
451 newobj()
452 out('<</Type /FontDescriptor')
452 out('<</Type /FontDescriptor')
453 out('/FontName /'+font['name'])
453 out('/FontName /'+font['name'])
454 out('/Flags 6')
454 out('/Flags 6')
455 out('/FontBBox [0 -200 1000 900]')
455 out('/FontBBox [0 -200 1000 900]')
456 out('/ItalicAngle 0')
456 out('/ItalicAngle 0')
457 out('/Ascent 800')
457 out('/Ascent 800')
458 out('/Descent -200')
458 out('/Descent -200')
459 out('/CapHeight 800')
459 out('/CapHeight 800')
460 out('/StemV 60')
460 out('/StemV 60')
461 out('>>')
461 out('>>')
462 out('endobj')
462 out('endobj')
463 end
463 end
464 end
464 end
@@ -1,432 +1,432
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
1 # Copyright (c) 2006 4ssoM LLC <www.4ssoM.com>
2 # 1.12 contributed by Ed Moss.
2 # 1.12 contributed by Ed Moss.
3 #
3 #
4 # The MIT License
4 # The MIT License
5 #
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
11 # furnished to do so, subject to the following conditions:
12 #
12 #
13 # The above copyright notice and this permission notice shall be included in
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
14 # all copies or substantial portions of the Software.
15 #
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 # THE SOFTWARE.
22 # THE SOFTWARE.
23 #
23 #
24 # This is direct port of korean.php
24 # This is direct port of korean.php
25 #
25 #
26 # Korean PDF support.
26 # Korean PDF support.
27 #
27 #
28 # Usage is as follows:
28 # Usage is as follows:
29 #
29 #
30 # require 'fpdf'
30 # require 'fpdf'
31 # require 'chinese'
31 # require 'chinese'
32 # pdf = FPDF.new
32 # pdf = FPDF.new
33 # pdf.extend(PDF_Korean)
33 # pdf.extend(PDF_Korean)
34 #
34 #
35 # This allows it to be combined with other extensions, such as the bookmark
35 # This allows it to be combined with other extensions, such as the bookmark
36 # module.
36 # module.
37
37
38 module PDF_Korean
38 module PDF_Korean
39
39
40 UHC_widths={' ' => 333, '!' => 416, '"' => 416, '#' => 833, '$' => 625, '%' => 916, '&' => 833, '\'' => 250,
40 UHC_widths={' ' => 333, '!' => 416, '"' => 416, '#' => 833, '$' => 625, '%' => 916, '&' => 833, '\'' => 250,
41 '(' => 500, ')' => 500, '*' => 500, '+' => 833, ',' => 291, '-' => 833, '.' => 291, '/' => 375, '0' => 625, '1' => 625,
41 '(' => 500, ')' => 500, '*' => 500, '+' => 833, ',' => 291, '-' => 833, '.' => 291, '/' => 375, '0' => 625, '1' => 625,
42 '2' => 625, '3' => 625, '4' => 625, '5' => 625, '6' => 625, '7' => 625, '8' => 625, '9' => 625, ':' => 333, ';' => 333,
42 '2' => 625, '3' => 625, '4' => 625, '5' => 625, '6' => 625, '7' => 625, '8' => 625, '9' => 625, ':' => 333, ';' => 333,
43 '<' => 833, '=' => 833, '>' => 916, '?' => 500, '@' => 1000, 'A' => 791, 'B' => 708, 'C' => 708, 'D' => 750, 'E' => 708,
43 '<' => 833, '=' => 833, '>' => 916, '?' => 500, '@' => 1000, 'A' => 791, 'B' => 708, 'C' => 708, 'D' => 750, 'E' => 708,
44 'F' => 666, 'G' => 750, 'H' => 791, 'I' => 375, 'J' => 500, 'K' => 791, 'L' => 666, 'M' => 916, 'N' => 791, 'O' => 750,
44 'F' => 666, 'G' => 750, 'H' => 791, 'I' => 375, 'J' => 500, 'K' => 791, 'L' => 666, 'M' => 916, 'N' => 791, 'O' => 750,
45 'P' => 666, 'Q' => 750, 'R' => 708, 'S' => 666, 'T' => 791, 'U' => 791, 'V' => 750, 'W' => 1000, 'X' => 708, 'Y' => 708,
45 'P' => 666, 'Q' => 750, 'R' => 708, 'S' => 666, 'T' => 791, 'U' => 791, 'V' => 750, 'W' => 1000, 'X' => 708, 'Y' => 708,
46 'Z' => 666, '[' => 500, '\\' => 375, ']' => 500, '^' => 500, '_' => 500, '`' => 333, 'a' => 541, 'b' => 583, 'c' => 541,
46 'Z' => 666, '[' => 500, '\\' => 375, ']' => 500, '^' => 500, '_' => 500, '`' => 333, 'a' => 541, 'b' => 583, 'c' => 541,
47 'd' => 583, 'e' => 583, 'f' => 375, 'g' => 583, 'h' => 583, 'i' => 291, 'j' => 333, 'k' => 583, 'l' => 291, 'm' => 875,
47 'd' => 583, 'e' => 583, 'f' => 375, 'g' => 583, 'h' => 583, 'i' => 291, 'j' => 333, 'k' => 583, 'l' => 291, 'm' => 875,
48 'n' => 583, 'o' => 583, 'p' => 583, 'q' => 583, 'r' => 458, 's' => 541, 't' => 375, 'u' => 583, 'v' => 583, 'w' => 833,
48 'n' => 583, 'o' => 583, 'p' => 583, 'q' => 583, 'r' => 458, 's' => 541, 't' => 375, 'u' => 583, 'v' => 583, 'w' => 833,
49 'x' => 625, 'y' => 625, 'z' => 500, '{' => 583, '|' => 583, '}' => 583, '~' => 750}
49 'x' => 625, 'y' => 625, 'z' => 500, '{' => 583, '|' => 583, '}' => 583, '~' => 750}
50
50
51 def AddCIDFont(family,style,name,cw,cMap,registry)
51 def AddCIDFont(family,style,name,cw,cMap,registry)
52 fontkey=family.downcase+style.upcase
52 fontkey=family.downcase+style.upcase
53 unless @fonts[fontkey].nil?
53 unless @fonts[fontkey].nil?
54 Error("Font already added: family style")
54 Error("Font already added: family style")
55 end
55 end
56 i=@fonts.length+1
56 i=@fonts.length+1
57 name=name.gsub(' ','')
57 name=name.gsub(' ','')
58 @fonts[fontkey]={'i'=>i,'type'=>'Type0','name'=>name,'up'=>-130,'ut'=>40,'cw'=>cw,
58 @fonts[fontkey]={'i'=>i,'type'=>'Type0','name'=>name,'up'=>-130,'ut'=>40,'cw'=>cw,
59 'CMap'=>cMap,'registry'=>registry}
59 'CMap'=>cMap,'registry'=>registry}
60 end
60 end
61
61
62 def AddCIDFonts(family,name,cw,cMap,registry)
62 def AddCIDFonts(family,name,cw,cMap,registry)
63 AddCIDFont(family,'',name,cw,cMap,registry)
63 AddCIDFont(family,'',name,cw,cMap,registry)
64 AddCIDFont(family,'B',name+',Bold',cw,cMap,registry)
64 AddCIDFont(family,'B',name+',Bold',cw,cMap,registry)
65 AddCIDFont(family,'I',name+',Italic',cw,cMap,registry)
65 AddCIDFont(family,'I',name+',Italic',cw,cMap,registry)
66 AddCIDFont(family,'BI',name+',BoldItalic',cw,cMap,registry)
66 AddCIDFont(family,'BI',name+',BoldItalic',cw,cMap,registry)
67 end
67 end
68
68
69 def AddUHCFont(family='UHC',name='HYSMyeongJoStd-Medium-Acro')
69 def AddUHCFont(family='UHC',name='HYSMyeongJoStd-Medium-Acro')
70 #Add UHC font with proportional Latin
70 #Add UHC font with proportional Latin
71 cw=UHC_widths
71 cw=UHC_widths
72 cMap='KSCms-UHC-H'
72 cMap='KSCms-UHC-H'
73 registry={'ordering'=>'Korea1','supplement'=>1}
73 registry={'ordering'=>'Korea1','supplement'=>1}
74 AddCIDFonts(family,name,cw,cMap,registry)
74 AddCIDFonts(family,name,cw,cMap,registry)
75 end
75 end
76
76
77 def AddUHChwFont(family='UHC-hw',name='HYSMyeongJoStd-Medium-Acro')
77 def AddUHChwFont(family='UHC-hw',name='HYSMyeongJoStd-Medium-Acro')
78 #Add UHC font with half-witdh Latin
78 #Add UHC font with half-witdh Latin
79 32.upto(126) do |i|
79 32.upto(126) do |i|
80 cw[i.chr]=500
80 cw[i.chr]=500
81 end
81 end
82 cMap='KSCms-UHC-HW-H'
82 cMap='KSCms-UHC-HW-H'
83 registry={'ordering'=>'Korea1','supplement'=>1}
83 registry={'ordering'=>'Korea1','supplement'=>1}
84 AddCIDFonts(family,name,cw,cMap,registry)
84 AddCIDFonts(family,name,cw,cMap,registry)
85 end
85 end
86
86
87 def GetStringWidth(s)
87 def GetStringWidth(s)
88 if(@CurrentFont['type']=='Type0')
88 if(@current_font['type']=='Type0')
89 return GetMBStringWidth(s)
89 return GetMBStringWidth(s)
90 else
90 else
91 return super(s)
91 return super(s)
92 end
92 end
93 end
93 end
94
94
95 def GetMBStringWidth(s)
95 def GetMBStringWidth(s)
96 #Multi-byte version of GetStringWidth()
96 #Multi-byte version of GetStringWidth()
97 l=0
97 l=0
98 cw=@CurrentFont['cw']
98 cw=@current_font['cw']
99 nb=s.length
99 nb=s.length
100 i=0
100 i=0
101 while(i<nb)
101 while(i<nb)
102 c = s[i].is_a?(String) ? s[i].ord : s[i]
102 c = s[i].is_a?(String) ? s[i].ord : s[i]
103 if(c<128)
103 if(c<128)
104 l+=cw[c.chr] if cw[c.chr]
104 l+=cw[c.chr] if cw[c.chr]
105 i+=1
105 i+=1
106 else
106 else
107 l+=1000
107 l+=1000
108 i+=2
108 i+=2
109 end
109 end
110 end
110 end
111 return l*@FontSize/1000
111 return l*@font_size/1000
112 end
112 end
113
113
114 def MultiCell(w,h,txt,border=0,align='L',fill=0)
114 def MultiCell(w,h,txt,border=0,align='L',fill=0)
115 if(@CurrentFont['type']=='Type0')
115 if(@current_font['type']=='Type0')
116 MBMultiCell(w,h,txt,border,align,fill)
116 MBMultiCell(w,h,txt,border,align,fill)
117 else
117 else
118 super(w,h,txt,border,align,fill)
118 super(w,h,txt,border,align,fill)
119 end
119 end
120 end
120 end
121
121
122 def MBMultiCell(w,h,txt,border=0,align='L',fill=0)
122 def MBMultiCell(w,h,txt,border=0,align='L',fill=0)
123 #Multi-byte version of MultiCell()
123 #Multi-byte version of MultiCell()
124 cw=@CurrentFont['cw']
124 cw=@current_font['cw']
125 if(w==0)
125 if(w==0)
126 w=@w-@rMargin-@x
126 w=@w-@r_margin-@x
127 end
127 end
128 wmax=(w-2*@cMargin)*1000/@FontSize
128 wmax=(w-2*@c_margin)*1000/@font_size
129 s=txt.gsub("\r",'')
129 s=txt.gsub("\r",'')
130 nb=s.length
130 nb=s.length
131 if(nb>0 and s[nb-1]=="\n")
131 if(nb>0 and s[nb-1]=="\n")
132 nb-=1
132 nb-=1
133 end
133 end
134 b=0
134 b=0
135 if(border)
135 if(border)
136 if(border==1)
136 if(border==1)
137 border='LTRB'
137 border='LTRB'
138 b='LRT'
138 b='LRT'
139 b2='LR'
139 b2='LR'
140 else
140 else
141 b2=''
141 b2=''
142 b2='L' unless border.to_s.index('L').nil?
142 b2='L' unless border.to_s.index('L').nil?
143 b2=b2+'R' unless border.to_s.index('R').nil?
143 b2=b2+'R' unless border.to_s.index('R').nil?
144 b=(border.to_s.index('T')) ? (b2+'T') : b2
144 b=(border.to_s.index('T')) ? (b2+'T') : b2
145 end
145 end
146 end
146 end
147 sep=-1
147 sep=-1
148 i=0
148 i=0
149 j=0
149 j=0
150 l=0
150 l=0
151 nl=1
151 nl=1
152 while(i<nb)
152 while(i<nb)
153 #Get next character
153 #Get next character
154 c = s[i].is_a?(String) ? s[i].ord : s[i]
154 c = s[i].is_a?(String) ? s[i].ord : s[i]
155 #Check if ASCII or MB
155 #Check if ASCII or MB
156 ascii=(c<128)
156 ascii=(c<128)
157 if(c.chr=="\n")
157 if(c.chr=="\n")
158 #Explicit line break
158 #Explicit line break
159 Cell(w,h,s[j,i-j],b,2,align,fill)
159 Cell(w,h,s[j,i-j],b,2,align,fill)
160 i+=1
160 i+=1
161 sep=-1
161 sep=-1
162 j=i
162 j=i
163 l=0
163 l=0
164 nl+=1
164 nl+=1
165 if(border and nl==2)
165 if(border and nl==2)
166 b=b2
166 b=b2
167 end
167 end
168 next
168 next
169 end
169 end
170 if(!ascii)
170 if(!ascii)
171 sep=i
171 sep=i
172 ls=l
172 ls=l
173 elsif(c.chr==' ')
173 elsif(c.chr==' ')
174 sep=i
174 sep=i
175 ls=l
175 ls=l
176 end
176 end
177 l+=(ascii ? cw[c.chr] : 1000) || 0
177 l+=(ascii ? cw[c.chr] : 1000) || 0
178 if(l>wmax)
178 if(l>wmax)
179 #Automatic line break
179 #Automatic line break
180 if(sep==-1 or i==j)
180 if(sep==-1 or i==j)
181 if(i==j)
181 if(i==j)
182 i+=ascii ? 1 : 2
182 i+=ascii ? 1 : 2
183 end
183 end
184 Cell(w,h,s[j,i-j],b,2,align,fill)
184 Cell(w,h,s[j,i-j],b,2,align,fill)
185 else
185 else
186 Cell(w,h,s[j,sep-j],b,2,align,fill)
186 Cell(w,h,s[j,sep-j],b,2,align,fill)
187 i=(s[sep].chr==' ') ? sep+1 : sep
187 i=(s[sep].chr==' ') ? sep+1 : sep
188 end
188 end
189 sep=-1
189 sep=-1
190 j=i
190 j=i
191 l=0
191 l=0
192 nl+=1
192 nl+=1
193 if(border and nl==2)
193 if(border and nl==2)
194 b=b2
194 b=b2
195 end
195 end
196 else
196 else
197 i+=ascii ? 1 : 2
197 i+=ascii ? 1 : 2
198 end
198 end
199 end
199 end
200 #Last chunk
200 #Last chunk
201 if(border and not border.to_s.index('B').nil?)
201 if(border and not border.to_s.index('B').nil?)
202 b+='B'
202 b+='B'
203 end
203 end
204 Cell(w,h,s[j,i-j],b,2,align,fill)
204 Cell(w,h,s[j,i-j],b,2,align,fill)
205 @x=@lMargin
205 @x=@l_margin
206 end
206 end
207
207
208 def Write(h,txt,link='')
208 def Write(h,txt,link='')
209 if(@CurrentFont['type']=='Type0')
209 if(@current_font['type']=='Type0')
210 MBWrite(h,txt,link)
210 MBWrite(h,txt,link)
211 else
211 else
212 super(h,txt,link)
212 super(h,txt,link)
213 end
213 end
214 end
214 end
215
215
216 def MBWrite(h,txt,link)
216 def MBWrite(h,txt,link)
217 #Multi-byte version of Write()
217 #Multi-byte version of Write()
218 cw=@CurrentFont['cw']
218 cw=@current_font['cw']
219 w=@w-@rMargin-@x
219 w=@w-@r_margin-@x
220 wmax=(w-2*@cMargin)*1000/@FontSize
220 wmax=(w-2*@c_margin)*1000/@font_size
221 s=txt.gsub("\r",'')
221 s=txt.gsub("\r",'')
222 nb=s.length
222 nb=s.length
223 sep=-1
223 sep=-1
224 i=0
224 i=0
225 j=0
225 j=0
226 l=0
226 l=0
227 nl=1
227 nl=1
228 while(i<nb)
228 while(i<nb)
229 #Get next character
229 #Get next character
230 c = s[i].is_a?(String) ? s[i].ord : s[i]
230 c = s[i].is_a?(String) ? s[i].ord : s[i]
231 #Check if ASCII or MB
231 #Check if ASCII or MB
232 ascii=(c<128)
232 ascii=(c<128)
233 if(c.chr=="\n")
233 if(c.chr=="\n")
234 #Explicit line break
234 #Explicit line break
235 Cell(w,h,s[j,i-j],0,2,'',0,link)
235 Cell(w,h,s[j,i-j],0,2,'',0,link)
236 i+=1
236 i+=1
237 sep=-1
237 sep=-1
238 j=i
238 j=i
239 l=0
239 l=0
240 if(nl==1)
240 if(nl==1)
241 @x=@lMargin
241 @x=@l_margin
242 w=@w-@rMargin-@x
242 w=@w-@r_margin-@x
243 wmax=(w-2*@cMargin)*1000/@FontSize
243 wmax=(w-2*@c_margin)*1000/@font_size
244 end
244 end
245 nl+=1
245 nl+=1
246 next
246 next
247 end
247 end
248 if(!ascii or c.chr==' ')
248 if(!ascii or c.chr==' ')
249 sep=i
249 sep=i
250 end
250 end
251 l+=(ascii ? cw[c.chr] : 1000) || 0
251 l+=(ascii ? cw[c.chr] : 1000) || 0
252 if(l>wmax)
252 if(l>wmax)
253 #Automatic line break
253 #Automatic line break
254 if(sep==-1 or i==j)
254 if(sep==-1 or i==j)
255 if(@x>@lMargin)
255 if(@x>@l_margin)
256 #Move to next line
256 #Move to next line
257 @x=@lMargin
257 @x=@l_margin
258 @y+=h
258 @y+=h
259 w=@w-@rMargin-@x
259 w=@w-@r_margin-@x
260 wmax=(w-2*@cMargin)*1000/@FontSize
260 wmax=(w-2*@c_margin)*1000/@font_size
261 i+=1
261 i+=1
262 nl+=1
262 nl+=1
263 next
263 next
264 end
264 end
265 if(i==j)
265 if(i==j)
266 i+=ascii ? 1 : 2
266 i+=ascii ? 1 : 2
267 end
267 end
268 Cell(w,h,s[j,i-j],0,2,'',0,link)
268 Cell(w,h,s[j,i-j],0,2,'',0,link)
269 else
269 else
270 Cell(w,h,s[j,sep-j],0,2,'',0,link)
270 Cell(w,h,s[j,sep-j],0,2,'',0,link)
271 i=(s[sep].chr==' ') ? sep+1 : sep
271 i=(s[sep].chr==' ') ? sep+1 : sep
272 end
272 end
273 sep=-1
273 sep=-1
274 j=i
274 j=i
275 l=0
275 l=0
276 if(nl==1)
276 if(nl==1)
277 @x=@lMargin
277 @x=@l_margin
278 w=@w-@rMargin-@x
278 w=@w-@r_margin-@x
279 wmax=(w-2*@cMargin)*1000/@FontSize
279 wmax=(w-2*@c_margin)*1000/@font_size
280 end
280 end
281 nl+=1
281 nl+=1
282 else
282 else
283 i+=ascii ? 1 : 2
283 i+=ascii ? 1 : 2
284 end
284 end
285 end
285 end
286 #Last chunk
286 #Last chunk
287 if(i!=j)
287 if(i!=j)
288 Cell(l/1000*@FontSize,h,s[j,i-j],0,0,'',0,link)
288 Cell(l/1000*@font_size,h,s[j,i-j],0,0,'',0,link)
289 end
289 end
290 end
290 end
291
291
292 private
292 private
293
293
294 def putfonts()
294 def putfonts()
295 nf=@n
295 nf=@n
296 @diffs.each do |diff|
296 @diffs.each do |diff|
297 #Encodings
297 #Encodings
298 newobj()
298 newobj()
299 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>')
299 out('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['+diff+']>>')
300 out('endobj')
300 out('endobj')
301 end
301 end
302 # mqr=get_magic_quotes_runtime()
302 # mqr=get_magic_quotes_runtime()
303 # set_magic_quotes_runtime(0)
303 # set_magic_quotes_runtime(0)
304 @FontFiles.each_pair do |file, info|
304 @font_files.each_pair do |file, info|
305 #Font file embedding
305 #Font file embedding
306 newobj()
306 newobj()
307 @FontFiles[file]['n']=@n
307 @font_files[file]['n']=@n
308 if(defined('FPDF_FONTPATH'))
308 if(defined('FPDF_FONTPATH'))
309 file=FPDF_FONTPATH+file
309 file=FPDF_FONTPATH+file
310 end
310 end
311 size=filesize(file)
311 size=filesize(file)
312 if(!size)
312 if(!size)
313 Error('Font file not found')
313 Error('Font file not found')
314 end
314 end
315 out('<</Length '+size)
315 out('<</Length '+size)
316 if(file[-2]=='.z')
316 if(file[-2]=='.z')
317 out('/Filter /FlateDecode')
317 out('/Filter /FlateDecode')
318 end
318 end
319 out('/Length1 '+info['length1'])
319 out('/Length1 '+info['length1'])
320 if(not info['length2'].nil?)
320 if(not info['length2'].nil?)
321 out('/Length2 '+info['length2']+' /Length3 0')
321 out('/Length2 '+info['length2']+' /Length3 0')
322 end
322 end
323 out('>>')
323 out('>>')
324 f=fopen(file,'rb')
324 f=fopen(file,'rb')
325 putstream(fread(f,size))
325 putstream(fread(f,size))
326 fclose(f)
326 fclose(f)
327 out('endobj')
327 out('endobj')
328 end
328 end
329 # set_magic_quotes_runtime(mqr)
329 # set_magic_quotes_runtime(mqr)
330 @fonts.each_pair do |k, font|
330 @fonts.each_pair do |k, font|
331 #Font objects
331 #Font objects
332 newobj()
332 newobj()
333 @fonts[k]['n']=@n
333 @fonts[k]['n']=@n
334 out('<</Type /Font')
334 out('<</Type /Font')
335 if(font['type']=='Type0')
335 if(font['type']=='Type0')
336 putType0(font)
336 putType0(font)
337 else
337 else
338 name=font['name']
338 name=font['name']
339 out('/BaseFont /'+name)
339 out('/BaseFont /'+name)
340 if(font['type']=='core')
340 if(font['type']=='core')
341 #Standard font
341 #Standard font
342 out('/Subtype /Type1')
342 out('/Subtype /Type1')
343 if(name!='Symbol' and name!='ZapfDingbats')
343 if(name!='Symbol' and name!='ZapfDingbats')
344 out('/Encoding /WinAnsiEncoding')
344 out('/Encoding /WinAnsiEncoding')
345 end
345 end
346 else
346 else
347 #Additional font
347 #Additional font
348 out('/Subtype /'+font['type'])
348 out('/Subtype /'+font['type'])
349 out('/FirstChar 32')
349 out('/FirstChar 32')
350 out('/LastChar 255')
350 out('/LastChar 255')
351 out('/Widths '+(@n+1)+' 0 R')
351 out('/Widths '+(@n+1)+' 0 R')
352 out('/FontDescriptor '+(@n+2)+' 0 R')
352 out('/FontDescriptor '+(@n+2)+' 0 R')
353 if(font['enc'])
353 if(font['enc'])
354 if(not font['diff'].nil?)
354 if(not font['diff'].nil?)
355 out('/Encoding '+(nf+font['diff'])+' 0 R')
355 out('/Encoding '+(nf+font['diff'])+' 0 R')
356 else
356 else
357 out('/Encoding /WinAnsiEncoding')
357 out('/Encoding /WinAnsiEncoding')
358 end
358 end
359 end
359 end
360 end
360 end
361 out('>>')
361 out('>>')
362 out('endobj')
362 out('endobj')
363 if(font['type']!='core')
363 if(font['type']!='core')
364 #Widths
364 #Widths
365 newobj()
365 newobj()
366 cw=font['cw']
366 cw=font['cw']
367 s='['
367 s='['
368 32.upto(255) do |i|
368 32.upto(255) do |i|
369 s+=cw[i.chr]+' '
369 s+=cw[i.chr]+' '
370 end
370 end
371 out(s+']')
371 out(s+']')
372 out('endobj')
372 out('endobj')
373 #Descriptor
373 #Descriptor
374 newobj()
374 newobj()
375 s='<</Type /FontDescriptor /FontName /'+name
375 s='<</Type /FontDescriptor /FontName /'+name
376 font['desc'].each_pair do |k, v|
376 font['desc'].each_pair do |k, v|
377 s+=' /'+k+' '+v
377 s+=' /'+k+' '+v
378 end
378 end
379 file=font['file']
379 file=font['file']
380 if(file)
380 if(file)
381 s+=' /FontFile'+(font['type']=='Type1' ? '' : '2')+' '+@FontFiles[file]['n']+' 0 R'
381 s+=' /FontFile'+(font['type']=='Type1' ? '' : '2')+' '+@font_files[file]['n']+' 0 R'
382 end
382 end
383 out(s+'>>')
383 out(s+'>>')
384 out('endobj')
384 out('endobj')
385 end
385 end
386 end
386 end
387 end
387 end
388 end
388 end
389
389
390 def putType0(font)
390 def putType0(font)
391 #Type0
391 #Type0
392 out('/Subtype /Type0')
392 out('/Subtype /Type0')
393 out('/BaseFont /'+font['name']+'-'+font['CMap'])
393 out('/BaseFont /'+font['name']+'-'+font['CMap'])
394 out('/Encoding /'+font['CMap'])
394 out('/Encoding /'+font['CMap'])
395 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
395 out('/DescendantFonts ['+(@n+1).to_s+' 0 R]')
396 out('>>')
396 out('>>')
397 out('endobj')
397 out('endobj')
398 #CIDFont
398 #CIDFont
399 newobj()
399 newobj()
400 out('<</Type /Font')
400 out('<</Type /Font')
401 out('/Subtype /CIDFontType0')
401 out('/Subtype /CIDFontType0')
402 out('/BaseFont /'+font['name'])
402 out('/BaseFont /'+font['name'])
403 out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
403 out('/CIDSystemInfo <</Registry (Adobe) /Ordering ('+font['registry']['ordering']+') /Supplement '+font['registry']['supplement'].to_s+'>>')
404 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
404 out('/FontDescriptor '+(@n+1).to_s+' 0 R')
405 if(font['CMap']=='KSCms-UHC-HW-H')
405 if(font['CMap']=='KSCms-UHC-HW-H')
406 w='8094 8190 500'
406 w='8094 8190 500'
407 else
407 else
408 w='1 ['
408 w='1 ['
409 font['cw'].keys.sort.each {|key|
409 font['cw'].keys.sort.each {|key|
410 w+=font['cw'][key].to_s + " "
410 w+=font['cw'][key].to_s + " "
411 # ActionController::Base::logger.debug key.to_s
411 # ActionController::Base::logger.debug key.to_s
412 # ActionController::Base::logger.debug font['cw'][key].to_s
412 # ActionController::Base::logger.debug font['cw'][key].to_s
413 }
413 }
414 w +=']'
414 w +=']'
415 end
415 end
416 out('/W ['+w+']>>')
416 out('/W ['+w+']>>')
417 out('endobj')
417 out('endobj')
418 #Font descriptor
418 #Font descriptor
419 newobj()
419 newobj()
420 out('<</Type /FontDescriptor')
420 out('<</Type /FontDescriptor')
421 out('/FontName /'+font['name'])
421 out('/FontName /'+font['name'])
422 out('/Flags 6')
422 out('/Flags 6')
423 out('/FontBBox [0 -200 1000 900]')
423 out('/FontBBox [0 -200 1000 900]')
424 out('/ItalicAngle 0')
424 out('/ItalicAngle 0')
425 out('/Ascent 800')
425 out('/Ascent 800')
426 out('/Descent -200')
426 out('/Descent -200')
427 out('/CapHeight 800')
427 out('/CapHeight 800')
428 out('/StemV 50')
428 out('/StemV 50')
429 out('>>')
429 out('>>')
430 out('endobj')
430 out('endobj')
431 end
431 end
432 end
432 end
General Comments 0
You need to be logged in to leave comments. Login now