Project

General

Profile

Feature #44396 » 0002-Reduce-memory-usage-by-not-loading-rbpdf-until-a-PDF.patch

Go MAEDA, 2026-08-29 07:50

View differences:

Gemfile
11 11
gem 'mail', '~> 2.9.0'
12 12
gem 'nokogiri', '~> 1.19.1'
13 13
gem 'i18n', '~> 1.15.2'
14
gem 'rbpdf', '~> 1.21.4'
14
# Loaded on demand by lib/redmine/export/pdf/itcpdf.rb, keep require: false
15
gem 'rbpdf', '~> 1.21.4', require: false
15 16
gem 'addressable'
16 17
gem 'rubyzip', '~> 3.5.0'
17 18
gem 'propshaft', '~> 1.3.0'
config/initializers/zeitwerk.rb
21 21
    'pdf' => 'PDF',
22 22
    'url' => 'URL',
23 23
    'pop3' => 'POP3',
24
    'imap' => 'IMAP'
24
    'imap' => 'IMAP',
25
    'itcpdf' => 'ITCPDF'
25 26
  )
26 27
  IGNORE_LIST.each do |mod|
27 28
    loader.ignore lib.join(mod)
28 29
  end
30
  # Keep ITCPDF out of eager loading so that rbpdf is only loaded when a PDF
31
  # is generated.
32
  loader.do_not_eager_load lib.join('export/pdf/itcpdf.rb')
29 33
end
lib/redmine/export/pdf.rb
18 18
# along with this program; if not, write to the Free Software
19 19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
require 'rbpdf'
21

  
22 20
module Redmine
23 21
  module Export
24 22
    module PDF
25
      # The camel cased methods below follow the RBPDF API and cannot be renamed.
26
      # rubocop:disable-next Naming/MethodName
27
      class ITCPDF < RBPDF
28
        include Redmine::I18n
29
        attr_accessor :footer_date
30

  
31
        def initialize(lang, orientation='P')
32
          set_language_if_valid lang
33
          super(orientation, 'mm', 'A4')
34
          set_print_header(false)
35
          set_rtl(l(:direction) == 'rtl')
36

  
37
          @font_for_content = l(:general_pdf_fontname)
38
          @monospaced_font_for_content = l(:general_pdf_monospaced_fontname)
39
          @font_for_footer  = l(:general_pdf_fontname)
40
          set_creator(Redmine::Info.app_name)
41
          set_font(@font_for_content)
42

  
43
          set_header_font([@font_for_content, '', 10])
44
          set_footer_font([@font_for_content, '', 8])
45
          set_default_monospaced_font(@monospaced_font_for_content)
46
          set_display_mode('default', 'OneColumn')
47
        end
48

  
49
        def SetFontStyle(style, size)
50
          set_font(@font_for_content, style, size)
51
        end
52

  
53
        def SetFont(family, style='', size=0, fontfile='')
54
          style = +style
55
          # FreeSerif Bold Thai font has problem.
56
          style.delete!('B') if family.to_s.casecmp('freeserif') == 0
57
          # DejaVuSans Italic Arabic and Persian font has problem.
58
          style.delete!('I') if family.to_s.casecmp('dejavusans') == 0 && current_language.to_s.casecmp("vi") != 0
59
          # DejaVuSansMono Italic Arabic font has problem
60
          style.delete!('I') if family.to_s.casecmp('dejavusansmono') == 0
61
          super
62
        end
63
        alias set_font SetFont
64

  
65
        def fix_text_encoding(txt)
66
          RDMPdfEncoding.rdm_from_utf8(txt, "UTF-8")
67
        end
68

  
69
        def formatted_text(text)
70
          Redmine::WikiFormatting.to_html(Setting.text_formatting, text)
71
        end
72

  
73
        def RDMCell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link='')
74
          cell(w, h, txt, border, ln, align, fill, link)
75
        end
76

  
77
        def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
78
          multi_cell(w, h, txt, border, align, fill, ln)
79
        end
80

  
81
        def RDMwriteFormattedCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
82
          @attachments = attachments
83

  
84
          css_tag = ' <style>
85
          table, td {
86
            border: 2px #ff0000 solid;
87
          }
88
          th {  background-color:#EEEEEE; padding: 4px; white-space:nowrap; text-align: center;  font-style: bold;}
89
          pre {
90
            background-color: #fafafa;
91
          }
92
          </style>'
93

  
94
          # Strip {{toc}} tags
95
          txt = txt.gsub(/<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i, '')
96
          writeHTMLCell(w, h, x, y, css_tag + txt, border, ln, fill)
97
        end
98

  
99
        def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
100
          txt = formatted_text(txt)
101
          RDMwriteFormattedCell(w, h, x, y, txt, attachments, border, ln, fill)
102
        end
103

  
104
        def get_image_filename(attrname)
105
          atta = RDMPdfEncoding.attach(@attachments, attrname, "UTF-8")
106
          if atta
107
            return atta.diskfile
108
          # rubocop:disable Lint/DuplicateBranch
109
          elsif %r{/attachments/download/(?<id>[^/]+)/} =~ attrname &&
110
                (atta = @attachments.find{|a| a.id.to_s == id}) &&
111
                atta.readable? && atta.visible?
112
            return atta.diskfile
113
          # rubocop:enable Lint/DuplicateBranch
114
          elsif %r{/attachments/thumbnail/(?<id>[^/]+)/(?<size>\d+)} =~ attrname &&
115
                (atta = @attachments.find{|a| a.id.to_s == id}) &&
116
                atta.readable? && atta.visible?
117
            return atta.thumbnail(size: size)
118
          else
119
            return nil
120
          end
121
        end
122

  
123
        def get_sever_url(url)
124
          if !empty_string(url) && url.start_with?('/')
125
            Setting.host_name.split('/')[0] + url
126
          else
127
            url
128
          end
129
        end
130

  
131
        def Footer
132
          set_font(@font_for_footer, 'I', 8)
133
          set_x(15)
134
          if get_rtl
135
            RDMCell(0, 5, @footer_date, 0, 0, 'R')
136
          else
137
            RDMCell(0, 5, @footer_date, 0, 0, 'L')
138
          end
139
          set_x(-30)
140
          RDMCell(0, 5, get_alias_num_page + '/' + get_alias_nb_pages, 0, 0, 'C')
141
        end
142
      end
143

  
144 23
      class RDMPdfEncoding
145 24
        def self.rdm_from_utf8(txt, encoding)
146 25
          txt ||= ''
lib/redmine/export/pdf/itcpdf.rb
1
# frozen_string_literal: true
2

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

  
21
require 'rbpdf'
22

  
23
module Redmine
24
  module Export
25
    module PDF
26
      # The camel cased methods below follow the RBPDF API and cannot be renamed.
27
      # rubocop:disable-next Naming/MethodName
28
      class ITCPDF < RBPDF
29
        include Redmine::I18n
30
        attr_accessor :footer_date
31

  
32
        def initialize(lang, orientation='P')
33
          set_language_if_valid lang
34
          super(orientation, 'mm', 'A4')
35
          set_print_header(false)
36
          set_rtl(l(:direction) == 'rtl')
37

  
38
          @font_for_content = l(:general_pdf_fontname)
39
          @monospaced_font_for_content = l(:general_pdf_monospaced_fontname)
40
          @font_for_footer  = l(:general_pdf_fontname)
41
          set_creator(Redmine::Info.app_name)
42
          set_font(@font_for_content)
43

  
44
          set_header_font([@font_for_content, '', 10])
45
          set_footer_font([@font_for_content, '', 8])
46
          set_default_monospaced_font(@monospaced_font_for_content)
47
          set_display_mode('default', 'OneColumn')
48
        end
49

  
50
        def SetFontStyle(style, size)
51
          set_font(@font_for_content, style, size)
52
        end
53

  
54
        def SetFont(family, style='', size=0, fontfile='')
55
          style = +style
56
          # FreeSerif Bold Thai font has problem.
57
          style.delete!('B') if family.to_s.casecmp('freeserif') == 0
58
          # DejaVuSans Italic Arabic and Persian font has problem.
59
          style.delete!('I') if family.to_s.casecmp('dejavusans') == 0 && current_language.to_s.casecmp("vi") != 0
60
          # DejaVuSansMono Italic Arabic font has problem
61
          style.delete!('I') if family.to_s.casecmp('dejavusansmono') == 0
62
          super
63
        end
64
        alias set_font SetFont
65

  
66
        def fix_text_encoding(txt)
67
          RDMPdfEncoding.rdm_from_utf8(txt, "UTF-8")
68
        end
69

  
70
        def formatted_text(text)
71
          Redmine::WikiFormatting.to_html(Setting.text_formatting, text)
72
        end
73

  
74
        def RDMCell(w, h=0, txt='', border=0, ln=0, align='', fill=0, link='')
75
          cell(w, h, txt, border, ln, align, fill, link)
76
        end
77

  
78
        def RDMMultiCell(w, h=0, txt='', border=0, align='', fill=0, ln=1)
79
          multi_cell(w, h, txt, border, align, fill, ln)
80
        end
81

  
82
        def RDMwriteFormattedCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
83
          @attachments = attachments
84

  
85
          css_tag = ' <style>
86
          table, td {
87
            border: 2px #ff0000 solid;
88
          }
89
          th {  background-color:#EEEEEE; padding: 4px; white-space:nowrap; text-align: center;  font-style: bold;}
90
          pre {
91
            background-color: #fafafa;
92
          }
93
          </style>'
94

  
95
          # Strip {{toc}} tags
96
          txt = txt.gsub(/<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i, '')
97
          writeHTMLCell(w, h, x, y, css_tag + txt, border, ln, fill)
98
        end
99

  
100
        def RDMwriteHTMLCell(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
101
          txt = formatted_text(txt)
102
          RDMwriteFormattedCell(w, h, x, y, txt, attachments, border, ln, fill)
103
        end
104

  
105
        def get_image_filename(attrname)
106
          atta = RDMPdfEncoding.attach(@attachments, attrname, "UTF-8")
107
          if atta
108
            return atta.diskfile
109
          # rubocop:disable Lint/DuplicateBranch
110
          elsif %r{/attachments/download/(?<id>[^/]+)/} =~ attrname &&
111
                (atta = @attachments.find{|a| a.id.to_s == id}) &&
112
                atta.readable? && atta.visible?
113
            return atta.diskfile
114
          # rubocop:enable Lint/DuplicateBranch
115
          elsif %r{/attachments/thumbnail/(?<id>[^/]+)/(?<size>\d+)} =~ attrname &&
116
                (atta = @attachments.find{|a| a.id.to_s == id}) &&
117
                atta.readable? && atta.visible?
118
            return atta.thumbnail(size: size)
119
          else
120
            return nil
121
          end
122
        end
123

  
124
        def get_sever_url(url)
125
          if !empty_string(url) && url.start_with?('/')
126
            Setting.host_name.split('/')[0] + url
127
          else
128
            url
129
          end
130
        end
131

  
132
        def Footer
133
          set_font(@font_for_footer, 'I', 8)
134
          set_x(15)
135
          if get_rtl
136
            RDMCell(0, 5, @footer_date, 0, 0, 'R')
137
          else
138
            RDMCell(0, 5, @footer_date, 0, 0, 'L')
139
          end
140
          set_x(-30)
141
          RDMCell(0, 5, get_alias_num_page + '/' + get_alias_nb_pages, 0, 0, 'C')
142
        end
143
      end
144
    end
145
  end
146
end
(2-2/2)