hexapdf 0.40.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -0
- data/examples/019-acro_form.rb +12 -23
- data/examples/027-composer_optional_content.rb +1 -1
- data/examples/030-pdfa.rb +6 -6
- data/examples/031-acro_form_java_script.rb +113 -0
- data/lib/hexapdf/cli/command.rb +25 -11
- data/lib/hexapdf/cli/files.rb +31 -7
- data/lib/hexapdf/cli/form.rb +46 -38
- data/lib/hexapdf/cli/info.rb +4 -0
- data/lib/hexapdf/cli/inspect.rb +1 -1
- data/lib/hexapdf/cli/usage.rb +215 -0
- data/lib/hexapdf/cli.rb +2 -0
- data/lib/hexapdf/configuration.rb +11 -1
- data/lib/hexapdf/content/canvas.rb +2 -0
- data/lib/hexapdf/document/layout.rb +8 -1
- data/lib/hexapdf/encryption/aes.rb +13 -6
- data/lib/hexapdf/encryption/security_handler.rb +6 -4
- data/lib/hexapdf/font/cmap/parser.rb +1 -5
- data/lib/hexapdf/font/cmap.rb +22 -3
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/variant_from_name.rb +72 -0
- data/lib/hexapdf/font_loader.rb +1 -0
- data/lib/hexapdf/layout/style.rb +5 -4
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +11 -76
- data/lib/hexapdf/type/acro_form/button_field.rb +7 -5
- data/lib/hexapdf/type/acro_form/field.rb +14 -0
- data/lib/hexapdf/type/acro_form/form.rb +70 -8
- data/lib/hexapdf/type/acro_form/java_script_actions.rb +649 -0
- data/lib/hexapdf/type/acro_form/text_field.rb +90 -0
- data/lib/hexapdf/type/acro_form.rb +1 -0
- data/lib/hexapdf/type/annotations/widget.rb +1 -1
- data/lib/hexapdf/type/resources.rb +2 -1
- data/lib/hexapdf/utils.rb +19 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/encryption/test_aes.rb +18 -8
- data/test/hexapdf/encryption/test_security_handler.rb +17 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/cmap/test_parser.rb +5 -3
- data/test/hexapdf/font/test_cmap.rb +8 -0
- data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
- data/test/hexapdf/font_loader/test_variant_from_name.rb +34 -0
- data/test/hexapdf/test_utils.rb +16 -0
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +31 -47
- data/test/hexapdf/type/acro_form/test_button_field.rb +5 -0
- data/test/hexapdf/type/acro_form/test_field.rb +11 -0
- data/test/hexapdf/type/acro_form/test_form.rb +80 -0
- data/test/hexapdf/type/acro_form/test_java_script_actions.rb +327 -0
- data/test/hexapdf/type/acro_form/test_text_field.rb +62 -0
- data/test/hexapdf/type/test_resources.rb +5 -0
- metadata +8 -2
data/lib/hexapdf/cli/info.rb
CHANGED
@@ -192,6 +192,10 @@ module HexaPDF
|
|
192
192
|
puts "WARNING: Parse error at position #{pos}: #{msg}"
|
193
193
|
false
|
194
194
|
end
|
195
|
+
options[:config]['encryption.on_decryption_error'] = lambda do |obj, msg|
|
196
|
+
puts "WARNING: Decryption problem for object (#{obj.oid},#{obj.gen}): #{msg}"
|
197
|
+
false
|
198
|
+
end
|
195
199
|
options
|
196
200
|
else
|
197
201
|
super
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
@@ -270,7 +270,7 @@ module HexaPDF
|
|
270
270
|
if (rev_index = data.shift)
|
271
271
|
rev_index = rev_index.to_i - 1
|
272
272
|
if rev_index < 0 || rev_index >= @doc.revisions.count
|
273
|
-
$stderr.puts("Error: Invalid revision
|
273
|
+
$stderr.puts("Error: Invalid revision number specified")
|
274
274
|
next
|
275
275
|
end
|
276
276
|
length = 0
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2024 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'hexapdf/cli/command'
|
38
|
+
|
39
|
+
module HexaPDF
|
40
|
+
module CLI
|
41
|
+
|
42
|
+
# Shows the space usage of various parts of a PDF file.
|
43
|
+
class Usage < Command
|
44
|
+
|
45
|
+
# Modifies the HexaPDF::PDFData class to store the size information
|
46
|
+
module PDFDataExtension
|
47
|
+
|
48
|
+
# Used to store the size of the indirect object.
|
49
|
+
attr_accessor :size
|
50
|
+
|
51
|
+
# Used to store the size of the object inside the object stream.
|
52
|
+
attr_accessor :size_in_object_stream
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# Modifies HexaPDF::Parser to retrieve space used by indirect objects.
|
57
|
+
module ParserExtension
|
58
|
+
|
59
|
+
# :nodoc:
|
60
|
+
def initialize(*)
|
61
|
+
super
|
62
|
+
@last_size = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# :nodoc:
|
66
|
+
def load_object(xref_entry)
|
67
|
+
super.tap do |obj|
|
68
|
+
if xref_entry.type == :compressed
|
69
|
+
obj.data.size_in_object_stream = @last_size
|
70
|
+
elsif xref_entry.type == :in_use
|
71
|
+
obj.data.size = @last_size
|
72
|
+
end
|
73
|
+
@last_size = nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# :nodoc:
|
78
|
+
def parse_indirect_object(offset = nil)
|
79
|
+
real_offset = (offset ? @header_offset + offset : @tokenizer.pos)
|
80
|
+
result = super
|
81
|
+
@last_size = @tokenizer.pos - real_offset
|
82
|
+
result
|
83
|
+
end
|
84
|
+
|
85
|
+
# :nodoc:
|
86
|
+
def load_compressed_object(xref_entry)
|
87
|
+
result = super
|
88
|
+
offsets = @object_stream_data[xref_entry.objstm].instance_variable_get(:@offsets)
|
89
|
+
@last_size = if xref_entry.pos == offsets.size - 1
|
90
|
+
@object_stream_data[xref_entry.objstm].instance_variable_get(:@tokenizer).
|
91
|
+
io.size - offsets[xref_entry.pos]
|
92
|
+
else
|
93
|
+
offsets[xref_entry.pos + 1] - offsets[xref_entry.pos]
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize #:nodoc:
|
101
|
+
super('usage', takes_commands: false)
|
102
|
+
short_desc("Show space usage of various parts of a PDF file")
|
103
|
+
long_desc(<<~EOF)
|
104
|
+
This command displays some usage statistics of the PDF file, i.e. which parts take which
|
105
|
+
approximate space in the file.
|
106
|
+
|
107
|
+
Each statistic line shows the space used followed by the number of indirect objects in
|
108
|
+
parentheses. If some of those objects are in object streams, that number is displayed
|
109
|
+
after a slash.
|
110
|
+
EOF
|
111
|
+
|
112
|
+
options.on("--password PASSWORD", "-p", String,
|
113
|
+
"The password for decryption. Use - for reading from standard input.") do |pwd|
|
114
|
+
@password = (pwd == '-' ? read_password : pwd)
|
115
|
+
end
|
116
|
+
|
117
|
+
@password = nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def execute(file) #:nodoc:
|
121
|
+
HexaPDF::Parser.prepend(ParserExtension)
|
122
|
+
HexaPDF::PDFData.prepend(PDFDataExtension)
|
123
|
+
|
124
|
+
with_document(file, password: @password) do |doc|
|
125
|
+
# Prepare cache of outline items
|
126
|
+
outline_item_cache = {}
|
127
|
+
if doc.catalog.key?(:Outlines)
|
128
|
+
doc.outline.each_item {|item| outline_item_cache[item] = true }
|
129
|
+
outline_item_cache[doc.outline] = true
|
130
|
+
end
|
131
|
+
|
132
|
+
doc.revisions.each.with_index do |rev, index|
|
133
|
+
sum = count = 0
|
134
|
+
categories = {
|
135
|
+
Content: [],
|
136
|
+
Files: [],
|
137
|
+
Fonts: [],
|
138
|
+
Images: [],
|
139
|
+
Metadata: [],
|
140
|
+
ObjectStreams: [],
|
141
|
+
Outline: [],
|
142
|
+
XObjects: [],
|
143
|
+
}
|
144
|
+
puts if index > 0
|
145
|
+
puts "Usage information for revision #{index + 1}" if doc.revisions.count > 1
|
146
|
+
rev.each do |obj|
|
147
|
+
if command_parser.verbosity_info?
|
148
|
+
print "(#{obj.oid},#{obj.gen}): #{obj.data.size.to_i}"
|
149
|
+
print " (#{obj.data.size_in_object_stream})" if obj.data.size.nil?
|
150
|
+
puts
|
151
|
+
end
|
152
|
+
next unless obj.kind_of?(HexaPDF::Dictionary)
|
153
|
+
|
154
|
+
case obj.type
|
155
|
+
when :Page
|
156
|
+
Array(obj[:Contents]).each do |content|
|
157
|
+
categories[:Content] << content if object_in_rev?(content, rev)
|
158
|
+
end
|
159
|
+
when :Font
|
160
|
+
categories[:Fonts] << obj
|
161
|
+
when :FontDescriptor
|
162
|
+
categories[:Fonts] << obj
|
163
|
+
[:FontFile, :FontFile2, :FontFile3].each do |name|
|
164
|
+
categories[:Fonts] << obj[name] if object_in_rev?(obj[name], rev)
|
165
|
+
end
|
166
|
+
when :Metadata
|
167
|
+
categories[:Metadata] << obj
|
168
|
+
when :Filespec
|
169
|
+
categories[:Files] << obj
|
170
|
+
categories[:Files] << obj.embedded_file_stream if obj.embedded_file?
|
171
|
+
when :ObjStm
|
172
|
+
categories[:ObjectStreams] << obj
|
173
|
+
else
|
174
|
+
if obj[:Subtype] == :Image
|
175
|
+
categories[:Images] << obj
|
176
|
+
elsif obj[:Subtype] == :Form
|
177
|
+
categories[:XObjects] << obj
|
178
|
+
end
|
179
|
+
end
|
180
|
+
sum += obj.data.size if obj.data.size
|
181
|
+
count += 1
|
182
|
+
end
|
183
|
+
|
184
|
+
# Populate Outline category
|
185
|
+
outline_item_cache.reject! do |obj, _val|
|
186
|
+
object_in_rev?(obj, rev) && categories[:Outline] << obj
|
187
|
+
end
|
188
|
+
|
189
|
+
categories.each do |name, data|
|
190
|
+
next if data.empty?
|
191
|
+
object_stream_count = 0
|
192
|
+
category_sum = data.sum do |o|
|
193
|
+
object_stream_count += 1 unless o.data.size
|
194
|
+
o.data.size.to_i
|
195
|
+
end
|
196
|
+
object_stream_count = object_stream_count > 0 ? "/#{object_stream_count}" : ''
|
197
|
+
size = human_readable_file_size(category_sum)
|
198
|
+
puts "#{name.to_s.ljust(15)} #{size.rjust(8)} (#{data.count}#{object_stream_count})"
|
199
|
+
end
|
200
|
+
puts "#{'Total'.ljust(15)} #{human_readable_file_size(sum).rjust(8)} (#{count})"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
# Returns +true+ if the +obj+ is in the given +rev+.
|
208
|
+
def object_in_rev?(obj, rev)
|
209
|
+
obj && rev.object(obj) == obj
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
data/lib/hexapdf/cli.rb
CHANGED
@@ -48,6 +48,7 @@ require 'hexapdf/cli/watermark'
|
|
48
48
|
require 'hexapdf/cli/image2pdf'
|
49
49
|
require 'hexapdf/cli/form'
|
50
50
|
require 'hexapdf/cli/fonts'
|
51
|
+
require 'hexapdf/cli/usage'
|
51
52
|
require 'hexapdf/version'
|
52
53
|
require 'hexapdf/document'
|
53
54
|
|
@@ -107,6 +108,7 @@ module HexaPDF
|
|
107
108
|
add_command(HexaPDF::CLI::Image2PDF.new)
|
108
109
|
add_command(HexaPDF::CLI::Form.new)
|
109
110
|
add_command(HexaPDF::CLI::Fonts.new)
|
111
|
+
add_command(HexaPDF::CLI::Usage.new)
|
110
112
|
add_command(CmdParse::HelpCommand.new)
|
111
113
|
version_command = CmdParse::VersionCommand.new(add_switches: false)
|
112
114
|
add_command(version_command)
|
@@ -255,6 +255,12 @@ module HexaPDF
|
|
255
255
|
# PDF defines a standard security handler that is implemented
|
256
256
|
# (HexaPDF::Encryption::StandardSecurityHandler) and assigned the :Standard name.
|
257
257
|
#
|
258
|
+
# encryption.on_decryption_error::
|
259
|
+
# Callback hook when HexaPDF encounters a decryption error that can potentially be ignored.
|
260
|
+
#
|
261
|
+
# The value needs to be an object that responds to \#call(obj, message) and returns +true+ if
|
262
|
+
# an error should be raised.
|
263
|
+
#
|
258
264
|
# encryption.sub_filter_map::
|
259
265
|
# A mapping from a PDF name (a Symbol) to a security handler class (see
|
260
266
|
# HexaPDF::Encryption::SecurityHandler). If the value is a String, it should contain the name
|
@@ -475,7 +481,7 @@ module HexaPDF
|
|
475
481
|
'acro_form.fallback_font' => 'Helvetica',
|
476
482
|
'acro_form.on_invalid_value' => proc do |field, value|
|
477
483
|
raise HexaPDF::Error, "Invalid value #{value.inspect} for " \
|
478
|
-
"#{field.concrete_field_type} field #{field.full_field_name}"
|
484
|
+
"#{field.concrete_field_type} field named '#{field.full_field_name}'"
|
479
485
|
end,
|
480
486
|
'acro_form.text_field.default_width' => 100,
|
481
487
|
'debug' => false,
|
@@ -488,6 +494,9 @@ module HexaPDF
|
|
488
494
|
'encryption.filter_map' => {
|
489
495
|
Standard: 'HexaPDF::Encryption::StandardSecurityHandler',
|
490
496
|
},
|
497
|
+
'encryption.on_decryption_error' => proc do |_obj, _error|
|
498
|
+
false
|
499
|
+
end,
|
491
500
|
'encryption.sub_filter_map' => {},
|
492
501
|
'filter.map' => {
|
493
502
|
ASCIIHexDecode: 'HexaPDF::Filter::ASCIIHexDecode',
|
@@ -523,6 +532,7 @@ module HexaPDF
|
|
523
532
|
'HexaPDF::FontLoader::Standard14',
|
524
533
|
'HexaPDF::FontLoader::FromConfiguration',
|
525
534
|
'HexaPDF::FontLoader::FromFile',
|
535
|
+
'HexaPDF::FontLoader::VariantFromName',
|
526
536
|
],
|
527
537
|
'graphic_object.arc.max_curves' => 6,
|
528
538
|
'graphic_object.map' => {
|
@@ -2245,6 +2245,8 @@ module HexaPDF
|
|
2245
2245
|
# canvas.text("Times at size 10", at: [10, 150])
|
2246
2246
|
# canvas.font("Times", variant: :bold_italic, size: 15)
|
2247
2247
|
# canvas.text("Times bold+italic at size 15", at: [10, 100])
|
2248
|
+
# canvas.font("Times bold")
|
2249
|
+
# canvas.text("Times bold using the variant-from-name method", at: [10, 50])
|
2248
2250
|
#
|
2249
2251
|
# See: PDF2.0 s9.2.2, #font_size, #text
|
2250
2252
|
def font(name = nil, size: nil, **options)
|
@@ -92,6 +92,13 @@ module HexaPDF
|
|
92
92
|
#
|
93
93
|
# style.font = ['Helvetica', variant: :bold]
|
94
94
|
#
|
95
|
+
# Helvetica in bold could also be set the conventional way:
|
96
|
+
#
|
97
|
+
# style.font = 'Helvetica bold'
|
98
|
+
#
|
99
|
+
# However, using an array it is also possible to specify other options when setting a font,
|
100
|
+
# like the :subset option.
|
101
|
+
#
|
95
102
|
class Layout
|
96
103
|
|
97
104
|
# This class is used when a box can contain child boxes and the creation of such boxes should
|
@@ -539,7 +546,7 @@ module HexaPDF
|
|
539
546
|
# # assign the predefined style :cell_text to all texts
|
540
547
|
# args[] = {style: :cell_text}
|
541
548
|
# # row 0 has a grey background and bold text
|
542
|
-
# args[0] = {font:
|
549
|
+
# args[0] = {font: 'Helvetica bold', cell: {background_color: 'eee'}}
|
543
550
|
# # text in last column is right aligned
|
544
551
|
# args[0..-1, -1] = {text_align: :right}
|
545
552
|
# end
|
@@ -112,11 +112,15 @@ module HexaPDF
|
|
112
112
|
# It is assumed that the initialization vector is included in the first BLOCK_SIZE bytes
|
113
113
|
# of the data. After the decryption the PKCS#5 padding is removed.
|
114
114
|
#
|
115
|
+
# If a problem is encountered, an error message is yielded. If no block is given or if the
|
116
|
+
# supplied block returns +true+, an error is raised.
|
117
|
+
#
|
115
118
|
# See: PDF2.0 s7.6.3
|
116
|
-
def decrypt(key, data)
|
119
|
+
def decrypt(key, data) # :yields: error_message
|
117
120
|
return data if data.empty? # Handle invalid files with empty strings
|
118
121
|
if data.length % BLOCK_SIZE != 0 || data.length < BLOCK_SIZE
|
119
|
-
|
122
|
+
msg = "Invalid data for decryption, need 32 + 16*n bytes"
|
123
|
+
(!block_given? || yield(msg)) && raise(HexaPDF::EncryptionError, msg)
|
120
124
|
end
|
121
125
|
iv = data.slice!(0, BLOCK_SIZE)
|
122
126
|
# Handle invalid files with missing padding
|
@@ -126,8 +130,9 @@ module HexaPDF
|
|
126
130
|
# Returns a Fiber object that decrypts the data from the given source fiber with the
|
127
131
|
# +key+.
|
128
132
|
#
|
129
|
-
# Padding
|
130
|
-
|
133
|
+
# Padding, the initialization vector and an optionally given block are handled like in
|
134
|
+
# #decrypt.
|
135
|
+
def decryption_fiber(key, source) # :yields: error_message
|
131
136
|
Fiber.new do
|
132
137
|
data = ''.b
|
133
138
|
while data.length < BLOCK_SIZE && source.alive? && (new_data = source.resume)
|
@@ -145,8 +150,10 @@ module HexaPDF
|
|
145
150
|
end
|
146
151
|
|
147
152
|
if data.length % BLOCK_SIZE != 0
|
148
|
-
|
149
|
-
|
153
|
+
msg = "Invalid data for decryption, need 32 + 16*n bytes"
|
154
|
+
(!block_given? || yield(msg)) && raise(HexaPDF::EncryptionError, msg)
|
155
|
+
end
|
156
|
+
if data.empty?
|
150
157
|
data # Handle invalid files with missing padding
|
151
158
|
else
|
152
159
|
unpad(algorithm.process(data))
|
@@ -153,17 +153,18 @@ module HexaPDF
|
|
153
153
|
|
154
154
|
# Creates a new encrypted stream data object by utilizing the given stream data object +obj+
|
155
155
|
# as template. The arguments +key+ and +algorithm+ are used for decrypting purposes.
|
156
|
-
def initialize(obj, key, algorithm)
|
156
|
+
def initialize(obj, key, algorithm, &error_block)
|
157
157
|
obj.instance_variables.each {|v| instance_variable_set(v, obj.instance_variable_get(v)) }
|
158
158
|
@key = key
|
159
159
|
@algorithm = algorithm
|
160
|
+
@error_block = error_block
|
160
161
|
end
|
161
162
|
|
162
163
|
alias undecrypted_fiber fiber
|
163
164
|
|
164
165
|
# Returns a fiber like HexaPDF::StreamData#fiber, but one wrapped in a decrypting fiber.
|
165
166
|
def fiber(*args)
|
166
|
-
@algorithm.decryption_fiber(@key, super(*args))
|
167
|
+
@algorithm.decryption_fiber(@key, super(*args), &@error_block)
|
167
168
|
end
|
168
169
|
|
169
170
|
end
|
@@ -268,17 +269,18 @@ module HexaPDF
|
|
268
269
|
def decrypt(obj)
|
269
270
|
return obj if @is_encrypt_dict[obj] || obj.type == :XRef
|
270
271
|
|
272
|
+
error_proc = proc {|msg| document.config['encryption.on_decryption_error'].call(obj, msg) }
|
271
273
|
key = object_key(obj.oid, obj.gen, string_algorithm)
|
272
274
|
each_string_in_object(obj.value) do |str|
|
273
275
|
next if str.empty? || (obj.type == :Sig && obj[:Contents].equal?(str))
|
274
|
-
str.replace(string_algorithm.decrypt(key, str))
|
276
|
+
str.replace(string_algorithm.decrypt(key, str, &error_proc))
|
275
277
|
end
|
276
278
|
|
277
279
|
if obj.kind_of?(HexaPDF::Stream) && obj.raw_stream.filter[0] != :Crypt
|
278
280
|
unless string_algorithm == stream_algorithm
|
279
281
|
key = object_key(obj.oid, obj.gen, stream_algorithm)
|
280
282
|
end
|
281
|
-
obj.data.stream = EncryptedStreamData.new(obj.raw_stream, key, stream_algorithm)
|
283
|
+
obj.data.stream = EncryptedStreamData.new(obj.raw_stream, key, stream_algorithm, &error_proc)
|
282
284
|
end
|
283
285
|
|
284
286
|
obj
|
@@ -163,11 +163,7 @@ module HexaPDF
|
|
163
163
|
dest = tokenizer.next_object
|
164
164
|
|
165
165
|
if dest.kind_of?(String)
|
166
|
-
|
167
|
-
code1.upto(code2) do |code|
|
168
|
-
cmap.add_unicode_mapping(code, +'' << codepoint)
|
169
|
-
codepoint += 1
|
170
|
-
end
|
166
|
+
cmap.add_unicode_range_mapping(code1, code2, dest.unpack("n*"))
|
171
167
|
elsif dest.kind_of?(Array)
|
172
168
|
code1.upto(code2) do |code|
|
173
169
|
str = dest[code - code1].encode!(::Encoding::UTF_8, ::Encoding::UTF_16BE)
|
data/lib/hexapdf/font/cmap.rb
CHANGED
@@ -100,8 +100,10 @@ module HexaPDF
|
|
100
100
|
# The writing mode of the CMap: 0 for horizontal, 1 for vertical writing.
|
101
101
|
attr_accessor :wmode
|
102
102
|
|
103
|
-
attr_reader :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping
|
104
|
-
|
103
|
+
attr_reader :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping,
|
104
|
+
:unicode_range_mappings # :nodoc:
|
105
|
+
protected :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping,
|
106
|
+
:unicode_range_mappings
|
105
107
|
|
106
108
|
# Creates a new CMap object.
|
107
109
|
def initialize
|
@@ -109,6 +111,7 @@ module HexaPDF
|
|
109
111
|
@cid_mapping = {}
|
110
112
|
@cid_range_mappings = []
|
111
113
|
@unicode_mapping = {}
|
114
|
+
@unicode_range_mappings = []
|
112
115
|
end
|
113
116
|
|
114
117
|
# Add all mappings from the given CMap to this CMap.
|
@@ -117,6 +120,7 @@ module HexaPDF
|
|
117
120
|
@cid_mapping.merge!(cmap.cid_mapping)
|
118
121
|
@cid_range_mappings.concat(cmap.cid_range_mappings)
|
119
122
|
@unicode_mapping.merge!(cmap.unicode_mapping)
|
123
|
+
@unicode_range_mappings.concat(cmap.unicode_range_mappings)
|
120
124
|
end
|
121
125
|
|
122
126
|
# Add a codespace range using an array of ranges for the individual bytes.
|
@@ -193,10 +197,25 @@ module HexaPDF
|
|
193
197
|
@unicode_mapping[code] = string
|
194
198
|
end
|
195
199
|
|
200
|
+
# Adds a mapping from a range of character codes to strings starting with the given 16-bit
|
201
|
+
# integer values (representing the raw UTF-16BE characters).
|
202
|
+
def add_unicode_range_mapping(start_code, end_code, start_values)
|
203
|
+
@unicode_range_mappings << [start_code..end_code, start_values]
|
204
|
+
end
|
205
|
+
|
196
206
|
# Returns the Unicode string in UTF-8 encoding for the given character code, or +nil+ if no
|
197
207
|
# mapping was found.
|
198
208
|
def to_unicode(code)
|
199
|
-
unicode_mapping
|
209
|
+
@unicode_mapping.fetch(code) do
|
210
|
+
@unicode_range_mappings.reverse_each do |range, start_values|
|
211
|
+
if range.cover?(code)
|
212
|
+
str = start_values[0..-2].append(start_values[-1] + code - range.first).
|
213
|
+
pack('n*').encode(::Encoding::UTF_8, ::Encoding::UTF_16BE)
|
214
|
+
return @unicode_mapping[code] = str
|
215
|
+
end
|
216
|
+
end
|
217
|
+
nil
|
218
|
+
end
|
200
219
|
end
|
201
220
|
|
202
221
|
end
|
@@ -62,7 +62,7 @@ module HexaPDF
|
|
62
62
|
# Specifies whether the font should be subset if possible.
|
63
63
|
#
|
64
64
|
# This method uses the FromFile font loader behind the scenes.
|
65
|
-
def self.call(document, name, variant: :none, subset: true)
|
65
|
+
def self.call(document, name, variant: :none, subset: true, **)
|
66
66
|
file = document.config['font.map'].dig(name, variant)
|
67
67
|
return nil if file.nil?
|
68
68
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# This file is part of HexaPDF.
|
5
|
+
#
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
7
|
+
# Copyright (C) 2014-2024 Thomas Leitner
|
8
|
+
#
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
16
|
+
#
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
20
|
+
# License for more details.
|
21
|
+
#
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
24
|
+
#
|
25
|
+
# The interactive user interfaces in modified source and object code
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
28
|
+
#
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
31
|
+
# is created or manipulated using HexaPDF.
|
32
|
+
#
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
35
|
+
#++
|
36
|
+
|
37
|
+
require 'hexapdf/font/true_type_wrapper'
|
38
|
+
require 'hexapdf/font_loader/from_file'
|
39
|
+
|
40
|
+
module HexaPDF
|
41
|
+
module FontLoader
|
42
|
+
|
43
|
+
# This module translates font names like 'Helvetica bold' into the arguments 'Helvetica' and
|
44
|
+
# {variant: :bold}.
|
45
|
+
#
|
46
|
+
# This eases the usage of font names where specifying a font variant is not straight-forward.
|
47
|
+
# The actual loading of the font is deferred to Document::Fonts#add.
|
48
|
+
#
|
49
|
+
# Note that this should be the last entry in the list of font loaders to ensure correct
|
50
|
+
# operation.
|
51
|
+
module VariantFromName
|
52
|
+
|
53
|
+
# Returns a font wrapper for the given font by splitting the font name into the font name part
|
54
|
+
# and variant selector part. If the the resulting font cannot be resolved, +nil+ is returned.
|
55
|
+
#
|
56
|
+
# A font name should have the form 'Fontname selector' where selector can be 'bold', 'italic'
|
57
|
+
# or 'bold_italic', for example 'Helvetica bold'.
|
58
|
+
#
|
59
|
+
# Note that a supplied :variant keyword argument is ignored!
|
60
|
+
def self.call(document, name, recursive_invocation: false, **options)
|
61
|
+
return if recursive_invocation
|
62
|
+
name, variant = name.split(/ (?=(?:bold|italic|bold_italic)\z)/, 2)
|
63
|
+
return if variant.nil?
|
64
|
+
|
65
|
+
options[:variant] = variant.to_sym
|
66
|
+
document.fonts.add(name, **options, recursive_invocation: true) rescue nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/hexapdf/font_loader.rb
CHANGED
@@ -88,6 +88,7 @@ module HexaPDF
|
|
88
88
|
autoload(:Standard14, 'hexapdf/font_loader/standard14')
|
89
89
|
autoload(:FromConfiguration, 'hexapdf/font_loader/from_configuration')
|
90
90
|
autoload(:FromFile, 'hexapdf/font_loader/from_file')
|
91
|
+
autoload(:VariantFromName, 'hexapdf/font_loader/variant_from_name')
|
91
92
|
|
92
93
|
end
|
93
94
|
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -614,7 +614,7 @@ module HexaPDF
|
|
614
614
|
# The font to be used, must be set to a valid font wrapper object before it can be used.
|
615
615
|
#
|
616
616
|
# HexaPDF::Composer handles this property specially in that it resolves a set string or array
|
617
|
-
# to a font wrapper object before doing else with the style object.
|
617
|
+
# to a font wrapper object before doing anything else with the style object.
|
618
618
|
#
|
619
619
|
# This is the only style property without a default value!
|
620
620
|
#
|
@@ -624,11 +624,12 @@ module HexaPDF
|
|
624
624
|
#
|
625
625
|
# #>pdf-composer100
|
626
626
|
# composer.text("Helvetica", font: composer.document.fonts.add("Helvetica"))
|
627
|
-
# composer.text("Courier", font: "Courier")
|
627
|
+
# composer.text("Courier", font: "Courier")
|
628
628
|
#
|
629
629
|
# helvetica_bold = composer.document.fonts.add("Helvetica", variant: :bold)
|
630
630
|
# composer.text("Helvetica Bold", font: helvetica_bold)
|
631
|
-
# composer.text("Courier Bold", font:
|
631
|
+
# composer.text("Courier Bold", font: "Courier bold")
|
632
|
+
# composer.text("Courier Bold also", font: ["Courier", variant: :bold])
|
632
633
|
|
633
634
|
##
|
634
635
|
# :method: font_size
|
@@ -1413,7 +1414,7 @@ module HexaPDF
|
|
1413
1414
|
# #>pdf-composer100
|
1414
1415
|
# composer.text("This is some longer text that does appear in two lines.")
|
1415
1416
|
# composer.text("This is some longer text that does not appear in two lines.",
|
1416
|
-
# height: 15,
|
1417
|
+
# height: 15, overflow: :truncate)
|
1417
1418
|
|
1418
1419
|
[
|
1419
1420
|
[:font, "raise HexaPDF::Error, 'No font set'"],
|