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 ||= ''