##// END OF EJS Templates
Merged r10956 from trunk (#12513)....
Jean-Philippe Lang -
r10775:d22f782d3f9b
parent child
Show More
@@ -1,792 +1,792
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, orientation='P')
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(orientation, '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.inline_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.inline_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.inline_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.inline_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.inline_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, "L")
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.inline_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 # use full width if the description is displayed
416 416 if table_width > 0 && query.has_column?(:description)
417 417 col_width = col_width.map {|w| w = w * (page_width - right_margin - 10 - col_id_width) / table_width}
418 418 table_width = col_width.inject(0) {|s,v| s += v}
419 419 end
420 420
421 421 # title
422 422 pdf.SetFontStyle('B',11)
423 423 pdf.RDMCell(190,10, title)
424 424 pdf.Ln
425 425 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
426 426 previous_group = false
427 427 issue_list(issues) do |issue, level|
428 428 if query.grouped? &&
429 429 (group = query.group_by_column.value(issue)) != previous_group
430 430 pdf.SetFontStyle('B',10)
431 group_label = group.blank? ? 'None' : group.to_s
431 group_label = group.blank? ? 'None' : group.to_s.dup
432 432 group_label << " (#{query.issue_count_by_group[group]})"
433 433 pdf.Bookmark group_label, 0, -1
434 434 pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
435 435 pdf.SetFontStyle('',8)
436 436 previous_group = group
437 437 end
438 438
439 439 # fetch row values
440 440 col_values = fetch_row_values(issue, query, level)
441 441
442 442 # render it off-page to find the max height used
443 443 base_x = pdf.GetX
444 444 base_y = pdf.GetY
445 445 pdf.SetY(2 * page_height)
446 446 max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
447 447 pdf.SetXY(base_x, base_y)
448 448
449 449 # make new page if it doesn't fit on the current one
450 450 space_left = page_height - base_y - bottom_margin
451 451 if max_height > space_left
452 452 pdf.AddPage("L")
453 453 render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
454 454 base_x = pdf.GetX
455 455 base_y = pdf.GetY
456 456 end
457 457
458 458 # write the cells on page
459 459 pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
460 460 issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
461 461 issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
462 462 pdf.SetY(base_y + max_height);
463 463
464 464 if query.has_column?(:description) && issue.description?
465 465 pdf.SetX(10)
466 466 pdf.SetAutoPageBreak(true, 20)
467 467 pdf.RDMwriteHTMLCell(0, 5, 10, 0, issue.description.to_s, issue.attachments, "LRBT")
468 468 pdf.SetAutoPageBreak(false)
469 469 end
470 470 end
471 471
472 472 if issues.size == Setting.issues_export_limit.to_i
473 473 pdf.SetFontStyle('B',10)
474 474 pdf.RDMCell(0, row_height, '...')
475 475 end
476 476 pdf.Output
477 477 end
478 478
479 479 # Renders MultiCells and returns the maximum height used
480 480 def issues_to_pdf_write_cells(pdf, col_values, col_widths,
481 481 row_height, head=false)
482 482 base_y = pdf.GetY
483 483 max_height = row_height
484 484 col_values.each_with_index do |column, i|
485 485 col_x = pdf.GetX
486 486 if head == true
487 487 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
488 488 else
489 489 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
490 490 end
491 491 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
492 492 pdf.SetXY(col_x + col_widths[i], base_y);
493 493 end
494 494 return max_height
495 495 end
496 496
497 497 # Draw lines to close the row (MultiCell border drawing in not uniform)
498 498 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
499 499 id_width, col_widths)
500 500 col_x = top_x + id_width
501 501 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
502 502 col_widths.each do |width|
503 503 col_x += width
504 504 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
505 505 end
506 506 pdf.Line(top_x, top_y, top_x, lower_y) # left border
507 507 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
508 508 end
509 509
510 510 # Returns a PDF string of a single issue
511 511 def issue_to_pdf(issue, assoc={})
512 512 pdf = ITCPDF.new(current_language)
513 513 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
514 514 pdf.alias_nb_pages
515 515 pdf.footer_date = format_date(Date.today)
516 516 pdf.AddPage
517 517 pdf.SetFontStyle('B',11)
518 518 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
519 519 pdf.RDMMultiCell(190, 5, buf)
520 520 pdf.SetFontStyle('',8)
521 521 base_x = pdf.GetX
522 522 i = 1
523 523 issue.ancestors.visible.each do |ancestor|
524 524 pdf.SetX(base_x + i)
525 525 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
526 526 pdf.RDMMultiCell(190 - i, 5, buf)
527 527 i += 1 if i < 35
528 528 end
529 529 pdf.SetFontStyle('B',11)
530 530 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
531 531 pdf.SetFontStyle('',8)
532 532 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
533 533 pdf.Ln
534 534
535 535 left = []
536 536 left << [l(:field_status), issue.status]
537 537 left << [l(:field_priority), issue.priority]
538 538 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
539 539 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
540 540 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
541 541
542 542 right = []
543 543 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
544 544 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
545 545 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
546 546 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
547 547 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
548 548
549 549 rows = left.size > right.size ? left.size : right.size
550 550 while left.size < rows
551 551 left << nil
552 552 end
553 553 while right.size < rows
554 554 right << nil
555 555 end
556 556
557 557 half = (issue.custom_field_values.size / 2.0).ceil
558 558 issue.custom_field_values.each_with_index do |custom_value, i|
559 559 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
560 560 end
561 561
562 562 rows = left.size > right.size ? left.size : right.size
563 563 rows.times do |i|
564 564 item = left[i]
565 565 pdf.SetFontStyle('B',9)
566 566 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
567 567 pdf.SetFontStyle('',9)
568 568 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
569 569
570 570 item = right[i]
571 571 pdf.SetFontStyle('B',9)
572 572 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
573 573 pdf.SetFontStyle('',9)
574 574 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
575 575 pdf.Ln
576 576 end
577 577
578 578 pdf.SetFontStyle('B',9)
579 579 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
580 580 pdf.SetFontStyle('',9)
581 581
582 582 # Set resize image scale
583 583 pdf.SetImageScale(1.6)
584 584 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
585 585 issue.description.to_s, issue.attachments, "LRB")
586 586
587 587 unless issue.leaf?
588 588 # for CJK
589 589 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
590 590
591 591 pdf.SetFontStyle('B',9)
592 592 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
593 593 pdf.Ln
594 594 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
595 595 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
596 596 :length => truncate_length)
597 597 level = 10 if level >= 10
598 598 pdf.SetFontStyle('',8)
599 599 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
600 600 pdf.SetFontStyle('B',8)
601 601 pdf.RDMCell(20,5, child.status.to_s, "R")
602 602 pdf.Ln
603 603 end
604 604 end
605 605
606 606 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
607 607 unless relations.empty?
608 608 # for CJK
609 609 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
610 610
611 611 pdf.SetFontStyle('B',9)
612 612 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
613 613 pdf.Ln
614 614 relations.each do |relation|
615 615 buf = ""
616 616 buf += "#{l(relation.label_for(issue))} "
617 617 if relation.delay && relation.delay != 0
618 618 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
619 619 end
620 620 if Setting.cross_project_issue_relations?
621 621 buf += "#{relation.other_issue(issue).project} - "
622 622 end
623 623 buf += "#{relation.other_issue(issue).tracker}" +
624 624 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
625 625 buf = truncate(buf, :length => truncate_length)
626 626 pdf.SetFontStyle('', 8)
627 627 pdf.RDMCell(35+155-60, 5, buf, "L")
628 628 pdf.SetFontStyle('B',8)
629 629 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
630 630 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
631 631 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
632 632 pdf.Ln
633 633 end
634 634 end
635 635 pdf.RDMCell(190,5, "", "T")
636 636 pdf.Ln
637 637
638 638 if issue.changesets.any? &&
639 639 User.current.allowed_to?(:view_changesets, issue.project)
640 640 pdf.SetFontStyle('B',9)
641 641 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
642 642 pdf.Ln
643 643 for changeset in issue.changesets
644 644 pdf.SetFontStyle('B',8)
645 645 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
646 646 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
647 647 pdf.RDMCell(190, 5, csstr)
648 648 pdf.Ln
649 649 unless changeset.comments.blank?
650 650 pdf.SetFontStyle('',8)
651 651 pdf.RDMwriteHTMLCell(190,5,0,0,
652 652 changeset.comments.to_s, issue.attachments, "")
653 653 end
654 654 pdf.Ln
655 655 end
656 656 end
657 657
658 658 if assoc[:journals].present?
659 659 pdf.SetFontStyle('B',9)
660 660 pdf.RDMCell(190,5, l(:label_history), "B")
661 661 pdf.Ln
662 662 assoc[:journals].each do |journal|
663 663 pdf.SetFontStyle('B',8)
664 664 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
665 665 title << " (#{l(:field_private_notes)})" if journal.private_notes?
666 666 pdf.RDMCell(190,5, title)
667 667 pdf.Ln
668 668 pdf.SetFontStyle('I',8)
669 669 details_to_strings(journal.details, true).each do |string|
670 670 pdf.RDMMultiCell(190,5, "- " + string)
671 671 end
672 672 if journal.notes?
673 673 pdf.Ln unless journal.details.empty?
674 674 pdf.SetFontStyle('',8)
675 675 pdf.RDMwriteHTMLCell(190,5,0,0,
676 676 journal.notes.to_s, issue.attachments, "")
677 677 end
678 678 pdf.Ln
679 679 end
680 680 end
681 681
682 682 if issue.attachments.any?
683 683 pdf.SetFontStyle('B',9)
684 684 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
685 685 pdf.Ln
686 686 for attachment in issue.attachments
687 687 pdf.SetFontStyle('',8)
688 688 pdf.RDMCell(80,5, attachment.filename)
689 689 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
690 690 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
691 691 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
692 692 pdf.Ln
693 693 end
694 694 end
695 695 pdf.Output
696 696 end
697 697
698 698 # Returns a PDF string of a set of wiki pages
699 699 def wiki_pages_to_pdf(pages, project)
700 700 pdf = ITCPDF.new(current_language)
701 701 pdf.SetTitle(project.name)
702 702 pdf.alias_nb_pages
703 703 pdf.footer_date = format_date(Date.today)
704 704 pdf.AddPage
705 705 pdf.SetFontStyle('B',11)
706 706 pdf.RDMMultiCell(190,5, project.name)
707 707 pdf.Ln
708 708 # Set resize image scale
709 709 pdf.SetImageScale(1.6)
710 710 pdf.SetFontStyle('',9)
711 711 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
712 712 pdf.Output
713 713 end
714 714
715 715 # Returns a PDF string of a single wiki page
716 716 def wiki_page_to_pdf(page, project)
717 717 pdf = ITCPDF.new(current_language)
718 718 pdf.SetTitle("#{project} - #{page.title}")
719 719 pdf.alias_nb_pages
720 720 pdf.footer_date = format_date(Date.today)
721 721 pdf.AddPage
722 722 pdf.SetFontStyle('B',11)
723 723 pdf.RDMMultiCell(190,5,
724 724 "#{project} - #{page.title} - # #{page.content.version}")
725 725 pdf.Ln
726 726 # Set resize image scale
727 727 pdf.SetImageScale(1.6)
728 728 pdf.SetFontStyle('',9)
729 729 write_wiki_page(pdf, page)
730 730 pdf.Output
731 731 end
732 732
733 733 def write_page_hierarchy(pdf, pages, node=nil, level=0)
734 734 if pages[node]
735 735 pages[node].each do |page|
736 736 if @new_page
737 737 pdf.AddPage
738 738 else
739 739 @new_page = true
740 740 end
741 741 pdf.Bookmark page.title, level
742 742 write_wiki_page(pdf, page)
743 743 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
744 744 end
745 745 end
746 746 end
747 747
748 748 def write_wiki_page(pdf, page)
749 749 pdf.RDMwriteHTMLCell(190,5,0,0,
750 750 page.content.text.to_s, page.attachments, 0)
751 751 if page.attachments.any?
752 752 pdf.Ln
753 753 pdf.SetFontStyle('B',9)
754 754 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
755 755 pdf.Ln
756 756 for attachment in page.attachments
757 757 pdf.SetFontStyle('',8)
758 758 pdf.RDMCell(80,5, attachment.filename)
759 759 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
760 760 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
761 761 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
762 762 pdf.Ln
763 763 end
764 764 end
765 765 end
766 766
767 767 class RDMPdfEncoding
768 768 def self.rdm_from_utf8(txt, encoding)
769 769 txt ||= ''
770 770 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
771 771 if txt.respond_to?(:force_encoding)
772 772 txt.force_encoding('ASCII-8BIT')
773 773 end
774 774 txt
775 775 end
776 776
777 777 def self.attach(attachments, filename, encoding)
778 778 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
779 779 atta = nil
780 780 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
781 781 atta = Attachment.latest_attach(attachments, filename_utf8)
782 782 end
783 783 if atta && atta.readable? && atta.visible?
784 784 return atta
785 785 else
786 786 return nil
787 787 end
788 788 end
789 789 end
790 790 end
791 791 end
792 792 end
General Comments 0
You need to be logged in to leave comments. Login now