hexapdf 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +126 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/command.rb +4 -2
  5. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  6. data/lib/hexapdf/cli/info.rb +51 -2
  7. data/lib/hexapdf/cli/inspect.rb +30 -8
  8. data/lib/hexapdf/cli/merge.rb +1 -1
  9. data/lib/hexapdf/cli/split.rb +74 -14
  10. data/lib/hexapdf/configuration.rb +15 -0
  11. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  12. data/lib/hexapdf/content/parser.rb +1 -1
  13. data/lib/hexapdf/dictionary.rb +4 -4
  14. data/lib/hexapdf/dictionary_fields.rb +1 -9
  15. data/lib/hexapdf/document.rb +41 -16
  16. data/lib/hexapdf/document/files.rb +0 -1
  17. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  18. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  20. data/lib/hexapdf/font/cmap.rb +1 -4
  21. data/lib/hexapdf/font/encoding/base.rb +8 -0
  22. data/lib/hexapdf/font/encoding/difference_encoding.rb +6 -0
  23. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  24. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  25. data/lib/hexapdf/font/type1_wrapper.rb +1 -1
  26. data/lib/hexapdf/image_loader/png.rb +3 -2
  27. data/lib/hexapdf/layout/line.rb +1 -1
  28. data/lib/hexapdf/layout/style.rb +23 -23
  29. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  30. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  31. data/lib/hexapdf/object.rb +52 -25
  32. data/lib/hexapdf/parser.rb +87 -3
  33. data/lib/hexapdf/pdf_array.rb +11 -4
  34. data/lib/hexapdf/revisions.rb +29 -21
  35. data/lib/hexapdf/serializer.rb +1 -1
  36. data/lib/hexapdf/task/optimize.rb +6 -4
  37. data/lib/hexapdf/tokenizer.rb +4 -3
  38. data/lib/hexapdf/type/acro_form/appearance_generator.rb +132 -28
  39. data/lib/hexapdf/type/acro_form/button_field.rb +21 -13
  40. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  41. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  42. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  43. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  44. data/lib/hexapdf/type/actions/uri.rb +3 -2
  45. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  46. data/lib/hexapdf/type/catalog.rb +2 -2
  47. data/lib/hexapdf/type/cid_font.rb +1 -1
  48. data/lib/hexapdf/type/file_specification.rb +1 -1
  49. data/lib/hexapdf/type/font.rb +1 -1
  50. data/lib/hexapdf/type/font_simple.rb +4 -2
  51. data/lib/hexapdf/type/font_true_type.rb +6 -2
  52. data/lib/hexapdf/type/font_type0.rb +4 -4
  53. data/lib/hexapdf/type/form.rb +15 -2
  54. data/lib/hexapdf/type/image.rb +2 -2
  55. data/lib/hexapdf/type/page.rb +37 -13
  56. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  57. data/lib/hexapdf/type/resources.rb +1 -0
  58. data/lib/hexapdf/type/trailer.rb +2 -3
  59. data/lib/hexapdf/utils/object_hash.rb +0 -1
  60. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  61. data/lib/hexapdf/version.rb +1 -1
  62. data/test/hexapdf/common_tokenizer_tests.rb +6 -1
  63. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  64. data/test/hexapdf/content/test_canvas.rb +3 -3
  65. data/test/hexapdf/content/test_color_space.rb +1 -1
  66. data/test/hexapdf/encryption/test_aes.rb +4 -4
  67. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  68. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  69. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  70. data/test/hexapdf/font/encoding/test_base.rb +10 -0
  71. data/test/hexapdf/font/encoding/test_difference_encoding.rb +8 -0
  72. data/test/hexapdf/font/test_type1_wrapper.rb +4 -3
  73. data/test/hexapdf/layout/test_style.rb +1 -1
  74. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  75. data/test/hexapdf/test_configuration.rb +2 -2
  76. data/test/hexapdf/test_dictionary.rb +3 -1
  77. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  78. data/test/hexapdf/test_document.rb +18 -10
  79. data/test/hexapdf/test_object.rb +71 -26
  80. data/test/hexapdf/test_parser.rb +159 -53
  81. data/test/hexapdf/test_pdf_array.rb +8 -1
  82. data/test/hexapdf/test_revisions.rb +35 -0
  83. data/test/hexapdf/test_writer.rb +2 -2
  84. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +296 -38
  85. data/test/hexapdf/type/acro_form/test_button_field.rb +22 -2
  86. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  87. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  88. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  89. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  90. data/test/hexapdf/type/test_font_simple.rb +2 -1
  91. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  92. data/test/hexapdf/type/test_form.rb +26 -1
  93. data/test/hexapdf/type/test_page.rb +45 -7
  94. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  95. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  96. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  97. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  98. data/test/test_helper.rb +2 -0
  99. metadata +6 -11
@@ -334,6 +334,20 @@ module HexaPDF
334
334
  # The value needs to be an object that responds to \#call(document, message, position) and
335
335
  # returns +true+ if an error should be raised.
336
336
  #
337
+ # parser.try_xref_reconstruction::
338
+ # A boolean specifying whether non-recoverable parsing errors should lead to reconstructing the
339
+ # main cross-reference table.
340
+ #
341
+ # The reconstructed cross-reference table might make damaged files usable but there is no way
342
+ # to ensure that the reconstructed file is equal to the undamaged original file (though
343
+ # generally it works out).
344
+ #
345
+ # There is also the possibility that reconstructing doesn't work because the algorithm has to
346
+ # assume that the PDF was written in a certain way (which is recommended by the PDF
347
+ # specification).
348
+ #
349
+ # Defaults to +true+.
350
+ #
337
351
  # sorted_tree.max_leaf_node_size::
338
352
  # The maximum number of nodes that should be in a leaf node of a node tree.
339
353
  #
@@ -412,6 +426,7 @@ module HexaPDF
412
426
  'page.default_media_box' => :A4,
413
427
  'page.default_media_orientation' => :portrait,
414
428
  'parser.on_correctable_error' => proc { false },
429
+ 'parser.try_xref_reconstruction' => true,
415
430
  'sorted_tree.max_leaf_node_size' => 64,
416
431
  'style.layers_map' => {
417
432
  link: 'HexaPDF::Layout::Style::LinkLayer',
@@ -45,7 +45,7 @@ module HexaPDF
45
45
  # all either in clockwise or counterclockwise direction and optionally inclined in respect to
46
46
  # the x-axis.
47
47
  #
48
- # See: ELL - https://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf
48
+ # See: ELL - https://spaceroots.org/documents/ellipse/elliptical-arc.pdf
49
49
  class Arc
50
50
 
51
51
  include HexaPDF::Utils::MathHelpers
@@ -202,8 +202,8 @@ module HexaPDF
202
202
  p2x_prime, p2y_prime = derivative_evaluate(eta2)
203
203
 
204
204
  result << [p2x, p2y,
205
- p1: [p1x + alpha * p1x_prime, p1y + alpha * p1y_prime],
206
- p2: [p2x - alpha * p2x_prime, p2y - alpha * p2y_prime]]
205
+ {p1: [p1x + alpha * p1x_prime, p1y + alpha * p1y_prime],
206
+ p2: [p2x - alpha * p2x_prime, p2y - alpha * p2y_prime]}]
207
207
  end
208
208
 
209
209
  result
@@ -177,7 +177,7 @@ module HexaPDF
177
177
  def parse(contents, processor = nil, &block) #:yields: object, params
178
178
  raise ArgumentError, "Argument processor or block is needed" if processor.nil? && block.nil?
179
179
  if processor.nil?
180
- block.singleton_class.alias_method(:process, :call)
180
+ block.singleton_class.send(:alias_method, :process, :call)
181
181
  processor = block
182
182
  end
183
183
 
@@ -97,7 +97,7 @@ module HexaPDF
97
97
  #
98
98
  # version:: Specifies the minimum version of the PDF specification needed for this value.
99
99
  def self.define_field(name, type:, required: false, default: nil, indirect: nil,
100
- allowed_values: nil, version: '1.2')
100
+ allowed_values: nil, version: '1.0')
101
101
  @fields ||= {}
102
102
  @fields[name] = Field.new(type, required: required, default: default, indirect: indirect,
103
103
  allowed_values: allowed_values, version: version)
@@ -163,7 +163,7 @@ module HexaPDF
163
163
  value[name] = field.default
164
164
  end
165
165
  value[name] = data = document.deref(data) if data.kind_of?(HexaPDF::Reference)
166
- if data.class == HexaPDF::Object || (data.kind_of?(HexaPDF::Object) && data.value.nil?)
166
+ if data.instance_of?(HexaPDF::Object) || (data.kind_of?(HexaPDF::Object) && data.value.nil?)
167
167
  data = data.value
168
168
  end
169
169
  if (result = field&.convert(data, document))
@@ -182,7 +182,7 @@ module HexaPDF
182
182
  raise ArgumentError, "Only Symbol (Name) keys are allowed to be used in PDF dictionaries"
183
183
  end
184
184
 
185
- if value[name].class == HexaPDF::Object && !data.kind_of?(HexaPDF::Object) &&
185
+ if value[name].instance_of?(HexaPDF::Object) && !data.kind_of?(HexaPDF::Object) &&
186
186
  !data.kind_of?(HexaPDF::Reference)
187
187
  value[name].value = data
188
188
  else
@@ -273,7 +273,7 @@ module HexaPDF
273
273
  # Check that required fields are set
274
274
  if field.required? && obj.nil?
275
275
  yield("Required field #{name} is not set", field.default?)
276
- self[name] = obj = field.default
276
+ self[name] = obj = field.default if field.default?
277
277
  end
278
278
 
279
279
  # Check if the document version is set high enough
@@ -151,17 +151,9 @@ module HexaPDF
151
151
  # Returns a duplicated default value, automatically taking unduplicatable classes into
152
152
  # account.
153
153
  def default
154
- duplicatable_default? ? @default.dup : @default
154
+ @default.dup
155
155
  end
156
156
 
157
- # Returns +true+ if the default value can safely be duplicated with #dup.
158
- def duplicatable_default?
159
- @duplicatable_default ||= HexaPDF::Object::NOT_DUPLICATABLE_CLASSES.none? do |klass|
160
- @default.kind_of?(klass)
161
- end
162
- end
163
- private :duplicatable_default?
164
-
165
157
  # Returns +true+ if the given object is valid for this field.
166
158
  def valid_object?(obj)
167
159
  type.any? {|t| obj.kind_of?(t) } ||
@@ -69,15 +69,35 @@ module HexaPDF
69
69
 
70
70
  autoload(:Composer, 'hexapdf/composer')
71
71
 
72
+ # == HexaPDF::Document
73
+ #
72
74
  # Represents one PDF document.
73
75
  #
74
76
  # A PDF document consists of (indirect) objects, so the main job of this class is to provide
75
77
  # methods for working with these objects. However, since a PDF document may also be
76
78
  # incrementally updated and can therefore contain one or more revisions, there are also methods
77
- # to work with these revisions.
79
+ # for working with these revisions.
78
80
  #
79
81
  # Note: This class provides everything to work on PDF documents on a low-level basis. This means
80
- # that there are no convenience methods for higher PDF functionality whatsoever.
82
+ # that there are no convenience methods for higher PDF functionality. Those can be found in the
83
+ # objects linked from here, like #catalog.
84
+ #
85
+ # == Known Messages
86
+ #
87
+ # The document object provides a basic message dispatch system via #register_listener and
88
+ # #dispatch_message.
89
+ #
90
+ # Following are the messages that are used by HexaPDF itself:
91
+ #
92
+ # :complete_objects::
93
+ # This message is called before the first step of writing a document. Listeners should
94
+ # complete PDF objects that are missing some information.
95
+ #
96
+ # For example, the font system uses this message to complete the font objects with
97
+ # information that is only available once all the used glyphs are known.
98
+ #
99
+ # :before_write::
100
+ # This message is called before a document is actually serialized and written.
81
101
  class Document
82
102
 
83
103
  autoload(:Pages, 'hexapdf/document/pages')
@@ -400,11 +420,11 @@ module HexaPDF
400
420
  # object in the PDF document. The block may either accept only the object or the object and the
401
421
  # revision it is in.
402
422
  #
403
- # By default, only the current version of each object is returned which implies that each
404
- # object number is yielded exactly once. If the +current+ option is +false+, all stored
405
- # objects from newest to oldest are returned, not only the current version of each object.
423
+ # By default, only the current version of each object is returned which implies that each object
424
+ # number is yielded exactly once. If the +only_current+ option is +false+, all stored objects
425
+ # from newest to oldest are returned, not only the current version of each object.
406
426
  #
407
- # The +current+ option can make a difference because the document can contain multiple
427
+ # The +only_current+ option can make a difference because the document can contain multiple
408
428
  # revisions:
409
429
  #
410
430
  # * Multiple revisions may contain objects with the same object and generation numbers, e.g.
@@ -442,19 +462,28 @@ module HexaPDF
442
462
  end
443
463
 
444
464
  # Dispatches the message +name+ with the given arguments to all registered listeners.
465
+ #
466
+ # See the main Document documentation for an overview of messages that are used by HexaPDF
467
+ # itself.
445
468
  def dispatch_message(name, *args)
446
469
  @listeners[name]&.each {|obj| obj.call(*args) }
447
470
  end
448
471
 
449
- # Caches the value or the return value of the given block using the given Object::PDFData and
450
- # key arguments as composite hash key. If a cached value already exists, it is just returned.
472
+ UNSET = ::Object.new # :nordoc:
473
+
474
+ # Caches and returns the given +value+ or the value of the given block using the given
475
+ # +pdf_data+ and +key+ arguments as composite cache key. If a cached value already exists and
476
+ # +update+ is +false+, the cached value is just returned.
477
+ #
478
+ # Set +update+ to +true+ to force an update of the cached value.
451
479
  #
452
480
  # This facility can be used to cache expensive operations in PDF objects that are easy to
453
481
  # compute again.
454
482
  #
455
483
  # Use #clear_cache to clear the cache if necessary.
456
- def cache(pdf_data, key, value = nil)
457
- @cache[pdf_data][key] ||= value || yield
484
+ def cache(pdf_data, key, value = UNSET, update: false)
485
+ return @cache[pdf_data][key] if cached?(pdf_data, key) && !update
486
+ @cache[pdf_data][key] = (value == UNSET ? yield : value)
458
487
  end
459
488
 
460
489
  # Returns +true+ if there is a value cached for the composite key consisting of the given
@@ -594,13 +623,9 @@ module HexaPDF
594
623
  # If a block is given, it is called on validation problems.
595
624
  #
596
625
  # See HexaPDF::Object#validate for more information.
597
- def validate(auto_correct: true, only_loaded: false) #:yield: object, msg, correctable
598
- cur_obj = trailer
599
- block = (block_given? ? lambda {|msg, correctable| yield(cur_obj, msg, correctable) } : nil)
600
-
626
+ def validate(auto_correct: true, only_loaded: false, &block) #:yield: msg, correctable, object
601
627
  result = trailer.validate(auto_correct: auto_correct, &block)
602
628
  each(only_current: false, only_loaded: only_loaded) do |obj|
603
- cur_obj = obj
604
629
  result &&= obj.validate(auto_correct: auto_correct, &block)
605
630
  end
606
631
  result
@@ -643,7 +668,7 @@ module HexaPDF
643
668
  end
644
669
 
645
670
  if validate
646
- self.validate(auto_correct: true) do |obj, msg, correctable|
671
+ self.validate(auto_correct: true) do |msg, correctable, obj|
647
672
  next if correctable
648
673
  raise HexaPDF::Error, "Validation error for (#{obj.oid},#{obj.gen}): #{msg}"
649
674
  end
@@ -117,7 +117,6 @@ module HexaPDF
117
117
 
118
118
  @document.pages.each do |page|
119
119
  page[:Annots]&.each do |annot|
120
- annot = @document.deref(annot)
121
120
  next unless annot[:Subtype] == :FileAttachment
122
121
  spec = @document.deref(annot[:FS])
123
122
  yield(spec) unless seen.key?(spec)
@@ -49,7 +49,7 @@ module HexaPDF
49
49
 
50
50
  # Creates a new FastARC4 object using the given encryption key.
51
51
  def initialize(key)
52
- @cipher = OpenSSL::Cipher::RC4.new
52
+ @cipher = OpenSSL::Cipher.new('rc4')
53
53
  @cipher.key_len = key.length
54
54
  @cipher.key = key
55
55
  end
@@ -72,6 +72,7 @@ module HexaPDF
72
72
  super
73
73
  unless [1, 2, 4, 5].include?(value[:V])
74
74
  yield("Value of /V is not one of 1, 2, 4 or 5", false)
75
+ return
75
76
  end
76
77
  if value[:V] == 2 && (!key?(:Length) || value[:Length] < 40 ||
77
78
  value[:Length] > 128 || value[:Length] % 8 != 0)
@@ -69,6 +69,7 @@ module HexaPDF
69
69
  when 6
70
70
  if !key?(:OE) || !key?(:UE) || !key?(:Perms)
71
71
  yield("Value of /OE, /UE or /Perms is missing for dictionary revision 6", false)
72
+ return
72
73
  end
73
74
  if value[:U].length != 48 || value[:O].length != 48 || value[:UE].length != 32 ||
74
75
  value[:OE].length != 32 || value[:Perms].length != 16
@@ -100,10 +100,7 @@ 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 #: nodoc:
104
- attr_reader :cid_mapping # :nodoc:
105
- attr_reader :cid_range_mappings # :nodoc:
106
- attr_reader :unicode_mapping # :nodoc:
103
+ attr_reader :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping # :nodoc:
107
104
  protected :codespace_ranges, :cid_mapping, :cid_range_mappings, :unicode_mapping
108
105
 
109
106
  # Creates a new CMap object.
@@ -73,6 +73,14 @@ module HexaPDF
73
73
  @unicode_cache[code] ||= GlyphList.name_to_unicode(name(code))
74
74
  end
75
75
 
76
+ # Returns the code for the given glyph name (a Symbol) or +nil+ if there is no code for the
77
+ # given glyph name.
78
+ #
79
+ # If multiple codes reference the given glyph name, the first found is always returned.
80
+ def code(name)
81
+ @code_to_name.key(name)
82
+ end
83
+
76
84
  end
77
85
 
78
86
  end
@@ -60,6 +60,12 @@ module HexaPDF
60
60
  code_to_name[code] || base_encoding.name(code)
61
61
  end
62
62
 
63
+ # Returns the code for the given glyph name, either from this object, if a code references
64
+ # the name, or from the base encoding.
65
+ def code(name)
66
+ code_to_name.key(name) || base_encoding.code(name)
67
+ end
68
+
63
69
  end
64
70
 
65
71
  end
@@ -76,6 +76,7 @@ module HexaPDF
76
76
 
77
77
  # Apple Mac style information.
78
78
  attr_accessor :mac_style
79
+
79
80
  bit_field(:mac_style, {bold: 0, italic: 1, underline: 2, outline: 3, shadow: 4,
80
81
  condensed: 5, extended: 6})
81
82
 
@@ -65,6 +65,7 @@ module HexaPDF
65
65
 
66
66
  # Characteristics and properties of this font.
67
67
  attr_accessor :type
68
+
68
69
  bit_field(:type, {restricted_license_embedding: 1, preview_and_print_embedding: 2,
69
70
  editable_embedding: 3, no_subsetting: 8, bitmap_embedding_only: 9})
70
71
 
@@ -112,6 +113,7 @@ module HexaPDF
112
113
 
113
114
  # Information concerning the nature of the font patterns.
114
115
  attr_accessor :selection
116
+
115
117
  bit_field(:selection, {italic: 0, underscore: 1, negative: 2, outlined: 3, strikeout: 4,
116
118
  bold: 5, regular: 6, use_typo_metrics: 7, wws: 8, oblique: 9})
117
119
 
@@ -192,7 +192,7 @@ module HexaPDF
192
192
  if glyph.name == @wrapped_font.missing_glyph_id
193
193
  raise HexaPDF::Error, "Glyph for #{glyph.str.inspect} missing"
194
194
  end
195
- code = @encoding.code_to_name.key(glyph.name)
195
+ code = @encoding.code(glyph.name)
196
196
  if code
197
197
  code.chr.freeze
198
198
  elsif @max_code < 255
@@ -162,9 +162,10 @@ module HexaPDF
162
162
  io.seek(length, IO::SEEK_CUR)
163
163
  end
164
164
  when 'tRNS' # PNG s11.3.2
165
- if @color_type == INDEXED
165
+ case @color_type
166
+ when INDEXED
166
167
  trns = io.read(length).unpack('C*')
167
- elsif @color_type == TRUECOLOR || @color_type == GREYSCALE
168
+ when TRUECOLOR, GREYSCALE
168
169
  dict[:Mask] = io.read(length).unpack('n*').map {|val| [val, val] }.flatten
169
170
  else
170
171
  io.seek(length, IO::SEEK_CUR)
@@ -198,7 +198,7 @@ module HexaPDF
198
198
  # Note: The cache is not cleared!
199
199
  def add(item)
200
200
  last = @items.last
201
- if last.class == item.class && item.kind_of?(TextFragment) && last.style == item.style
201
+ if last.instance_of?(item.class) && item.kind_of?(TextFragment) && last.style == item.style
202
202
  if last.items.frozen?
203
203
  @items[-1] = last = last.dup
204
204
  last.items = last.items.dup
@@ -524,7 +524,7 @@ module HexaPDF
524
524
  # Style.new(font_size: 15, align: :center, valign: center)
525
525
  def initialize(**properties)
526
526
  update(**properties)
527
- @scaled_item_widths = {}
527
+ @scaled_item_widths = {}.compare_by_identity
528
528
  end
529
529
 
530
530
  # Duplicates the complex properties that can be modified, as well as the cache.
@@ -883,41 +883,41 @@ module HexaPDF
883
883
  [:text_rise, 0],
884
884
  [:font_features, {}],
885
885
  [:text_rendering_mode, "Content::TextRenderingMode::FILL",
886
- setter: "Content::TextRenderingMode.normalize(value)"],
886
+ {setter: "Content::TextRenderingMode.normalize(value)"}],
887
887
  [:subscript, false,
888
- setter: "value; superscript(false) if superscript",
889
- valid_values: [true, false]],
888
+ {setter: "value; superscript(false) if superscript",
889
+ valid_values: [true, false]}],
890
890
  [:superscript, false,
891
- setter: "value; subscript(false) if subscript",
892
- valid_values: [true, false]],
893
- [:underline, false, valid_values: [true, false]],
894
- [:strikeout, false, valid_values: [true, false]],
891
+ {setter: "value; subscript(false) if subscript",
892
+ valid_values: [true, false]}],
893
+ [:underline, false, {valid_values: [true, false]}],
894
+ [:strikeout, false, {valid_values: [true, false]}],
895
895
  [:fill_color, "default_color"],
896
896
  [:fill_alpha, 1],
897
897
  [:stroke_color, "default_color"],
898
898
  [:stroke_alpha, 1],
899
899
  [:stroke_width, 1],
900
900
  [:stroke_cap_style, "Content::LineCapStyle::BUTT_CAP",
901
- setter: "Content::LineCapStyle.normalize(value)"],
901
+ {setter: "Content::LineCapStyle.normalize(value)"}],
902
902
  [:stroke_join_style, "Content::LineJoinStyle::MITER_JOIN",
903
- setter: "Content::LineJoinStyle.normalize(value)"],
903
+ {setter: "Content::LineJoinStyle.normalize(value)"}],
904
904
  [:stroke_miter_limit, 10.0],
905
905
  [:stroke_dash_pattern, "Content::LineDashPattern.new",
906
- setter: "Content::LineDashPattern.normalize(value, phase)", extra_args: ", phase = 0"],
907
- [:align, :left, valid_values: [:left, :center, :right, :justify]],
908
- [:valign, :top, valid_values: [:top, :center, :bottom]],
906
+ {setter: "Content::LineDashPattern.normalize(value, phase)", extra_args: ", phase = 0"}],
907
+ [:align, :left, {valid_values: [:left, :center, :right, :justify]}],
908
+ [:valign, :top, {valid_values: [:top, :center, :bottom]}],
909
909
  [:text_indent, 0],
910
910
  [:line_spacing, "LineSpacing.new(type: :single)",
911
- setter: "LineSpacing.new(**(value.kind_of?(Symbol) ? {type: value, value: extra_arg} : value))",
912
- extra_args: ", extra_arg = nil"],
913
- [:last_line_gap, false, valid_values: [true, false]],
911
+ {setter: "LineSpacing.new(**(value.kind_of?(Symbol) ? {type: value, value: extra_arg} : value))",
912
+ extra_args: ", extra_arg = nil"}],
913
+ [:last_line_gap, false, {valid_values: [true, false]}],
914
914
  [:background_color, nil],
915
- [:padding, "Quad.new(0)", setter: "Quad.new(value)"],
916
- [:margin, "Quad.new(0)", setter: "Quad.new(value)"],
917
- [:border, "Border.new", setter: "Border.new(**value)"],
918
- [:overlays, "Layers.new", setter: "Layers.new(value)"],
919
- [:underlays, "Layers.new", setter: "Layers.new(value)"],
920
- [:position, :default, valid_values: [:default, :float, :flow, :absolute]],
915
+ [:padding, "Quad.new(0)", {setter: "Quad.new(value)"}],
916
+ [:margin, "Quad.new(0)", {setter: "Quad.new(value)"}],
917
+ [:border, "Border.new", {setter: "Border.new(**value)"}],
918
+ [:overlays, "Layers.new", {setter: "Layers.new(value)"}],
919
+ [:underlays, "Layers.new", {setter: "Layers.new(value)"}],
920
+ [:position, :default, {valid_values: [:default, :float, :flow, :absolute]}],
921
921
  [:position_hint, nil],
922
922
  ].each do |name, default, options = {}|
923
923
  default = default.inspect unless default.kind_of?(String)
@@ -1075,7 +1075,7 @@ module HexaPDF
1075
1075
  # The item may be a (singleton) glyph object or an integer/float, i.e. items that can appear
1076
1076
  # inside a TextFragment.
1077
1077
  def scaled_item_width(item)
1078
- @scaled_item_widths[item.object_id] ||=
1078
+ @scaled_item_widths[item] ||=
1079
1079
  begin
1080
1080
  if item.kind_of?(Numeric)
1081
1081
  -item * scaled_font_size
@@ -388,7 +388,7 @@ module HexaPDF
388
388
  end
389
389
  when :penalty
390
390
  if item.penalty <= -Penalty::INFINITY
391
- add_box_item(item.item) if item.item
391
+ add_box_item(item.item) if item.width > 0
392
392
  break unless yield(create_unjustified_line, item)
393
393
  reset_after_line_break(index + 1)
394
394
  elsif item.penalty >= Penalty::INFINITY
@@ -458,7 +458,7 @@ module HexaPDF
458
458
  end
459
459
  when :penalty
460
460
  if item.penalty <= -Penalty::INFINITY
461
- add_box_item(item.item) if item.item
461
+ add_box_item(item.item) if item.width > 0
462
462
  break unless (action = yield(create_unjustified_line, item))
463
463
  reset_after_line_break_variable_width(index + 1, true, action)
464
464
  elsif item.penalty >= Penalty::INFINITY