Project

General

Profile

Defect #61 » pdf.rb

Jun NAITOH, 2011-03-29 16:08

 
1
# encoding: utf-8
2
#
3
# Redmine - project management software
4
# Copyright (C) 2006-2009  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 'rfpdf/fpdf'
22
require 'fpdf/chinese'
23
require 'fpdf/japanese'
24
require 'fpdf/korean'
25

    
26
module Redmine
27
  module Export
28
    module PDF
29
      include ActionView::Helpers::TextHelper
30
      include ActionView::Helpers::NumberHelper
31
      
32
      class ITCPDF < TCPDF
33
        include Redmine::I18n
34
        attr_accessor :footer_date
35
        
36
        def initialize(lang)
37
          super()
38
          set_language_if_valid lang
39
          @font_for_content = 'FreeSans'
40
          @font_for_footer = 'FreeSans'              
41
          SetCreator(Redmine::Info.app_name)
42
          SetFont(@font_for_content)
43
        end
44
        
45
        def SetFontStyle(style, size)
46
          SetFont(@font_for_content, style, size)
47
        end
48
        
49
        def SetTitle(txt)
50
          txt = begin
51
            utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
52
            hextxt = "<FEFF"  # FEFF is BOM
53
            hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
54
            hextxt << ">"
55
          rescue
56
            txt
57
          end || ''
58
          super(txt)
59
        end
60
    
61
        def textstring(s)
62
          # Format a text string
63
          if s =~ /^</  # This means the string is hex-dumped.
64
            return s
65
          else
66
            return '('+escape(s)+')'
67
          end
68
        end
69
         
70
        alias UTF8Cell Cell
71
        alias UTF8MultiCell MultiCell
72
         
73
        def Footer
74
          SetFont(@font_for_footer, 'I', 8)
75
          SetY(-15)
76
          SetX(15)
77
          UTF8Cell(0, 5, @footer_date, 0, 0, 'L')
78
          SetY(-15)
79
          SetX(-30)
80
          UTF8Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
81
        end
82
      end
83

    
84
      class IFPDF < FPDF
85
        include Redmine::I18n
86
        attr_accessor :footer_date
87
        
88
        def initialize(lang)
89
          super()
90
          set_language_if_valid lang
91
          case current_language.to_s.downcase
92
          when 'ko'
93
            extend(PDF_Korean)
94
            AddUHCFont()
95
            @font_for_content = 'UHC'
96
            @font_for_footer = 'UHC'
97
          when 'ja'
98
            extend(PDF_Japanese)
99
            AddSJISFont()
100
            @font_for_content = 'SJIS'
101
            @font_for_footer = 'SJIS'
102
          when 'zh'
103
            extend(PDF_Chinese)
104
            AddGBFont()
105
            @font_for_content = 'GB'
106
            @font_for_footer = 'GB'
107
          when 'zh-tw'
108
            extend(PDF_Chinese)
109
            AddBig5Font()
110
            @font_for_content = 'Big5'
111
            @font_for_footer = 'Big5'
112
          else
113
            @font_for_content = 'Arial'
114
            @font_for_footer = 'Helvetica'              
115
          end
116
          SetCreator(Redmine::Info.app_name)
117
          SetFont(@font_for_content)
118
        end
119
        
120
        def SetFontStyle(style, size)
121
          SetFont(@font_for_content, style, size)
122
        end
123
        
124
        def SetTitle(txt)
125
          txt = begin
126
            utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
127
            hextxt = "<FEFF"  # FEFF is BOM
128
            hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
129
            hextxt << ">"
130
          rescue
131
            txt
132
          end || ''
133
          super(txt)
134
        end
135
    
136
        def textstring(s)
137
          # Format a text string
138
          if s =~ /^</  # This means the string is hex-dumped.
139
            return s
140
          else
141
            return '('+escape(s)+')'
142
          end
143
        end
144
          
145
        def fix_text_encoding(txt)
146
          @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
147
          # these quotation marks are not correctly rendered in the pdf
148
          txt = txt.gsub(/[“�]/, '"') if txt
149
          txt = begin
150
            # 0x5c char handling
151
            txtar = txt.split('\\')
152
            txtar << '' if txt[-1] == ?\\
153
            txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
154
          rescue
155
            txt
156
          end || ''
157
            return txt
158
        end
159
        
160
        def UTF8Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
161
            Cell(w,h,fix_text_encoding(txt),border,ln,align,fill,link)
162
        end
163
        
164
        def UTF8MultiCell(w,h=0,txt='',border=0,align='',fill=0)
165
            MultiCell(w,h,fix_text_encoding(txt),border,align,fill)
166
        end
167
        
168
        def Footer
169
          SetFont(@font_for_footer, 'I', 8)
170
          SetY(-15)
171
          SetX(15)
172
          UTF8Cell(0, 5, @footer_date, 0, 0, 'L')
173
          SetY(-15)
174
          SetX(-30)
175
          UTF8Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
176
        end
177
        alias alias_nb_pages AliasNbPages
178
      end
179
      
180
      # Returns a PDF string of a list of issues
181
      def issues_to_pdf(issues, project, query)
182
        if l(:general_pdf_encoding).upcase == 'UTF-8'
183
          pdf = ITCPDF.new(current_language)
184
        else
185
          pdf = IFPDF.new(current_language)
186
        end
187

    
188
        title = query.new_record? ? l(:label_issue_plural) : query.name
189
        title = "#{project} - #{title}" if project
190
        pdf.SetTitle(title)
191
        pdf.alias_nb_pages
192
        pdf.footer_date = format_date(Date.today)
193
        pdf.AddPage("L")
194
        
195
        row_height = 6
196
        col_width = []
197
        unless query.columns.empty?
198
          col_width = query.columns.collect {|column| column.name == :subject ? 4.0 : 1.0 }
199
          ratio = 262.0 / col_width.inject(0) {|s,w| s += w}
200
          col_width = col_width.collect {|w| w * ratio}
201
        end
202
        
203
        # title
204
        pdf.SetFontStyle('B',11)    
205
        pdf.UTF8Cell(190,10, title)
206
        pdf.Ln
207
        
208
        # headers
209
        pdf.SetFontStyle('B',8)
210
        pdf.SetFillColor(230, 230, 230)
211
        pdf.UTF8Cell(15, row_height, "#", 1, 0, 'L', 1)
212
        query.columns.each_with_index do |column, i|
213
          pdf.UTF8Cell(col_width[i], row_height, column.caption, 1, 0, 'L', 1)
214
        end
215
        pdf.Ln
216
        
217
        # rows
218
        pdf.SetFontStyle('',8)
219
        pdf.SetFillColor(255, 255, 255)
220
        previous_group = false
221
        issues.each do |issue|
222
          if query.grouped? && (group = query.group_by_column.value(issue)) != previous_group
223
            pdf.SetFontStyle('B',9)
224
            pdf.UTF8Cell(277, row_height, 
225
              (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})",
226
              1, 1, 'L')
227
            pdf.SetFontStyle('',8)
228
            previous_group = group
229
          end
230
          pdf.UTF8Cell(15, row_height, issue.id.to_s, 1, 0, 'L', 1)
231
          query.columns.each_with_index do |column, i|
232
            s = if column.is_a?(QueryCustomFieldColumn)
233
              cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
234
              show_value(cv)
235
            else
236
              value = issue.send(column.name)
237
              if value.is_a?(Date)
238
                format_date(value)
239
              elsif value.is_a?(Time)
240
                format_time(value)
241
              else
242
                value
243
              end
244
            end
245
            pdf.UTF8Cell(col_width[i], row_height, s.to_s, 1, 0, 'L', 1)
246
          end
247
          pdf.Ln
248
        end
249
        if issues.size == Setting.issues_export_limit.to_i
250
          pdf.SetFontStyle('B',10)
251
          pdf.UTF8Cell(0, row_height, '...')
252
        end
253
        pdf.Output
254
      end
255

    
256
      # Returns a PDF string of a single issue
257
      def issue_to_pdf(issue)
258
        if l(:general_pdf_encoding).upcase == 'UTF-8'
259
          pdf = ITCPDF.new(current_language)
260
        else
261
          pdf = IFPDF.new(current_language)
262
        end
263
        pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
264
        pdf.alias_nb_pages
265
        pdf.footer_date = format_date(Date.today)
266
        pdf.AddPage
267
        
268
        pdf.SetFontStyle('B',11)    
269
        pdf.UTF8Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
270
        pdf.Ln
271
        
272
        y0 = pdf.GetY
273
        
274
        pdf.SetFontStyle('B',9)
275
        pdf.UTF8Cell(35,5, l(:field_status) + ":","LT")
276
        pdf.SetFontStyle('',9)
277
        pdf.UTF8Cell(60,5, issue.status.to_s,"RT")
278
        pdf.SetFontStyle('B',9)
279
        pdf.UTF8Cell(35,5, l(:field_priority) + ":","LT")
280
        pdf.SetFontStyle('',9)
281
        pdf.UTF8Cell(60,5, issue.priority.to_s,"RT")        
282
        pdf.Ln
283
        
284
        pdf.SetFontStyle('B',9)
285
        pdf.UTF8Cell(35,5, l(:field_author) + ":","L")
286
        pdf.SetFontStyle('',9)
287
        pdf.UTF8Cell(60,5, issue.author.to_s,"R")
288
        pdf.SetFontStyle('B',9)
289
        pdf.UTF8Cell(35,5, l(:field_category) + ":","L")
290
        pdf.SetFontStyle('',9)
291
        pdf.UTF8Cell(60,5, issue.category.to_s,"R")
292
        pdf.Ln   
293
        
294
        pdf.SetFontStyle('B',9)
295
        pdf.UTF8Cell(35,5, l(:field_created_on) + ":","L")
296
        pdf.SetFontStyle('',9)
297
        pdf.UTF8Cell(60,5, format_date(issue.created_on),"R")
298
        pdf.SetFontStyle('B',9)
299
        pdf.UTF8Cell(35,5, l(:field_assigned_to) + ":","L")
300
        pdf.SetFontStyle('',9)
301
        pdf.UTF8Cell(60,5, issue.assigned_to.to_s,"R")
302
        pdf.Ln
303
        
304
        pdf.SetFontStyle('B',9)
305
        pdf.UTF8Cell(35,5, l(:field_updated_on) + ":","LB")
306
        pdf.SetFontStyle('',9)
307
        pdf.UTF8Cell(60,5, format_date(issue.updated_on),"RB")
308
        pdf.SetFontStyle('B',9)
309
        pdf.UTF8Cell(35,5, l(:field_due_date) + ":","LB")
310
        pdf.SetFontStyle('',9)
311
        pdf.UTF8Cell(60,5, format_date(issue.due_date),"RB")
312
        pdf.Ln
313
        
314
        for custom_value in issue.custom_field_values
315
          pdf.SetFontStyle('B',9)
316
          pdf.UTF8Cell(35,5, custom_value.custom_field.name + ":","L")
317
          pdf.SetFontStyle('',9)
318
          pdf.UTF8MultiCell(155,5, (show_value custom_value),"R")
319
        end
320
        
321
        pdf.SetFontStyle('B',9)
322
        pdf.UTF8Cell(35,5, l(:field_subject) + ":","LTB")
323
        pdf.SetFontStyle('',9)
324
        pdf.UTF8Cell(155,5, issue.subject,"RTB")
325
        pdf.Ln    
326
        
327
        pdf.SetFontStyle('B',9)
328
        pdf.UTF8Cell(35,5, l(:field_description) + ":")
329
        pdf.SetFontStyle('',9)
330
        pdf.UTF8MultiCell(155,5, issue.description.to_s,"BR")
331
        
332
        pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
333
        pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
334
        pdf.Ln
335
        
336
        if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
337
          pdf.SetFontStyle('B',9)
338
          pdf.UTF8Cell(190,5, l(:label_associated_revisions), "B")
339
          pdf.Ln
340
          for changeset in issue.changesets
341
            pdf.SetFontStyle('B',8)
342
            pdf.UTF8Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
343
            pdf.Ln
344
            unless changeset.comments.blank?
345
              pdf.SetFontStyle('',8)
346
              pdf.UTF8MultiCell(190,5, changeset.comments.to_s)
347
            end   
348
            pdf.Ln
349
          end
350
        end
351
        
352
        pdf.SetFontStyle('B',9)
353
        pdf.UTF8Cell(190,5, l(:label_history), "B")
354
        pdf.Ln  
355
        for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
356
          pdf.SetFontStyle('B',8)
357
          pdf.UTF8Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
358
          pdf.Ln
359
          pdf.SetFontStyle('I',8)
360
          for detail in journal.details
361
            pdf.UTF8Cell(190,5, "- " + show_detail(detail, true))
362
            pdf.Ln
363
          end
364
          if journal.notes?
365
            pdf.SetFontStyle('',8)
366
            pdf.UTF8MultiCell(190,5, journal.notes.to_s)
367
          end   
368
          pdf.Ln
369
        end
370
        
371
        if issue.attachments.any?
372
          pdf.SetFontStyle('B',9)
373
          pdf.UTF8Cell(190,5, l(:label_attachment_plural), "B")
374
          pdf.Ln
375
          for attachment in issue.attachments
376
            pdf.SetFontStyle('',8)
377
            pdf.UTF8Cell(80,5, attachment.filename)
378
            pdf.UTF8Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
379
            pdf.UTF8Cell(25,5, format_date(attachment.created_on),0,0,"R")
380
            pdf.UTF8Cell(65,5, attachment.author.name,0,0,"R")
381
            pdf.Ln
382
          end
383
        end
384
        pdf.Output
385
      end
386

    
387
    end
388
  end
389
end
(29-29/46)