Feature #44396 » 0002-Reduce-memory-usage-by-not-loading-rbpdf-until-a-PDF.patch
| 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>\{\{((<|<)|(>|>))?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>\{\{((<|<)|(>|>))?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 |
|
- « Previous
- 1
- 2
- Next »