##// END OF EJS Templates
Move PDF stuff to a single helper....
Jean-Philippe Lang -
r2224:ceb2320ef078
parent child
Show More
@@ -0,0 +1,438
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require 'iconv'
19 require 'rfpdf/chinese'
20
21 module Redmine
22 module Export
23 module PDF
24 class IFPDF < FPDF
25 include GLoc
26 attr_accessor :footer_date
27
28 def initialize(lang)
29 super()
30 set_language_if_valid lang
31 case current_language.to_s
32 when 'ja'
33 extend(PDF_Japanese)
34 AddSJISFont()
35 @font_for_content = 'SJIS'
36 @font_for_footer = 'SJIS'
37 when 'zh'
38 extend(PDF_Chinese)
39 AddGBFont()
40 @font_for_content = 'GB'
41 @font_for_footer = 'GB'
42 when 'zh-tw'
43 extend(PDF_Chinese)
44 AddBig5Font()
45 @font_for_content = 'Big5'
46 @font_for_footer = 'Big5'
47 else
48 @font_for_content = 'Arial'
49 @font_for_footer = 'Helvetica'
50 end
51 SetCreator(Redmine::Info.app_name)
52 SetFont(@font_for_content)
53 end
54
55 def SetFontStyle(style, size)
56 SetFont(@font_for_content, style, size)
57 end
58
59 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
60 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
61 # these quotation marks are not correctly rendered in the pdf
62 txt = txt.gsub(/[“�]/, '"') if txt
63 txt = begin
64 # 0x5c char handling
65 txtar = txt.split('\\')
66 txtar << '' if txt[-1] == ?\\
67 txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
68 rescue
69 txt
70 end || ''
71 super w,h,txt,border,ln,align,fill,link
72 end
73
74 def Footer
75 SetFont(@font_for_footer, 'I', 8)
76 SetY(-15)
77 SetX(15)
78 Cell(0, 5, @footer_date, 0, 0, 'L')
79 SetY(-15)
80 SetX(-30)
81 Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
82 end
83 end
84
85 # Returns a PDF string of a list of issues
86 def issues_to_pdf(issues, project)
87 pdf = IFPDF.new(current_language)
88 title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
89 pdf.SetTitle(title)
90 pdf.AliasNbPages
91 pdf.footer_date = format_date(Date.today)
92 pdf.AddPage("L")
93 row_height = 7
94
95 # title
96 pdf.SetFontStyle('B',11)
97 pdf.Cell(190,10, title)
98 pdf.Ln
99
100 # headers
101 pdf.SetFontStyle('B',10)
102 pdf.SetFillColor(230, 230, 230)
103 pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
104 pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
105 pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
106 pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
107 pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
108 pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
109 pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
110 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
111 pdf.Ln
112 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
113 pdf.SetY(pdf.GetY() + 1)
114
115 # rows
116 pdf.SetFontStyle('',9)
117 pdf.SetFillColor(255, 255, 255)
118 issues.each do |issue|
119 pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
120 pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
121 pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
122 pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
123 pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1)
124 pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
125 pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
126 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
127 pdf.SetY(pdf.GetY() + 1)
128 end
129 pdf.Output
130 end
131
132 # Returns a PDF string of a single issue
133 def issue_to_pdf(issue)
134 pdf = IFPDF.new(current_language)
135 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
136 pdf.AliasNbPages
137 pdf.footer_date = format_date(Date.today)
138 pdf.AddPage
139
140 pdf.SetFontStyle('B',11)
141 pdf.Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
142 pdf.Ln
143
144 y0 = pdf.GetY
145
146 pdf.SetFontStyle('B',9)
147 pdf.Cell(35,5, l(:field_status) + ":","LT")
148 pdf.SetFontStyle('',9)
149 pdf.Cell(60,5, issue.status.to_s,"RT")
150 pdf.SetFontStyle('B',9)
151 pdf.Cell(35,5, l(:field_priority) + ":","LT")
152 pdf.SetFontStyle('',9)
153 pdf.Cell(60,5, issue.priority.to_s,"RT")
154 pdf.Ln
155
156 pdf.SetFontStyle('B',9)
157 pdf.Cell(35,5, l(:field_author) + ":","L")
158 pdf.SetFontStyle('',9)
159 pdf.Cell(60,5, issue.author.to_s,"R")
160 pdf.SetFontStyle('B',9)
161 pdf.Cell(35,5, l(:field_category) + ":","L")
162 pdf.SetFontStyle('',9)
163 pdf.Cell(60,5, issue.category.to_s,"R")
164 pdf.Ln
165
166 pdf.SetFontStyle('B',9)
167 pdf.Cell(35,5, l(:field_created_on) + ":","L")
168 pdf.SetFontStyle('',9)
169 pdf.Cell(60,5, format_date(issue.created_on),"R")
170 pdf.SetFontStyle('B',9)
171 pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
172 pdf.SetFontStyle('',9)
173 pdf.Cell(60,5, issue.assigned_to.to_s,"R")
174 pdf.Ln
175
176 pdf.SetFontStyle('B',9)
177 pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
178 pdf.SetFontStyle('',9)
179 pdf.Cell(60,5, format_date(issue.updated_on),"RB")
180 pdf.SetFontStyle('B',9)
181 pdf.Cell(35,5, l(:field_due_date) + ":","LB")
182 pdf.SetFontStyle('',9)
183 pdf.Cell(60,5, format_date(issue.due_date),"RB")
184 pdf.Ln
185
186 for custom_value in issue.custom_values
187 pdf.SetFontStyle('B',9)
188 pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
189 pdf.SetFontStyle('',9)
190 pdf.MultiCell(155,5, (show_value custom_value),"R")
191 end
192
193 pdf.SetFontStyle('B',9)
194 pdf.Cell(35,5, l(:field_subject) + ":","LTB")
195 pdf.SetFontStyle('',9)
196 pdf.Cell(155,5, issue.subject,"RTB")
197 pdf.Ln
198
199 pdf.SetFontStyle('B',9)
200 pdf.Cell(35,5, l(:field_description) + ":")
201 pdf.SetFontStyle('',9)
202 pdf.MultiCell(155,5, @issue.description,"BR")
203
204 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
205 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
206 pdf.Ln
207
208 if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
209 pdf.SetFontStyle('B',9)
210 pdf.Cell(190,5, l(:label_associated_revisions), "B")
211 pdf.Ln
212 for changeset in issue.changesets
213 pdf.SetFontStyle('B',8)
214 pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
215 pdf.Ln
216 unless changeset.comments.blank?
217 pdf.SetFontStyle('',8)
218 pdf.MultiCell(190,5, changeset.comments)
219 end
220 pdf.Ln
221 end
222 end
223
224 pdf.SetFontStyle('B',9)
225 pdf.Cell(190,5, l(:label_history), "B")
226 pdf.Ln
227 for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
228 pdf.SetFontStyle('B',8)
229 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
230 pdf.Ln
231 pdf.SetFontStyle('I',8)
232 for detail in journal.details
233 pdf.Cell(190,5, "- " + show_detail(detail, true))
234 pdf.Ln
235 end
236 if journal.notes?
237 pdf.SetFontStyle('',8)
238 pdf.MultiCell(190,5, journal.notes)
239 end
240 pdf.Ln
241 end
242
243 if issue.attachments.any?
244 pdf.SetFontStyle('B',9)
245 pdf.Cell(190,5, l(:label_attachment_plural), "B")
246 pdf.Ln
247 for attachment in issue.attachments
248 pdf.SetFontStyle('',8)
249 pdf.Cell(80,5, attachment.filename)
250 pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
251 pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
252 pdf.Cell(65,5, attachment.author.name,0,0,"R")
253 pdf.Ln
254 end
255 end
256 pdf.Output
257 end
258
259 # Returns a PDF string of a gantt chart
260 def gantt_to_pdf(gantt, project)
261 pdf = IFPDF.new(current_language)
262 pdf.SetTitle("#{l(:label_gantt)} #{project}")
263 pdf.AliasNbPages
264 pdf.footer_date = format_date(Date.today)
265 pdf.AddPage("L")
266 pdf.SetFontStyle('B',12)
267 pdf.SetX(15)
268 pdf.Cell(70, 20, project.to_s)
269 pdf.Ln
270 pdf.SetFontStyle('B',9)
271
272 subject_width = 70
273 header_heigth = 5
274
275 headers_heigth = header_heigth
276 show_weeks = false
277 show_days = false
278
279 if gantt.months < 7
280 show_weeks = true
281 headers_heigth = 2*header_heigth
282 if gantt.months < 3
283 show_days = true
284 headers_heigth = 3*header_heigth
285 end
286 end
287
288 g_width = 210
289 zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
290 g_height = 120
291 t_height = g_height + headers_heigth
292
293 y_start = pdf.GetY
294
295 # Months headers
296 month_f = gantt.date_from
297 left = subject_width
298 height = header_heigth
299 gantt.months.times do
300 width = ((month_f >> 1) - month_f) * zoom
301 pdf.SetY(y_start)
302 pdf.SetX(left)
303 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
304 left = left + width
305 month_f = month_f >> 1
306 end
307
308 # Weeks headers
309 if show_weeks
310 left = subject_width
311 height = header_heigth
312 if gantt.date_from.cwday == 1
313 # gantt.date_from is monday
314 week_f = gantt.date_from
315 else
316 # find next monday after gantt.date_from
317 week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
318 width = (7 - gantt.date_from.cwday + 1) * zoom-1
319 pdf.SetY(y_start + header_heigth)
320 pdf.SetX(left)
321 pdf.Cell(width + 1, height, "", "LTR")
322 left = left + width+1
323 end
324 while week_f <= gantt.date_to
325 width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
326 pdf.SetY(y_start + header_heigth)
327 pdf.SetX(left)
328 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
329 left = left + width
330 week_f = week_f+7
331 end
332 end
333
334 # Days headers
335 if show_days
336 left = subject_width
337 height = header_heigth
338 wday = gantt.date_from.cwday
339 pdf.SetFontStyle('B',7)
340 (gantt.date_to - gantt.date_from + 1).to_i.times do
341 width = zoom
342 pdf.SetY(y_start + 2 * header_heigth)
343 pdf.SetX(left)
344 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
345 left = left + width
346 wday = wday + 1
347 wday = 1 if wday > 7
348 end
349 end
350
351 pdf.SetY(y_start)
352 pdf.SetX(15)
353 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
354
355 # Tasks
356 top = headers_heigth + y_start
357 pdf.SetFontStyle('B',7)
358 gantt.events.each do |i|
359 pdf.SetY(top)
360 pdf.SetX(15)
361
362 if i.is_a? Issue
363 pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
364 else
365 pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
366 end
367
368 pdf.SetY(top)
369 pdf.SetX(subject_width)
370 pdf.Cell(g_width, 5, "", "LR")
371
372 pdf.SetY(top+1.5)
373
374 if i.is_a? Issue
375 i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
376 i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
377
378 i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
379 i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
380 i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
381
382 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
383
384 i_left = ((i_start_date - gantt.date_from)*zoom)
385 i_width = ((i_end_date - i_start_date + 1)*zoom)
386 d_width = ((i_done_date - i_start_date)*zoom)
387 l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
388 l_width ||= 0
389
390 pdf.SetX(subject_width + i_left)
391 pdf.SetFillColor(200,200,200)
392 pdf.Cell(i_width, 2, "", 0, 0, "", 1)
393
394 if l_width > 0
395 pdf.SetY(top+1.5)
396 pdf.SetX(subject_width + i_left)
397 pdf.SetFillColor(255,100,100)
398 pdf.Cell(l_width, 2, "", 0, 0, "", 1)
399 end
400 if d_width > 0
401 pdf.SetY(top+1.5)
402 pdf.SetX(subject_width + i_left)
403 pdf.SetFillColor(100,100,255)
404 pdf.Cell(d_width, 2, "", 0, 0, "", 1)
405 end
406
407 pdf.SetY(top+1.5)
408 pdf.SetX(subject_width + i_left + i_width)
409 pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
410 else
411 i_left = ((i.start_date - gantt.date_from)*zoom)
412
413 pdf.SetX(subject_width + i_left)
414 pdf.SetFillColor(50,200,50)
415 pdf.Cell(2, 2, "", 0, 0, "", 1)
416
417 pdf.SetY(top+1.5)
418 pdf.SetX(subject_width + i_left + 3)
419 pdf.Cell(30, 2, "#{i.name}")
420 end
421
422 top = top + 5
423 pdf.SetDrawColor(200, 200, 200)
424 pdf.Line(15, top, subject_width+g_width, top)
425 if pdf.GetY() > 180
426 pdf.AddPage("L")
427 top = 20
428 pdf.Line(15, top, subject_width+g_width, top)
429 end
430 pdf.SetDrawColor(0, 0, 0)
431 end
432
433 pdf.Line(15, top, subject_width+g_width, top)
434 pdf.Output
435 end
436 end
437 end
438 end
@@ -30,8 +30,6 class IssuesController < ApplicationController
30 include ProjectsHelper
30 include ProjectsHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33 helper :ifpdf
34 include IfpdfHelper
35 helper :issue_relations
33 helper :issue_relations
36 include IssueRelationsHelper
34 include IssueRelationsHelper
37 helper :watchers
35 helper :watchers
@@ -43,6 +41,7 class IssuesController < ApplicationController
43 include SortHelper
41 include SortHelper
44 include IssuesHelper
42 include IssuesHelper
45 helper :timelog
43 helper :timelog
44 include Redmine::Export::PDF
46
45
47 def index
46 def index
48 retrieve_query
47 retrieve_query
@@ -68,7 +67,7 class IssuesController < ApplicationController
68 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
67 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
69 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
68 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
70 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
69 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
71 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
70 format.pdf { send_data(issues_to_pdf(@issues, @project), :type => 'application/pdf', :filename => 'export.pdf') }
72 end
71 end
73 else
72 else
74 # Send html if the query is not valid
73 # Send html if the query is not valid
@@ -106,7 +105,7 class IssuesController < ApplicationController
106 respond_to do |format|
105 respond_to do |format|
107 format.html { render :template => 'issues/show.rhtml' }
106 format.html { render :template => 'issues/show.rhtml' }
108 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
107 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
109 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
108 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
110 end
109 end
111 end
110 end
112
111
@@ -346,7 +345,7 class IssuesController < ApplicationController
346 respond_to do |format|
345 respond_to do |format|
347 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
346 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
348 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
347 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
349 format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
348 format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
350 end
349 end
351 end
350 end
352
351
@@ -33,8 +33,6 class ProjectsController < ApplicationController
33 include SortHelper
33 include SortHelper
34 helper :custom_fields
34 helper :custom_fields
35 include CustomFieldsHelper
35 include CustomFieldsHelper
36 helper :ifpdf
37 include IfpdfHelper
38 helper :issues
36 helper :issues
39 helper IssuesHelper
37 helper IssuesHelper
40 helper :queries
38 helper :queries
@@ -177,16 +177,16 class IssuesControllerTest < Test::Unit::TestCase
177 def test_gantt_export_to_pdf
177 def test_gantt_export_to_pdf
178 get :gantt, :project_id => 1, :format => 'pdf'
178 get :gantt, :project_id => 1, :format => 'pdf'
179 assert_response :success
179 assert_response :success
180 assert_template 'gantt.rfpdf'
181 assert_equal 'application/pdf', @response.content_type
180 assert_equal 'application/pdf', @response.content_type
181 assert @response.body.starts_with?('%PDF')
182 assert_not_nil assigns(:gantt)
182 assert_not_nil assigns(:gantt)
183 end
183 end
184
184
185 def test_cross_project_gantt_export_to_pdf
185 def test_cross_project_gantt_export_to_pdf
186 get :gantt, :format => 'pdf'
186 get :gantt, :format => 'pdf'
187 assert_response :success
187 assert_response :success
188 assert_template 'gantt.rfpdf'
189 assert_equal 'application/pdf', @response.content_type
188 assert_equal 'application/pdf', @response.content_type
189 assert @response.body.starts_with?('%PDF')
190 assert_not_nil assigns(:gantt)
190 assert_not_nil assigns(:gantt)
191 end
191 end
192
192
@@ -252,6 +252,14 class IssuesControllerTest < Test::Unit::TestCase
252 :content => /Notes/ } }
252 :content => /Notes/ } }
253 end
253 end
254
254
255 def test_show_export_to_pdf
256 get :show, :id => 1, :format => 'pdf'
257 assert_response :success
258 assert_equal 'application/pdf', @response.content_type
259 assert @response.body.starts_with?('%PDF')
260 assert_not_nil assigns(:issue)
261 end
262
255 def test_get_new
263 def test_get_new
256 @request.session[:user_id] = 2
264 @request.session[:user_id] = 2
257 get :new, :project_id => 1, :tracker_id => 1
265 get :new, :project_id => 1, :tracker_id => 1
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now