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