hexapdf 0.20.2 → 0.21.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 +49 -0
- data/README.md +5 -3
- data/Rakefile +10 -1
- data/examples/018-composer.rb +10 -10
- data/lib/hexapdf/cli/batch.rb +4 -6
- data/lib/hexapdf/cli/form.rb +9 -1
- data/lib/hexapdf/cli/info.rb +5 -1
- data/lib/hexapdf/cli/inspect.rb +59 -0
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/composer.rb +147 -53
- data/lib/hexapdf/configuration.rb +7 -3
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/color_space.rb +1 -1
- data/lib/hexapdf/content/operator.rb +7 -7
- data/lib/hexapdf/content/parser.rb +3 -3
- data/lib/hexapdf/content/processor.rb +9 -9
- data/lib/hexapdf/document/signatures.rb +5 -4
- data/lib/hexapdf/document.rb +7 -0
- data/lib/hexapdf/encryption/security_handler.rb +5 -1
- data/lib/hexapdf/font/true_type/font.rb +7 -7
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type_wrapper.rb +9 -14
- data/lib/hexapdf/font/type1/font.rb +10 -12
- data/lib/hexapdf/font/type1_wrapper.rb +15 -17
- data/lib/hexapdf/layout/box.rb +12 -9
- data/lib/hexapdf/layout/image_box.rb +1 -1
- data/lib/hexapdf/layout/style.rb +28 -8
- data/lib/hexapdf/layout/text_fragment.rb +10 -9
- data/lib/hexapdf/parser.rb +5 -0
- data/lib/hexapdf/tokenizer.rb +3 -3
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +6 -4
- data/lib/hexapdf/type/acro_form/choice_field.rb +2 -2
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/annotation.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/resources.rb +4 -4
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
- data/lib/hexapdf/type/signature.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +3 -3
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +11 -3
- data/lib/hexapdf/xref_section.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +5 -5
- data/test/hexapdf/content/test_graphics_state.rb +1 -0
- data/test/hexapdf/content/test_operator.rb +2 -2
- data/test/hexapdf/content/test_processor.rb +1 -1
- data/test/hexapdf/encryption/test_security_handler.rb +11 -3
- data/test/hexapdf/encryption/test_standard_security_handler.rb +23 -29
- data/test/hexapdf/filter/test_predictor.rb +16 -20
- data/test/hexapdf/font/test_type1_wrapper.rb +5 -1
- data/test/hexapdf/font/true_type/table/common.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +1 -1
- data/test/hexapdf/image_loader/test_pdf.rb +6 -8
- data/test/hexapdf/image_loader/test_png.rb +2 -2
- data/test/hexapdf/layout/test_box.rb +11 -1
- data/test/hexapdf/layout/test_style.rb +23 -0
- data/test/hexapdf/layout/test_text_fragment.rb +21 -21
- data/test/hexapdf/test_composer.rb +115 -52
- data/test/hexapdf/test_dictionary.rb +2 -2
- data/test/hexapdf/test_document.rb +11 -9
- data/test/hexapdf/test_object.rb +1 -1
- data/test/hexapdf/test_parser.rb +13 -7
- data/test/hexapdf/test_serializer.rb +20 -22
- data/test/hexapdf/test_stream.rb +7 -9
- data/test/hexapdf/test_writer.rb +13 -2
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
- data/test/hexapdf/type/acro_form/test_choice_field.rb +1 -1
- data/test/hexapdf/type/signature/common.rb +1 -1
- data/test/hexapdf/type/test_annotation.rb +7 -1
- data/test/hexapdf/type/test_font_type0.rb +1 -1
- data/test/hexapdf/type/test_font_type1.rb +7 -7
- data/test/hexapdf/type/test_image.rb +13 -17
- metadata +2 -2
|
@@ -160,7 +160,7 @@ module HexaPDF
|
|
|
160
160
|
end
|
|
161
161
|
return unless entry.kind_of?(HexaPDF::Stream)
|
|
162
162
|
|
|
163
|
-
if entry.type == :XObject && entry[:Subtype] == :Form
|
|
163
|
+
if entry.type == :XObject && entry[:Subtype] == :Form && !entry.instance_of?(HexaPDF::Stream)
|
|
164
164
|
entry
|
|
165
165
|
elsif (entry[:Type].nil? || entry[:Type] == :XObject) &&
|
|
166
166
|
(entry[:Subtype].nil? || entry[:Subtype] == :Form) && entry[:BBox]
|
|
@@ -141,7 +141,7 @@ module HexaPDF
|
|
|
141
141
|
registry = system_info[:Registry]
|
|
142
142
|
ordering = system_info[:Ordering]
|
|
143
143
|
if (encoding.kind_of?(Symbol) && HexaPDF::Font::CMap.predefined?(encoding.to_s) &&
|
|
144
|
-
encoding != :
|
|
144
|
+
encoding != :'Identity-H' && encoding != :'Identity-V') ||
|
|
145
145
|
(registry == "Adobe" && ['GB1', 'CNS1', 'Japan1', 'Korea1'].include?(ordering))
|
|
146
146
|
HexaPDF::Font::CMap.for_name("#{registry}-#{ordering}-UCS2")
|
|
147
147
|
end
|
|
@@ -59,7 +59,7 @@ module HexaPDF
|
|
|
59
59
|
def bounding_box
|
|
60
60
|
matrix = self[:FontMatrix]
|
|
61
61
|
bbox = self[:FontBBox].value
|
|
62
|
-
if matrix[3] < 0
|
|
62
|
+
if matrix[3] < 0 # Some writers invert the y-axis
|
|
63
63
|
bbox = bbox.dup
|
|
64
64
|
bbox[1], bbox[3] = -bbox[3], -bbox[1]
|
|
65
65
|
end
|
|
@@ -218,10 +218,7 @@ module HexaPDF
|
|
|
218
218
|
def perform_validation
|
|
219
219
|
super
|
|
220
220
|
val = self[:ProcSet]
|
|
221
|
-
if
|
|
222
|
-
yield("No procedure set specified", true)
|
|
223
|
-
self[:ProcSet] = [:PDF, :Text, :ImageB, :ImageC, :ImageI]
|
|
224
|
-
else
|
|
221
|
+
if val
|
|
225
222
|
if val.kind_of?(Symbol)
|
|
226
223
|
yield("Procedure set is a single value instead of an Array", true)
|
|
227
224
|
val = value[:ProcSet] = [val]
|
|
@@ -235,6 +232,9 @@ module HexaPDF
|
|
|
235
232
|
true
|
|
236
233
|
end
|
|
237
234
|
end
|
|
235
|
+
else
|
|
236
|
+
yield("No procedure set specified", true)
|
|
237
|
+
self[:ProcSet] = [:PDF, :Text, :ImageB, :ImageC, :ImageI]
|
|
238
238
|
end
|
|
239
239
|
end
|
|
240
240
|
|
|
@@ -100,7 +100,7 @@ module HexaPDF
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
|
|
103
|
-
unless key_usage.value.split(', ').include?("Digital Signature")
|
|
103
|
+
unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
|
|
104
104
|
result.log(:error, "Certificate key usage is missing 'Digital Signature'")
|
|
105
105
|
end
|
|
106
106
|
|
|
@@ -75,7 +75,7 @@ module HexaPDF
|
|
|
75
75
|
|
|
76
76
|
# For DocMDP, also used by UR
|
|
77
77
|
define_field :P, type: [Integer, Boolean]
|
|
78
|
-
define_field :V, type: Symbol, allowed_values: [:
|
|
78
|
+
define_field :V, type: Symbol, allowed_values: [:'1.2', :'2.2']
|
|
79
79
|
|
|
80
80
|
# For UR
|
|
81
81
|
define_field :Document, type: PDFArray
|
data/lib/hexapdf/type/trailer.rb
CHANGED
|
@@ -87,10 +87,10 @@ module HexaPDF
|
|
|
87
87
|
# Updates the second part of the /ID field (the first part should always be the same for a
|
|
88
88
|
# PDF file, the second part should change with each write).
|
|
89
89
|
def update_id
|
|
90
|
-
if
|
|
91
|
-
set_random_id
|
|
92
|
-
else
|
|
90
|
+
if self[:ID].kind_of?(PDFArray)
|
|
93
91
|
value[:ID][1] = Digest::MD5.digest(rand.to_s)
|
|
92
|
+
else
|
|
93
|
+
set_random_id
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
|
@@ -90,12 +90,20 @@ module HexaPDF
|
|
|
90
90
|
#
|
|
91
91
|
# For this method to work the document must have been created from an existing file.
|
|
92
92
|
def write_incremental
|
|
93
|
-
@document.revisions.parser
|
|
94
|
-
|
|
93
|
+
parser = @document.revisions.parser
|
|
94
|
+
|
|
95
|
+
_, orig_trailer = parser.load_revision(parser.startxref_offset)
|
|
96
|
+
orig_trailer = @document.wrap(orig_trailer, type: :XXTrailer)
|
|
97
|
+
if @document.revisions.current.trailer[:Encrypt]&.value != orig_trailer[:Encrypt]&.value
|
|
98
|
+
raise HexaPDF::Error, "Used encryption cannot be modified when doing incremental writing"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
parser.io.seek(0, IO::SEEK_SET)
|
|
102
|
+
IO.copy_stream(parser.io, @io)
|
|
95
103
|
@io << "\n"
|
|
96
104
|
|
|
97
105
|
@rev_size = @document.revisions.current.next_free_oid
|
|
98
|
-
@use_xref_streams =
|
|
106
|
+
@use_xref_streams = parser.contains_xref_streams?
|
|
99
107
|
|
|
100
108
|
revision = Revision.new(@document.revisions.current.trailer)
|
|
101
109
|
@document.revisions.each do |rev|
|
data/lib/hexapdf/xref_section.rb
CHANGED
|
@@ -60,9 +60,9 @@ module CommonTokenizerTests
|
|
|
60
60
|
'obj', 'endobj', 'f*', '*f', '{', '}',
|
|
61
61
|
"parentheses ( ) and (\nspecial \0053++characters\n (*!&}^% and so on).\n", '',
|
|
62
62
|
"Nov shmoz ka pop.", "\x90\x1F\xA3", "\x90\x1F\xA0",
|
|
63
|
-
:Name1, :ASomewhatLongerName, :
|
|
64
|
-
:
|
|
65
|
-
:
|
|
63
|
+
:Name1, :ASomewhatLongerName, :'A;Name_With-Various***Characters?',
|
|
64
|
+
:'1.2', :$$, :@pattern, :'.notdef', :'lime Green', :'paired()parentheses',
|
|
65
|
+
:'The_Key_of_F#_Minor', :AB, :"",
|
|
66
66
|
'[', 5, 6, :Name, ']', '[', 5, 6, :Name, ']',
|
|
67
67
|
'<<', :Name, 5, '>>'
|
|
68
68
|
].each {|t| t.force_encoding('BINARY') if t.respond_to?(:force_encoding) }
|
|
@@ -85,12 +85,12 @@ module CommonTokenizerTests
|
|
|
85
85
|
|
|
86
86
|
create_tokenizer("/Hößgang")
|
|
87
87
|
token = @tokenizer.next_token
|
|
88
|
-
assert_equal(:
|
|
88
|
+
assert_equal(:Hößgang, token)
|
|
89
89
|
assert_equal(Encoding::UTF_8, token.encoding)
|
|
90
90
|
|
|
91
91
|
create_tokenizer('/H#c3#b6#c3#9fgang')
|
|
92
92
|
token = @tokenizer.next_token
|
|
93
|
-
assert_equal(:
|
|
93
|
+
assert_equal(:Hößgang, token)
|
|
94
94
|
assert_equal(Encoding::UTF_8, token.encoding)
|
|
95
95
|
|
|
96
96
|
create_tokenizer('/H#E8lp')
|
|
@@ -391,7 +391,7 @@ describe_operator :CurveToNoSecondControlPoint, :y do
|
|
|
391
391
|
end
|
|
392
392
|
end
|
|
393
393
|
|
|
394
|
-
[:S, :s, :f, :F, 'f*'
|
|
394
|
+
[:S, :s, :f, :F, :'f*', :B, :'B*', :b, :'b*', :n].each do |sym|
|
|
395
395
|
describe_operator :EndPath, sym do
|
|
396
396
|
it "changes the graphics object to none" do
|
|
397
397
|
@processor.graphics_object = :path
|
|
@@ -401,7 +401,7 @@ end
|
|
|
401
401
|
end
|
|
402
402
|
end
|
|
403
403
|
|
|
404
|
-
[:W, 'W*'
|
|
404
|
+
[:W, :'W*'].each do |sym|
|
|
405
405
|
describe_operator :ClipPath, sym do
|
|
406
406
|
it "changes the graphics object to clipping_path for clip path operations" do
|
|
407
407
|
invoke
|
|
@@ -119,7 +119,7 @@ describe HexaPDF::Content::Processor do
|
|
|
119
119
|
@processor.process(:BT)
|
|
120
120
|
@processor.graphics_state.font = @font = @doc.add({Type: :Font, Subtype: :Type1,
|
|
121
121
|
Encoding: :WinAnsiEncoding,
|
|
122
|
-
BaseFont: :
|
|
122
|
+
BaseFont: :'Times-Roman'})
|
|
123
123
|
@processor.graphics_state.font_size = 10
|
|
124
124
|
@processor.graphics_state.text_rise = 10
|
|
125
125
|
@processor.graphics_state.character_spacing = 1
|
|
@@ -294,9 +294,17 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
|
294
294
|
assert_equal('string', obj.stream)
|
|
295
295
|
end
|
|
296
296
|
|
|
297
|
-
it "doesn't decrypt a document's Encrypt
|
|
298
|
-
@document
|
|
299
|
-
|
|
297
|
+
it "doesn't decrypt a document's Encrypt dictionaries" do
|
|
298
|
+
@document = HexaPDF::Document.new
|
|
299
|
+
@document.trailer[:Encrypt] = @document.add({Key: "Something"})
|
|
300
|
+
@document.revisions.add
|
|
301
|
+
@document.trailer[:Encrypt] = @document.add({Key: "Otherthing"})
|
|
302
|
+
@handler = TestHandler.new(@document)
|
|
303
|
+
|
|
304
|
+
assert_equal("Something",
|
|
305
|
+
@handler.decrypt(@document.revisions[0].trailer[:Encrypt])[:Key])
|
|
306
|
+
assert_equal("Otherthing",
|
|
307
|
+
@handler.decrypt(@document.revisions[1].trailer[:Encrypt])[:Key])
|
|
300
308
|
end
|
|
301
309
|
|
|
302
310
|
it "defers handling encryption to a Crypt filter is specified" do
|
|
@@ -62,29 +62,25 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
|
62
62
|
test_files.each do |file|
|
|
63
63
|
basename = File.basename(file)
|
|
64
64
|
it "can decrypt, encrypt and decrypt the encrypted file #{basename} with the user password" do
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
flunk("Error processing #{basename}: #{e}")
|
|
76
|
-
end
|
|
65
|
+
doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
|
|
66
|
+
decryption_opts: {password: user_password})
|
|
67
|
+
assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
|
|
68
|
+
|
|
69
|
+
out = StringIO.new(''.b)
|
|
70
|
+
HexaPDF::Writer.new(doc, out).write
|
|
71
|
+
doc = HexaPDF::Document.new(io: out, decryption_opts: {password: user_password})
|
|
72
|
+
assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
|
|
73
|
+
rescue HexaPDF::EncryptionError => e
|
|
74
|
+
flunk("Error processing #{basename}: #{e}")
|
|
77
75
|
end
|
|
78
76
|
|
|
79
77
|
unless basename.start_with?("userpwd")
|
|
80
78
|
it "can decrypt the encrypted file #{basename} with the owner password" do
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
flunk("Error processing #{basename}: #{e}")
|
|
87
|
-
end
|
|
79
|
+
doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
|
|
80
|
+
decryption_opts: {password: owner_password})
|
|
81
|
+
assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
|
|
82
|
+
rescue HexaPDF::EncryptionError => e
|
|
83
|
+
flunk("Error processing #{basename}: #{e}")
|
|
88
84
|
end
|
|
89
85
|
end
|
|
90
86
|
end
|
|
@@ -97,16 +93,14 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
|
97
93
|
it "can encrypt and then decrypt with all encryption variations" do
|
|
98
94
|
{arc4: [40, 48, 128], aes: [128, 256]}.each do |algorithm, key_lengths|
|
|
99
95
|
key_lengths.each do |key_length|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
flunk("Error using variation: #{algorithm} #{key_length} bits\n" << e.message)
|
|
109
|
-
end
|
|
96
|
+
doc = HexaPDF::Document.new
|
|
97
|
+
doc.encrypt(algorithm: algorithm, key_length: key_length)
|
|
98
|
+
sio = StringIO.new
|
|
99
|
+
doc.write(sio)
|
|
100
|
+
doc = HexaPDF::Document.new(io: sio)
|
|
101
|
+
assert_kind_of(Time, doc.trailer.info[:ModDate], "alg: #{algorithm} #{key_length} bits")
|
|
102
|
+
rescue HexaPDF::Error => e
|
|
103
|
+
flunk("Error using variation: #{algorithm} #{key_length} bits\n" << e.message)
|
|
110
104
|
end
|
|
111
105
|
end
|
|
112
106
|
end
|
|
@@ -106,17 +106,15 @@ describe HexaPDF::Filter::Predictor do
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
it "fails if the last row is missing data and 'filter.predictor.strict' is true " do
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
collector(encoder)
|
|
116
|
-
end
|
|
117
|
-
ensure
|
|
118
|
-
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
|
109
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = true
|
|
110
|
+
assert_raises(HexaPDF::FilterError) do
|
|
111
|
+
data = @testcases['up']
|
|
112
|
+
encoder = @obj.png_execute(:encoder, feeder(data[:source][0..-2], 1), data[:Predictor],
|
|
113
|
+
data[:Colors], data[:BitsPerComponent], data[:Columns])
|
|
114
|
+
collector(encoder)
|
|
119
115
|
end
|
|
116
|
+
ensure
|
|
117
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
|
120
118
|
end
|
|
121
119
|
end
|
|
122
120
|
|
|
@@ -139,17 +137,15 @@ describe HexaPDF::Filter::Predictor do
|
|
|
139
137
|
end
|
|
140
138
|
|
|
141
139
|
it "fails if the last row is missing data and 'filter.predictor.strict' is true " do
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
collector(encoder)
|
|
149
|
-
end
|
|
150
|
-
ensure
|
|
151
|
-
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
|
140
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = true
|
|
141
|
+
assert_raises(HexaPDF::FilterError) do
|
|
142
|
+
data = @testcases['up']
|
|
143
|
+
encoder = @obj.png_execute(:decoder, feeder(data[:result][0..-2], 1), data[:Predictor],
|
|
144
|
+
data[:Colors], data[:BitsPerComponent], data[:Columns])
|
|
145
|
+
collector(encoder)
|
|
152
146
|
end
|
|
147
|
+
ensure
|
|
148
|
+
HexaPDF::GlobalConfiguration['filter.predictor.strict'] = false
|
|
153
149
|
end
|
|
154
150
|
end
|
|
155
151
|
end
|
|
@@ -14,7 +14,7 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
|
14
14
|
|
|
15
15
|
it "can be used with an existing PDF object" do
|
|
16
16
|
font = @doc.add({Type: :Font, Subtype: :Type1, Encoding: {Differences: [65, :B]},
|
|
17
|
-
BaseFont: :
|
|
17
|
+
BaseFont: :'Times-Roman'})
|
|
18
18
|
wrapper = HexaPDF::Font::Type1Wrapper.new(@doc, FONT_TIMES, pdf_object: font)
|
|
19
19
|
assert_equal([:B, :E, :A, :S, :T], wrapper.decode_utf8("BEAST").map(&:name))
|
|
20
20
|
assert_equal("A", wrapper.encode(wrapper.glyph(:A)))
|
|
@@ -103,5 +103,9 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
|
103
103
|
it "sets the circular reference" do
|
|
104
104
|
assert_same(@times_wrapper, @times_wrapper.pdf_object.font_wrapper)
|
|
105
105
|
end
|
|
106
|
+
|
|
107
|
+
it "makes sure that the PDF dictionaries are indirect" do
|
|
108
|
+
assert(@times_wrapper.pdf_object.indirect?)
|
|
109
|
+
end
|
|
106
110
|
end
|
|
107
111
|
end
|
|
@@ -16,7 +16,7 @@ module TestHelper
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def create_table(name, data = nil, standalone: false)
|
|
19
|
-
font, entry =
|
|
19
|
+
font, entry = standalone ? set_up_stub_true_type_font(register_vars: false) : [@font, @entry]
|
|
20
20
|
if data
|
|
21
21
|
font.io.string = data
|
|
22
22
|
entry.length = font.io.length
|
|
@@ -11,7 +11,7 @@ describe HexaPDF::Font::TrueType::Table::Cmap do
|
|
|
11
11
|
[0, 1, 28],
|
|
12
12
|
[3, 1, 28 + f0.length],
|
|
13
13
|
[1, 0, 28],
|
|
14
|
-
].map {|a| a.pack('n2N') }.join
|
|
14
|
+
].map {|a| a.pack('n2N') }.join << f0 << [10, 22, 0, 0, 2, 10, 13].pack('nN2N2n2')
|
|
15
15
|
set_up_stub_true_type_font(data)
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -55,7 +55,7 @@ describe HexaPDF::Font::TrueType::Table::CmapSubtable do
|
|
|
55
55
|
|
|
56
56
|
it "works for format 2" do
|
|
57
57
|
f2 = ([0, 8] + [0] * 254).pack('n*') + \
|
|
58
|
-
[[0, 256, 0, 2 + 8], [0x33, 3, 5, 2 + 256 * 2]].map {|a| a.pack('n2s>n') }.join
|
|
58
|
+
[[0, 256, 0, 2 + 8], [0x33, 3, 5, 2 + 256 * 2]].map {|a| a.pack('n2s>n') }.join + \
|
|
59
59
|
((0..255).to_a + [35, 65534, 0]).pack('n*')
|
|
60
60
|
t = table([2, f2.length + 6, 0].pack('n3') << f2)
|
|
61
61
|
assert_nil(t[0x0132])
|
|
@@ -33,14 +33,12 @@ describe HexaPDF::ImageLoader::PDF do
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
it "works for PDF files using a string object and use_stringio=false" do
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
file.close if file.path == @pdf && !file.closed?
|
|
43
|
-
end
|
|
36
|
+
@doc.config['image_loader.pdf.use_stringio'] = false
|
|
37
|
+
form = @loader.load(@doc, @pdf)
|
|
38
|
+
assert_equal(:Form, form[:Subtype])
|
|
39
|
+
ensure
|
|
40
|
+
ObjectSpace.each_object(File) do |file|
|
|
41
|
+
file.close if file.path == @pdf && !file.closed?
|
|
44
42
|
end
|
|
45
43
|
end
|
|
46
44
|
end
|
|
@@ -30,11 +30,11 @@ describe HexaPDF::ImageLoader::PNG do
|
|
|
30
30
|
assert_equal(height, image[:Height])
|
|
31
31
|
assert_equal(bpc, image[:BitsPerComponent])
|
|
32
32
|
assert_equal(color_space, @doc.unwrap(image[:ColorSpace])) if color_space
|
|
33
|
-
data = stream.map {|row| [row.map {|i| i.to_s(2).rjust(bpc, '0') }.join
|
|
33
|
+
data = stream.map {|row| [row.map {|i| i.to_s(2).rjust(bpc, '0') }.join].pack('B*') }.join
|
|
34
34
|
assert_equal(data, image.stream)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
#
|
|
37
|
+
# NOTE: colors and image data for comparisons were extracted using GIMP and its color tools
|
|
38
38
|
describe "load" do
|
|
39
39
|
before do
|
|
40
40
|
@greyscale_1bit_data = [[1, 1, 0, 0, 0],
|
|
@@ -17,11 +17,21 @@ describe HexaPDF::Layout::Box do
|
|
|
17
17
|
assert_same(block, box.instance_eval { @draw_block })
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
it "allows specifying style
|
|
20
|
+
it "allows specifying a style object" do
|
|
21
|
+
box = HexaPDF::Layout::Box.create(style: {background_color: 20})
|
|
22
|
+
assert_equal(20, box.style.background_color)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "allows specifying style properties" do
|
|
21
26
|
box = HexaPDF::Layout::Box.create(background_color: 20)
|
|
22
27
|
assert_equal(20, box.style.background_color)
|
|
23
28
|
end
|
|
24
29
|
|
|
30
|
+
it "applies the additional style properties to the style object" do
|
|
31
|
+
box = HexaPDF::Layout::Box.create(style: {background_color: 20}, background_color: 15)
|
|
32
|
+
assert_equal(15, box.style.background_color)
|
|
33
|
+
end
|
|
34
|
+
|
|
25
35
|
it "takes content width and height" do
|
|
26
36
|
box = HexaPDF::Layout::Box.create(width: 100, height: 200, content_box: true,
|
|
27
37
|
padding: [10, 8, 6, 4],
|
|
@@ -604,6 +604,26 @@ describe HexaPDF::Layout::Style do
|
|
|
604
604
|
end
|
|
605
605
|
end
|
|
606
606
|
|
|
607
|
+
describe "self.create" do
|
|
608
|
+
it "returns the provided style argument" do
|
|
609
|
+
assert_same(@style, HexaPDF::Layout::Style.create(@style))
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
it "creates a new Style object based on the passed hash" do
|
|
613
|
+
style = HexaPDF::Layout::Style.create(font_size: 10, fill_color: 'green')
|
|
614
|
+
assert_equal(10, style.font_size)
|
|
615
|
+
assert_equal('green', style.fill_color)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
it "creates an empty Style object if nil is passed" do
|
|
619
|
+
assert_kind_of(HexaPDF::Layout::Style, HexaPDF::Layout::Style.create(nil))
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
it "raises an error if an invalid object is provided" do
|
|
623
|
+
assert_raises(ArgumentError) { HexaPDF::Layout::Style.create(5) }
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
607
627
|
it "can assign values on initialization" do
|
|
608
628
|
style = HexaPDF::Layout::Style.new(font_size: 10)
|
|
609
629
|
assert_equal(10, style.font_size)
|
|
@@ -692,6 +712,9 @@ describe HexaPDF::Layout::Style do
|
|
|
692
712
|
|
|
693
713
|
@style.stroke_dash_pattern(5, 2)
|
|
694
714
|
assert_equal([[5], 2], @style.stroke_dash_pattern.to_operands)
|
|
715
|
+
|
|
716
|
+
@style.line_spacing(1.2)
|
|
717
|
+
assert_equal([:proportional, 1.2], [@style.line_spacing.type, @style.line_spacing.value])
|
|
695
718
|
end
|
|
696
719
|
|
|
697
720
|
it "allows checking for valid values" do
|
|
@@ -20,7 +20,7 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
describe "create" do
|
|
23
|
-
it "creates a TextFragment from text and
|
|
23
|
+
it "creates a TextFragment from text and style" do
|
|
24
24
|
frag = HexaPDF::Layout::TextFragment.create("Tom", font: @font, font_size: 20,
|
|
25
25
|
font_features: {kern: true})
|
|
26
26
|
assert_equal(4, frag.items.length)
|
|
@@ -180,16 +180,16 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
180
180
|
[:set_line_width, [5]],
|
|
181
181
|
[:set_line_cap_style, [1]],
|
|
182
182
|
[:set_line_dash_pattern, [[5], 0]]],
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
back: [[:end_text],
|
|
184
|
+
[:save_graphics_state],
|
|
185
|
+
[:set_device_gray_stroking_color, [0]],
|
|
186
|
+
[:set_line_width, [@fragment.style.calculated_underline_thickness]],
|
|
187
|
+
[:set_line_cap_style, [0]],
|
|
188
|
+
[:set_line_dash_pattern, [[], 0]],
|
|
189
|
+
[:move_to, [10, 15]],
|
|
190
|
+
[:line_to, [40.88, 15]],
|
|
191
|
+
[:stroke_path],
|
|
192
|
+
[:restore_graphics_state]])
|
|
193
193
|
end
|
|
194
194
|
|
|
195
195
|
it "draws the strikeout line" do
|
|
@@ -201,16 +201,16 @@ describe HexaPDF::Layout::TextFragment do
|
|
|
201
201
|
[:set_line_width, [5]],
|
|
202
202
|
[:set_line_cap_style, [1]],
|
|
203
203
|
[:set_line_dash_pattern, [[5], 0]]],
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
204
|
+
back: [[:end_text],
|
|
205
|
+
[:save_graphics_state],
|
|
206
|
+
[:set_device_gray_stroking_color, [0]],
|
|
207
|
+
[:set_line_width, [@fragment.style.calculated_strikeout_thickness]],
|
|
208
|
+
[:set_line_cap_style, [0]],
|
|
209
|
+
[:set_line_dash_pattern, [[], 0]],
|
|
210
|
+
[:move_to, [10, 21.01]],
|
|
211
|
+
[:line_to, [40.88, 21.01]],
|
|
212
|
+
[:stroke_path],
|
|
213
|
+
[:restore_graphics_state]])
|
|
214
214
|
end
|
|
215
215
|
end
|
|
216
216
|
|