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