Project

General

Profile

Patch #10869 » pdf-patch-fix.rb

added fix to fullpdf patch - Peter sørensen, 2012-07-09 13:27

 
1
# encoding: utf-8
2
#
3
# Redmine - project management software
4
# Copyright (C) 2006-2012  Jean-Philippe Lang
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19

    
20
require 'iconv'
21
require 'fpdf/chinese'
22
require 'fpdf/japanese'
23
require 'fpdf/korean'
24
require 'core/rmagick'
25

    
26
module Redmine
27
  module Export
28
    module PDF
29
      include ActionView::Helpers::TextHelper
30
      include ActionView::Helpers::NumberHelper
31
      include IssuesHelper
32

    
33
      class ITCPDF < TCPDF
34
        include Redmine::I18n
35
        attr_accessor :footer_date
36

    
37
        def initialize(lang)
38
          @@k_path_cache = Rails.root.join('tmp', 'pdf')
39
          FileUtils.mkdir_p @@k_path_cache unless File::exist?(@@k_path_cache)
40
          set_language_if_valid lang
41
          pdf_encoding = l(:general_pdf_encoding).upcase
42
          super('P', 'mm', 'A4', (pdf_encoding == 'UTF-8'), pdf_encoding)
43
          case current_language.to_s.downcase
44
          when 'vi'
45
            @font_for_content = 'DejaVuSans'
46
            @font_for_footer  = 'DejaVuSans'
47
          else
48
            case pdf_encoding
49
            when 'UTF-8'
50
              @font_for_content = 'FreeSans'
51
              @font_for_footer  = 'FreeSans'
52
            when 'CP949'
53
              extend(PDF_Korean)
54
              AddUHCFont()
55
              @font_for_content = 'UHC'
56
              @font_for_footer  = 'UHC'
57
            when 'CP932', 'SJIS', 'SHIFT_JIS'
58
              extend(PDF_Japanese)
59
              AddSJISFont()
60
              @font_for_content = 'SJIS'
61
              @font_for_footer  = 'SJIS'
62
            when 'GB18030'
63
              extend(PDF_Chinese)
64
              AddGBFont()
65
              @font_for_content = 'GB'
66
              @font_for_footer  = 'GB'
67
            when 'BIG5'
68
              extend(PDF_Chinese)
69
              AddBig5Font()
70
              @font_for_content = 'Big5'
71
              @font_for_footer  = 'Big5'
72
            else
73
              @font_for_content = 'Arial'
74
              @font_for_footer  = 'Helvetica'
75
            end
76
          end
77
          SetCreator(Redmine::Info.app_name)
78
          SetFont(@font_for_content)
79
          @outlines = []
80
          @outlineRoot = nil
81
        end
82

    
83
        def SetFontStyle(style, size)
84
          SetFont(@font_for_content, style, size)
85
        end
86

    
87
        def SetTitle(txt)
88
          txt = begin
89
            utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
90
            hextxt = "<FEFF"  # FEFF is BOM
91
            hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
92
            hextxt << ">"
93
          rescue
94
            txt
95
          end || ''
96
          super(txt)
97
        end
98

    
99
        def textstring(s)
100
          # Format a text string
101
          if s =~ /^</  # This means the string is hex-dumped.
102
            return s
103
          else
104
            return '('+escape(s)+')'
105
          end
106
        end
107

    
108
        def fix_text_encoding(txt)
109
          RDMPdfEncoding::rdm_from_utf8(txt, l(:general_pdf_encoding))
110
        end
111

    
112
        def RDMCell(w ,h=0, txt='', border=0, ln=0, align='', fill=0, link='')
113
          Cell(w, h, fix_text_encoding(txt), border, ln, align, fill, link)
114
        end
115

    
116
        def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
117
          MultiCell(w, h, fix_text_encoding(txt), border, align, fill, ln)
118
        end
119

    
120
        def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
121
          @attachments = attachments
122
          writeHTMLCell(w, h, x, y,
123
            fix_text_encoding(
124
              Redmine::WikiFormatting.to_html(Setting.text_formatting, txt)),
125
            border, ln, fill)
126
        end
127

    
128
        def getImageFilename(attrname)
129
          # attrname: general_pdf_encoding string file/uri name
130
          atta = RDMPdfEncoding.attach(@attachments, attrname, l(:general_pdf_encoding))
131
          if atta
132
            return atta.diskfile
133
          else
134
            return nil
135
          end
136
        end
137

    
138
        def Footer
139
          SetFont(@font_for_footer, 'I', 8)
140
          SetY(-15)
141
          SetX(15)
142
          RDMCell(0, 5, @footer_date, 0, 0, 'L')
143
          SetY(-15)
144
          SetX(-30)
145
          RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
146
        end
147

    
148
        def Bookmark(txt, level=0, y=0)
149
          if (y == -1)
150
            y = GetY()
151
          end
152
          @outlines << {:t => txt, :l => level, :p => PageNo(), :y => (@h - y)*@k}
153
        end
154

    
155
        def bookmark_title(txt)
156
          txt = begin
157
            utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
158
            hextxt = "<FEFF"  # FEFF is BOM
159
            hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
160
            hextxt << ">"
161
          rescue
162
            txt
163
          end || ''
164
        end
165

    
166
        def putbookmarks
167
          nb=@outlines.size
168
          return if (nb==0)
169
          lru=[]
170
          level=0
171
          @outlines.each_with_index do |o, i|
172
            if(o[:l]>0)
173
              parent=lru[o[:l]-1]
174
              #Set parent and last pointers
175
              @outlines[i][:parent]=parent
176
              @outlines[parent][:last]=i
177
              if (o[:l]>level)
178
                #Level increasing: set first pointer
179
                @outlines[parent][:first]=i
180
              end
181
            else
182
              @outlines[i][:parent]=nb
183
            end
184
            if (o[:l]<=level && i>0)
185
              #Set prev and next pointers
186
              prev=lru[o[:l]]
187
              @outlines[prev][:next]=i
188
              @outlines[i][:prev]=prev
189
            end
190
            lru[o[:l]]=i
191
            level=o[:l]
192
          end
193
          #Outline items
194
          n=self.n+1
195
          @outlines.each_with_index do |o, i|
196
            newobj()
197
            out('<</Title '+bookmark_title(o[:t]))
198
            out("/Parent #{n+o[:parent]} 0 R")
199
            if (o[:prev])
200
              out("/Prev #{n+o[:prev]} 0 R")
201
            end
202
            if (o[:next])
203
              out("/Next #{n+o[:next]} 0 R")
204
            end
205
            if (o[:first])
206
              out("/First #{n+o[:first]} 0 R")
207
            end
208
            if (o[:last])
209
              out("/Last #{n+o[:last]} 0 R")
210
            end
211
            out("/Dest [%d 0 R /XYZ 0 %.2f null]" % [1+2*o[:p], o[:y]])
212
            out('/Count 0>>')
213
            out('endobj')
214
          end
215
          #Outline root
216
          newobj()
217
          @outlineRoot=self.n
218
          out("<</Type /Outlines /First #{n} 0 R");
219
          out("/Last #{n+lru[0]} 0 R>>");
220
          out('endobj');
221
        end
222

    
223
        def putresources()
224
          super
225
          putbookmarks()
226
        end
227

    
228
        def putcatalog()
229
          super
230
          if(@outlines.size > 0)
231
            out("/Outlines #{@outlineRoot} 0 R");
232
            out('/PageMode /UseOutlines');
233
          end
234
        end
235
      end
236

    
237
      # fetch row values
238
      def fetch_row_values(issue, query, level)
239
        query.columns.collect do |column|
240
          s = if column.is_a?(QueryCustomFieldColumn)
241
            cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
242
            show_value(cv)
243
          else
244
            value = issue.send(column.name)
245
            if column.name == :subject
246
              value = "  " * level + value
247
            end
248
            if value.is_a?(Date)
249
              format_date(value)
250
            elsif value.is_a?(Time)
251
              format_time(value)
252
            else
253
              value
254
            end
255
          end
256
          s.to_s
257
        end
258
      end
259

    
260
      # calculate columns width
261
      def calc_col_width(issues, query, table_width, pdf)
262
        # calculate statistics
263
        #  by captions
264
        pdf.SetFontStyle('B',8)
265
        col_padding = pdf.GetStringWidth('OO')
266
        col_width_min = query.columns.map {|v| pdf.GetStringWidth(v.caption) + col_padding}
267
        col_width_max = Array.new(col_width_min)
268
        col_width_avg = Array.new(col_width_min)
269
        word_width_max = query.columns.map {|c|
270
          n = 10
271
          c.caption.split.each {|w|
272
            x = pdf.GetStringWidth(w) + col_padding
273
            n = x if n < x
274
          }
275
          n
276
        }
277

    
278
        #  by properties of issues
279
        pdf.SetFontStyle('',8)
280
        col_padding = pdf.GetStringWidth('OO')
281
        k = 1
282
        issue_list(issues) {|issue, level|
283
          k += 1
284
          values = fetch_row_values(issue, query, level)
285
          values.each_with_index {|v,i|
286
            n = pdf.GetStringWidth(v) + col_padding
287
            col_width_max[i] = n if col_width_max[i] < n
288
            col_width_min[i] = n if col_width_min[i] > n
289
            col_width_avg[i] += n
290
            v.split.each {|w|
291
              x = pdf.GetStringWidth(w) + col_padding
292
              word_width_max[i] = x if word_width_max[i] < x
293
            }
294
          }
295
        }
296
        col_width_avg.map! {|x| x / k}
297

    
298
        # calculate columns width
299
        ratio = table_width / col_width_avg.inject(0) {|s,w| s += w}
300
        col_width = col_width_avg.map {|w| w * ratio}
301

    
302
        # correct max word width if too many columns
303
        ratio = table_width / word_width_max.inject(0) {|s,w| s += w}
304
        word_width_max.map! {|v| v * ratio} if ratio < 1
305

    
306
        # correct and lock width of some columns
307
        done = 1
308
        col_fix = []
309
        col_width.each_with_index do |w,i|
310
          if w > col_width_max[i]
311
            col_width[i] = col_width_max[i]
312
            col_fix[i] = 1
313
            done = 0
314
          elsif w < word_width_max[i]
315
            col_width[i] = word_width_max[i]
316
            col_fix[i] = 1
317
            done = 0
318
          else
319
            col_fix[i] = 0
320
          end
321
        end
322

    
323
        # iterate while need to correct and lock coluns width
324
        while done == 0
325
          # calculate free & locked columns width
326
          done = 1
327
          fix_col_width = 0
328
          free_col_width = 0
329
          col_width.each_with_index do |w,i|
330
            if col_fix[i] == 1
331
              fix_col_width += w
332
            else
333
              free_col_width += w
334
            end
335
          end
336

    
337
          # calculate column normalizing ratio
338
          if free_col_width == 0
339
            ratio = table_width / col_width.inject(0) {|s,w| s += w}
340
          else
341
            ratio = (table_width - fix_col_width) / free_col_width
342
          end
343

    
344
          # correct columns width
345
          col_width.each_with_index do |w,i|
346
            if col_fix[i] == 0
347
              col_width[i] = w * ratio
348

    
349
              # check if column width less then max word width
350
              if col_width[i] < word_width_max[i]
351
                col_width[i] = word_width_max[i]
352
                col_fix[i] = 1
353
                done = 0
354
              elsif col_width[i] > col_width_max[i]
355
                col_width[i] = col_width_max[i]
356
                col_fix[i] = 1
357
                done = 0
358
              end
359
            end
360
          end
361
        end
362
        col_width
363
      end
364

    
365
      def render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
366
        # headers
367
        pdf.SetFontStyle('B',8)
368
        pdf.SetFillColor(230, 230, 230)
369

    
370
        # render it background to find the max height used
371
        base_x = pdf.GetX
372
        base_y = pdf.GetY
373
        max_height = issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
374
        pdf.Rect(base_x, base_y, table_width + col_id_width, max_height, 'FD');
375
        pdf.SetXY(base_x, base_y);
376

    
377
        # write the cells on page
378
        pdf.RDMCell(col_id_width, row_height, "#", "T", 0, 'C', 1)
379
        issues_to_pdf_write_cells(pdf, query.columns, col_width, row_height, true)
380
        issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
381
        pdf.SetY(base_y + max_height);
382

    
383
        # rows
384
        pdf.SetFontStyle('',8)
385
        pdf.SetFillColor(255, 255, 255)
386
      end
387

    
388
      def issues_to_pdf_dispatch(issues, project, query)
389
        # export_type is filled by view template thanks to spec_format_link_to
390
        (params['outputType'] == 'full') ? issues_to_pdf_full(issues, project, query) : issues_to_pdf(issues, project, query)
391
      end
392

    
393
      def issues_to_pdf_full(issues, project, query)
394
        pdf = ITCPDF.new(current_language)
395
        ## pdf = IFPDF.new(current_language)
396

    
397
        title = query.new_record? ? l(:label_issue_plural) : query.name
398
        title = "#{project} - #{title}" if project
399

    
400
	    breakMargin = pdf.GetBreakMargin
401
        issues_to_pdf_header(pdf, title, issues, project, query)
402
        issues_to_pdf_content(pdf, title, issues, project, query)
403
        pdf.SetAutoPageBreak(true, breakMargin)
404
        issues.each do |issue|
405
          issue_to_pdf_content(pdf, issue)
406
        end
407
        pdf.Output
408
     end
409

    
410

    
411
      # Returns a PDF string of a list of issues
412
      def issues_to_pdf(issues, project, query)
413
        pdf = ITCPDF.new(current_language)
414

    
415
        title = query.new_record? ? l(:label_issue_plural) : query.name
416
        title = "#{project} - #{title}" if project
417
        issues_to_pdf_header(pdf, title, issues, project, query)
418
        issues_to_pdf_content(pdf, title, issues, project, query)
419
        pdf.Output
420
      end
421

    
422
      def issues_to_pdf_header(pdf, title, issues, project, query)
423
        pdf.SetTitle(title)
424
        pdf.alias_nb_pages
425
        pdf.footer_date = format_date(Date.today)
426
        pdf.SetAutoPageBreak(false)
427
      end
428

    
429

    
430
      def issues_to_pdf_content(pdf, title, issues, project, query)
431
        pdf.AddPage("L")
432

    
433
        # Landscape A4 = 210 x 297 mm
434
        page_height   = 210
435
        page_width    = 297
436
        right_margin  = 10
437
        bottom_margin = 20
438
        col_id_width  = 10
439
        row_height    = 4
440

    
441
        # column widths
442
        table_width = page_width - right_margin - 10  # fixed left margin
443
        col_width = []
444
        unless query.columns.empty?
445
          col_width = calc_col_width(issues, query, table_width - col_id_width, pdf)
446
          table_width = col_width.inject(0) {|s,v| s += v}
447
        end
448

    
449
        # title
450
        pdf.SetFontStyle('B',11)
451
        pdf.RDMCell(190,10, title)
452
        pdf.Ln
453
        render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
454
        previous_group = false
455
        issue_list(issues) do |issue, level|
456
          if query.grouped? &&
457
               (group = query.group_by_column.value(issue)) != previous_group
458
            pdf.SetFontStyle('B',10)
459
            group_label = group.blank? ? 'None' : group.to_s
460
            group_label << " (#{query.issue_count_by_group[group]})"
461
            pdf.Bookmark group_label, 0, -1
462
            pdf.RDMCell(table_width + col_id_width, row_height * 2, group_label, 1, 1, 'L')
463
            pdf.SetFontStyle('',8)
464
            previous_group = group
465
          end
466

    
467
          # fetch row values
468
          col_values = fetch_row_values(issue, query, level)
469

    
470
          # render it off-page to find the max height used
471
          base_x = pdf.GetX
472
          base_y = pdf.GetY
473
          pdf.SetY(2 * page_height)
474
          max_height = issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
475
          pdf.SetXY(base_x, base_y)
476

    
477
          # make new page if it doesn't fit on the current one
478
          space_left = page_height - base_y - bottom_margin
479
          if max_height > space_left
480
            pdf.AddPage("L")
481
            render_table_header(pdf, query, col_width, row_height, col_id_width, table_width)
482
            base_x = pdf.GetX
483
            base_y = pdf.GetY
484
          end
485

    
486
          # write the cells on page
487
          pdf.RDMCell(col_id_width, row_height, issue.id.to_s, "T", 0, 'C', 1)
488
          issues_to_pdf_write_cells(pdf, col_values, col_width, row_height)
489
          issues_to_pdf_draw_borders(pdf, base_x, base_y, base_y + max_height, col_id_width, col_width)
490
          pdf.SetY(base_y + max_height);
491
        end
492

    
493
        if issues.size == Setting.issues_export_limit.to_i
494
          pdf.SetFontStyle('B',10)
495
          pdf.RDMCell(0, row_height, '...')
496
        end
497
      end
498

    
499
      # Renders MultiCells and returns the maximum height used
500
      def issues_to_pdf_write_cells(pdf, col_values, col_widths,
501
                                    row_height, head=false)
502
        base_y = pdf.GetY
503
        max_height = row_height
504
        col_values.each_with_index do |column, i|
505
          col_x = pdf.GetX
506
          if head == true
507
            pdf.RDMMultiCell(col_widths[i], row_height, column.caption, "T", 'L', 1)
508
          else
509
            pdf.RDMMultiCell(col_widths[i], row_height, column, "T", 'L', 1)
510
          end
511
          max_height = (pdf.GetY - base_y) if (pdf.GetY - base_y) > max_height
512
          pdf.SetXY(col_x + col_widths[i], base_y);
513
        end
514
        return max_height
515
      end
516

    
517
      # Draw lines to close the row (MultiCell border drawing in not uniform)
518
      def issues_to_pdf_draw_borders(pdf, top_x, top_y, lower_y,
519
                                     id_width, col_widths)
520
        col_x = top_x + id_width
521
        pdf.Line(col_x, top_y, col_x, lower_y)    # id right border
522
        col_widths.each do |width|
523
          col_x += width
524
          pdf.Line(col_x, top_y, col_x, lower_y)  # columns right border
525
        end
526
        pdf.Line(top_x, top_y, top_x, lower_y)    # left border
527
        pdf.Line(top_x, lower_y, col_x, lower_y)  # bottom border
528
      end
529

    
530
      # Returns a PDF string of a single issue
531
      def issue_to_pdf(issue)
532
        pdf = ITCPDF.new(current_language)
533

    
534
        issue_to_pdf_header(pdf, issue)
535
        issue_to_pdf_content(pdf, issue)
536
        pdf.Output
537
      end
538

    
539
      def issue_to_pdf_header(pdf, issue)
540
        pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
541
        pdf.alias_nb_pages
542
        pdf.footer_date = format_date(Date.today)
543
      end
544

    
545
      def issue_to_pdf_content(pdf,issue)
546
        pdf.AddPage
547
        pdf.SetFontStyle('B',11)
548
        buf = "#{issue.project} - #{issue.tracker} # #{issue.id}"
549
        pdf.RDMMultiCell(190, 5, buf)
550
        pdf.Ln
551
        pdf.SetFontStyle('',8)
552
        base_x = pdf.GetX
553
        i = 1
554
        issue.ancestors.each do |ancestor|
555
          pdf.SetX(base_x + i)
556
          buf = "#{ancestor.tracker} # #{ancestor.id} (#{ancestor.status.to_s}): #{ancestor.subject}"
557
          pdf.RDMMultiCell(190 - i, 5, buf)
558
          i += 1 if i < 35
559
        end
560
        pdf.Ln
561

    
562
        pdf.SetFontStyle('B',9)
563
        pdf.RDMCell(35,5, l(:field_status) + ":","LT")
564
        pdf.SetFontStyle('',9)
565
        pdf.RDMCell(60,5, issue.status.to_s,"RT")
566
        pdf.SetFontStyle('B',9)
567
        pdf.RDMCell(35,5, l(:field_priority) + ":","LT")
568
        pdf.SetFontStyle('',9)
569
        pdf.RDMCell(60,5, issue.priority.to_s,"RT")
570
        pdf.Ln
571

    
572
        pdf.SetFontStyle('B',9)
573
        pdf.RDMCell(35,5, l(:field_author) + ":","L")
574
        pdf.SetFontStyle('',9)
575
        pdf.RDMCell(60,5, issue.author.to_s,"R")
576
        pdf.SetFontStyle('B',9)
577
        pdf.RDMCell(35,5, l(:field_category) + ":","L")
578
        pdf.SetFontStyle('',9)
579
        pdf.RDMCell(60,5, issue.category.to_s,"R")
580
        pdf.Ln
581

    
582
        pdf.SetFontStyle('B',9)
583
        pdf.RDMCell(35,5, l(:field_created_on) + ":","L")
584
        pdf.SetFontStyle('',9)
585
        pdf.RDMCell(60,5, format_date(issue.created_on),"R")
586
        pdf.SetFontStyle('B',9)
587
        pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L")
588
        pdf.SetFontStyle('',9)
589
        pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
590
        pdf.Ln
591

    
592
        pdf.SetFontStyle('B',9)
593
        pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
594
        pdf.SetFontStyle('',9)
595
        pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
596
        pdf.SetFontStyle('B',9)
597
        pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
598
        pdf.SetFontStyle('',9)
599
        pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
600
        pdf.Ln
601

    
602
        for custom_value in issue.custom_field_values
603
          pdf.SetFontStyle('B',9)
604
          pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
605
          pdf.SetFontStyle('',9)
606
          pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
607
        end
608

    
609
        y0 = pdf.GetY
610

    
611
        pdf.SetFontStyle('B',9)
612
        pdf.RDMCell(35,5, l(:field_subject) + ":","LT")
613
        pdf.SetFontStyle('',9)
614
        pdf.RDMMultiCell(155,5, issue.subject,"RT")
615
        pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
616

    
617
        pdf.SetFontStyle('B',9)
618
        pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)
619
        pdf.SetFontStyle('',9)
620

    
621
        # Set resize image scale
622
        pdf.SetImageScale(1.6)
623
        pdf.RDMwriteHTMLCell(35+155, 5, 0, 0,
624
              issue.description.to_s, issue.attachments, "LRB")
625

    
626
        unless issue.leaf?
627
          # for CJK
628
          truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 90 : 65 )
629

    
630
          pdf.SetFontStyle('B',9)
631
          pdf.RDMCell(35+155,5, l(:label_subtask_plural) + ":", "LTR")
632
          pdf.Ln
633
          issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
634
            buf = truncate("#{child.tracker} # #{child.id}: #{child.subject}",
635
                           :length => truncate_length)
636
            level = 10 if level >= 10
637
            pdf.SetFontStyle('',8)
638
            pdf.RDMCell(35+135,5, (level >=1 ? "  " * level : "") + buf, "L")
639
            pdf.SetFontStyle('B',8)
640
            pdf.RDMCell(20,5, child.status.to_s, "R")
641
            pdf.Ln
642
          end
643
        end
644

    
645
        relations = issue.relations.select { |r| r.other_issue(issue).visible? }
646
        unless relations.empty?
647
          # for CJK
648
          truncate_length = ( l(:general_pdf_encoding).upcase == "UTF-8" ? 80 : 60 )
649

    
650
          pdf.SetFontStyle('B',9)
651
          pdf.RDMCell(35+155,5, l(:label_related_issues) + ":", "LTR")
652
          pdf.Ln
653
          relations.each do |relation|
654
            buf = ""
655
            buf += "#{l(relation.label_for(issue))} "
656
            if relation.delay && relation.delay != 0
657
              buf += "(#{l('datetime.distance_in_words.x_days', :count => relation.delay)}) "
658
            end
659
            if Setting.cross_project_issue_relations?
660
              buf += "#{relation.other_issue(issue).project} - "
661
            end
662
            buf += "#{relation.other_issue(issue).tracker}" +
663
                   " # #{relation.other_issue(issue).id}: #{relation.other_issue(issue).subject}"
664
            buf = truncate(buf, :length => truncate_length)
665
            pdf.SetFontStyle('', 8)
666
            pdf.RDMCell(35+155-60, 5, buf, "L")
667
            pdf.SetFontStyle('B',8)
668
            pdf.RDMCell(20,5, relation.other_issue(issue).status.to_s, "")
669
            pdf.RDMCell(20,5, format_date(relation.other_issue(issue).start_date), "")
670
            pdf.RDMCell(20,5, format_date(relation.other_issue(issue).due_date), "R")
671
            pdf.Ln
672
          end
673
        end
674
        pdf.RDMCell(190,5, "", "T")
675
        pdf.Ln
676

    
677
        if issue.changesets.any? &&
678
             User.current.allowed_to?(:view_changesets, issue.project)
679
          pdf.SetFontStyle('B',9)
680
          pdf.RDMCell(190,5, l(:label_associated_revisions), "B")
681
          pdf.Ln
682
          for changeset in issue.changesets
683
            pdf.SetFontStyle('B',8)
684
            csstr  = "#{l(:label_revision)} #{changeset.format_identifier} - "
685
            csstr += format_time(changeset.committed_on) + " - " + changeset.author.to_s
686
            pdf.RDMCell(190, 5, csstr)
687
            pdf.Ln
688
            unless changeset.comments.blank?
689
              pdf.SetFontStyle('',8)
690
              pdf.RDMwriteHTMLCell(190,5,0,0,
691
                    changeset.comments.to_s, issue.attachments, "")
692
            end
693
            pdf.Ln
694
          end
695
        end
696

    
697
        pdf.SetFontStyle('B',9)
698
        pdf.RDMCell(190,5, l(:label_history), "B")
699
        pdf.Ln
700
        indice = 0
701
        for journal in issue.journals.find(
702
                          :all, :include => [:user, :details],
703
                          :order => "#{Journal.table_name}.created_on ASC")
704
          indice = indice + 1
705
          pdf.SetFontStyle('B',8)
706
          pdf.RDMCell(190,5,
707
             "#" + indice.to_s +
708
             " - " + format_time(journal.created_on) +
709
             " - " + journal.user.name)
710
          pdf.Ln
711
          pdf.SetFontStyle('I',8)
712
          details_to_strings(journal.details, true).each do |string|
713
            pdf.RDMMultiCell(190,5, "- " + string)
714
          end
715
          if journal.notes?
716
            pdf.Ln unless journal.details.empty?
717
            pdf.SetFontStyle('',8)
718
            pdf.RDMwriteHTMLCell(190,5,0,0,
719
                  journal.notes.to_s, issue.attachments, "")
720
          end
721
          pdf.Ln
722
        end
723

    
724
        if issue.attachments.any?
725
          pdf.SetFontStyle('B',9)
726
          pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
727
          pdf.Ln
728
          for attachment in issue.attachments
729
            pdf.SetFontStyle('',8)
730
            pdf.RDMCell(80,5, attachment.filename)
731
            pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
732
            pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
733
            pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
734
            pdf.Ln
735
          end
736
        end
737
      end
738

    
739
      # Returns a PDF string of a set of wiki pages
740
      def wiki_pages_to_pdf(pages, project)
741
        pdf = ITCPDF.new(current_language)
742
        pdf.SetTitle(project.name)
743
        pdf.alias_nb_pages
744
        pdf.footer_date = format_date(Date.today)
745
        pdf.AddPage
746
        pdf.SetFontStyle('B',11)
747
        pdf.RDMMultiCell(190,5, project.name)
748
        pdf.Ln
749
        # Set resize image scale
750
        pdf.SetImageScale(1.6)
751
        pdf.SetFontStyle('',9)
752
        write_page_hierarchy(pdf, pages.group_by(&:parent_id))
753
        pdf.Output
754
      end
755

    
756
      # Returns a PDF string of a single wiki page
757
      def wiki_page_to_pdf(page, project)
758
        pdf = ITCPDF.new(current_language)
759
        pdf.SetTitle("#{project} - #{page.title}")
760
        pdf.alias_nb_pages
761
        pdf.footer_date = format_date(Date.today)
762
        pdf.AddPage
763
        pdf.SetFontStyle('B',11)
764
        pdf.RDMMultiCell(190,5,
765
             "#{project} - #{page.title} - # #{page.content.version}")
766
        pdf.Ln
767
        # Set resize image scale
768
        pdf.SetImageScale(1.6)
769
        pdf.SetFontStyle('',9)
770
        write_wiki_page(pdf, page)
771
        pdf.Output
772
      end
773

    
774
      def write_page_hierarchy(pdf, pages, node=nil, level=0)
775
        if pages[node]
776
          pages[node].each do |page|
777
            if @new_page
778
              pdf.AddPage
779
            else
780
              @new_page = true
781
            end
782
            pdf.Bookmark page.title, level
783
            write_wiki_page(pdf, page)
784
            write_page_hierarchy(pdf, pages, page.id, level + 1) if pages[page.id]
785
          end
786
        end
787
      end
788

    
789
      def write_wiki_page(pdf, page)
790
        pdf.RDMwriteHTMLCell(190,5,0,0,
791
              page.content.text.to_s, page.attachments, 0)
792
        if page.attachments.any?
793
          pdf.Ln
794
          pdf.SetFontStyle('B',9)
795
          pdf.RDMCell(190,5, l(:label_attachment_plural), "B")
796
          pdf.Ln
797
          for attachment in page.attachments
798
            pdf.SetFontStyle('',8)
799
            pdf.RDMCell(80,5, attachment.filename)
800
            pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
801
            pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R")
802
            pdf.RDMCell(65,5, attachment.author.name,0,0,"R")
803
            pdf.Ln
804
          end
805
        end
806
      end
807

    
808
      class RDMPdfEncoding
809
        def self.rdm_from_utf8(txt, encoding)
810
          txt ||= ''
811
          txt = Redmine::CodesetUtil.from_utf8(txt, encoding)
812
          if txt.respond_to?(:force_encoding)
813
            txt.force_encoding('ASCII-8BIT')
814
          end
815
          txt
816
        end
817

    
818
        def self.attach(attachments, filename, encoding)
819
          filename_utf8 = Redmine::CodesetUtil.to_utf8(filename, encoding)
820
          atta = nil
821
          if filename_utf8 =~ /^[^\/"]+\.(gif|jpg|jpe|jpeg|png)$/i
822
            atta = Attachment.latest_attach(attachments, filename_utf8)
823
          end
824
          if atta && atta.readable? && atta.visible?
825
            return atta
826
          else
827
            return nil
828
          end
829
        end
830
      end
831
    end
832
  end
833
end
(7-7/7)