##// END OF EJS Templates
Do not show parents/subtasks subjects that are not visible....
Jean-Philippe Lang -
r10375:b0013d9f68a0
parent child
Show More
@@ -1,779 +1,779
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require 'iconv'
21 21 require 'tcpdf'
22 22 require 'fpdf/chinese'
23 23 require 'fpdf/japanese'
24 24 require 'fpdf/korean'
25 25
26 26 module Redmine
27 27 module Export
28 28 module PDF
29 29 include ActionView::Helpers::TextHelper
30 30 include ActionView::Helpers::NumberHelper
31 31 include IssuesHelper
32 32
33 33 class ITCPDF < TCPDF
34 34 include Redmine::I18n
35 35 attr_accessor :footer_date
36 36
37 37 def initialize(lang)
38 38 @@k_path_cache = Rails.root.join('tmp', 'pdf')
39 39 FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
40 40 set_language_if_valid lang
41 41 pdf_encoding = l(:general_pdf_encoding).upcase
42 42 super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
43 43 case current_language.to_s.downcase
44 44 when 'vi'
45 45 @font_for_content = 'DejaVuSans'
46 46 @font_for_footer = 'DejaVuSans'
47 47 else
48 48 case pdf_encoding
49 49 when 'UTF-8'
50 50 @font_for_content = 'FreeSans'
51 51 @font_for_footer = 'FreeSans'
52 52 when 'CP949'
53 53 extend(PDF_Korean)
54 54 AddUHCFont()
55 55 @font_for_content = 'UHC'
56 56 @font_for_footer = 'UHC'
57 57 when 'CP932', 'SJIS', 'SHIFT_JIS'
58 58 extend(PDF_Japanese)
59 59 AddSJISFont()
60 60 @font_for_content = 'SJIS'
61 61 @font_for_footer = 'SJIS'
62 62 when 'GB18030'
63 63 extend(PDF_Chinese)
64 64 AddGBFont()
65 65 @font_for_content = 'GB'
66 66 @font_for_footer = 'GB'
67 67 when 'BIG5'
68 68 extend(PDF_Chinese)
69 69 AddBig5Font()
70 70 @font_for_content = 'Big5'
71 71 @font_for_footer = 'Big5'
72 72 else
73 73 @font_for_content = 'Arial'
74 74 @font_for_footer = 'Helvetica'
75 75 end
76 76 end
77 77 SetCreator(Redmine::Info.app_name)
78 78 SetFont(@font_for_content)
79 79 @outlines = []
80 80 @outlineRoot = nil
81 81 end
82 82
83 83 def SetFontStyle(style, size)
84 84 SetFont(@font_for_content, style, size)
85 85 end
86 86
87 87 def SetTitle(txt)
88 88 txt = begin
89 89 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
90 90 hextxt = "<FEFF" # FEFF is BOM
91 91 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
92 92 hextxt << ">"
93 93 rescue
94 94 txt
95 95 end || ''
96 96 super(txt)
97 97 end
98 98
99 99 def textstring(s)
100 100 # Format a text string
101 101 if s =~ /^</ # This means the string is hex-dumped.
102 102 return s
103 103 else
104 104 return '('+escape(s)+')'
105 105 end
106 106 end
107 107
108 108 def fix_text_encoding(txt)
109 109 RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
110 110 end
111 111
112 112 def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
113 113 Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
114 114 end
115 115
116 116 def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
117 117 MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
118 118 end
119 119
120 120 def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
121 121 @attachments = attachments
122 122 writeHTMLCell(w, h, x, y,
123 123 fix_text_encoding(
124 124 Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
125 125 border, ln, fill)
126 126 end
127 127
128 128 def getImageFilename(attrname)
129 129 # attrname: general_pdf_encoding string file/uri name
130 130 atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
131 131 if atta
132 132 return atta.diskfile
133 133 else
134 134 return nil
135 135 end
136 136 end
137 137
138 138 def Footer
139 139 SetFont(@font_for_footer, 'I', 8)
140 140 SetY(-15)
141 141 SetX(15)
142 142 RDMCell(0, 5, @footer_date, 0, 0, 'L')
143 143 SetY(-15)
144 144 SetX(-30)
145 145 RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
146 146 end
147 147
148 148 def Bookmark(txt, level=0, y=0)
149 149 if (y == -1)
150 150 y = GetY()
151 151 end
152 152 @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k}
153 153 end
154 154
155 155 def bookmark_title(txt)
156 156 txt = begin
157 157 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
158 158 hextxt = "<FEFF" # FEFF is BOM
159 159 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
160 160 hextxt << ">"
161 161 rescue
162 162 txt
163 163 end || ''
164 164 end
165 165
166 166 def putbookmarks
167 167 nb=@outlines.size
168 168 return if (nb==0)
169 169 lru=[]
170 170 level=0
171 171 @outlines.each_with_index do |o, i|
172 172 if(o[:l]>0)
173 173 parent=lru[o[:l]-1]
174 174 #Set parent and last pointers
175 175 @outlines[i][:parent]=parent
176 176 @outlines[parent][:last]=i
177 177 if (o[:l]>level)
178 178 #Level increasing: set first pointer
179 179 @outlines[parent][:first]=i
180 180 end
181 181 else
182 182 @outlines[i][:parent]=nb
183 183 end
184 184 if (o[:l]<=level && i>0)
185 185 #Set prev and next pointers
186 186 prev=lru[o[:l]]
187 187 @outlines[prev][:next]=i
188 188 @outlines[i][:prev]=prev
189 189 end
190 190 lru[o[:l]]=i
191 191 level=o[:l]
192 192 end
193 193 #Outline items
194 194 n=self.n+1
195 195 @outlines.each_with_index do |o, i|
196 196 newobj()
197 197 out('<</Title '+bookmark_title(o[:t]))
198 198 out("/Parent #{n+o[:parent]} 0 R")
199 199 if (o[:prev])
200 200 out("/Prev #{n+o[:prev]} 0 R")
201 201 end
202 202 if (o[:next])
203 203 out("/Next #{n+o[:next]} 0 R")
204 204 end
205 205 if (o[:first])
206 206 out("/First #{n+o[:first]} 0 R")
207 207 end
208 208 if (o[:last])
209 209 out("/Last #{n+o[:last]} 0 R")
210 210 end
211 211 out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
212 212 out('/Count 0>>')
213 213 out('endobj')
214 214 end
215 215 #Outline root
216 216 newobj()
217 217 @outlineRoot=self.n
218 218 out("<</Type /Outlines /First #{n} 0 R");
219 219 out("/Last #{n+lru[0]} 0 R>>");
220 220 out('endobj');
221 221 end
222 222
223 223 def putresources()
224 224 super
225 225 putbookmarks()
226 226 end
227 227
228 228 def putcatalog()
229 229 super
230 230 if(@outlines.size > 0)
231 231 out("/Outlines #{@outlineRoot} 0 R");
232 232 out('/PageMode /UseOutlines');
233 233 end
234 234 end
235 235 end
236 236
237 237 # fetch row values
238 238 def fetch_row_values(issue, query, level)
239 239 query.columns.collect do |column|
240 240 s = if column.is_a?(QueryCustomFieldColumn)
241 241 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
242 242 show_value(cv)
243 243 else
244 244 value = issue.send(column.name)
245 245 if column.name == :subject
246 246 value = " " * level + value
247 247 end
248 248 if value.is_a?(Date)
249 249 format_date(value)
250 250 elsif value.is_a?(Time)
251 251 format_time(value)
252 252 else
253 253 value
254 254 end
255 255 end
256 256 s.to_s
257 257 end
258 258 end
259 259
260 260 # calculate columns width
261 261 def calc_col_width(issues, query, table_width, pdf)
262 262 # calculate statistics
263 263 # by captions
264 264 pdf.SetFontStyle('B',8)
265 265 col_padding = pdf.GetStringWidth('OO')
266 266 col_width_min = query.columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding}
267 267 col_width_max = Array.new(col_width_min)
268 268 col_width_avg = Array.new(col_width_min)
269 269 word_width_max = query.columns.map {|c|
270 270 n = 10
271 271 c.caption.split.each {|w|
272 272 x = pdf.GetStringWidth(w) + col_padding
273 273 n = x if n < x
274 274 }
275 275 n
276 276 }
277 277
278 278 # by properties of issues
279 279 pdf.SetFontStyle('',8)
280 280 col_padding = pdf.GetStringWidth('OO')
281 281 k = 1
282 282 issue_list(issues) {|issue, level|
283 283 k += 1
284 284 values = fetch_row_values(issue, query, level)
285 285 values.each_with_index {|v,i|
286 286 n = pdf.GetStringWidth(v) + col_padding
287 287 col_width_max[i] = n if col_width_max[i] < n
288 288 col_width_min[i] = n if col_width_min[i] > n
289 289 col_width_avg[i] += n
290 290 v.split.each {|w|
291 291 x = pdf.GetStringWidth(w) + col_padding
292 292 word_width_max[i] = x if word_width_max[i] < x
293 293 }
294 294 }
295 295 }
296 296 col_width_avg.map! {|x| x / k}
297 297
298 298 # calculate columns width
299 299 ratio = table_width / col_width_avg.inject(0) {|s,w| s += w}
300 300 col_width = col_width_avg.map {|w| w * ratio}
301 301
302 302 # correct max word width if too many columns
303 303 ratio = table_width / word_width_max.inject(0) {|s,w| s += w}
304 304 word_width_max.map! {|v| v * ratio} if ratio < 1
305 305
306 306 # correct and lock width of some columns
307 307 done = 1
308 308 col_fix = []
309 309 col_width.each_with_index do |w,i|
310 310 if w > col_width_max[i]
311 311 col_width[i] = col_width_max[i]
312 312 col_fix[i] = 1
313 313 done = 0
314 314 elsif w < word_width_max[i]
315 315 col_width[i] = word_width_max[i]
316 316 col_fix[i] = 1
317 317 done = 0
318 318 else
319 319 col_fix[i] = 0
320 320 end
321 321 end
322 322
323 323 # iterate while need to correct and lock coluns width
324 324 while done == 0
325 325 # calculate free & locked columns width
326 326 done = 1
327 327 fix_col_width = 0
328 328 free_col_width = 0
329 329 col_width.each_with_index do |w,i|
330 330 if col_fix[i] == 1
331 331 fix_col_width += w
332 332 else
333 333 free_col_width += w
334 334 end
335 335 end
336 336
337 337 # calculate column normalizing ratio
338 338 if free_col_width == 0
339 339 ratio = table_width / col_width.inject(0) {|s,w| s += w}
340 340 else
341 341 ratio = (table_width - fix_col_width) / free_col_width
342 342 end
343 343
344 344 # correct columns width
345 345 col_width.each_with_index do |w,i|
346 346 if col_fix[i] == 0
347 347 col_width[i] = w * ratio
348 348
349 349 # check if column width less then max word width
350 350 if col_width[i] < word_width_max[i]
351 351 col_width[i] = word_width_max[i]
352 352 col_fix[i] = 1
353 353 done = 0
354 354 elsif col_width[i] > col_width_max[i]
355 355 col_width[i] = col_width_max[i]
356 356 col_fix[i] = 1
357 357 done = 0
358 358 end
359 359 end
360 360 end
361 361 end
362 362 col_width
363 363 end
364 364
365 365 def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
366 366 # headers
367 367 pdf.SetFontStyle('B',8)
368 368 pdf.SetFillColor(230, 230, 230)
369 369
370 370 # render it background to find the max height used
371 371 base_x = pdf.GetX
372 372 base_y = pdf.GetY
373 373 max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
374 374 pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD');
375 375 pdf.SetXY(base_x, base_y);
376 376
377 377 # write the cells on page
378 378 pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
379 379 issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
380 380 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
381 381 pdf.SetY(base_y + max_height);
382 382
383 383 # rows
384 384 pdf.SetFontStyle('',8)
385 385 pdf.SetFillColor(255, 255, 255)
386 386 end
387 387
388 388 # Returns a PDF string of a list of issues
389 389 def issues_to_pdf(issues, project, query)
390 390 pdf = ITCPDF.new(current_language)
391 391 title = query.new_record? ? l(:label_issue_plural) : query.name
392 392 title = "#{project} - #{title}" if project
393 393 pdf.SetTitle(title)
394 394 pdf.alias_nb_pages
395 395 pdf.footer_date = format_date(Date.today)
396 396 pdf.SetAutoPageBreak(false)
397 397 pdf.AddPage("L")
398 398
399 399 # Landscape A4 = 210 x 297 mm
400 400 page_height = 210
401 401 page_width = 297
402 402 right_margin = 10
403 403 bottom_margin = 20
404 404 col_id_width = 10
405 405 row_height = 4
406 406
407 407 # column widths
408 408 table_width = page_width - right_margin - 10 # fixed left margin
409 409 col_width = []
410 410 unless query.columns.empty?
411 411 col_width = calc_col_width(issues, query, table_width - col_id_width, pdf)
412 412 table_width = col_width.inject(0) {|s,v| s += v}
413 413 end
414 414
415 415 # title
416 416 pdf.SetFontStyle('B',11)
417 417 pdf.RDMCell(190,10, title)
418 418 pdf.Ln
419 419 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
420 420 previous_group = false
421 421 issue_list(issues) do |issue, level|
422 422 if query.grouped? &&
423 423 (group = query.group_by_column.value(issue)) != previous_group
424 424 pdf.SetFontStyle('B',10)
425 425 group_label = group.blank? ? 'None' : group.to_s
426 426 group_label << " (#{query.issue_count_by_group[group]})"
427 427 pdf.Bookmark group_label, 0, -1
428 428 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
429 429 pdf.SetFontStyle('',8)
430 430 previous_group = group
431 431 end
432 432
433 433 # fetch row values
434 434 col_values = fetch_row_values(issue, query, level)
435 435
436 436 # render it off-page to find the max height used
437 437 base_x = pdf.GetX
438 438 base_y = pdf.GetY
439 439 pdf.SetY(2 * page_height)
440 440 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
441 441 pdf.SetXY(base_x, base_y)
442 442
443 443 # make new page if it doesn't fit on the current one
444 444 space_left = page_height - base_y - bottom_margin
445 445 if max_height > space_left
446 446 pdf.AddPage("L")
447 447 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
448 448 base_x = pdf.GetX
449 449 base_y = pdf.GetY
450 450 end
451 451
452 452 # write the cells on page
453 453 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
454 454 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
455 455 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
456 456 pdf.SetY(base_y + max_height);
457 457 end
458 458
459 459 if issues.size == Setting.issues_export_limit.to_i
460 460 pdf.SetFontStyle('B',10)
461 461 pdf.RDMCell(0, row_height, '...')
462 462 end
463 463 pdf.Output
464 464 end
465 465
466 466 # Renders MultiCells and returns the maximum height used
467 467 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
468 468 row_height, head=false)
469 469 base_y = pdf.GetY
470 470 max_height = row_height
471 471 col_values.each_with_index do |column, i|
472 472 col_x = pdf.GetX
473 473 if head == true
474 474 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
475 475 else
476 476 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
477 477 end
478 478 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
479 479 pdf.SetXY(col_x + col_widths[i], base_y);
480 480 end
481 481 return max_height
482 482 end
483 483
484 484 # Draw lines to close the row (MultiCell border drawing in not uniform)
485 485 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
486 486 id_width, col_widths)
487 487 col_x = top_x + id_width
488 488 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
489 489 col_widths.each do |width|
490 490 col_x += width
491 491 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
492 492 end
493 493 pdf.Line(top_x, top_y, top_x, lower_y) # left border
494 494 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
495 495 end
496 496
497 497 # Returns a PDF string of a single issue
498 498 def issue_to_pdf(issue, assoc={})
499 499 pdf = ITCPDF.new(current_language)
500 500 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
501 501 pdf.alias_nb_pages
502 502 pdf.footer_date = format_date(Date.today)
503 503 pdf.AddPage
504 504 pdf.SetFontStyle('B',11)
505 505 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
506 506 pdf.RDMMultiCell(190, 5, buf)
507 507 pdf.SetFontStyle('',8)
508 508 base_x = pdf.GetX
509 509 i = 1
510 issue.ancestors.each do |ancestor|
510 issue.ancestors.visible.each do |ancestor|
511 511 pdf.SetX(base_x + i)
512 512 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
513 513 pdf.RDMMultiCell(190 - i, 5, buf)
514 514 i += 1 if i < 35
515 515 end
516 516 pdf.SetFontStyle('B',11)
517 517 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
518 518 pdf.SetFontStyle('',8)
519 519 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
520 520 pdf.Ln
521 521
522 522 left = []
523 523 left << [l(:field_status), issue.status]
524 524 left << [l(:field_priority), issue.priority]
525 525 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
526 526 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
527 527 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
528 528
529 529 right = []
530 530 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
531 531 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
532 532 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
533 533 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
534 534 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
535 535
536 536 rows = left.size > right.size ? left.size : right.size
537 537 while left.size < rows
538 538 left << nil
539 539 end
540 540 while right.size < rows
541 541 right << nil
542 542 end
543 543
544 544 half = (issue.custom_field_values.size / 2.0).ceil
545 545 issue.custom_field_values.each_with_index do |custom_value, i|
546 546 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
547 547 end
548 548
549 549 rows = left.size > right.size ? left.size : right.size
550 550 rows.times do |i|
551 551 item = left[i]
552 552 pdf.SetFontStyle('B',9)
553 553 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
554 554 pdf.SetFontStyle('',9)
555 555 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
556 556
557 557 item = right[i]
558 558 pdf.SetFontStyle('B',9)
559 559 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
560 560 pdf.SetFontStyle('',9)
561 561 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
562 562 pdf.Ln
563 563 end
564 564
565 565 pdf.SetFontStyle('B',9)
566 566 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
567 567 pdf.SetFontStyle('',9)
568 568
569 569 # Set resize image scale
570 570 pdf.SetImageScale(1.6)
571 571 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
572 572 issue.description.to_s, issue.attachments, "LRB")
573 573
574 574 unless issue.leaf?
575 575 # for CJK
576 576 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
577 577
578 578 pdf.SetFontStyle('B',9)
579 579 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
580 580 pdf.Ln
581 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
581 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
582 582 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
583 583 :length => truncate_length)
584 584 level = 10 if level >= 10
585 585 pdf.SetFontStyle('',8)
586 586 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
587 587 pdf.SetFontStyle('B',8)
588 588 pdf.RDMCell(20,5, child.status.to_s, "R")
589 589 pdf.Ln
590 590 end
591 591 end
592 592
593 593 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
594 594 unless relations.empty?
595 595 # for CJK
596 596 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
597 597
598 598 pdf.SetFontStyle('B',9)
599 599 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
600 600 pdf.Ln
601 601 relations.each do |relation|
602 602 buf = ""
603 603 buf += "#{l(relation.label_for(issue))} "
604 604 if relation.delay && relation.delay != 0
605 605 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
606 606 end
607 607 if Setting.cross_project_issue_relations?
608 608 buf += "#{relation.other_issue(issue).project} - "
609 609 end
610 610 buf += "#{relation.other_issue(issue).tracker}" +
611 611 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
612 612 buf = truncate(buf, :length => truncate_length)
613 613 pdf.SetFontStyle('', 8)
614 614 pdf.RDMCell(35+155-60, 5, buf, "L")
615 615 pdf.SetFontStyle('B',8)
616 616 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
617 617 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
618 618 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
619 619 pdf.Ln
620 620 end
621 621 end
622 622 pdf.RDMCell(190,5, "", "T")
623 623 pdf.Ln
624 624
625 625 if issue.changesets.any? &&
626 626 User.current.allowed_to?(:view_changesets, issue.project)
627 627 pdf.SetFontStyle('B',9)
628 628 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
629 629 pdf.Ln
630 630 for changeset in issue.changesets
631 631 pdf.SetFontStyle('B',8)
632 632 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
633 633 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
634 634 pdf.RDMCell(190, 5, csstr)
635 635 pdf.Ln
636 636 unless changeset.comments.blank?
637 637 pdf.SetFontStyle('',8)
638 638 pdf.RDMwriteHTMLCell(190,5,0,0,
639 639 changeset.comments.to_s, issue.attachments, "")
640 640 end
641 641 pdf.Ln
642 642 end
643 643 end
644 644
645 645 if assoc[:journals].present?
646 646 pdf.SetFontStyle('B',9)
647 647 pdf.RDMCell(190,5, l(:label_history), "B")
648 648 pdf.Ln
649 649 assoc[:journals].each do |journal|
650 650 pdf.SetFontStyle('B',8)
651 651 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
652 652 title << " (#{l(:field_private_notes)})" if journal.private_notes?
653 653 pdf.RDMCell(190,5, title)
654 654 pdf.Ln
655 655 pdf.SetFontStyle('I',8)
656 656 details_to_strings(journal.details, true).each do |string|
657 657 pdf.RDMMultiCell(190,5, "- " + string)
658 658 end
659 659 if journal.notes?
660 660 pdf.Ln unless journal.details.empty?
661 661 pdf.SetFontStyle('',8)
662 662 pdf.RDMwriteHTMLCell(190,5,0,0,
663 663 journal.notes.to_s, issue.attachments, "")
664 664 end
665 665 pdf.Ln
666 666 end
667 667 end
668 668
669 669 if issue.attachments.any?
670 670 pdf.SetFontStyle('B',9)
671 671 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
672 672 pdf.Ln
673 673 for attachment in issue.attachments
674 674 pdf.SetFontStyle('',8)
675 675 pdf.RDMCell(80,5, attachment.filename)
676 676 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
677 677 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
678 678 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
679 679 pdf.Ln
680 680 end
681 681 end
682 682 pdf.Output
683 683 end
684 684
685 685 # Returns a PDF string of a set of wiki pages
686 686 def wiki_pages_to_pdf(pages, project)
687 687 pdf = ITCPDF.new(current_language)
688 688 pdf.SetTitle(project.name)
689 689 pdf.alias_nb_pages
690 690 pdf.footer_date = format_date(Date.today)
691 691 pdf.AddPage
692 692 pdf.SetFontStyle('B',11)
693 693 pdf.RDMMultiCell(190,5, project.name)
694 694 pdf.Ln
695 695 # Set resize image scale
696 696 pdf.SetImageScale(1.6)
697 697 pdf.SetFontStyle('',9)
698 698 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
699 699 pdf.Output
700 700 end
701 701
702 702 # Returns a PDF string of a single wiki page
703 703 def wiki_page_to_pdf(page, project)
704 704 pdf = ITCPDF.new(current_language)
705 705 pdf.SetTitle("#{project} - #{page.title}")
706 706 pdf.alias_nb_pages
707 707 pdf.footer_date = format_date(Date.today)
708 708 pdf.AddPage
709 709 pdf.SetFontStyle('B',11)
710 710 pdf.RDMMultiCell(190,5,
711 711 "#{project} - #{page.title} - # #{page.content.version}")
712 712 pdf.Ln
713 713 # Set resize image scale
714 714 pdf.SetImageScale(1.6)
715 715 pdf.SetFontStyle('',9)
716 716 write_wiki_page(pdf, page)
717 717 pdf.Output
718 718 end
719 719
720 720 def write_page_hierarchy(pdf, pages, node=nil, level=0)
721 721 if pages[node]
722 722 pages[node].each do |page|
723 723 if @new_page
724 724 pdf.AddPage
725 725 else
726 726 @new_page = true
727 727 end
728 728 pdf.Bookmark page.title, level
729 729 write_wiki_page(pdf, page)
730 730 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
731 731 end
732 732 end
733 733 end
734 734
735 735 def write_wiki_page(pdf, page)
736 736 pdf.RDMwriteHTMLCell(190,5,0,0,
737 737 page.content.text.to_s, page.attachments, 0)
738 738 if page.attachments.any?
739 739 pdf.Ln
740 740 pdf.SetFontStyle('B',9)
741 741 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
742 742 pdf.Ln
743 743 for attachment in page.attachments
744 744 pdf.SetFontStyle('',8)
745 745 pdf.RDMCell(80,5, attachment.filename)
746 746 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
747 747 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
748 748 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
749 749 pdf.Ln
750 750 end
751 751 end
752 752 end
753 753
754 754 class RDMPdfEncoding
755 755 def self.rdm_from_utf8(txt, encoding)
756 756 txt ||= ''
757 757 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
758 758 if txt.respond_to?(:force_encoding)
759 759 txt.force_encoding('ASCII-8BIT')
760 760 end
761 761 txt
762 762 end
763 763
764 764 def self.attach(attachments, filename, encoding)
765 765 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
766 766 atta = nil
767 767 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
768 768 atta = Attachment.latest_attach(attachments, filename_utf8)
769 769 end
770 770 if atta && atta.readable? && atta.visible?
771 771 return atta
772 772 else
773 773 return nil
774 774 end
775 775 end
776 776 end
777 777 end
778 778 end
779 779 end
General Comments 0
You need to be logged in to leave comments. Login now