hexapdf 0.20.3 → 0.21.1
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 +42 -1
- data/README.md +5 -3
- data/Rakefile +10 -1
- data/examples/018-composer.rb +10 -10
- data/examples/020-column_box.rb +57 -0
- data/lib/hexapdf/cli/batch.rb +4 -6
- 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/aes.rb +9 -5
- 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/column_box.rb +168 -0
- 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/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/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_aes.rb +8 -0
- 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 +2 -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_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 +4 -2
@@ -56,7 +56,7 @@ module HexaPDF
|
|
56
56
|
# Creates a new Image box object for the given +image+ argument which needs to be an image
|
57
57
|
# object (e.g. returned by HexaPDF::Document::Images#add).
|
58
58
|
def initialize(image, **kwargs)
|
59
|
-
super(**kwargs
|
59
|
+
super(**kwargs)
|
60
60
|
@image = image
|
61
61
|
end
|
62
62
|
|
data/lib/hexapdf/layout/style.rb
CHANGED
@@ -518,6 +518,27 @@ module HexaPDF
|
|
518
518
|
|
519
519
|
UNSET = ::Object.new # :nodoc:
|
520
520
|
|
521
|
+
# :call-seq:
|
522
|
+
# Style.create(style) -> style
|
523
|
+
# Style.create(properties_hash) -> style
|
524
|
+
#
|
525
|
+
# Creates a Style object based on the +style+ argument and returns it:
|
526
|
+
#
|
527
|
+
# * If +style+ is already a Style object, it is just returned.
|
528
|
+
#
|
529
|
+
# * If +style+ is a hash, a new Style object with the style properties specified by the hash
|
530
|
+
# * is created.
|
531
|
+
#
|
532
|
+
# * If +style+ is +nil+, a new Style object with only default values is created.
|
533
|
+
def self.create(style)
|
534
|
+
case style
|
535
|
+
when self then style
|
536
|
+
when Hash then new(**style)
|
537
|
+
when nil then new
|
538
|
+
else raise ArgumentError, "Invalid argument class #{style.class}"
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
521
542
|
# Creates a new Style object.
|
522
543
|
#
|
523
544
|
# The +properties+ hash may be used to set the initial values of properties by using keys
|
@@ -945,7 +966,8 @@ module HexaPDF
|
|
945
966
|
[:valign, :top, {valid_values: [:top, :center, :bottom]}],
|
946
967
|
[:text_indent, 0],
|
947
968
|
[:line_spacing, "LineSpacing.new(type: :single)",
|
948
|
-
{setter: "LineSpacing.new(**(value.kind_of?(Symbol)
|
969
|
+
{setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric) ? " \
|
970
|
+
"{type: value, value: extra_arg} : value))",
|
949
971
|
extra_args: ", extra_arg = nil"}],
|
950
972
|
[:last_line_gap, false, {valid_values: [true, false]}],
|
951
973
|
[:background_color, nil],
|
@@ -1115,13 +1137,11 @@ module HexaPDF
|
|
1115
1137
|
# inside a TextFragment.
|
1116
1138
|
def scaled_item_width(item)
|
1117
1139
|
@scaled_item_widths[item] ||=
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
item.
|
1123
|
-
(item.apply_word_spacing? ? scaled_word_spacing : 0)
|
1124
|
-
end
|
1140
|
+
if item.kind_of?(Numeric)
|
1141
|
+
-item * scaled_font_size
|
1142
|
+
else
|
1143
|
+
item.width * scaled_font_size + scaled_character_spacing +
|
1144
|
+
(item.apply_word_spacing? ? scaled_word_spacing : 0)
|
1125
1145
|
end
|
1126
1146
|
end
|
1127
1147
|
|
@@ -61,11 +61,11 @@ module HexaPDF
|
|
61
61
|
|
62
62
|
# Creates a new TextFragment object for the given text, shapes it and returns it.
|
63
63
|
#
|
64
|
-
# The needed style of the text fragment
|
65
|
-
#
|
66
|
-
#
|
67
|
-
def self.create(text, style
|
68
|
-
style =
|
64
|
+
# The needed style of the text fragment is specified by the +style+ argument (see
|
65
|
+
# Style::create for details). Note that the resulting style object needs at least the font
|
66
|
+
# set.
|
67
|
+
def self.create(text, style)
|
68
|
+
style = Style.create(style)
|
69
69
|
fragment = new(style.font.decode_utf8(text), style)
|
70
70
|
TextShaper.new.shape_text(fragment)
|
71
71
|
end
|
@@ -97,16 +97,17 @@ module HexaPDF
|
|
97
97
|
# * Style#stroke_join_style
|
98
98
|
# * Style#stroke_miter_limit
|
99
99
|
# * Style#stroke_dash_pattern
|
100
|
-
# * Style#
|
101
|
-
# * Style#
|
100
|
+
# * Style#underlays
|
101
|
+
# * Style#overlays
|
102
102
|
attr_reader :style
|
103
103
|
|
104
104
|
# Creates a new TextFragment object with the given items and style.
|
105
105
|
#
|
106
|
-
# The argument +style+ can either be a Style object or a hash of style
|
106
|
+
# The argument +style+ can either be a Style object or a hash of style properties, see
|
107
|
+
# Style::create for details.
|
107
108
|
def initialize(items, style)
|
108
109
|
@items = items
|
109
|
-
@style =
|
110
|
+
@style = Style.create(style)
|
110
111
|
end
|
111
112
|
|
112
113
|
# The precision used to determine whether two floats represent the same value.
|
data/lib/hexapdf/parser.rb
CHANGED
@@ -71,6 +71,11 @@ module HexaPDF
|
|
71
71
|
@contains_xref_streams
|
72
72
|
end
|
73
73
|
|
74
|
+
# Returns +true+ if the PDF file was damaged and could be reconstructed.
|
75
|
+
def reconstructed?
|
76
|
+
!@reconstructed_revision.nil?
|
77
|
+
end
|
78
|
+
|
74
79
|
# Loads the indirect (potentially compressed) object specified by the given cross-reference
|
75
80
|
# entry.
|
76
81
|
#
|
data/lib/hexapdf/tokenizer.rb
CHANGED
@@ -125,11 +125,11 @@ module HexaPDF
|
|
125
125
|
elsif byte == 40 # (
|
126
126
|
parse_literal_string
|
127
127
|
elsif byte == 60 # <
|
128
|
-
if @ss.string.getbyte(@ss.pos + 1)
|
129
|
-
parse_hex_string
|
130
|
-
else
|
128
|
+
if @ss.string.getbyte(@ss.pos + 1) == 60
|
131
129
|
@ss.pos += 2
|
132
130
|
TOKEN_DICT_START
|
131
|
+
else
|
132
|
+
parse_hex_string
|
133
133
|
end
|
134
134
|
elsif byte == 62 # >
|
135
135
|
unless @ss.string.getbyte(@ss.pos + 1) == 62
|
@@ -260,9 +260,10 @@ module HexaPDF
|
|
260
260
|
canvas.save_graphics_state do
|
261
261
|
canvas.rectangle(padding, padding, rect.width - 2 * padding,
|
262
262
|
rect.height - 2 * padding).clip_path.end_path
|
263
|
-
|
263
|
+
case @field.concrete_field_type
|
264
|
+
when :multiline_text_field
|
264
265
|
draw_multiline_text(canvas, rect, style, padding)
|
265
|
-
|
266
|
+
when :list_box
|
266
267
|
draw_list_box(canvas, rect, style, padding)
|
267
268
|
else
|
268
269
|
draw_single_line_text(canvas, rect, style, padding)
|
@@ -503,9 +504,10 @@ module HexaPDF
|
|
503
504
|
# appearance string, the annotation rectangle and the border style.
|
504
505
|
def calculate_font_size(font, font_size, rect, border_style)
|
505
506
|
if font_size == 0
|
506
|
-
|
507
|
+
case @field.concrete_field_type
|
508
|
+
when :multiline_text_field
|
507
509
|
0 # Handled by multiline drawing code
|
508
|
-
|
510
|
+
when :list_box
|
509
511
|
12 # Seems to be Adobe's default
|
510
512
|
else
|
511
513
|
unit_font_size = (font.wrapped_font.bounding_box[3] - font.wrapped_font.bounding_box[1]) *
|
@@ -128,8 +128,8 @@ module HexaPDF
|
|
128
128
|
items = option_items
|
129
129
|
array_value = [value].flatten
|
130
130
|
all_included = array_value.all? {|v| items.include?(v) }
|
131
|
-
self[:V] = if
|
132
|
-
(flagged?(:edit) || all_included)
|
131
|
+
self[:V] = if combo_box? && value.kind_of?(String) &&
|
132
|
+
(flagged?(:edit) || all_included)
|
133
133
|
delete(:I)
|
134
134
|
value
|
135
135
|
elsif list_box? && all_included &&
|
@@ -258,7 +258,7 @@ module HexaPDF
|
|
258
258
|
#
|
259
259
|
# See: HexaPDF::Type::Annotations::Widget
|
260
260
|
def each_widget(direct_only: false, &block) # :yields: widget
|
261
|
-
return to_enum(__method__) unless block_given?
|
261
|
+
return to_enum(__method__, direct_only: direct_only) unless block_given?
|
262
262
|
|
263
263
|
if embedded_widget?
|
264
264
|
yield(document.wrap(self))
|
@@ -336,7 +336,7 @@ module HexaPDF
|
|
336
336
|
|
337
337
|
if embedded_widget?
|
338
338
|
WIDGET_FIELDS.each {|key| delete(key) }
|
339
|
-
document.revisions.each {|revision| break if revision.update(self)}
|
339
|
+
document.revisions.each {|revision| break if revision.update(self) }
|
340
340
|
else
|
341
341
|
self[:Kids].delete_at(widget_index)
|
342
342
|
document.delete(widget)
|
@@ -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/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
|
@@ -48,6 +48,10 @@ describe HexaPDF::Encryption::AES do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
it "handles invalid files with missing 16 byte padding" do
|
52
|
+
assert_equal('', @algorithm_class.decrypt('some key' * 2, 'iv' * 8))
|
53
|
+
end
|
54
|
+
|
51
55
|
it "fails on decryption if not enough bytes are provided" do
|
52
56
|
assert_raises(HexaPDF::EncryptionError) do
|
53
57
|
@algorithm_class.decrypt('some' * 4, 'no iv')
|
@@ -97,6 +101,10 @@ describe HexaPDF::Encryption::AES do
|
|
97
101
|
end
|
98
102
|
|
99
103
|
it "decryption works if the padding is invalid" do
|
104
|
+
f = Fiber.new { 'a' * 16 }
|
105
|
+
result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
|
106
|
+
assert_equal('', result)
|
107
|
+
|
100
108
|
f = Fiber.new { 'a' * 32 }
|
101
109
|
result = TestHelper.collector(@algorithm_class.decryption_fiber('some' * 4, f))
|
102
110
|
assert_equal('a' * 16, result)
|
@@ -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],
|