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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/README.md +5 -3
  4. data/Rakefile +10 -1
  5. data/examples/018-composer.rb +10 -10
  6. data/examples/020-column_box.rb +57 -0
  7. data/lib/hexapdf/cli/batch.rb +4 -6
  8. data/lib/hexapdf/cli/info.rb +5 -1
  9. data/lib/hexapdf/cli/inspect.rb +59 -0
  10. data/lib/hexapdf/cli/split.rb +1 -1
  11. data/lib/hexapdf/composer.rb +147 -53
  12. data/lib/hexapdf/configuration.rb +7 -3
  13. data/lib/hexapdf/content/canvas.rb +1 -1
  14. data/lib/hexapdf/content/color_space.rb +1 -1
  15. data/lib/hexapdf/content/operator.rb +7 -7
  16. data/lib/hexapdf/content/parser.rb +3 -3
  17. data/lib/hexapdf/content/processor.rb +9 -9
  18. data/lib/hexapdf/document/signatures.rb +5 -4
  19. data/lib/hexapdf/document.rb +7 -0
  20. data/lib/hexapdf/encryption/aes.rb +9 -5
  21. data/lib/hexapdf/font/true_type/font.rb +7 -7
  22. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  23. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  24. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  25. data/lib/hexapdf/font/true_type_wrapper.rb +9 -14
  26. data/lib/hexapdf/font/type1/font.rb +10 -12
  27. data/lib/hexapdf/font/type1_wrapper.rb +15 -17
  28. data/lib/hexapdf/layout/box.rb +12 -9
  29. data/lib/hexapdf/layout/column_box.rb +168 -0
  30. data/lib/hexapdf/layout/image_box.rb +1 -1
  31. data/lib/hexapdf/layout/style.rb +28 -8
  32. data/lib/hexapdf/layout/text_fragment.rb +10 -9
  33. data/lib/hexapdf/parser.rb +5 -0
  34. data/lib/hexapdf/tokenizer.rb +3 -3
  35. data/lib/hexapdf/type/acro_form/appearance_generator.rb +6 -4
  36. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -2
  37. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  38. data/lib/hexapdf/type/font_type0.rb +1 -1
  39. data/lib/hexapdf/type/font_type3.rb +1 -1
  40. data/lib/hexapdf/type/resources.rb +4 -4
  41. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
  42. data/lib/hexapdf/type/signature.rb +1 -1
  43. data/lib/hexapdf/type/trailer.rb +3 -3
  44. data/lib/hexapdf/version.rb +1 -1
  45. data/lib/hexapdf/xref_section.rb +1 -1
  46. data/test/hexapdf/common_tokenizer_tests.rb +5 -5
  47. data/test/hexapdf/content/test_graphics_state.rb +1 -0
  48. data/test/hexapdf/content/test_operator.rb +2 -2
  49. data/test/hexapdf/content/test_processor.rb +1 -1
  50. data/test/hexapdf/encryption/test_aes.rb +8 -0
  51. data/test/hexapdf/encryption/test_standard_security_handler.rb +23 -29
  52. data/test/hexapdf/filter/test_predictor.rb +16 -20
  53. data/test/hexapdf/font/test_type1_wrapper.rb +5 -1
  54. data/test/hexapdf/font/true_type/table/common.rb +1 -1
  55. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  56. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +1 -1
  57. data/test/hexapdf/image_loader/test_pdf.rb +6 -8
  58. data/test/hexapdf/image_loader/test_png.rb +2 -2
  59. data/test/hexapdf/layout/test_box.rb +11 -1
  60. data/test/hexapdf/layout/test_style.rb +23 -0
  61. data/test/hexapdf/layout/test_text_fragment.rb +21 -21
  62. data/test/hexapdf/test_composer.rb +115 -52
  63. data/test/hexapdf/test_dictionary.rb +2 -2
  64. data/test/hexapdf/test_document.rb +11 -9
  65. data/test/hexapdf/test_object.rb +1 -1
  66. data/test/hexapdf/test_parser.rb +13 -7
  67. data/test/hexapdf/test_serializer.rb +20 -22
  68. data/test/hexapdf/test_stream.rb +7 -9
  69. data/test/hexapdf/test_writer.rb +2 -2
  70. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
  71. data/test/hexapdf/type/acro_form/test_choice_field.rb +1 -1
  72. data/test/hexapdf/type/signature/common.rb +1 -1
  73. data/test/hexapdf/type/test_font_type0.rb +1 -1
  74. data/test/hexapdf/type/test_font_type1.rb +7 -7
  75. data/test/hexapdf/type/test_image.rb +13 -17
  76. 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, &:unused_draw_block)
59
+ super(**kwargs)
60
60
  @image = image
61
61
  end
62
62
 
@@ -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) ? {type: value, value: extra_arg} : value))",
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
- begin
1119
- if item.kind_of?(Numeric)
1120
- -item * scaled_font_size
1121
- else
1122
- item.width * scaled_font_size + scaled_character_spacing +
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 can either be specified by the +style+ argument or via
65
- # the +options+ (in which case a new Style object is created). Regardless of the way, the
66
- # resulting style object needs at least the font set.
67
- def self.create(text, style = nil, **options)
68
- style = (style.nil? ? Style.new(**options) : 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#underlay_callback
101
- # * Style#overlay_callback
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 options.
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 = (style.kind_of?(Style) ? style : Style.new(**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.
@@ -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
  #
@@ -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) != 60
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
- if @field.concrete_field_type == :multiline_text_field
263
+ case @field.concrete_field_type
264
+ when :multiline_text_field
264
265
  draw_multiline_text(canvas, rect, style, padding)
265
- elsif @field.concrete_field_type == :list_box
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
- if @field.concrete_field_type == :multiline_text_field
507
+ case @field.concrete_field_type
508
+ when :multiline_text_field
507
509
  0 # Handled by multiline drawing code
508
- elsif @field.concrete_field_type == :list_box
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 (combo_box? && value.kind_of?(String) &&
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 != :"Identity-H" && encoding != :"Identity-V") ||
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 # Some writers invert the y-axis
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 !val
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: [:"1.2", :"2.2"]
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
@@ -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 !self[:ID].kind_of?(PDFArray)
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
 
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.20.3'
40
+ VERSION = '0.21.1'
41
41
 
42
42
  end
@@ -109,7 +109,7 @@ module HexaPDF
109
109
 
110
110
  # Make the assignment method private so that only the provided convenience methods can be
111
111
  # used.
112
- private :"[]="
112
+ private :'[]='
113
113
 
114
114
  # Adds an in-use entry to the cross-reference section.
115
115
  #
@@ -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, :"A;Name_With-Various***Characters?",
64
- :"1.2", :"$$", :"@pattern", :".notdef", :"lime Green", :"paired()parentheses",
65
- :"The_Key_of_F#_Minor", :AB, :"",
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(:"Hößgang", token)
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(:"Hößgang", token)
93
+ assert_equal(:Hößgang, token)
94
94
  assert_equal(Encoding::UTF_8, token.encoding)
95
95
 
96
96
  create_tokenizer('/H#E8lp')
@@ -156,4 +156,5 @@ class GraphicsStateWrapper < Minitest::Spec
156
156
  assert_equal(0.02, @gs.scaled_font_size)
157
157
  end
158
158
  end
159
+
159
160
  end
@@ -391,7 +391,7 @@ describe_operator :CurveToNoSecondControlPoint, :y do
391
391
  end
392
392
  end
393
393
 
394
- [:S, :s, :f, :F, 'f*'.intern, :B, 'B*'.intern, :b, 'b*'.intern, :n].each do |sym|
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*'.intern].each do |sym|
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: :"Times-Roman"})
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
- begin
66
- doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
67
- decryption_opts: {password: user_password})
68
- assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
69
-
70
- out = StringIO.new(''.b)
71
- HexaPDF::Writer.new(doc, out).write
72
- doc = HexaPDF::Document.new(io: out, decryption_opts: {password: user_password})
73
- assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
74
- rescue HexaPDF::EncryptionError => e
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
- begin
82
- doc = HexaPDF::Document.new(io: StringIO.new(File.binread(file)),
83
- decryption_opts: {password: owner_password})
84
- assert_equal(minimal_doc.trailer[:Info][:ModDate], doc.trailer[:Info][:ModDate])
85
- rescue HexaPDF::EncryptionError => e
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
- begin
101
- doc = HexaPDF::Document.new
102
- doc.encrypt(algorithm: algorithm, key_length: key_length)
103
- sio = StringIO.new
104
- doc.write(sio)
105
- doc = HexaPDF::Document.new(io: sio)
106
- assert_kind_of(Time, doc.trailer.info[:ModDate], "alg: #{algorithm} #{key_length} bits")
107
- rescue HexaPDF::Error => e
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
- begin
110
- HexaPDF::GlobalConfiguration['filter.predictor.strict'] = true
111
- assert_raises(HexaPDF::FilterError) do
112
- data = @testcases['up']
113
- encoder = @obj.png_execute(:encoder, feeder(data[:source][0..-2], 1), data[:Predictor],
114
- data[:Colors], data[:BitsPerComponent], data[:Columns])
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
- begin
143
- HexaPDF::GlobalConfiguration['filter.predictor.strict'] = true
144
- assert_raises(HexaPDF::FilterError) do
145
- data = @testcases['up']
146
- encoder = @obj.png_execute(:decoder, feeder(data[:result][0..-2], 1), data[:Predictor],
147
- data[:Colors], data[:BitsPerComponent], data[:Columns])
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: :"Times-Roman"})
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 = !standalone ? [@font, @entry] : set_up_stub_true_type_font(register_vars: false)
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('') << f0 << [10, 22, 0, 0, 2, 10, 13].pack('nN2N2n2')
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
- begin
37
- @doc.config['image_loader.pdf.use_stringio'] = false
38
- form = @loader.load(@doc, @pdf)
39
- assert_equal(:Form, form[:Subtype])
40
- ensure
41
- ObjectSpace.each_object(File) do |file|
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("")].pack('B*') }.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
- # Note: colors and image data for comparisons were extracted using GIMP and its color tools
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 options" do
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],