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