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