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