##// END OF EJS Templates
Adds support for macro and Redmine links in PDF export (#13051)....
Jean-Philippe Lang -
r13562:7a0432d85c01
parent child
Show More
@@ -0,0 +1,1
1 <%= raw issues_to_pdf(@issues, @project, @query) %> No newline at end of file
@@ -0,0 +1,1
1 <%= raw issue_to_pdf(@issue, :journals => @journals) %> No newline at end of file
@@ -0,0 +1,1
1 <%= raw wiki_pages_to_pdf(@pages, @project) %> No newline at end of file
@@ -0,0 +1,1
1 <%= raw wiki_page_to_pdf(@page, @project) %> No newline at end of file
This diff has been collapsed as it changes many lines, (508 lines changed) Show them Hide them
@@ -0,0 +1,508
1 # encoding: utf-8
2 #
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
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
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 module Redmine
21 module Export
22 module PDF
23 module IssuesPdfHelper
24 # Returns a PDF string of a single issue
25 def issue_to_pdf(issue, assoc={})
26 pdf = ITCPDF.new(current_language)
27 pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}")
28 pdf.alias_nb_pages
29 pdf.footer_date = format_date(Date.today)
30 pdf.add_page
31 pdf.SetFontStyle('B',11)
32 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
33 pdf.RDMMultiCell(190, 5, buf)
34 pdf.SetFontStyle('',8)
35 base_x = pdf.get_x
36 i = 1
37 issue.ancestors.visible.each do |ancestor|
38 pdf.set_x(base_x + i)
39 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
40 pdf.RDMMultiCell(190 - i, 5, buf)
41 i += 1 if i < 35
42 end
43 pdf.SetFontStyle('B',11)
44 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
45 pdf.SetFontStyle('',8)
46 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
47 pdf.ln
48
49 left = []
50 left << [l(:field_status), issue.status]
51 left << [l(:field_priority), issue.priority]
52 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
53 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
54 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
55
56 right = []
57 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
58 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
59 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
60 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
61 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
62
63 rows = left.size > right.size ? left.size : right.size
64 while left.size < rows
65 left << nil
66 end
67 while right.size < rows
68 right << nil
69 end
70
71 half = (issue.visible_custom_field_values.size / 2.0).ceil
72 issue.visible_custom_field_values.each_with_index do |custom_value, i|
73 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)]
74 end
75
76 if pdf.get_rtl
77 border_first_top = 'RT'
78 border_last_top = 'LT'
79 border_first = 'R'
80 border_last = 'L'
81 else
82 border_first_top = 'LT'
83 border_last_top = 'RT'
84 border_first = 'L'
85 border_last = 'R'
86 end
87
88 rows = left.size > right.size ? left.size : right.size
89 rows.times do |i|
90 heights = []
91 pdf.SetFontStyle('B',9)
92 item = left[i]
93 heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
94 item = right[i]
95 heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
96 pdf.SetFontStyle('',9)
97 item = left[i]
98 heights << pdf.get_string_height(60, item ? item.last.to_s : "")
99 item = right[i]
100 heights << pdf.get_string_height(60, item ? item.last.to_s : "")
101 height = heights.max
102
103 item = left[i]
104 pdf.SetFontStyle('B',9)
105 pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
106 pdf.SetFontStyle('',9)
107 pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0)
108
109 item = right[i]
110 pdf.SetFontStyle('B',9)
111 pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
112 pdf.SetFontStyle('',9)
113 pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2)
114
115 pdf.set_x(base_x)
116 end
117
118 pdf.SetFontStyle('B',9)
119 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
120 pdf.SetFontStyle('',9)
121
122 # Set resize image scale
123 pdf.set_image_scale(1.6)
124 pdf.RDMwriteHTMLCell(35+155, 5, '', '',
125 issue.description.to_s, issue.attachments, "LRB")
126
127 unless issue.leaf?
128 truncate_length = (!is_cjk? ? 90 : 65)
129 pdf.SetFontStyle('B',9)
130 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
131 pdf.ln
132 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
133 buf = "#{child.tracker} # #{child.id}: #{child.subject}".
134 truncate(truncate_length)
135 level = 10 if level >= 10
136 pdf.SetFontStyle('',8)
137 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first)
138 pdf.SetFontStyle('B',8)
139 pdf.RDMCell(20,5, child.status.to_s, border_last)
140 pdf.ln
141 end
142 end
143
144 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
145 unless relations.empty?
146 truncate_length = (!is_cjk? ? 80 : 60)
147 pdf.SetFontStyle('B',9)
148 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
149 pdf.ln
150 relations.each do |relation|
151 buf = relation.to_s(issue) {|other|
152 text = ""
153 if Setting.cross_project_issue_relations?
154 text += "#{relation.other_issue(issue).project} - "
155 end
156 text += "#{other.tracker} ##{other.id}: #{other.subject}"
157 text
158 }
159 buf = buf.truncate(truncate_length)
160 pdf.SetFontStyle('', 8)
161 pdf.RDMCell(35+155-60, 5, buf, border_first)
162 pdf.SetFontStyle('B',8)
163 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
164 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
165 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last)
166 pdf.ln
167 end
168 end
169 pdf.RDMCell(190,5, "", "T")
170 pdf.ln
171
172 if issue.changesets.any? &&
173 User.current.allowed_to?(:view_changesets, issue.project)
174 pdf.SetFontStyle('B',9)
175 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
176 pdf.ln
177 for changeset in issue.changesets
178 pdf.SetFontStyle('B',8)
179 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
180 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
181 pdf.RDMCell(190, 5, csstr)
182 pdf.ln
183 unless changeset.comments.blank?
184 pdf.SetFontStyle('',8)
185 pdf.RDMwriteHTMLCell(190,5,'','',
186 changeset.comments.to_s, issue.attachments, "")
187 end
188 pdf.ln
189 end
190 end
191
192 if assoc[:journals].present?
193 pdf.SetFontStyle('B',9)
194 pdf.RDMCell(190,5, l(:label_history), "B")
195 pdf.ln
196 assoc[:journals].each do |journal|
197 pdf.SetFontStyle('B',8)
198 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
199 title << " (#{l(:field_private_notes)})" if journal.private_notes?
200 pdf.RDMCell(190,5, title)
201 pdf.ln
202 pdf.SetFontStyle('I',8)
203 details_to_strings(journal.visible_details, true).each do |string|
204 pdf.RDMMultiCell(190,5, "- " + string)
205 end
206 if journal.notes?
207 pdf.ln unless journal.details.empty?
208 pdf.SetFontStyle('',8)
209 pdf.RDMwriteHTMLCell(190,5,'','',
210 journal.notes.to_s, issue.attachments, "")
211 end
212 pdf.ln
213 end
214 end
215
216 if issue.attachments.any?
217 pdf.SetFontStyle('B',9)
218 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
219 pdf.ln
220 for attachment in issue.attachments
221 pdf.SetFontStyle('',8)
222 pdf.RDMCell(80,5, attachment.filename)
223 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
224 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
225 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
226 pdf.ln
227 end
228 end
229 pdf.output
230 end
231
232 # Returns a PDF string of a list of issues
233 def issues_to_pdf(issues, project, query)
234 pdf = ITCPDF.new(current_language, "L")
235 title = query.new_record? ? l(:label_issue_plural) : query.name
236 title = "#{project} - #{title}" if project
237 pdf.set_title(title)
238 pdf.alias_nb_pages
239 pdf.footer_date = format_date(Date.today)
240 pdf.set_auto_page_break(false)
241 pdf.add_page("L")
242
243 # Landscape A4 = 210 x 297 mm
244 page_height = pdf.get_page_height # 210
245 page_width = pdf.get_page_width # 297
246 left_margin = pdf.get_original_margins['left'] # 10
247 right_margin = pdf.get_original_margins['right'] # 10
248 bottom_margin = pdf.get_footer_margin
249 row_height = 4
250
251 # column widths
252 table_width = page_width - right_margin - left_margin
253 col_width = []
254 unless query.inline_columns.empty?
255 col_width = calc_col_width(issues, query, table_width, pdf)
256 table_width = col_width.inject(0, :+)
257 end
258
259 # use full width if the description is displayed
260 if table_width > 0 && query.has_column?(:description)
261 col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width}
262 table_width = col_width.inject(0, :+)
263 end
264
265 # title
266 pdf.SetFontStyle('B',11)
267 pdf.RDMCell(190,10, title)
268 pdf.ln
269
270 render_table_header(pdf, query, col_width, row_height, table_width)
271 previous_group = false
272 issue_list(issues) do |issue, level|
273 if query.grouped? &&
274 (group = query.group_by_column.value(issue)) != previous_group
275 pdf.SetFontStyle('B',10)
276 group_label = group.blank? ? 'None' : group.to_s.dup
277 group_label << " (#{query.issue_count_by_group[group]})"
278 pdf.bookmark group_label, 0, -1
279 pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L')
280 pdf.SetFontStyle('',8)
281 previous_group = group
282 end
283
284 # fetch row values
285 col_values = fetch_row_values(issue, query, level)
286
287 # make new page if it doesn't fit on the current one
288 base_y = pdf.get_y
289 max_height = get_issues_to_pdf_write_cells(pdf, col_values, col_width)
290 space_left = page_height - base_y - bottom_margin
291 if max_height > space_left
292 pdf.add_page("L")
293 render_table_header(pdf, query, col_width, row_height, table_width)
294 base_y = pdf.get_y
295 end
296
297 # write the cells on page
298 issues_to_pdf_write_cells(pdf, col_values, col_width, max_height)
299 pdf.set_y(base_y + max_height)
300
301 if query.has_column?(:description) && issue.description?
302 pdf.set_x(10)
303 pdf.set_auto_page_break(true, bottom_margin)
304 pdf.RDMwriteHTMLCell(0, 5, 10, '', issue.description.to_s, issue.attachments, "LRBT")
305 pdf.set_auto_page_break(false)
306 end
307 end
308
309 if issues.size == Setting.issues_export_limit.to_i
310 pdf.SetFontStyle('B',10)
311 pdf.RDMCell(0, row_height, '...')
312 end
313 pdf.output
314 end
315
316 def is_cjk?
317 case current_language.to_s.downcase
318 when 'ja', 'zh-tw', 'zh', 'ko'
319 true
320 else
321 false
322 end
323 end
324
325 # fetch row values
326 def fetch_row_values(issue, query, level)
327 query.inline_columns.collect do |column|
328 s = if column.is_a?(QueryCustomFieldColumn)
329 cv = issue.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
330 show_value(cv, false)
331 else
332 value = issue.send(column.name)
333 if column.name == :subject
334 value = " " * level + value
335 end
336 if value.is_a?(Date)
337 format_date(value)
338 elsif value.is_a?(Time)
339 format_time(value)
340 else
341 value
342 end
343 end
344 s.to_s
345 end
346 end
347
348 # calculate columns width
349 def calc_col_width(issues, query, table_width, pdf)
350 # calculate statistics
351 # by captions
352 pdf.SetFontStyle('B',8)
353 margins = pdf.get_margins
354 col_padding = margins['cell']
355 col_width_min = query.inline_columns.map {|v| pdf.get_string_width(v.caption) + col_padding}
356 col_width_max = Array.new(col_width_min)
357 col_width_avg = Array.new(col_width_min)
358 col_min = pdf.get_string_width('OO') + col_padding * 2
359 if table_width > col_min * col_width_avg.length
360 table_width -= col_min * col_width_avg.length
361 else
362 col_min = pdf.get_string_width('O') + col_padding * 2
363 if table_width > col_min * col_width_avg.length
364 table_width -= col_min * col_width_avg.length
365 else
366 ratio = table_width / col_width_avg.inject(0, :+)
367 return col_width = col_width_avg.map {|w| w * ratio}
368 end
369 end
370 word_width_max = query.inline_columns.map {|c|
371 n = 10
372 c.caption.split.each {|w|
373 x = pdf.get_string_width(w) + col_padding
374 n = x if n < x
375 }
376 n
377 }
378
379 # by properties of issues
380 pdf.SetFontStyle('',8)
381 k = 1
382 issue_list(issues) {|issue, level|
383 k += 1
384 values = fetch_row_values(issue, query, level)
385 values.each_with_index {|v,i|
386 n = pdf.get_string_width(v) + col_padding * 2
387 col_width_max[i] = n if col_width_max[i] < n
388 col_width_min[i] = n if col_width_min[i] > n
389 col_width_avg[i] += n
390 v.split.each {|w|
391 x = pdf.get_string_width(w) + col_padding
392 word_width_max[i] = x if word_width_max[i] < x
393 }
394 }
395 }
396 col_width_avg.map! {|x| x / k}
397
398 # calculate columns width
399 ratio = table_width / col_width_avg.inject(0, :+)
400 col_width = col_width_avg.map {|w| w * ratio}
401
402 # correct max word width if too many columns
403 ratio = table_width / word_width_max.inject(0, :+)
404 word_width_max.map! {|v| v * ratio} if ratio < 1
405
406 # correct and lock width of some columns
407 done = 1
408 col_fix = []
409 col_width.each_with_index do |w,i|
410 if w > col_width_max[i]
411 col_width[i] = col_width_max[i]
412 col_fix[i] = 1
413 done = 0
414 elsif w < word_width_max[i]
415 col_width[i] = word_width_max[i]
416 col_fix[i] = 1
417 done = 0
418 else
419 col_fix[i] = 0
420 end
421 end
422
423 # iterate while need to correct and lock coluns width
424 while done == 0
425 # calculate free & locked columns width
426 done = 1
427 ratio = table_width / col_width.inject(0, :+)
428
429 # correct columns width
430 col_width.each_with_index do |w,i|
431 if col_fix[i] == 0
432 col_width[i] = w * ratio
433
434 # check if column width less then max word width
435 if col_width[i] < word_width_max[i]
436 col_width[i] = word_width_max[i]
437 col_fix[i] = 1
438 done = 0
439 elsif col_width[i] > col_width_max[i]
440 col_width[i] = col_width_max[i]
441 col_fix[i] = 1
442 done = 0
443 end
444 end
445 end
446 end
447
448 ratio = table_width / col_width.inject(0, :+)
449 col_width.map! {|v| v * ratio + col_min}
450 col_width
451 end
452
453 def render_table_header(pdf, query, col_width, row_height, table_width)
454 # headers
455 pdf.SetFontStyle('B',8)
456 pdf.set_fill_color(230, 230, 230)
457
458 base_x = pdf.get_x
459 base_y = pdf.get_y
460 max_height = get_issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, true)
461
462 # write the cells on page
463 issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, max_height, true)
464 pdf.set_xy(base_x, base_y + max_height)
465
466 # rows
467 pdf.SetFontStyle('',8)
468 pdf.set_fill_color(255, 255, 255)
469 end
470
471 # returns the maximum height of MultiCells
472 def get_issues_to_pdf_write_cells(pdf, col_values, col_widths, head=false)
473 heights = []
474 col_values.each_with_index do |column, i|
475 heights << pdf.get_string_height(col_widths[i], head ? column.caption : column)
476 end
477 return heights.max
478 end
479
480 # Renders MultiCells and returns the maximum height used
481 def issues_to_pdf_write_cells(pdf, col_values, col_widths, row_height, head=false)
482 col_values.each_with_index do |column, i|
483 pdf.RDMMultiCell(col_widths[i], row_height, head ? column.caption : column.strip, 1, '', 1, 0)
484 end
485 end
486
487 # Draw lines to close the row (MultiCell border drawing in not uniform)
488 #
489 # parameter "col_id_width" is not used. it is kept for compatibility.
490 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
491 col_id_width, col_widths, rtl=false)
492 col_x = top_x
493 pdf.line(col_x, top_y, col_x, lower_y) # id right border
494 col_widths.each do |width|
495 if rtl
496 col_x -= width
497 else
498 col_x += width
499 end
500 pdf.line(col_x, top_y, col_x, lower_y) # columns right border
501 end
502 pdf.line(top_x, top_y, top_x, lower_y) # left border
503 pdf.line(top_x, lower_y, col_x, lower_y) # bottom border
504 end
505 end
506 end
507 end
508 end
@@ -0,0 +1,100
1 # encoding: utf-8
2 #
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
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
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 module Redmine
21 module Export
22 module PDF
23 module WikiPdfHelper
24 # Returns a PDF string of a set of wiki pages
25 def wiki_pages_to_pdf(pages, project)
26 pdf = Redmine::Export::PDF::ITCPDF.new(current_language)
27 pdf.set_title(project.name)
28 pdf.alias_nb_pages
29 pdf.footer_date = format_date(Date.today)
30 pdf.add_page
31 pdf.SetFontStyle('B',11)
32 pdf.RDMMultiCell(190,5, project.name)
33 pdf.ln
34 # Set resize image scale
35 pdf.set_image_scale(1.6)
36 pdf.SetFontStyle('',9)
37 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
38 pdf.output
39 end
40
41 # Returns a PDF string of a single wiki page
42 def wiki_page_to_pdf(page, project)
43 pdf = ITCPDF.new(current_language)
44 pdf.set_title("#{project} - #{page.title}")
45 pdf.alias_nb_pages
46 pdf.footer_date = format_date(Date.today)
47 pdf.add_page
48 pdf.SetFontStyle('B',11)
49 pdf.RDMMultiCell(190,5,
50 "#{project} - #{page.title} - # #{page.content.version}")
51 pdf.ln
52 # Set resize image scale
53 pdf.set_image_scale(1.6)
54 pdf.SetFontStyle('',9)
55 write_wiki_page(pdf, page)
56 pdf.output
57 end
58
59 def write_page_hierarchy(pdf, pages, node=nil, level=0)
60 if pages[node]
61 pages[node].each do |page|
62 if @new_page
63 pdf.add_page
64 else
65 @new_page = true
66 end
67 pdf.bookmark page.title, level
68 write_wiki_page(pdf, page)
69 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
70 end
71 end
72 end
73
74 def write_wiki_page(pdf, page)
75 text = textilizable(page.content, :text,
76 :only_path => false,
77 :edit_section_links => false,
78 :headings => false,
79 :inline_attachments => false
80 )
81 pdf.RDMwriteFormattedCell(190,5,'','', text, page.attachments, 0)
82 if page.attachments.any?
83 pdf.ln(5)
84 pdf.SetFontStyle('B',9)
85 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
86 pdf.ln
87 for attachment in page.attachments
88 pdf.SetFontStyle('',8)
89 pdf.RDMCell(80,5, attachment.filename)
90 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
91 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
92 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
93 pdf.ln
94 end
95 end
96 end
97 end
98 end
99 end
100 end
@@ -49,7 +49,6 class IssuesController < ApplicationController
49 include SortHelper
49 include SortHelper
50 include IssuesHelper
50 include IssuesHelper
51 helper :timelog
51 helper :timelog
52 include Redmine::Export::PDF
53
52
54 def index
53 def index
55 retrieve_query
54 retrieve_query
@@ -89,7 +88,7 class IssuesController < ApplicationController
89 }
88 }
90 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
89 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
91 format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
90 format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
92 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') }
91 format.pdf { send_file_headers! :type => 'application/pdf', :filename => 'issues.pdf' }
93 end
92 end
94 else
93 else
95 respond_to do |format|
94 respond_to do |format|
@@ -130,8 +129,7 class IssuesController < ApplicationController
130 format.api
129 format.api
131 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
130 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
132 format.pdf {
131 format.pdf {
133 pdf = issue_to_pdf(@issue, :journals => @journals)
132 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf"
134 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
135 }
133 }
136 end
134 end
137 end
135 end
@@ -78,7 +78,7 class WikiController < ApplicationController
78 end
78 end
79 if User.current.allowed_to?(:export_wiki_pages, @project)
79 if User.current.allowed_to?(:export_wiki_pages, @project)
80 if params[:format] == 'pdf'
80 if params[:format] == 'pdf'
81 send_data(wiki_page_to_pdf(@page, @project), :type => 'application/pdf', :filename => "#{@page.title}.pdf")
81 send_file_headers! :type => 'application/pdf', :filename => "#{@page.title}.pdf"
82 return
82 return
83 elsif params[:format] == 'html'
83 elsif params[:format] == 'html'
84 export = render_to_string :action => 'export', :layout => false
84 export = render_to_string :action => 'export', :layout => false
@@ -283,9 +283,7 class WikiController < ApplicationController
283 send_data(export, :type => 'text/html', :filename => "wiki.html")
283 send_data(export, :type => 'text/html', :filename => "wiki.html")
284 }
284 }
285 format.pdf {
285 format.pdf {
286 send_data(wiki_pages_to_pdf(@pages, @project),
286 send_file_headers! :type => 'application/pdf', :filename => "#{@project.identifier}.pdf"
287 :type => 'application/pdf',
288 :filename => "#{@project.identifier}.pdf")
289 }
287 }
290 end
288 end
291 end
289 end
@@ -622,6 +622,8 module ApplicationHelper
622 end
622 end
623
623
624 def parse_inline_attachments(text, project, obj, attr, only_path, options)
624 def parse_inline_attachments(text, project, obj, attr, only_path, options)
625 return if options[:inline_attachments] == false
626
625 # when using an image link, try to use an attachment, if possible
627 # when using an image link, try to use an attachment, if possible
626 attachments = options[:attachments] || []
628 attachments = options[:attachments] || []
627 attachments += obj.attachments if obj.respond_to?(:attachments)
629 attachments += obj.attachments if obj.respond_to?(:attachments)
@@ -19,6 +19,7
19
19
20 module IssuesHelper
20 module IssuesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22 include Redmine::Export::PDF::IssuesPdfHelper
22
23
23 def issue_list(issues, &block)
24 def issue_list(issues, &block)
24 ancestors = []
25 ancestors = []
@@ -18,6 +18,7
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 module WikiHelper
20 module WikiHelper
21 include Redmine::Export::PDF::WikiPdfHelper
21
22
22 def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
23 def wiki_page_options_for_select(pages, selected = nil, parent = nil, level = 0)
23 pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
24 pages = pages.group_by(&:parent) unless pages.is_a?(Hash)
This diff has been collapsed as it changes many lines, (573 lines changed) Show them Hide them
@@ -22,10 +22,6 require 'rbpdf'
22 module Redmine
22 module Redmine
23 module Export
23 module Export
24 module PDF
24 module PDF
25 include ActionView::Helpers::TextHelper
26 include ActionView::Helpers::NumberHelper
27 include IssuesHelper
28
29 class ITCPDF < RBPDF
25 class ITCPDF < RBPDF
30 include Redmine::I18n
26 include Redmine::I18n
31 attr_accessor :footer_date
27 attr_accessor :footer_date
@@ -59,10 +55,7 module Redmine
59 end
55 end
60
56
61 def formatted_text(text)
57 def formatted_text(text)
62 html = Redmine::WikiFormatting.to_html(Setting.text_formatting, text)
58 Redmine::WikiFormatting.to_html(Setting.text_formatting, text)
63 # Strip {{toc}} tags
64 html.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i, '')
65 html
66 end
59 end
67
60
68 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
61 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
@@ -73,7 +66,7 module Redmine
73 multi_cell(w, h, txt, border, align, fill, ln)
66 multi_cell(w, h, txt, border, align, fill, ln)
74 end
67 end
75
68
76 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
69 def RDMwriteFormattedCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
77 @attachments = attachments
70 @attachments = attachments
78
71
79 css_tag = ' <style>
72 css_tag = ' <style>
@@ -86,9 +79,14 module Redmine
86 }
79 }
87 </style>'
80 </style>'
88
81
89 writeHTMLCell(w, h, x, y,
82 # Strip {{toc}} tags
90 css_tag + formatted_text(txt),
83 txt.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i, '')
91 border, ln, fill)
84 writeHTMLCell(w, h, x, y, css_tag + txt, border, ln, fill)
85 end
86
87 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
88 txt = formatted_text(txt)
89 RDMwriteFormattedCell(w, h, x, y, txt, attachments, border, ln, fill)
92 end
90 end
93
91
94 def get_image_filename(attrname)
92 def get_image_filename(attrname)
@@ -121,557 +119,6 module Redmine
121 end
119 end
122 end
120 end
123
121
124 def is_cjk?
125 case current_language.to_s.downcase
126 when 'ja', 'zh-tw', 'zh', 'ko'
127 true
128 else
129 false
130 end
131 end
132
133 # fetch row values
134 def fetch_row_values(issue, query, level)
135 query.inline_columns.collect do |column|
136 s = if column.is_a?(QueryCustomFieldColumn)
137 cv = issue.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
138 show_value(cv, false)
139 else
140 value = issue.send(column.name)
141 if column.name == :subject
142 value = " " * level + value
143 end
144 if value.is_a?(Date)
145 format_date(value)
146 elsif value.is_a?(Time)
147 format_time(value)
148 else
149 value
150 end
151 end
152 s.to_s
153 end
154 end
155
156 # calculate columns width
157 def calc_col_width(issues, query, table_width, pdf)
158 # calculate statistics
159 # by captions
160 pdf.SetFontStyle('B',8)
161 margins = pdf.get_margins
162 col_padding = margins['cell']
163 col_width_min = query.inline_columns.map {|v| pdf.get_string_width(v.caption) + col_padding}
164 col_width_max = Array.new(col_width_min)
165 col_width_avg = Array.new(col_width_min)
166 col_min = pdf.get_string_width('OO') + col_padding * 2
167 if table_width > col_min * col_width_avg.length
168 table_width -= col_min * col_width_avg.length
169 else
170 col_min = pdf.get_string_width('O') + col_padding * 2
171 if table_width > col_min * col_width_avg.length
172 table_width -= col_min * col_width_avg.length
173 else
174 ratio = table_width / col_width_avg.inject(0, :+)
175 return col_width = col_width_avg.map {|w| w * ratio}
176 end
177 end
178 word_width_max = query.inline_columns.map {|c|
179 n = 10
180 c.caption.split.each {|w|
181 x = pdf.get_string_width(w) + col_padding
182 n = x if n < x
183 }
184 n
185 }
186
187 # by properties of issues
188 pdf.SetFontStyle('',8)
189 k = 1
190 issue_list(issues) {|issue, level|
191 k += 1
192 values = fetch_row_values(issue, query, level)
193 values.each_with_index {|v,i|
194 n = pdf.get_string_width(v) + col_padding * 2
195 col_width_max[i] = n if col_width_max[i] < n
196 col_width_min[i] = n if col_width_min[i] > n
197 col_width_avg[i] += n
198 v.split.each {|w|
199 x = pdf.get_string_width(w) + col_padding
200 word_width_max[i] = x if word_width_max[i] < x
201 }
202 }
203 }
204 col_width_avg.map! {|x| x / k}
205
206 # calculate columns width
207 ratio = table_width / col_width_avg.inject(0, :+)
208 col_width = col_width_avg.map {|w| w * ratio}
209
210 # correct max word width if too many columns
211 ratio = table_width / word_width_max.inject(0, :+)
212 word_width_max.map! {|v| v * ratio} if ratio < 1
213
214 # correct and lock width of some columns
215 done = 1
216 col_fix = []
217 col_width.each_with_index do |w,i|
218 if w > col_width_max[i]
219 col_width[i] = col_width_max[i]
220 col_fix[i] = 1
221 done = 0
222 elsif w < word_width_max[i]
223 col_width[i] = word_width_max[i]
224 col_fix[i] = 1
225 done = 0
226 else
227 col_fix[i] = 0
228 end
229 end
230
231 # iterate while need to correct and lock coluns width
232 while done == 0
233 # calculate free & locked columns width
234 done = 1
235 ratio = table_width / col_width.inject(0, :+)
236
237 # correct columns width
238 col_width.each_with_index do |w,i|
239 if col_fix[i] == 0
240 col_width[i] = w * ratio
241
242 # check if column width less then max word width
243 if col_width[i] < word_width_max[i]
244 col_width[i] = word_width_max[i]
245 col_fix[i] = 1
246 done = 0
247 elsif col_width[i] > col_width_max[i]
248 col_width[i] = col_width_max[i]
249 col_fix[i] = 1
250 done = 0
251 end
252 end
253 end
254 end
255
256 ratio = table_width / col_width.inject(0, :+)
257 col_width.map! {|v| v * ratio + col_min}
258 col_width
259 end
260
261 def render_table_header(pdf, query, col_width, row_height, table_width)
262 # headers
263 pdf.SetFontStyle('B',8)
264 pdf.set_fill_color(230, 230, 230)
265
266 base_x = pdf.get_x
267 base_y = pdf.get_y
268 max_height = get_issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, true)
269
270 # write the cells on page
271 issues_to_pdf_write_cells(pdf, query.inline_columns, col_width, max_height, true)
272 pdf.set_xy(base_x, base_y + max_height)
273
274 # rows
275 pdf.SetFontStyle('',8)
276 pdf.set_fill_color(255, 255, 255)
277 end
278
279 # Returns a PDF string of a list of issues
280 def issues_to_pdf(issues, project, query)
281 pdf = ITCPDF.new(current_language, "L")
282 title = query.new_record? ? l(:label_issue_plural) : query.name
283 title = "#{project} - #{title}" if project
284 pdf.set_title(title)
285 pdf.alias_nb_pages
286 pdf.footer_date = format_date(Date.today)
287 pdf.set_auto_page_break(false)
288 pdf.add_page("L")
289
290 # Landscape A4 = 210 x 297 mm
291 page_height = pdf.get_page_height # 210
292 page_width = pdf.get_page_width # 297
293 left_margin = pdf.get_original_margins['left'] # 10
294 right_margin = pdf.get_original_margins['right'] # 10
295 bottom_margin = pdf.get_footer_margin
296 row_height = 4
297
298 # column widths
299 table_width = page_width - right_margin - left_margin
300 col_width = []
301 unless query.inline_columns.empty?
302 col_width = calc_col_width(issues, query, table_width, pdf)
303 table_width = col_width.inject(0, :+)
304 end
305
306 # use full width if the description is displayed
307 if table_width > 0 && query.has_column?(:description)
308 col_width = col_width.map {|w| w * (page_width - right_margin - left_margin) / table_width}
309 table_width = col_width.inject(0, :+)
310 end
311
312 # title
313 pdf.SetFontStyle('B',11)
314 pdf.RDMCell(190,10, title)
315 pdf.ln
316
317 render_table_header(pdf, query, col_width, row_height, table_width)
318 previous_group = false
319 issue_list(issues) do |issue, level|
320 if query.grouped? &&
321 (group = query.group_by_column.value(issue)) != previous_group
322 pdf.SetFontStyle('B',10)
323 group_label = group.blank? ? 'None' : group.to_s.dup
324 group_label << " (#{query.issue_count_by_group[group]})"
325 pdf.bookmark group_label, 0, -1
326 pdf.RDMCell(table_width, row_height * 2, group_label, 1, 1, 'L')
327 pdf.SetFontStyle('',8)
328 previous_group = group
329 end
330
331 # fetch row values
332 col_values = fetch_row_values(issue, query, level)
333
334 # make new page if it doesn't fit on the current one
335 base_y = pdf.get_y
336 max_height = get_issues_to_pdf_write_cells(pdf, col_values, col_width)
337 space_left = page_height - base_y - bottom_margin
338 if max_height > space_left
339 pdf.add_page("L")
340 render_table_header(pdf, query, col_width, row_height, table_width)
341 base_y = pdf.get_y
342 end
343
344 # write the cells on page
345 issues_to_pdf_write_cells(pdf, col_values, col_width, max_height)
346 pdf.set_y(base_y + max_height)
347
348 if query.has_column?(:description) && issue.description?
349 pdf.set_x(10)
350 pdf.set_auto_page_break(true, bottom_margin)
351 pdf.RDMwriteHTMLCell(0, 5, 10, '', issue.description.to_s, issue.attachments, "LRBT")
352 pdf.set_auto_page_break(false)
353 end
354 end
355
356 if issues.size == Setting.issues_export_limit.to_i
357 pdf.SetFontStyle('B',10)
358 pdf.RDMCell(0, row_height, '...')
359 end
360 pdf.output
361 end
362
363 # returns the maximum height of MultiCells
364 def get_issues_to_pdf_write_cells(pdf, col_values, col_widths, head=false)
365 heights = []
366 col_values.each_with_index do |column, i|
367 heights << pdf.get_string_height(col_widths[i], head ? column.caption : column)
368 end
369 return heights.max
370 end
371
372 # Renders MultiCells and returns the maximum height used
373 def issues_to_pdf_write_cells(pdf, col_values, col_widths, row_height, head=false)
374 col_values.each_with_index do |column, i|
375 pdf.RDMMultiCell(col_widths[i], row_height, head ? column.caption : column.strip, 1, '', 1, 0)
376 end
377 end
378
379 # Draw lines to close the row (MultiCell border drawing in not uniform)
380 #
381 # parameter "col_id_width" is not used. it is kept for compatibility.
382 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
383 col_id_width, col_widths, rtl=false)
384 col_x = top_x
385 pdf.line(col_x, top_y, col_x, lower_y) # id right border
386 col_widths.each do |width|
387 if rtl
388 col_x -= width
389 else
390 col_x += width
391 end
392 pdf.line(col_x, top_y, col_x, lower_y) # columns right border
393 end
394 pdf.line(top_x, top_y, top_x, lower_y) # left border
395 pdf.line(top_x, lower_y, col_x, lower_y) # bottom border
396 end
397
398 # Returns a PDF string of a single issue
399 def issue_to_pdf(issue, assoc={})
400 pdf = ITCPDF.new(current_language)
401 pdf.set_title("#{issue.project} - #{issue.tracker} ##{issue.id}")
402 pdf.alias_nb_pages
403 pdf.footer_date = format_date(Date.today)
404 pdf.add_page
405 pdf.SetFontStyle('B',11)
406 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
407 pdf.RDMMultiCell(190, 5, buf)
408 pdf.SetFontStyle('',8)
409 base_x = pdf.get_x
410 i = 1
411 issue.ancestors.visible.each do |ancestor|
412 pdf.set_x(base_x + i)
413 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
414 pdf.RDMMultiCell(190 - i, 5, buf)
415 i += 1 if i < 35
416 end
417 pdf.SetFontStyle('B',11)
418 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
419 pdf.SetFontStyle('',8)
420 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
421 pdf.ln
422
423 left = []
424 left << [l(:field_status), issue.status]
425 left << [l(:field_priority), issue.priority]
426 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
427 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
428 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
429
430 right = []
431 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
432 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
433 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
434 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
435 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
436
437 rows = left.size > right.size ? left.size : right.size
438 while left.size < rows
439 left << nil
440 end
441 while right.size < rows
442 right << nil
443 end
444
445 half = (issue.visible_custom_field_values.size / 2.0).ceil
446 issue.visible_custom_field_values.each_with_index do |custom_value, i|
447 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)]
448 end
449
450 if pdf.get_rtl
451 border_first_top = 'RT'
452 border_last_top = 'LT'
453 border_first = 'R'
454 border_last = 'L'
455 else
456 border_first_top = 'LT'
457 border_last_top = 'RT'
458 border_first = 'L'
459 border_last = 'R'
460 end
461
462 rows = left.size > right.size ? left.size : right.size
463 rows.times do |i|
464 heights = []
465 pdf.SetFontStyle('B',9)
466 item = left[i]
467 heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
468 item = right[i]
469 heights << pdf.get_string_height(35, item ? "#{item.first}:" : "")
470 pdf.SetFontStyle('',9)
471 item = left[i]
472 heights << pdf.get_string_height(60, item ? item.last.to_s : "")
473 item = right[i]
474 heights << pdf.get_string_height(60, item ? item.last.to_s : "")
475 height = heights.max
476
477 item = left[i]
478 pdf.SetFontStyle('B',9)
479 pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
480 pdf.SetFontStyle('',9)
481 pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 0)
482
483 item = right[i]
484 pdf.SetFontStyle('B',9)
485 pdf.RDMMultiCell(35, height, item ? "#{item.first}:" : "", (i == 0 ? border_first_top : border_first), '', 0, 0)
486 pdf.SetFontStyle('',9)
487 pdf.RDMMultiCell(60, height, item ? item.last.to_s : "", (i == 0 ? border_last_top : border_last), '', 0, 2)
488
489 pdf.set_x(base_x)
490 end
491
492 pdf.SetFontStyle('B',9)
493 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
494 pdf.SetFontStyle('',9)
495
496 # Set resize image scale
497 pdf.set_image_scale(1.6)
498 pdf.RDMwriteHTMLCell(35+155, 5, '', '',
499 issue.description.to_s, issue.attachments, "LRB")
500
501 unless issue.leaf?
502 truncate_length = (!is_cjk? ? 90 : 65)
503 pdf.SetFontStyle('B',9)
504 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
505 pdf.ln
506 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
507 buf = "#{child.tracker} # #{child.id}: #{child.subject}".
508 truncate(truncate_length)
509 level = 10 if level >= 10
510 pdf.SetFontStyle('',8)
511 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, border_first)
512 pdf.SetFontStyle('B',8)
513 pdf.RDMCell(20,5, child.status.to_s, border_last)
514 pdf.ln
515 end
516 end
517
518 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
519 unless relations.empty?
520 truncate_length = (!is_cjk? ? 80 : 60)
521 pdf.SetFontStyle('B',9)
522 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
523 pdf.ln
524 relations.each do |relation|
525 buf = relation.to_s(issue) {|other|
526 text = ""
527 if Setting.cross_project_issue_relations?
528 text += "#{relation.other_issue(issue).project} - "
529 end
530 text += "#{other.tracker} ##{other.id}: #{other.subject}"
531 text
532 }
533 buf = buf.truncate(truncate_length)
534 pdf.SetFontStyle('', 8)
535 pdf.RDMCell(35+155-60, 5, buf, border_first)
536 pdf.SetFontStyle('B',8)
537 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
538 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
539 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), border_last)
540 pdf.ln
541 end
542 end
543 pdf.RDMCell(190,5, "", "T")
544 pdf.ln
545
546 if issue.changesets.any? &&
547 User.current.allowed_to?(:view_changesets, issue.project)
548 pdf.SetFontStyle('B',9)
549 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
550 pdf.ln
551 for changeset in issue.changesets
552 pdf.SetFontStyle('B',8)
553 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
554 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
555 pdf.RDMCell(190, 5, csstr)
556 pdf.ln
557 unless changeset.comments.blank?
558 pdf.SetFontStyle('',8)
559 pdf.RDMwriteHTMLCell(190,5,'','',
560 changeset.comments.to_s, issue.attachments, "")
561 end
562 pdf.ln
563 end
564 end
565
566 if assoc[:journals].present?
567 pdf.SetFontStyle('B',9)
568 pdf.RDMCell(190,5, l(:label_history), "B")
569 pdf.ln
570 assoc[:journals].each do |journal|
571 pdf.SetFontStyle('B',8)
572 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
573 title << " (#{l(:field_private_notes)})" if journal.private_notes?
574 pdf.RDMCell(190,5, title)
575 pdf.ln
576 pdf.SetFontStyle('I',8)
577 details_to_strings(journal.visible_details, true).each do |string|
578 pdf.RDMMultiCell(190,5, "- " + string)
579 end
580 if journal.notes?
581 pdf.ln unless journal.details.empty?
582 pdf.SetFontStyle('',8)
583 pdf.RDMwriteHTMLCell(190,5,'','',
584 journal.notes.to_s, issue.attachments, "")
585 end
586 pdf.ln
587 end
588 end
589
590 if issue.attachments.any?
591 pdf.SetFontStyle('B',9)
592 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
593 pdf.ln
594 for attachment in issue.attachments
595 pdf.SetFontStyle('',8)
596 pdf.RDMCell(80,5, attachment.filename)
597 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
598 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
599 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
600 pdf.ln
601 end
602 end
603 pdf.output
604 end
605
606 # Returns a PDF string of a set of wiki pages
607 def wiki_pages_to_pdf(pages, project)
608 pdf = ITCPDF.new(current_language)
609 pdf.set_title(project.name)
610 pdf.alias_nb_pages
611 pdf.footer_date = format_date(Date.today)
612 pdf.add_page
613 pdf.SetFontStyle('B',11)
614 pdf.RDMMultiCell(190,5, project.name)
615 pdf.ln
616 # Set resize image scale
617 pdf.set_image_scale(1.6)
618 pdf.SetFontStyle('',9)
619 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
620 pdf.output
621 end
622
623 # Returns a PDF string of a single wiki page
624 def wiki_page_to_pdf(page, project)
625 pdf = ITCPDF.new(current_language)
626 pdf.set_title("#{project} - #{page.title}")
627 pdf.alias_nb_pages
628 pdf.footer_date = format_date(Date.today)
629 pdf.add_page
630 pdf.SetFontStyle('B',11)
631 pdf.RDMMultiCell(190,5,
632 "#{project} - #{page.title} - # #{page.content.version}")
633 pdf.ln
634 # Set resize image scale
635 pdf.set_image_scale(1.6)
636 pdf.SetFontStyle('',9)
637 write_wiki_page(pdf, page)
638 pdf.output
639 end
640
641 def write_page_hierarchy(pdf, pages, node=nil, level=0)
642 if pages[node]
643 pages[node].each do |page|
644 if @new_page
645 pdf.add_page
646 else
647 @new_page = true
648 end
649 pdf.bookmark page.title, level
650 write_wiki_page(pdf, page)
651 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
652 end
653 end
654 end
655
656 def write_wiki_page(pdf, page)
657 pdf.RDMwriteHTMLCell(190,5,'','',
658 page.content.text.to_s, page.attachments, 0)
659 if page.attachments.any?
660 pdf.ln(5)
661 pdf.SetFontStyle('B',9)
662 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
663 pdf.ln
664 for attachment in page.attachments
665 pdf.SetFontStyle('',8)
666 pdf.RDMCell(80,5, attachment.filename)
667 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
668 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
669 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
670 pdf.ln
671 end
672 end
673 end
674
675 class RDMPdfEncoding
122 class RDMPdfEncoding
676 def self.rdm_from_utf8(txt, encoding)
123 def self.rdm_from_utf8(txt, encoding)
677 txt ||= ''
124 txt ||= ''
General Comments 0
You need to be logged in to leave comments. Login now