##// END OF EJS Templates
Rails4: replace ActionView::Helpers::TextHelper#truncate by String#truncate at lib/redmine/export/pdf.rb...
Toshi MARUYAMA -
r12557:fd6fea57136b
parent child
Show More
@@ -1,809 +1,807
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2014 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.visible_custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
260 260 show_value(cv, false)
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, :+)
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, :+)
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, :+)
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 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, :+)
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, :+)
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 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, row_height, head=false)
497 497 base_y = pdf.GetY
498 498 max_height = row_height
499 499 col_values.each_with_index do |column, i|
500 500 col_x = pdf.GetX
501 501 if head == true
502 502 pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
503 503 else
504 504 pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
505 505 end
506 506 max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
507 507 pdf.SetXY(col_x + col_widths[i], base_y);
508 508 end
509 509 return max_height
510 510 end
511 511
512 512 # Draw lines to close the row (MultiCell border drawing in not uniform)
513 513 #
514 514 # parameter "col_id_width" is not used. it is kept for compatibility.
515 515 def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
516 516 col_id_width, col_widths)
517 517 col_x = top_x
518 518 pdf.Line(col_x, top_y, col_x, lower_y) # id right border
519 519 col_widths.each do |width|
520 520 col_x += width
521 521 pdf.Line(col_x, top_y, col_x, lower_y) # columns right border
522 522 end
523 523 pdf.Line(top_x, top_y, top_x, lower_y) # left border
524 524 pdf.Line(top_x, lower_y, col_x, lower_y) # bottom border
525 525 end
526 526
527 527 # Returns a PDF string of a single issue
528 528 def issue_to_pdf(issue, assoc={})
529 529 pdf = ITCPDF.new(current_language)
530 530 pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
531 531 pdf.alias_nb_pages
532 532 pdf.footer_date = format_date(Date.today)
533 533 pdf.AddPage
534 534 pdf.SetFontStyle('B',11)
535 535 buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
536 536 pdf.RDMMultiCell(190, 5, buf)
537 537 pdf.SetFontStyle('',8)
538 538 base_x = pdf.GetX
539 539 i = 1
540 540 issue.ancestors.visible.each do |ancestor|
541 541 pdf.SetX(base_x + i)
542 542 buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
543 543 pdf.RDMMultiCell(190 - i, 5, buf)
544 544 i += 1 if i < 35
545 545 end
546 546 pdf.SetFontStyle('B',11)
547 547 pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
548 548 pdf.SetFontStyle('',8)
549 549 pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
550 550 pdf.Ln
551 551
552 552 left = []
553 553 left << [l(:field_status), issue.status]
554 554 left << [l(:field_priority), issue.priority]
555 555 left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
556 556 left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
557 557 left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
558 558
559 559 right = []
560 560 right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
561 561 right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
562 562 right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
563 563 right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
564 564 right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
565 565
566 566 rows = left.size > right.size ? left.size : right.size
567 567 while left.size < rows
568 568 left << nil
569 569 end
570 570 while right.size < rows
571 571 right << nil
572 572 end
573 573
574 574 half = (issue.visible_custom_field_values.size / 2.0).ceil
575 575 issue.visible_custom_field_values.each_with_index do |custom_value, i|
576 576 (i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value, false)]
577 577 end
578 578
579 579 rows = left.size > right.size ? left.size : right.size
580 580 rows.times do |i|
581 581 item = left[i]
582 582 pdf.SetFontStyle('B',9)
583 583 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
584 584 pdf.SetFontStyle('',9)
585 585 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
586 586
587 587 item = right[i]
588 588 pdf.SetFontStyle('B',9)
589 589 pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
590 590 pdf.SetFontStyle('',9)
591 591 pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
592 592 pdf.Ln
593 593 end
594 594
595 595 pdf.SetFontStyle('B',9)
596 596 pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
597 597 pdf.SetFontStyle('',9)
598 598
599 599 # Set resize image scale
600 600 pdf.SetImageScale(1.6)
601 601 pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
602 602 issue.description.to_s, issue.attachments, "LRB")
603 603
604 604 unless issue.leaf?
605 605 # for CJK
606 606 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
607
608 607 pdf.SetFontStyle('B',9)
609 608 pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
610 609 pdf.Ln
611 610 issue_list(issue.descendants.visible.sort_by(&:lft)) do |child, level|
612 buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
613 :length => truncate_length)
611 buf = "#{child.tracker} # #{child.id}: #{child.subject}".
612 truncate(truncate_length)
614 613 level = 10 if level >= 10
615 614 pdf.SetFontStyle('',8)
616 615 pdf.RDMCell(35+135,5, (level >=1 ? " " * level : "") + buf, "L")
617 616 pdf.SetFontStyle('B',8)
618 617 pdf.RDMCell(20,5, child.status.to_s, "R")
619 618 pdf.Ln
620 619 end
621 620 end
622 621
623 622 relations = issue.relations.select { |r| r.other_issue(issue).visible? }
624 623 unless relations.empty?
625 624 # for CJK
626 625 truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
627
628 626 pdf.SetFontStyle('B',9)
629 627 pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
630 628 pdf.Ln
631 629 relations.each do |relation|
632 630 buf = ""
633 631 buf += "#{l(relation.label_for(issue))} "
634 632 if relation.delay && relation.delay != 0
635 633 buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
636 634 end
637 635 if Setting.cross_project_issue_relations?
638 636 buf += "#{relation.other_issue(issue).project} - "
639 637 end
640 638 buf += "#{relation.other_issue(issue).tracker}" +
641 639 " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
642 buf = truncate(buf, :length => truncate_length)
640 buf = buf.truncate(truncate_length)
643 641 pdf.SetFontStyle('', 8)
644 642 pdf.RDMCell(35+155-60, 5, buf, "L")
645 643 pdf.SetFontStyle('B',8)
646 644 pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
647 645 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
648 646 pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
649 647 pdf.Ln
650 648 end
651 649 end
652 650 pdf.RDMCell(190,5, "", "T")
653 651 pdf.Ln
654 652
655 653 if issue.changesets.any? &&
656 654 User.current.allowed_to?(:view_changesets, issue.project)
657 655 pdf.SetFontStyle('B',9)
658 656 pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
659 657 pdf.Ln
660 658 for changeset in issue.changesets
661 659 pdf.SetFontStyle('B',8)
662 660 csstr = "#{l(:label_revision)} #{changeset.format_identifier} - "
663 661 csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
664 662 pdf.RDMCell(190, 5, csstr)
665 663 pdf.Ln
666 664 unless changeset.comments.blank?
667 665 pdf.SetFontStyle('',8)
668 666 pdf.RDMwriteHTMLCell(190,5,0,0,
669 667 changeset.comments.to_s, issue.attachments, "")
670 668 end
671 669 pdf.Ln
672 670 end
673 671 end
674 672
675 673 if assoc[:journals].present?
676 674 pdf.SetFontStyle('B',9)
677 675 pdf.RDMCell(190,5, l(:label_history), "B")
678 676 pdf.Ln
679 677 assoc[:journals].each do |journal|
680 678 pdf.SetFontStyle('B',8)
681 679 title = "##{journal.indice} - #{format_time(journal.created_on)} - #{journal.user}"
682 680 title << " (#{l(:field_private_notes)})" if journal.private_notes?
683 681 pdf.RDMCell(190,5, title)
684 682 pdf.Ln
685 683 pdf.SetFontStyle('I',8)
686 684 details_to_strings(journal.visible_details, true).each do |string|
687 685 pdf.RDMMultiCell(190,5, "- " + string)
688 686 end
689 687 if journal.notes?
690 688 pdf.Ln unless journal.details.empty?
691 689 pdf.SetFontStyle('',8)
692 690 pdf.RDMwriteHTMLCell(190,5,0,0,
693 691 journal.notes.to_s, issue.attachments, "")
694 692 end
695 693 pdf.Ln
696 694 end
697 695 end
698 696
699 697 if issue.attachments.any?
700 698 pdf.SetFontStyle('B',9)
701 699 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
702 700 pdf.Ln
703 701 for attachment in issue.attachments
704 702 pdf.SetFontStyle('',8)
705 703 pdf.RDMCell(80,5, attachment.filename)
706 704 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
707 705 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
708 706 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
709 707 pdf.Ln
710 708 end
711 709 end
712 710 pdf.Output
713 711 end
714 712
715 713 # Returns a PDF string of a set of wiki pages
716 714 def wiki_pages_to_pdf(pages, project)
717 715 pdf = ITCPDF.new(current_language)
718 716 pdf.SetTitle(project.name)
719 717 pdf.alias_nb_pages
720 718 pdf.footer_date = format_date(Date.today)
721 719 pdf.AddPage
722 720 pdf.SetFontStyle('B',11)
723 721 pdf.RDMMultiCell(190,5, project.name)
724 722 pdf.Ln
725 723 # Set resize image scale
726 724 pdf.SetImageScale(1.6)
727 725 pdf.SetFontStyle('',9)
728 726 write_page_hierarchy(pdf, pages.group_by(&:parent_id))
729 727 pdf.Output
730 728 end
731 729
732 730 # Returns a PDF string of a single wiki page
733 731 def wiki_page_to_pdf(page, project)
734 732 pdf = ITCPDF.new(current_language)
735 733 pdf.SetTitle("#{project} - #{page.title}")
736 734 pdf.alias_nb_pages
737 735 pdf.footer_date = format_date(Date.today)
738 736 pdf.AddPage
739 737 pdf.SetFontStyle('B',11)
740 738 pdf.RDMMultiCell(190,5,
741 739 "#{project} - #{page.title} - # #{page.content.version}")
742 740 pdf.Ln
743 741 # Set resize image scale
744 742 pdf.SetImageScale(1.6)
745 743 pdf.SetFontStyle('',9)
746 744 write_wiki_page(pdf, page)
747 745 pdf.Output
748 746 end
749 747
750 748 def write_page_hierarchy(pdf, pages, node=nil, level=0)
751 749 if pages[node]
752 750 pages[node].each do |page|
753 751 if @new_page
754 752 pdf.AddPage
755 753 else
756 754 @new_page = true
757 755 end
758 756 pdf.Bookmark page.title, level
759 757 write_wiki_page(pdf, page)
760 758 write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
761 759 end
762 760 end
763 761 end
764 762
765 763 def write_wiki_page(pdf, page)
766 764 pdf.RDMwriteHTMLCell(190,5,0,0,
767 765 page.content.text.to_s, page.attachments, 0)
768 766 if page.attachments.any?
769 767 pdf.Ln
770 768 pdf.SetFontStyle('B',9)
771 769 pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
772 770 pdf.Ln
773 771 for attachment in page.attachments
774 772 pdf.SetFontStyle('',8)
775 773 pdf.RDMCell(80,5, attachment.filename)
776 774 pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
777 775 pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
778 776 pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
779 777 pdf.Ln
780 778 end
781 779 end
782 780 end
783 781
784 782 class RDMPdfEncoding
785 783 def self.rdm_from_utf8(txt, encoding)
786 784 txt ||= ''
787 785 txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
788 786 if txt.respond_to?(:force_encoding)
789 787 txt.force_encoding('ASCII-8BIT')
790 788 end
791 789 txt
792 790 end
793 791
794 792 def self.attach(attachments, filename, encoding)
795 793 filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
796 794 atta = nil
797 795 if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
798 796 atta = Attachment.latest_attach(attachments, filename_utf8)
799 797 end
800 798 if atta && atta.readable? && atta.visible?
801 799 return atta
802 800 else
803 801 return nil
804 802 end
805 803 end
806 804 end
807 805 end
808 806 end
809 807 end
General Comments 0
You need to be logged in to leave comments. Login now