Mercurial > hg > soundsoftware-site
comparison lib/redmine/export/pdf.rb @ 1115:433d4f72a19b redmine-2.2
Update to Redmine SVN revision 11137 on 2.2-stable branch
author | Chris Cannam |
---|---|
date | Mon, 07 Jan 2013 12:01:42 +0000 |
parents | cbb26bc654de |
children | 3e4c3460b6ca |
comparison
equal
deleted
inserted
replaced
929:5f33065ddc4b | 1115:433d4f72a19b |
---|---|
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-2012 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. |
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 'tcpdf' | |
21 require 'fpdf/chinese' | 22 require 'fpdf/chinese' |
22 require 'fpdf/japanese' | 23 require 'fpdf/japanese' |
23 require 'fpdf/korean' | 24 require 'fpdf/korean' |
24 require 'core/rmagick' | |
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 |
32 | 32 |
33 class ITCPDF < TCPDF | 33 class ITCPDF < TCPDF |
34 include Redmine::I18n | 34 include Redmine::I18n |
35 attr_accessor :footer_date | 35 attr_accessor :footer_date |
36 | 36 |
37 def initialize(lang) | 37 def initialize(lang, orientation='P') |
38 @@k_path_cache = Rails.root.join('tmp', 'pdf') | 38 @@k_path_cache = Rails.root.join('tmp', 'pdf') |
39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) | 39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache) |
40 set_language_if_valid lang | 40 set_language_if_valid lang |
41 pdf_encoding = l(:general_pdf_encoding).upcase | 41 pdf_encoding = l(:general_pdf_encoding).upcase |
42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) | 42 super(orientation, 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding) |
43 case current_language.to_s.downcase | 43 case current_language.to_s.downcase |
44 when 'vi' | 44 when 'vi' |
45 @font_for_content = 'DejaVuSans' | 45 @font_for_content = 'DejaVuSans' |
46 @font_for_footer = 'DejaVuSans' | 46 @font_for_footer = 'DejaVuSans' |
47 else | 47 else |
74 @font_for_footer = 'Helvetica' | 74 @font_for_footer = 'Helvetica' |
75 end | 75 end |
76 end | 76 end |
77 SetCreator(Redmine::Info.app_name) | 77 SetCreator(Redmine::Info.app_name) |
78 SetFont(@font_for_content) | 78 SetFont(@font_for_content) |
79 @outlines = [] | |
80 @outlineRoot = nil | |
79 end | 81 end |
80 | 82 |
81 def SetFontStyle(style, size) | 83 def SetFontStyle(style, size) |
82 SetFont(@font_for_content, style, size) | 84 SetFont(@font_for_content, style, size) |
83 end | 85 end |
140 RDMCell(0, 5, @footer_date, 0, 0, 'L') | 142 RDMCell(0, 5, @footer_date, 0, 0, 'L') |
141 SetY(-15) | 143 SetY(-15) |
142 SetX(-30) | 144 SetX(-30) |
143 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') | 145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') |
144 end | 146 end |
147 | |
148 def Bookmark(txt, level=0, y=0) | |
149 if (y == -1) | |
150 y = GetY() | |
151 end | |
152 @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k} | |
153 end | |
154 | |
155 def bookmark_title(txt) | |
156 txt = begin | |
157 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) | |
158 hextxt = "<FEFF" # FEFF is BOM | |
159 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join | |
160 hextxt << ">" | |
161 rescue | |
162 txt | |
163 end || '' | |
164 end | |
165 | |
166 def putbookmarks | |
167 nb=@outlines.size | |
168 return if (nb==0) | |
169 lru=[] | |
170 level=0 | |
171 @outlines.each_with_index do |o, i| | |
172 if(o[:l]>0) | |
173 parent=lru[o[:l]-1] | |
174 #Set parent and last pointers | |
175 @outlines[i][:parent]=parent | |
176 @outlines[parent][:last]=i | |
177 if (o[:l]>level) | |
178 #Level increasing: set first pointer | |
179 @outlines[parent][:first]=i | |
180 end | |
181 else | |
182 @outlines[i][:parent]=nb | |
183 end | |
184 if (o[:l]<=level && i>0) | |
185 #Set prev and next pointers | |
186 prev=lru[o[:l]] | |
187 @outlines[prev][:next]=i | |
188 @outlines[i][:prev]=prev | |
189 end | |
190 lru[o[:l]]=i | |
191 level=o[:l] | |
192 end | |
193 #Outline items | |
194 n=self.n+1 | |
195 @outlines.each_with_index do |o, i| | |
196 newobj() | |
197 out('<</Title '+bookmark_title(o[:t])) | |
198 out("/Parent #{n+o[:parent]} 0 R") | |
199 if (o[:prev]) | |
200 out("/Prev #{n+o[:prev]} 0 R") | |
201 end | |
202 if (o[:next]) | |
203 out("/Next #{n+o[:next]} 0 R") | |
204 end | |
205 if (o[:first]) | |
206 out("/First #{n+o[:first]} 0 R") | |
207 end | |
208 if (o[:last]) | |
209 out("/Last #{n+o[:last]} 0 R") | |
210 end | |
211 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]]) | |
212 out('/Count 0>>') | |
213 out('endobj') | |
214 end | |
215 #Outline root | |
216 newobj() | |
217 @outlineRoot=self.n | |
218 out("<</Type /Outlines /First #{n} 0 R"); | |
219 out("/Last #{n+lru[0]} 0 R>>"); | |
220 out('endobj'); | |
221 end | |
222 | |
223 def putresources() | |
224 super | |
225 putbookmarks() | |
226 end | |
227 | |
228 def putcatalog() | |
229 super | |
230 if(@outlines.size > 0) | |
231 out("/Outlines #{@outlineRoot} 0 R"); | |
232 out('/PageMode /UseOutlines'); | |
233 end | |
234 end | |
235 end | |
236 | |
237 # fetch row values | |
238 def fetch_row_values(issue, query, level) | |
239 query.inline_columns.collect do |column| | |
240 s = if column.is_a?(QueryCustomFieldColumn) | |
241 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id} | |
242 show_value(cv) | |
243 else | |
244 value = issue.send(column.name) | |
245 if column.name == :subject | |
246 value = " " * level + value | |
247 end | |
248 if value.is_a?(Date) | |
249 format_date(value) | |
250 elsif value.is_a?(Time) | |
251 format_time(value) | |
252 else | |
253 value | |
254 end | |
255 end | |
256 s.to_s | |
257 end | |
258 end | |
259 | |
260 # calculate columns width | |
261 def calc_col_width(issues, query, table_width, pdf) | |
262 # calculate statistics | |
263 # by captions | |
264 pdf.SetFontStyle('B',8) | |
265 col_padding = pdf.GetStringWidth('OO') | |
266 col_width_min = query.inline_columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding} | |
267 col_width_max = Array.new(col_width_min) | |
268 col_width_avg = Array.new(col_width_min) | |
269 word_width_max = query.inline_columns.map {|c| | |
270 n = 10 | |
271 c.caption.split.each {|w| | |
272 x = pdf.GetStringWidth(w) + col_padding | |
273 n = x if n < x | |
274 } | |
275 n | |
276 } | |
277 | |
278 # by properties of issues | |
279 pdf.SetFontStyle('',8) | |
280 col_padding = pdf.GetStringWidth('OO') | |
281 k = 1 | |
282 issue_list(issues) {|issue, level| | |
283 k += 1 | |
284 values = fetch_row_values(issue, query, level) | |
285 values.each_with_index {|v,i| | |
286 n = pdf.GetStringWidth(v) + col_padding | |
287 col_width_max[i] = n if col_width_max[i] < n | |
288 col_width_min[i] = n if col_width_min[i] > n | |
289 col_width_avg[i] += n | |
290 v.split.each {|w| | |
291 x = pdf.GetStringWidth(w) + col_padding | |
292 word_width_max[i] = x if word_width_max[i] < x | |
293 } | |
294 } | |
295 } | |
296 col_width_avg.map! {|x| x / k} | |
297 | |
298 # calculate columns width | |
299 ratio = table_width / col_width_avg.inject(0) {|s,w| s += w} | |
300 col_width = col_width_avg.map {|w| w * ratio} | |
301 | |
302 # correct max word width if too many columns | |
303 ratio = table_width / word_width_max.inject(0) {|s,w| s += w} | |
304 word_width_max.map! {|v| v * ratio} if ratio < 1 | |
305 | |
306 # correct and lock width of some columns | |
307 done = 1 | |
308 col_fix = [] | |
309 col_width.each_with_index do |w,i| | |
310 if w > col_width_max[i] | |
311 col_width[i] = col_width_max[i] | |
312 col_fix[i] = 1 | |
313 done = 0 | |
314 elsif w < word_width_max[i] | |
315 col_width[i] = word_width_max[i] | |
316 col_fix[i] = 1 | |
317 done = 0 | |
318 else | |
319 col_fix[i] = 0 | |
320 end | |
321 end | |
322 | |
323 # iterate while need to correct and lock coluns width | |
324 while done == 0 | |
325 # calculate free & locked columns width | |
326 done = 1 | |
327 fix_col_width = 0 | |
328 free_col_width = 0 | |
329 col_width.each_with_index do |w,i| | |
330 if col_fix[i] == 1 | |
331 fix_col_width += w | |
332 else | |
333 free_col_width += w | |
334 end | |
335 end | |
336 | |
337 # calculate column normalizing ratio | |
338 if free_col_width == 0 | |
339 ratio = table_width / col_width.inject(0) {|s,w| s += w} | |
340 else | |
341 ratio = (table_width - fix_col_width) / free_col_width | |
342 end | |
343 | |
344 # correct columns width | |
345 col_width.each_with_index do |w,i| | |
346 if col_fix[i] == 0 | |
347 col_width[i] = w * ratio | |
348 | |
349 # check if column width less then max word width | |
350 if col_width[i] < word_width_max[i] | |
351 col_width[i] = word_width_max[i] | |
352 col_fix[i] = 1 | |
353 done = 0 | |
354 elsif col_width[i] > col_width_max[i] | |
355 col_width[i] = col_width_max[i] | |
356 col_fix[i] = 1 | |
357 done = 0 | |
358 end | |
359 end | |
360 end | |
361 end | |
362 col_width | |
363 end | |
364 | |
365 def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) | |
366 # headers | |
367 pdf.SetFontStyle('B',8) | |
368 pdf.SetFillColor(230, 230, 230) | |
369 | |
370 # render it background to find the max height used | |
371 base_x = pdf.GetX | |
372 base_y = pdf.GetY | |
373 max_height = issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) | |
374 pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD'); | |
375 pdf.SetXY(base_x, base_y); | |
376 | |
377 # write the cells on page | |
378 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) | |
379 issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, row_height, true) | |
380 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | |
381 pdf.SetY(base_y + max_height); | |
382 | |
383 # rows | |
384 pdf.SetFontStyle('',8) | |
385 pdf.SetFillColor(255, 255, 255) | |
145 end | 386 end |
146 | 387 |
147 # Returns a PDF string of a list of issues | 388 # Returns a PDF string of a list of issues |
148 def issues_to_pdf(issues, project, query) | 389 def issues_to_pdf(issues, project, query) |
149 pdf = ITCPDF.new(current_language) | 390 pdf = ITCPDF.new(current_language, "L") |
150 title = query.new_record? ? l(:label_issue_plural) : query.name | 391 title = query.new_record? ? l(:label_issue_plural) : query.name |
151 title = "#{project} - #{title}" if project | 392 title = "#{project} - #{title}" if project |
152 pdf.SetTitle(title) | 393 pdf.SetTitle(title) |
153 pdf.alias_nb_pages | 394 pdf.alias_nb_pages |
154 pdf.footer_date = format_date(Date.today) | 395 pdf.footer_date = format_date(Date.today) |
159 page_height = 210 | 400 page_height = 210 |
160 page_width = 297 | 401 page_width = 297 |
161 right_margin = 10 | 402 right_margin = 10 |
162 bottom_margin = 20 | 403 bottom_margin = 20 |
163 col_id_width = 10 | 404 col_id_width = 10 |
164 row_height = 5 | 405 row_height = 4 |
165 | 406 |
166 # column widths | 407 # column widths |
167 table_width = page_width - right_margin - 10 # fixed left margin | 408 table_width = page_width - right_margin - 10 # fixed left margin |
168 col_width = [] | 409 col_width = [] |
169 unless query.columns.empty? | 410 unless query.inline_columns.empty? |
170 col_width = query.columns.collect do |c| | 411 col_width = calc_col_width(issues, query, table_width - col_id_width, pdf) |
171 (c.name == :subject || (c.is_a?(QueryCustomFieldColumn) && | 412 table_width = col_width.inject(0) {|s,v| s += v} |
172 ['string', 'text'].include?(c.custom_field.field_format))) ? 4.0 : 1.0 | 413 end |
173 end | 414 |
174 ratio = (table_width - col_id_width) / col_width.inject(0) {|s,w| s += w} | 415 # use full width if the description is displayed |
175 col_width = col_width.collect {|w| w * ratio} | 416 if table_width > 0 && query.has_column?(:description) |
417 col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width} | |
418 table_width = col_width.inject(0) {|s,v| s += v} | |
176 end | 419 end |
177 | 420 |
178 # title | 421 # title |
179 pdf.SetFontStyle('B',11) | 422 pdf.SetFontStyle('B',11) |
180 pdf.RDMCell(190,10, title) | 423 pdf.RDMCell(190,10, title) |
181 pdf.Ln | 424 pdf.Ln |
182 | 425 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) |
183 # headers | |
184 pdf.SetFontStyle('B',8) | |
185 pdf.SetFillColor(230, 230, 230) | |
186 | |
187 # render it background to find the max height used | |
188 base_x = pdf.GetX | |
189 base_y = pdf.GetY | |
190 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true) | |
191 pdf.Rect(base_x, base_y, table_width, max_height, 'FD'); | |
192 pdf.SetXY(base_x, base_y); | |
193 | |
194 # write the cells on page | |
195 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1) | |
196 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true) | |
197 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | |
198 pdf.SetY(base_y + max_height); | |
199 | |
200 # rows | |
201 pdf.SetFontStyle('',8) | |
202 pdf.SetFillColor(255, 255, 255) | |
203 previous_group = false | 426 previous_group = false |
204 issue_list(issues) do |issue, level| | 427 issue_list(issues) do |issue, level| |
205 if query.grouped? && | 428 if query.grouped? && |
206 (group = query.group_by_column.value(issue)) != previous_group | 429 (group = query.group_by_column.value(issue)) != previous_group |
207 pdf.SetFontStyle('B',9) | 430 pdf.SetFontStyle('B',10) |
208 pdf.RDMCell(277, row_height, | 431 group_label = group.blank? ? 'None' : group.to_s.dup |
209 (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})", | 432 group_label << " (#{query.issue_count_by_group[group]})" |
210 1, 1, 'L') | 433 pdf.Bookmark group_label, 0, -1 |
434 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L') | |
211 pdf.SetFontStyle('',8) | 435 pdf.SetFontStyle('',8) |
212 previous_group = group | 436 previous_group = group |
213 end | 437 end |
214 # fetch all the row values | 438 |
215 col_values = query.columns.collect do |column| | 439 # fetch row values |
216 s = if column.is_a?(QueryCustomFieldColumn) | 440 col_values = fetch_row_values(issue, query, level) |
217 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id} | |
218 show_value(cv) | |
219 else | |
220 value = issue.send(column.name) | |
221 if column.name == :subject | |
222 value = " " * level + value | |
223 end | |
224 if value.is_a?(Date) | |
225 format_date(value) | |
226 elsif value.is_a?(Time) | |
227 format_time(value) | |
228 else | |
229 value | |
230 end | |
231 end | |
232 s.to_s | |
233 end | |
234 | 441 |
235 # render it off-page to find the max height used | 442 # render it off-page to find the max height used |
236 base_x = pdf.GetX | 443 base_x = pdf.GetX |
237 base_y = pdf.GetY | 444 base_y = pdf.GetY |
238 pdf.SetY(2 * page_height) | 445 pdf.SetY(2 * page_height) |
241 | 448 |
242 # make new page if it doesn't fit on the current one | 449 # make new page if it doesn't fit on the current one |
243 space_left = page_height - base_y - bottom_margin | 450 space_left = page_height - base_y - bottom_margin |
244 if max_height > space_left | 451 if max_height > space_left |
245 pdf.AddPage("L") | 452 pdf.AddPage("L") |
453 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width) | |
246 base_x = pdf.GetX | 454 base_x = pdf.GetX |
247 base_y = pdf.GetY | 455 base_y = pdf.GetY |
248 end | 456 end |
249 | 457 |
250 # write the cells on page | 458 # write the cells on page |
251 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1) | 459 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1) |
252 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) | 460 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height) |
253 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) | 461 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width) |
254 pdf.SetY(base_y + max_height); | 462 pdf.SetY(base_y + max_height); |
463 | |
464 if query.has_column?(:description) && issue.description? | |
465 pdf.SetX(10) | |
466 pdf.SetAutoPageBreak(true, 20) | |
467 pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT") | |
468 pdf.SetAutoPageBreak(false) | |
469 end | |
255 end | 470 end |
256 | 471 |
257 if issues.size == Setting.issues_export_limit.to_i | 472 if issues.size == Setting.issues_export_limit.to_i |
258 pdf.SetFontStyle('B',10) | 473 pdf.SetFontStyle('B',10) |
259 pdf.RDMCell(0, row_height, '...') | 474 pdf.RDMCell(0, row_height, '...') |
291 pdf.Line(top_x, top_y, top_x, lower_y) # left border | 506 pdf.Line(top_x, top_y, top_x, lower_y) # left border |
292 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border | 507 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border |
293 end | 508 end |
294 | 509 |
295 # Returns a PDF string of a single issue | 510 # Returns a PDF string of a single issue |
296 def issue_to_pdf(issue) | 511 def issue_to_pdf(issue, assoc={}) |
297 pdf = ITCPDF.new(current_language) | 512 pdf = ITCPDF.new(current_language) |
298 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}") | 513 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}") |
299 pdf.alias_nb_pages | 514 pdf.alias_nb_pages |
300 pdf.footer_date = format_date(Date.today) | 515 pdf.footer_date = format_date(Date.today) |
301 pdf.AddPage | 516 pdf.AddPage |
302 pdf.SetFontStyle('B',11) | 517 pdf.SetFontStyle('B',11) |
303 buf = "#{issue.project} - #{issue.tracker} # #{issue.id}" | 518 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}" |
304 pdf.RDMMultiCell(190, 5, buf) | 519 pdf.RDMMultiCell(190, 5, buf) |
305 pdf.Ln | |
306 pdf.SetFontStyle('',8) | 520 pdf.SetFontStyle('',8) |
307 base_x = pdf.GetX | 521 base_x = pdf.GetX |
308 i = 1 | 522 i = 1 |
309 issue.ancestors.each do |ancestor| | 523 issue.ancestors.visible.each do |ancestor| |
310 pdf.SetX(base_x + i) | 524 pdf.SetX(base_x + i) |
311 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" | 525 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}" |
312 pdf.RDMMultiCell(190 - i, 5, buf) | 526 pdf.RDMMultiCell(190 - i, 5, buf) |
313 i += 1 if i < 35 | 527 i += 1 if i < 35 |
314 end | 528 end |
529 pdf.SetFontStyle('B',11) | |
530 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s) | |
531 pdf.SetFontStyle('',8) | |
532 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}") | |
315 pdf.Ln | 533 pdf.Ln |
316 | 534 |
317 pdf.SetFontStyle('B',9) | 535 left = [] |
318 pdf.RDMCell(35,5, l(:field_status) + ":","LT") | 536 left << [l(:field_status), issue.status] |
319 pdf.SetFontStyle('',9) | 537 left << [l(:field_priority), issue.priority] |
320 pdf.RDMCell(60,5, issue.status.to_s,"RT") | 538 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id') |
321 pdf.SetFontStyle('B',9) | 539 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id') |
322 pdf.RDMCell(35,5, l(:field_priority) + ":","LT") | 540 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id') |
323 pdf.SetFontStyle('',9) | 541 |
324 pdf.RDMCell(60,5, issue.priority.to_s,"RT") | 542 right = [] |
325 pdf.Ln | 543 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date') |
326 | 544 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date') |
327 pdf.SetFontStyle('B',9) | 545 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio') |
328 pdf.RDMCell(35,5, l(:field_author) + ":","L") | 546 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours') |
329 pdf.SetFontStyle('',9) | 547 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project) |
330 pdf.RDMCell(60,5, issue.author.to_s,"R") | 548 |
331 pdf.SetFontStyle('B',9) | 549 rows = left.size > right.size ? left.size : right.size |
332 pdf.RDMCell(35,5, l(:field_category) + ":","L") | 550 while left.size < rows |
333 pdf.SetFontStyle('',9) | 551 left << nil |
334 pdf.RDMCell(60,5, issue.category.to_s,"R") | 552 end |
335 pdf.Ln | 553 while right.size < rows |
336 | 554 right << nil |
337 pdf.SetFontStyle('B',9) | 555 end |
338 pdf.RDMCell(35,5, l(:field_created_on) + ":","L") | 556 |
339 pdf.SetFontStyle('',9) | 557 half = (issue.custom_field_values.size / 2.0).ceil |
340 pdf.RDMCell(60,5, format_date(issue.created_on),"R") | 558 issue.custom_field_values.each_with_index do |custom_value, i| |
341 pdf.SetFontStyle('B',9) | 559 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)] |
342 pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L") | 560 end |
343 pdf.SetFontStyle('',9) | 561 |
344 pdf.RDMCell(60,5, issue.assigned_to.to_s,"R") | 562 rows = left.size > right.size ? left.size : right.size |
345 pdf.Ln | 563 rows.times do |i| |
346 | 564 item = left[i] |
347 pdf.SetFontStyle('B',9) | |
348 pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB") | |
349 pdf.SetFontStyle('',9) | |
350 pdf.RDMCell(60,5, format_date(issue.updated_on),"RB") | |
351 pdf.SetFontStyle('B',9) | |
352 pdf.RDMCell(35,5, l(:field_due_date) + ":","LB") | |
353 pdf.SetFontStyle('',9) | |
354 pdf.RDMCell(60,5, format_date(issue.due_date),"RB") | |
355 pdf.Ln | |
356 | |
357 for custom_value in issue.custom_field_values | |
358 pdf.SetFontStyle('B',9) | 565 pdf.SetFontStyle('B',9) |
359 pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L") | 566 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") |
360 pdf.SetFontStyle('',9) | 567 pdf.SetFontStyle('',9) |
361 pdf.RDMMultiCell(155,5, (show_value custom_value),"R") | 568 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") |
362 end | 569 |
363 | 570 item = right[i] |
364 y0 = pdf.GetY | 571 pdf.SetFontStyle('B',9) |
365 | 572 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L") |
366 pdf.SetFontStyle('B',9) | 573 pdf.SetFontStyle('',9) |
367 pdf.RDMCell(35,5, l(:field_subject) + ":","LT") | 574 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R") |
368 pdf.SetFontStyle('',9) | 575 pdf.Ln |
369 pdf.RDMMultiCell(155,5, issue.subject,"RT") | 576 end |
370 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) | |
371 | 577 |
372 pdf.SetFontStyle('B',9) | 578 pdf.SetFontStyle('B',9) |
373 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) | 579 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) |
374 pdf.SetFontStyle('',9) | 580 pdf.SetFontStyle('',9) |
375 | 581 |
379 issue.description.to_s, issue.attachments, "LRB") | 585 issue.description.to_s, issue.attachments, "LRB") |
380 | 586 |
381 unless issue.leaf? | 587 unless issue.leaf? |
382 # for CJK | 588 # for CJK |
383 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) | 589 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 ) |
384 | 590 |
385 pdf.SetFontStyle('B',9) | 591 pdf.SetFontStyle('B',9) |
386 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") | 592 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR") |
387 pdf.Ln | 593 pdf.Ln |
388 issue_list(issue.descendants.sort_by(&:lft)) do |child, level| | 594 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level| |
389 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}", | 595 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}", |
390 :length => truncate_length) | 596 :length => truncate_length) |
391 level = 10 if level >= 10 | 597 level = 10 if level >= 10 |
392 pdf.SetFontStyle('',8) | 598 pdf.SetFontStyle('',8) |
393 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") | 599 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L") |
399 | 605 |
400 relations = issue.relations.select { |r| r.other_issue(issue).visible? } | 606 relations = issue.relations.select { |r| r.other_issue(issue).visible? } |
401 unless relations.empty? | 607 unless relations.empty? |
402 # for CJK | 608 # for CJK |
403 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) | 609 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 ) |
404 | 610 |
405 pdf.SetFontStyle('B',9) | 611 pdf.SetFontStyle('B',9) |
406 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") | 612 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR") |
407 pdf.Ln | 613 pdf.Ln |
408 relations.each do |relation| | 614 relations.each do |relation| |
409 buf = "" | 615 buf = "" |
447 end | 653 end |
448 pdf.Ln | 654 pdf.Ln |
449 end | 655 end |
450 end | 656 end |
451 | 657 |
452 pdf.SetFontStyle('B',9) | 658 if assoc[:journals].present? |
453 pdf.RDMCell(190,5, l(:label_history), "B") | 659 pdf.SetFontStyle('B',9) |
454 pdf.Ln | 660 pdf.RDMCell(190,5, l(:label_history), "B") |
455 indice = 0 | |
456 for journal in issue.journals.find( | |
457 :all, :include => [:user, :details], | |
458 :order => "#{Journal.table_name}.created_on ASC") | |
459 indice = indice + 1 | |
460 pdf.SetFontStyle('B',8) | |
461 pdf.RDMCell(190,5, | |
462 "#" + indice.to_s + | |
463 " - " + format_time(journal.created_on) + | |
464 " - " + journal.user.name) | |
465 pdf.Ln | 661 pdf.Ln |
466 pdf.SetFontStyle('I',8) | 662 assoc[:journals].each do |journal| |
467 for detail in journal.details | 663 pdf.SetFontStyle('B',8) |
468 pdf.RDMMultiCell(190,5, "- " + show_detail(detail, true)) | 664 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}" |
469 end | 665 title << " (#{l(:field_private_notes)})" if journal.private_notes? |
470 if journal.notes? | 666 pdf.RDMCell(190,5, title) |
471 pdf.Ln unless journal.details.empty? | 667 pdf.Ln |
472 pdf.SetFontStyle('',8) | 668 pdf.SetFontStyle('I',8) |
473 pdf.RDMwriteHTMLCell(190,5,0,0, | 669 details_to_strings(journal.details, true).each do |string| |
474 journal.notes.to_s, issue.attachments, "") | 670 pdf.RDMMultiCell(190,5, "- " + string) |
475 end | 671 end |
476 pdf.Ln | 672 if journal.notes? |
673 pdf.Ln unless journal.details.empty? | |
674 pdf.SetFontStyle('',8) | |
675 pdf.RDMwriteHTMLCell(190,5,0,0, | |
676 journal.notes.to_s, issue.attachments, "") | |
677 end | |
678 pdf.Ln | |
679 end | |
477 end | 680 end |
478 | 681 |
479 if issue.attachments.any? | 682 if issue.attachments.any? |
480 pdf.SetFontStyle('B',9) | 683 pdf.SetFontStyle('B',9) |
481 pdf.RDMCell(190,5, l(:label_attachment_plural), "B") | 684 pdf.RDMCell(190,5, l(:label_attachment_plural), "B") |
490 end | 693 end |
491 end | 694 end |
492 pdf.Output | 695 pdf.Output |
493 end | 696 end |
494 | 697 |
698 # Returns a PDF string of a set of wiki pages | |
699 def wiki_pages_to_pdf(pages, project) | |
700 pdf = ITCPDF.new(current_language) | |
701 pdf.SetTitle(project.name) | |
702 pdf.alias_nb_pages | |
703 pdf.footer_date = format_date(Date.today) | |
704 pdf.AddPage | |
705 pdf.SetFontStyle('B',11) | |
706 pdf.RDMMultiCell(190,5, project.name) | |
707 pdf.Ln | |
708 # Set resize image scale | |
709 pdf.SetImageScale(1.6) | |
710 pdf.SetFontStyle('',9) | |
711 write_page_hierarchy(pdf, pages.group_by(&:parent_id)) | |
712 pdf.Output | |
713 end | |
714 | |
495 # Returns a PDF string of a single wiki page | 715 # Returns a PDF string of a single wiki page |
496 def wiki_to_pdf(page, project) | 716 def wiki_page_to_pdf(page, project) |
497 pdf = ITCPDF.new(current_language) | 717 pdf = ITCPDF.new(current_language) |
498 pdf.SetTitle("#{project} - #{page.title}") | 718 pdf.SetTitle("#{project} - #{page.title}") |
499 pdf.alias_nb_pages | 719 pdf.alias_nb_pages |
500 pdf.footer_date = format_date(Date.today) | 720 pdf.footer_date = format_date(Date.today) |
501 pdf.AddPage | 721 pdf.AddPage |
504 "#{project} - #{page.title} - # #{page.content.version}") | 724 "#{project} - #{page.title} - # #{page.content.version}") |
505 pdf.Ln | 725 pdf.Ln |
506 # Set resize image scale | 726 # Set resize image scale |
507 pdf.SetImageScale(1.6) | 727 pdf.SetImageScale(1.6) |
508 pdf.SetFontStyle('',9) | 728 pdf.SetFontStyle('',9) |
729 write_wiki_page(pdf, page) | |
730 pdf.Output | |
731 end | |
732 | |
733 def write_page_hierarchy(pdf, pages, node=nil, level=0) | |
734 if pages[node] | |
735 pages[node].each do |page| | |
736 if @new_page | |
737 pdf.AddPage | |
738 else | |
739 @new_page = true | |
740 end | |
741 pdf.Bookmark page.title, level | |
742 write_wiki_page(pdf, page) | |
743 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id] | |
744 end | |
745 end | |
746 end | |
747 | |
748 def write_wiki_page(pdf, page) | |
509 pdf.RDMwriteHTMLCell(190,5,0,0, | 749 pdf.RDMwriteHTMLCell(190,5,0,0, |
510 page.content.text.to_s, page.attachments, "TLRB") | 750 page.content.text.to_s, page.attachments, 0) |
511 if page.attachments.any? | 751 if page.attachments.any? |
512 pdf.Ln | 752 pdf.Ln |
513 pdf.SetFontStyle('B',9) | 753 pdf.SetFontStyle('B',9) |
514 pdf.RDMCell(190,5, l(:label_attachment_plural), "B") | 754 pdf.RDMCell(190,5, l(:label_attachment_plural), "B") |
515 pdf.Ln | 755 pdf.Ln |
520 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") | 760 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") |
521 pdf.RDMCell(65,5, attachment.author.name,0,0,"R") | 761 pdf.RDMCell(65,5, attachment.author.name,0,0,"R") |
522 pdf.Ln | 762 pdf.Ln |
523 end | 763 end |
524 end | 764 end |
525 pdf.Output | |
526 end | 765 end |
527 | 766 |
528 class RDMPdfEncoding | 767 class RDMPdfEncoding |
529 def self.rdm_from_utf8(txt, encoding) | 768 def self.rdm_from_utf8(txt, encoding) |
530 txt ||= '' | 769 txt ||= '' |