@@ -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'> </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'> </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'> </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'> </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'> </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'> </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'> </div>" |
|
752 | output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'> </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'> </div>" |
|
755 | output << "<div style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'> </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(@ |
|
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=@ |
|
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*@ |
|
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(@ |
|
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=@ |
|
155 | cw=@current_font['cw'] | |
156 | if(w==0) |
|
156 | if(w==0) | |
157 |
w=@w-@r |
|
157 | w=@w-@r_margin-@x | |
158 | end |
|
158 | end | |
159 |
wmax=(w-2*@c |
|
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=@l |
|
236 | @x=@l_margin | |
237 | end |
|
237 | end | |
238 |
|
238 | |||
239 | def Write(h,txt,link='') |
|
239 | def Write(h,txt,link='') | |
240 |
if(@ |
|
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=@ |
|
249 | cw=@current_font['cw'] | |
250 |
w=@w-@r |
|
250 | w=@w-@r_margin-@x | |
251 |
wmax=(w-2*@c |
|
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=@l |
|
272 | @x=@l_margin | |
273 |
w=@w-@r |
|
273 | w=@w-@r_margin-@x | |
274 |
wmax=(w-2*@c |
|
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>@l |
|
286 | if(@x>@l_margin) | |
287 | #Move to next line |
|
287 | #Move to next line | |
288 |
@x=@l |
|
288 | @x=@l_margin | |
289 | @y+=h |
|
289 | @y+=h | |
290 |
w=@w-@r |
|
290 | w=@w-@r_margin-@x | |
291 |
wmax=(w-2*@c |
|
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=@l |
|
308 | @x=@l_margin | |
309 |
w=@w-@r |
|
309 | w=@w-@r_margin-@x | |
310 |
wmax=(w-2*@c |
|
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*@ |
|
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 |
@ |
|
335 | @font_files.each_pair do |file, info| | |
336 | #Font file embedding |
|
336 | #Font file embedding | |
337 | newobj() |
|
337 | newobj() | |
338 |
@ |
|
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')+' '+@ |
|
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(@ |
|
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=@ |
|
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*@ |
|
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(@ |
|
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=@ |
|
131 | cw=@current_font['cw'] | |
132 | if(w==0) |
|
132 | if(w==0) | |
133 |
w=@w-@r |
|
133 | w=@w-@r_margin-@x | |
134 | end |
|
134 | end | |
135 |
wmax=(w-2*@c |
|
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=@l |
|
224 | @x=@l_margin | |
225 | end |
|
225 | end | |
226 |
|
226 | |||
227 | def Write(h,txt,link='') |
|
227 | def Write(h,txt,link='') | |
228 |
if(@ |
|
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=@ |
|
237 | cw=@current_font['cw'] | |
238 |
w=@w-@r |
|
238 | w=@w-@r_margin-@x | |
239 |
wmax=(w-2*@c |
|
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=@l |
|
260 | @x=@l_margin | |
261 |
w=@w-@r |
|
261 | w=@w-@r_margin-@x | |
262 |
wmax=(w-2*@c |
|
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>@l |
|
288 | if(@x>@l_margin) | |
289 | #Move to next line |
|
289 | #Move to next line | |
290 |
@x=@l |
|
290 | @x=@l_margin | |
291 | @y+=h |
|
291 | @y+=h | |
292 |
w=@w-@r |
|
292 | w=@w-@r_margin-@x | |
293 |
wmax=(w-2*@c |
|
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=@l |
|
310 | @x=@l_margin | |
311 |
w=@w-@r |
|
311 | w=@w-@r_margin-@x | |
312 |
wmax=(w-2*@c |
|
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*@ |
|
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 |
@ |
|
340 | @font_files.each_pair do |file, info| | |
341 | #Font file embedding |
|
341 | #Font file embedding | |
342 | newobj() |
|
342 | newobj() | |
343 |
@ |
|
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')+' '+@ |
|
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(@ |
|
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=@ |
|
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*@ |
|
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(@ |
|
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=@ |
|
124 | cw=@current_font['cw'] | |
125 | if(w==0) |
|
125 | if(w==0) | |
126 |
w=@w-@r |
|
126 | w=@w-@r_margin-@x | |
127 | end |
|
127 | end | |
128 |
wmax=(w-2*@c |
|
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=@l |
|
205 | @x=@l_margin | |
206 | end |
|
206 | end | |
207 |
|
207 | |||
208 | def Write(h,txt,link='') |
|
208 | def Write(h,txt,link='') | |
209 |
if(@ |
|
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=@ |
|
218 | cw=@current_font['cw'] | |
219 |
w=@w-@r |
|
219 | w=@w-@r_margin-@x | |
220 |
wmax=(w-2*@c |
|
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=@l |
|
241 | @x=@l_margin | |
242 |
w=@w-@r |
|
242 | w=@w-@r_margin-@x | |
243 |
wmax=(w-2*@c |
|
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>@l |
|
255 | if(@x>@l_margin) | |
256 | #Move to next line |
|
256 | #Move to next line | |
257 |
@x=@l |
|
257 | @x=@l_margin | |
258 | @y+=h |
|
258 | @y+=h | |
259 |
w=@w-@r |
|
259 | w=@w-@r_margin-@x | |
260 |
wmax=(w-2*@c |
|
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=@l |
|
277 | @x=@l_margin | |
278 |
w=@w-@r |
|
278 | w=@w-@r_margin-@x | |
279 |
wmax=(w-2*@c |
|
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*@ |
|
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 |
@ |
|
304 | @font_files.each_pair do |file, info| | |
305 | #Font file embedding |
|
305 | #Font file embedding | |
306 | newobj() |
|
306 | newobj() | |
307 |
@ |
|
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')+' '+@ |
|
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