hexapdf 0.20.4 → 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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/README.md +5 -3
  4. data/Rakefile +10 -1
  5. data/examples/018-composer.rb +10 -10
  6. data/lib/hexapdf/cli/batch.rb +4 -6
  7. data/lib/hexapdf/cli/info.rb +5 -1
  8. data/lib/hexapdf/cli/inspect.rb +59 -0
  9. data/lib/hexapdf/cli/split.rb +1 -1
  10. data/lib/hexapdf/composer.rb +147 -53
  11. data/lib/hexapdf/configuration.rb +7 -3
  12. data/lib/hexapdf/content/canvas.rb +1 -1
  13. data/lib/hexapdf/content/color_space.rb +1 -1
  14. data/lib/hexapdf/content/operator.rb +7 -7
  15. data/lib/hexapdf/content/parser.rb +3 -3
  16. data/lib/hexapdf/content/processor.rb +9 -9
  17. data/lib/hexapdf/document/signatures.rb +5 -4
  18. data/lib/hexapdf/document.rb +7 -0
  19. data/lib/hexapdf/font/true_type/font.rb +7 -7
  20. data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
  21. data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
  22. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  23. data/lib/hexapdf/font/true_type_wrapper.rb +9 -14
  24. data/lib/hexapdf/font/type1/font.rb +10 -12
  25. data/lib/hexapdf/font/type1_wrapper.rb +1 -2
  26. data/lib/hexapdf/layout/box.rb +12 -9
  27. data/lib/hexapdf/layout/image_box.rb +1 -1
  28. data/lib/hexapdf/layout/style.rb +28 -8
  29. data/lib/hexapdf/layout/text_fragment.rb +10 -9
  30. data/lib/hexapdf/parser.rb +5 -0
  31. data/lib/hexapdf/tokenizer.rb +3 -3
  32. data/lib/hexapdf/type/acro_form/appearance_generator.rb +6 -4
  33. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -2
  34. data/lib/hexapdf/type/acro_form/field.rb +2 -2
  35. data/lib/hexapdf/type/font_type0.rb +1 -1
  36. data/lib/hexapdf/type/font_type3.rb +1 -1
  37. data/lib/hexapdf/type/resources.rb +4 -4
  38. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
  39. data/lib/hexapdf/type/signature.rb +1 -1
  40. data/lib/hexapdf/type/trailer.rb +3 -3
  41. data/lib/hexapdf/version.rb +1 -1
  42. data/lib/hexapdf/xref_section.rb +1 -1
  43. data/test/hexapdf/common_tokenizer_tests.rb +5 -5
  44. data/test/hexapdf/content/test_graphics_state.rb +1 -0
  45. data/test/hexapdf/content/test_operator.rb +2 -2
  46. data/test/hexapdf/content/test_processor.rb +1 -1
  47. data/test/hexapdf/encryption/test_standard_security_handler.rb +23 -29
  48. data/test/hexapdf/filter/test_predictor.rb +16 -20
  49. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  50. data/test/hexapdf/font/true_type/table/common.rb +1 -1
  51. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  52. data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +1 -1
  53. data/test/hexapdf/image_loader/test_pdf.rb +6 -8
  54. data/test/hexapdf/image_loader/test_png.rb +2 -2
  55. data/test/hexapdf/layout/test_box.rb +11 -1
  56. data/test/hexapdf/layout/test_style.rb +23 -0
  57. data/test/hexapdf/layout/test_text_fragment.rb +21 -21
  58. data/test/hexapdf/test_composer.rb +115 -52
  59. data/test/hexapdf/test_dictionary.rb +2 -2
  60. data/test/hexapdf/test_document.rb +11 -9
  61. data/test/hexapdf/test_object.rb +1 -1
  62. data/test/hexapdf/test_parser.rb +13 -7
  63. data/test/hexapdf/test_serializer.rb +20 -22
  64. data/test/hexapdf/test_stream.rb +7 -9
  65. data/test/hexapdf/test_writer.rb +2 -2
  66. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -2
  67. data/test/hexapdf/type/acro_form/test_choice_field.rb +1 -1
  68. data/test/hexapdf/type/signature/common.rb +1 -1
  69. data/test/hexapdf/type/test_font_type0.rb +1 -1
  70. data/test/hexapdf/type/test_font_type1.rb +7 -7
  71. data/test/hexapdf/type/test_image.rb +13 -17
  72. metadata +2 -2
@@ -187,7 +187,7 @@ module HexaPDF
187
187
 
188
188
  # Returns the concatenated text of the boxes.
189
189
  def string
190
- @boxes.map(&:string).join('')
190
+ @boxes.map(&:string).join
191
191
  end
192
192
 
193
193
  # :call-seq:
@@ -260,14 +260,14 @@ module HexaPDF
260
260
  s: :close_and_stroke_path,
261
261
  f: :fill_path_non_zero,
262
262
  F: :fill_path_non_zero,
263
- 'f*'.to_sym => :fill_path_even_odd,
263
+ 'f*': :fill_path_even_odd,
264
264
  B: :fill_and_stroke_path_non_zero,
265
- 'B*'.to_sym => :fill_and_stroke_path_even_odd,
265
+ 'B*': :fill_and_stroke_path_even_odd,
266
266
  b: :close_fill_and_stroke_path_non_zero,
267
- 'b*'.to_sym => :close_fill_and_stroke_path_even_odd,
267
+ 'b*': :close_fill_and_stroke_path_even_odd,
268
268
  n: :end_path,
269
269
  W: :clip_path_non_zero,
270
- 'W*'.to_sym => :clip_path_even_odd,
270
+ 'W*': :clip_path_even_odd,
271
271
  BT: :begin_text,
272
272
  ET: :end_text,
273
273
  Tc: :set_character_spacing,
@@ -280,10 +280,10 @@ module HexaPDF
280
280
  Td: :move_text,
281
281
  TD: :move_text_and_set_leading,
282
282
  Tm: :set_text_matrix,
283
- 'T*'.to_sym => :move_text_next_line,
283
+ 'T*': :move_text_next_line,
284
284
  Tj: :show_text,
285
- '\''.to_sym => :move_text_next_line_and_show_text,
286
- '"'.to_sym => :set_spacing_move_text_next_line_and_show_text,
285
+ "'": :move_text_next_line_and_show_text,
286
+ '"': :set_spacing_move_text_next_line_and_show_text,
287
287
  TJ: :show_text_with_positioning,
288
288
  d0: :set_glyph_width, # only for Type 3 fonts
289
289
  d1: :set_glyph_width_and_bounding_box, # only for Type 3 fonts
@@ -392,7 +392,7 @@ module HexaPDF
392
392
  data = data.each_with_object(''.b) {|obj, result| result << obj if obj.kind_of?(String) }
393
393
  end
394
394
  font = graphics_state.font
395
- font.decode(data).map {|code_point| font.to_utf8(code_point) }.join('')
395
+ font.decode(data).map {|code_point| font.to_utf8(code_point) }.join
396
396
  end
397
397
 
398
398
  # Decodes the given text object and returns it as a CompositeBox object.
@@ -93,12 +93,12 @@ module HexaPDF
93
93
 
94
94
  # Returns the name to be set on the /Filter key when using this signing handler.
95
95
  def filter_name
96
- :"Adobe.PPKLite"
96
+ :'Adobe.PPKLite'
97
97
  end
98
98
 
99
99
  # Returns the name to be set on the /SubFilter key when using this signing handler.
100
100
  def sub_filter_name
101
- :"adbe.pkcs7.detached"
101
+ :'adbe.pkcs7.detached'
102
102
  end
103
103
 
104
104
  # Sets the DocMDP permissions that should be applied to the document.
@@ -171,7 +171,7 @@ module HexaPDF
171
171
  # Creates a signing handler with the given options and returns it.
172
172
  #
173
173
  # A signing handler name is mapped to a class via the 'signature.signing_handler'
174
- # configuration option.
174
+ # configuration option. The default signing handler is DefaultHandler.
175
175
  def handler(name: :default, **options)
176
176
  handler = @document.config.constantize('signature.signing_handler', name) do
177
177
  raise HexaPDF::Error, "No signing handler named '#{name}' is available"
@@ -183,7 +183,8 @@ module HexaPDF
183
183
  #
184
184
  # This method will add a new signature to the document and write the updated document to the
185
185
  # given file or IO stream. Afterwards the document can't be modified anymore and still retain
186
- # a correct digital signature; create a new document based on the file or IO stream instead.
186
+ # a correct digital signature. To modify the signed document (e.g. for adding another
187
+ # signature) create a new document based on the given file or IO stream instead.
187
188
  #
188
189
  # +signature+::
189
190
  # Can either be a signature object (determined via the /Type key), a signature field or
@@ -638,6 +638,9 @@ module HexaPDF
638
638
  # of the keyword arguments (see HexaPDF::Document::Signatures#handler for details).
639
639
  #
640
640
  # If not changed, the default signing handler is HexaPDF::Document::Signatures::DefaultHandler.
641
+ #
642
+ # *Note*: Once signing is done the document cannot be changed anymore since it was written. If a
643
+ # document needs to be signed multiple times, it needs to be loaded again after writing.
641
644
  def sign(file_or_io, handler: :default, signature: nil, write_options: {}, **handler_options)
642
645
  handler = signatures.handler(name: handler, **handler_options)
643
646
  signatures.add(file_or_io, handler, signature: signature, write_options: write_options)
@@ -714,6 +717,10 @@ module HexaPDF
714
717
  end
715
718
  end
716
719
 
720
+ def inspect #:nodoc:
721
+ "<#{self.class.name}:#{object_id}>"
722
+ end
723
+
717
724
  end
718
725
 
719
726
  end
@@ -123,7 +123,7 @@ module HexaPDF
123
123
 
124
124
  # Returns the weight of the font.
125
125
  def weight
126
- self[:"OS/2"].weight_class || 0
126
+ self[:'OS/2'].weight_class || 0
127
127
  end
128
128
 
129
129
  # Returns the bounding of the font.
@@ -133,22 +133,22 @@ module HexaPDF
133
133
 
134
134
  # Returns the cap height of the font.
135
135
  def cap_height
136
- self[:"OS/2"].cap_height
136
+ self[:'OS/2'].cap_height
137
137
  end
138
138
 
139
139
  # Returns the x-height of the font.
140
140
  def x_height
141
- self[:"OS/2"].x_height
141
+ self[:'OS/2'].x_height
142
142
  end
143
143
 
144
144
  # Returns the ascender of the font.
145
145
  def ascender
146
- self[:"OS/2"].typo_ascender || self[:hhea].ascent
146
+ self[:'OS/2'].typo_ascender || self[:hhea].ascent
147
147
  end
148
148
 
149
149
  # Returns the descender of the font.
150
150
  def descender
151
- self[:"OS/2"].typo_descender || self[:hhea].descent
151
+ self[:'OS/2'].typo_descender || self[:hhea].descent
152
152
  end
153
153
 
154
154
  # Returns the italic angle of the font, in degrees counter-clockwise from the vertical.
@@ -176,12 +176,12 @@ module HexaPDF
176
176
 
177
177
  # Returns the distance from the baseline to the top of the strikeout line.
178
178
  def strikeout_position
179
- self[:"OS/2"].strikeout_position
179
+ self[:'OS/2'].strikeout_position
180
180
  end
181
181
 
182
182
  # Returns the stroke width for the strikeout line.
183
183
  def strikeout_thickness
184
- self[:"OS/2"].strikeout_size
184
+ self[:'OS/2'].strikeout_size
185
185
  end
186
186
 
187
187
  # Returns th glyph ID of the missing glyph, i.e. 0.
@@ -55,7 +55,7 @@ module HexaPDF
55
55
  'hmtx' => font[:hmtx].raw_data,
56
56
  }
57
57
  tables['cmap'] = font[:cmap].raw_data if font[:cmap]
58
- tables['cvt '] = font[:"cvt "].raw_data if font[:"cvt "]
58
+ tables['cvt '] = font[:'cvt '].raw_data if font[:'cvt ']
59
59
  tables['fpgm'] = font[:fpgm].raw_data if font[:fpgm]
60
60
  tables['prep'] = font[:prep].raw_data if font[:prep]
61
61
  Builder.build(tables)
@@ -99,7 +99,7 @@ module HexaPDF
99
99
  'loca' => loca,
100
100
  'hmtx' => hmtx,
101
101
  }
102
- tables['cvt '] = @font[:"cvt "].raw_data if @font[:"cvt "]
102
+ tables['cvt '] = @font[:'cvt '].raw_data if @font[:'cvt ']
103
103
  tables['fpgm'] = @font[:fpgm].raw_data if @font[:fpgm]
104
104
  tables['prep'] = @font[:prep].raw_data if @font[:prep]
105
105
 
@@ -282,12 +282,12 @@ module HexaPDF
282
282
  def self.mapper(end_codes, start_codes, id_deltas, id_range_offsets, glyph_indexes)
283
283
  compute_glyph_id = lambda do |index, code|
284
284
  offset = id_range_offsets[index]
285
- if offset != 0
285
+ if offset == 0
286
+ glyph_id = (code + id_deltas[index]) % 65536
287
+ else
286
288
  glyph_id = glyph_indexes[offset - end_codes.length + (code - start_codes[index])]
287
289
  glyph_id ||= 0 # Handle invalid subtable entries
288
290
  glyph_id = (glyph_id + id_deltas[index]) % 65536 if glyph_id != 0
289
- else
290
- glyph_id = (code + id_deltas[index]) % 65536
291
291
  end
292
292
  glyph_id
293
293
  end
@@ -126,7 +126,6 @@ module HexaPDF
126
126
  # If +subset+ is true, the font is subset.
127
127
  def initialize(document, font, pdf_object: nil, subset: true)
128
128
  @wrapped_font = font
129
- @missing_glyph_callable = document.config['font.on_missing_glyph']
130
129
 
131
130
  @subsetter = (subset ? HexaPDF::Font::TrueType::Subsetter.new(font) : nil)
132
131
 
@@ -165,12 +164,10 @@ module HexaPDF
165
164
  # Note: Although this method is public, it should normally not be used by application code!
166
165
  def glyph(id, str = nil)
167
166
  @id_to_glyph[id] ||=
168
- begin
169
- if id >= 0 && id < @wrapped_font[:maxp].num_glyphs
170
- Glyph.new(@wrapped_font, id, str || (+'' << (@cmap.gid_to_code(id) || 0xFFFD)))
171
- else
172
- @missing_glyph_callable.call("\u{FFFD}", font_type, @wrapped_font)
173
- end
167
+ if id >= 0 && id < @wrapped_font[:maxp].num_glyphs
168
+ Glyph.new(@wrapped_font, id, str || (+'' << (@cmap.gid_to_code(id) || 0xFFFD)))
169
+ else
170
+ @pdf_object.document.config['font.on_missing_glyph'].call("\u{FFFD}", self)
174
171
  end
175
172
  end
176
173
 
@@ -178,12 +175,10 @@ module HexaPDF
178
175
  def decode_utf8(str)
179
176
  str.codepoints.map! do |c|
180
177
  @codepoint_to_glyph[c] ||=
181
- begin
182
- if (gid = @cmap[c])
183
- glyph(gid, +'' << c)
184
- else
185
- @missing_glyph_callable.call(+'' << c, font_type, @wrapped_font)
186
- end
178
+ if (gid = @cmap[c])
179
+ glyph(gid, +'' << c)
180
+ else
181
+ @pdf_object.document.config['font.on_missing_glyph'].call(+'' << c, self)
187
182
  end
188
183
  end
189
184
  end
@@ -250,7 +245,7 @@ module HexaPDF
250
245
  Supplement: 0},
251
246
  CIDToGIDMap: :Identity})
252
247
  dict = document.add({Type: :Font, Subtype: :Type0, BaseFont: cid_font[:BaseFont],
253
- Encoding: :"Identity-H", DescendantFonts: [cid_font]})
248
+ Encoding: :'Identity-H', DescendantFonts: [cid_font]})
254
249
  dict.font_wrapper = self
255
250
 
256
251
  document.register_listener(:complete_objects) do
@@ -89,19 +89,17 @@ module HexaPDF
89
89
  # Returns the built-in encoding of the font.
90
90
  def encoding
91
91
  @encoding ||=
92
- begin
93
- if @metrics.encoding_scheme == 'AdobeStandardEncoding'
94
- Encoding.for_name(:StandardEncoding)
95
- elsif font_name == 'ZapfDingbats' || font_name == 'Symbol'
96
- Encoding.for_name((font_name + "Encoding").to_sym)
97
- else
98
- encoding = Encoding::Base.new
99
- @metrics.character_metrics.each do |key, char_metric|
100
- next unless key.kind_of?(Integer) && key >= 0
101
- encoding.code_to_name[key] = char_metric.name
102
- end
103
- encoding
92
+ if @metrics.encoding_scheme == 'AdobeStandardEncoding'
93
+ Encoding.for_name(:StandardEncoding)
94
+ elsif font_name == 'ZapfDingbats' || font_name == 'Symbol'
95
+ Encoding.for_name("#{font_name}Encoding".to_sym)
96
+ else
97
+ encoding = Encoding::Base.new
98
+ @metrics.character_metrics.each do |key, char_metric|
99
+ next unless key.kind_of?(Integer) && key >= 0
100
+ encoding.code_to_name[key] = char_metric.name
104
101
  end
102
+ encoding
105
103
  end
106
104
  end
107
105
 
@@ -119,7 +119,6 @@ module HexaPDF
119
119
  def initialize(document, font, pdf_object: nil, custom_encoding: false)
120
120
  @wrapped_font = font
121
121
  @pdf_object = pdf_object || create_pdf_object(document)
122
- @missing_glyph_callable = document.config['font.on_missing_glyph']
123
122
 
124
123
  if pdf_object
125
124
  @encoding = pdf_object.encoding
@@ -160,7 +159,7 @@ module HexaPDF
160
159
  if @wrapped_font.metrics.character_metrics.key?(name)
161
160
  Glyph.new(@wrapped_font, name, str)
162
161
  else
163
- @missing_glyph_callable.call(str, font_type, @wrapped_font)
162
+ @pdf_object.document.config['font.on_missing_glyph'].call(str, self)
164
163
  end
165
164
  end
166
165
  end
@@ -51,13 +51,16 @@ module HexaPDF
51
51
  # * If width or height is set to zero, they are determined automatically during layouting.
52
52
  class Box
53
53
 
54
- # Creates a new Box object, using the provided block as drawing block (see ::new). Any
55
- # additional keyword arguments are used for creating the box's Style object.
54
+ # Creates a new Box object, using the provided block as drawing block (see ::new).
56
55
  #
57
56
  # If +content_box+ is +true+, the width and height are taken to mean the content width and
58
57
  # height and the style's padding and border are removed from them appropriately.
59
- def self.create(width: 0, height: 0, content_box: false, **style, &block)
60
- style = Style.new(**style)
58
+ #
59
+ # The +style+ argument defines the Style object (see Style::create for details) for the box.
60
+ # Any additional keyword arguments have to be style properties and are applied to the style
61
+ # object.
62
+ def self.create(width: 0, height: 0, content_box: false, style: nil, **style_properties, &block)
63
+ style = Style.create(style).update(**style_properties)
61
64
  if content_box
62
65
  width += style.padding.left + style.padding.right +
63
66
  style.border.width.left + style.border.width.right
@@ -81,12 +84,12 @@ module HexaPDF
81
84
  # * Style#background_alpha
82
85
  # * Style#padding
83
86
  # * Style#border
84
- # * Style#overlay_callback
85
- # * Style#underlay_callback
87
+ # * Style#overlays
88
+ # * Style#underlays
86
89
  attr_reader :style
87
90
 
88
91
  # :call-seq:
89
- # Box.new(width: 0, height: 0, style: Style.new) {|canv, box| block} -> box
92
+ # Box.new(width: 0, height: 0, style: nil) {|canv, box| block} -> box
90
93
  #
91
94
  # Creates a new Box object with the given width and height that uses the provided block when
92
95
  # it is asked to draw itself on a canvas (see #draw).
@@ -94,10 +97,10 @@ module HexaPDF
94
97
  # Since the final location of the box is not known beforehand, the drawing operations inside
95
98
  # the block should draw inside the rectangle (0, 0, content_width, content_height) - note that
96
99
  # the width and height of the box may not be known beforehand.
97
- def initialize(width: 0, height: 0, style: Style.new, &block)
100
+ def initialize(width: 0, height: 0, style: nil, &block)
98
101
  @width = @initial_width = width
99
102
  @height = @initial_height = height
100
- @style = (style.kind_of?(Style) ? style : Style.new(**style))
103
+ @style = Style.create(style)
101
104
  @draw_block = block
102
105
  end
103
106
 
@@ -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.4'
40
+ VERSION = '0.21.0'
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
  #