hexapdf 1.1.1 → 1.3.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -0
  3. data/README.md +1 -1
  4. data/lib/hexapdf/cli/command.rb +63 -63
  5. data/lib/hexapdf/cli/inspect.rb +14 -5
  6. data/lib/hexapdf/cli/modify.rb +0 -1
  7. data/lib/hexapdf/cli/optimize.rb +5 -5
  8. data/lib/hexapdf/composer.rb +14 -0
  9. data/lib/hexapdf/configuration.rb +26 -0
  10. data/lib/hexapdf/content/graphics_state.rb +1 -1
  11. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +1 -1
  12. data/lib/hexapdf/document/annotations.rb +173 -0
  13. data/lib/hexapdf/document/layout.rb +45 -6
  14. data/lib/hexapdf/document.rb +28 -7
  15. data/lib/hexapdf/error.rb +11 -3
  16. data/lib/hexapdf/font/true_type/subsetter.rb +15 -2
  17. data/lib/hexapdf/font/true_type_wrapper.rb +1 -0
  18. data/lib/hexapdf/font/type1_wrapper.rb +1 -0
  19. data/lib/hexapdf/layout/style.rb +101 -7
  20. data/lib/hexapdf/object.rb +2 -2
  21. data/lib/hexapdf/pdf_array.rb +25 -3
  22. data/lib/hexapdf/tokenizer.rb +4 -1
  23. data/lib/hexapdf/type/acro_form/appearance_generator.rb +57 -8
  24. data/lib/hexapdf/type/acro_form/field.rb +1 -0
  25. data/lib/hexapdf/type/acro_form/form.rb +7 -6
  26. data/lib/hexapdf/type/acro_form/java_script_actions.rb +9 -2
  27. data/lib/hexapdf/type/acro_form/text_field.rb +9 -2
  28. data/lib/hexapdf/type/annotation.rb +71 -1
  29. data/lib/hexapdf/type/annotations/appearance_generator.rb +348 -0
  30. data/lib/hexapdf/type/annotations/border_effect.rb +99 -0
  31. data/lib/hexapdf/type/annotations/border_styling.rb +160 -0
  32. data/lib/hexapdf/type/annotations/circle.rb +65 -0
  33. data/lib/hexapdf/type/annotations/interior_color.rb +84 -0
  34. data/lib/hexapdf/type/annotations/line.rb +490 -0
  35. data/lib/hexapdf/type/annotations/square.rb +65 -0
  36. data/lib/hexapdf/type/annotations/square_circle.rb +77 -0
  37. data/lib/hexapdf/type/annotations/widget.rb +52 -116
  38. data/lib/hexapdf/type/annotations.rb +8 -0
  39. data/lib/hexapdf/type/form.rb +2 -2
  40. data/lib/hexapdf/version.rb +1 -1
  41. data/lib/hexapdf/writer.rb +0 -1
  42. data/lib/hexapdf/xref_section.rb +7 -4
  43. data/test/hexapdf/content/test_graphics_state.rb +2 -3
  44. data/test/hexapdf/content/test_operator.rb +4 -5
  45. data/test/hexapdf/digital_signature/test_cms_handler.rb +7 -8
  46. data/test/hexapdf/digital_signature/test_handler.rb +2 -3
  47. data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +1 -2
  48. data/test/hexapdf/document/test_annotations.rb +55 -0
  49. data/test/hexapdf/document/test_layout.rb +24 -2
  50. data/test/hexapdf/font/test_true_type_wrapper.rb +7 -0
  51. data/test/hexapdf/font/test_type1_wrapper.rb +7 -0
  52. data/test/hexapdf/font/true_type/test_subsetter.rb +10 -0
  53. data/test/hexapdf/layout/test_style.rb +27 -2
  54. data/test/hexapdf/task/test_optimize.rb +1 -1
  55. data/test/hexapdf/test_composer.rb +7 -0
  56. data/test/hexapdf/test_document.rb +11 -3
  57. data/test/hexapdf/test_object.rb +1 -1
  58. data/test/hexapdf/test_pdf_array.rb +36 -3
  59. data/test/hexapdf/test_stream.rb +1 -2
  60. data/test/hexapdf/test_xref_section.rb +1 -1
  61. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +78 -3
  62. data/test/hexapdf/type/acro_form/test_button_field.rb +7 -6
  63. data/test/hexapdf/type/acro_form/test_field.rb +5 -0
  64. data/test/hexapdf/type/acro_form/test_form.rb +17 -1
  65. data/test/hexapdf/type/acro_form/test_java_script_actions.rb +21 -0
  66. data/test/hexapdf/type/acro_form/test_text_field.rb +7 -1
  67. data/test/hexapdf/type/annotations/test_appearance_generator.rb +482 -0
  68. data/test/hexapdf/type/annotations/test_border_effect.rb +59 -0
  69. data/test/hexapdf/type/annotations/test_border_styling.rb +114 -0
  70. data/test/hexapdf/type/annotations/test_interior_color.rb +37 -0
  71. data/test/hexapdf/type/annotations/test_line.rb +169 -0
  72. data/test/hexapdf/type/annotations/test_widget.rb +35 -81
  73. data/test/hexapdf/type/test_annotation.rb +55 -0
  74. data/test/hexapdf/type/test_form.rb +6 -0
  75. metadata +17 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 902a2c520a3fa1a6c0128edc938ab82f604faca6f08548d68e0226a7f9426422
4
- data.tar.gz: 942551d188d12f5a841f4a1631982e813a85823570b4ba3ec085cf846cf164dd
3
+ metadata.gz: e0e469ae650b98b48c88b662dab27cb405b676c706639c8c1cf358d6aefc8f53
4
+ data.tar.gz: d170526233e1e9aa37403c1bd8d2aa38697641c1b4fb4d4a5f6d8f02f299585b
5
5
  SHA512:
6
- metadata.gz: 26be23186793f7d704831969652e2392135eb5e0d8460ba7505b27fb546ac5043c10513cf358f16890723978ee0cbe28129f97185cba8a6f9c69f279f446cdd4
7
- data.tar.gz: c094967af75096e58d6493d459a46ac345d109ef1feb58de0bd5ca746ce0dc39880467c7208928e881c325bbb32c9c5c5ac55738e3b5a0570ad330c2a0fd39ba
6
+ metadata.gz: fd18408ed2c2474e3395bf59b3a0281cb23005d16bf7d5b9d93f673dfa131fc215f342018d0cdce2de053db85c000a68c28a76ad980b0432b1a159bd51b1ed0c
7
+ data.tar.gz: c58b13ed27c980bb9670b9521a12b1640fc3960aea1188f6135951cfd1503a28e7633cb3f6be8c63d478d64a88e0bbdc9b84bdbc982aa3ec000aeac1a8627597
data/CHANGELOG.md CHANGED
@@ -1,3 +1,82 @@
1
+ ## 1.3.0 - 2025-04-23
2
+
3
+ ### Added
4
+
5
+ * [HexaPDF::Type::Annotations::Square] for rectangle annotations as well as
6
+ [HexaPDF::Document::Annotations#create_rectangle]
7
+ * [HexaPDF::Type::Annotations::Circle] for ellipse annotations as well as
8
+ [HexaPDF::Document::Annotations#create_ellipse]
9
+ * Basic appearance generation for push button fields
10
+ * [HexaPDF::Type::Annotation::BorderEffect] type class
11
+ * [HexaPDF::Type::Annotations::BorderEffect] module that provides convenience
12
+ access to the border effect dictionary
13
+ * [HexaPDF::Document::Layout#style?] and [HexaPDF::Composer#style?] for checking
14
+ whether a given style (name) exists
15
+ * [HexaPDF::Layout::Style#each_property] for iterating over all set properties
16
+ * [HexaPDF::Layout::Style#merge] for merging another style instance
17
+ * [HexaPDF::Layout::Style#box_options] for specifying box initialization options
18
+ * [HexaPDF::Layout::Style#font_bold] and [HexaPDF::Layout::Style#font_italic]
19
+ for setting bold and/or italic variants independently of the font name
20
+ * [HexaPDF::PDFArray#map!] for mapping elements in-place
21
+ * [HexaPDF::PDFArray#compact!] for removing `nil` elements
22
+
23
+ ### Changed
24
+
25
+ * **Breaking change**: [HexaPDF::Type::Annotations::Widget::MarkerStyle::new]
26
+ got a new positional argument
27
+ * [HexaPDF::Type::Annotations::Widget#marker_style] to allow setting and
28
+ retrieving the font for push buttons
29
+ * Extracted `#interior_color` from [HexaPDF::Type::Annotations::Line] into
30
+ [HexaPDF::Type::Annotations::InteriorColor]
31
+ * CLI command `hexapdf inspect` to support decoding Form XObject streams
32
+ * [HexaPDF::Layout::Style#line_spacing] to accept a `LineSpacing` object when
33
+ setting the value
34
+
35
+ ### Fixed
36
+
37
+ * Text extraction with macOS Preview due a bug in Preview
38
+ * [HexaPDF::PDFArray#reject!] to work according to documented method signature
39
+ * [HexaPDF::Type::AcroForm::Field#create_widget] to ensure the proper type
40
+ class is stored in the document in case an embedded widget is extracted
41
+ * [HexaPDF::Type::AcroForm::Form] validation to ensure that all field objects in
42
+ the field hierarchy are using a field type class
43
+ * [HexaPDF::Type::AcroForm::Form] validation to delete merged fields
44
+
45
+
46
+ ## 1.2.0 - 2025-02-10
47
+
48
+ ### Added
49
+
50
+ * **Breaking change**: Argument `compact` to [HexaPDF::Document#write] to
51
+ automatically run the 'compact' optimization task
52
+ * [HexaPDF::Document::Annotations], accessible via
53
+ [HexaPDF::Document#annotations], as convenience interface for working with
54
+ annotations
55
+ * [HexaPDF::Type::Annotations::AppearanceGenerator] as central class for
56
+ generating appearance streams
57
+ * [HexaPDF::Type::Annotations::Line] for line annotations
58
+ * [HexaPDF::Type::Annotation#opacity] for setting the opacity values when
59
+ regenerating the appearance stream
60
+ * [HexaPDF::Type::Annotation#contents] for setting the text of the annotation
61
+ * Configuration option 'acro_form.text_field.on_max_len_exceeded' to allow
62
+ custom handling of too long values
63
+
64
+ ### Changed
65
+
66
+ * **Breaking change**: Extracted `#border_style` and associated data class from
67
+ [HexaPDF::Type::Annotations::Widget] into
68
+ [HexaPDF::Type::Annotations::BorderStyling]
69
+ * [HexaPDF::Type::Form#canvas] to allow getting the canvas without the initial
70
+ translation
71
+
72
+ ### Fixed
73
+
74
+ * AcroForm Javascript actions to gracefully handle the special values infinity
75
+ and NaN
76
+ * Type1 and TrueType font wrappers to handle the case where fonts are first
77
+ added and later deleted
78
+
79
+
1
80
  ## 1.1.1 - 2025-01-08
2
81
 
3
82
  ### Fixed
data/README.md CHANGED
@@ -82,7 +82,7 @@ canvas.text("Hello World!", at: [20, 400])
82
82
  doc.write("hello-world.pdf")
83
83
  ~~~
84
84
 
85
- For detailed information have a look at the [HexaPDF website][website] where you will the API
85
+ For detailed information have a look at the [HexaPDF website][website] where you will find the API
86
86
  documentation, example code and more.
87
87
 
88
88
  It is recommend to use the HTML API documentation provided by the HexaPDF website as it is enhanced
@@ -35,7 +35,6 @@
35
35
  #++
36
36
 
37
37
  require 'io/console'
38
- require 'ostruct'
39
38
  require 'cmdparse'
40
39
  require 'hexapdf/document'
41
40
  require 'hexapdf/font/true_type'
@@ -68,21 +67,22 @@ module HexaPDF
68
67
 
69
68
  def initialize(*args, **kwargs, &block) #:nodoc:
70
69
  super
71
- @out_options = OpenStruct.new
72
- @out_options.compact = true
73
- @out_options.compress_pages = false
74
- @out_options.object_streams = :preserve
75
- @out_options.xref_streams = :preserve
76
- @out_options.streams = :preserve
77
- @out_options.optimize_fonts = false
78
- @out_options.prune_page_resources = false
79
-
80
- @out_options.encryption = :preserve
81
- @out_options.enc_user_pwd = @out_options.enc_owner_pwd = nil
82
- @out_options.enc_key_length = 128
83
- @out_options.enc_algorithm = :aes
84
- @out_options.enc_force_v4 = false
85
- @out_options.enc_permissions = []
70
+ @out_options = {
71
+ compact: true,
72
+ compress_pages: false,
73
+ object_streams: :preserve,
74
+ xref_streams: :preserve,
75
+ streams: :preserve,
76
+ optimize_fonts: false,
77
+ prune_page_resources: false,
78
+ encryption: :preserve,
79
+ enc_user_pwd: nil,
80
+ enc_owner_pwd: nil,
81
+ enc_key_length: 128,
82
+ enc_algorithm: :aes,
83
+ enc_force_v4: false,
84
+ enc_permissions: [],
85
+ }
86
86
  end
87
87
 
88
88
  protected
@@ -163,7 +163,7 @@ module HexaPDF
163
163
  if command_parser.verbosity_info?
164
164
  puts "Creating output document #{out_file}"
165
165
  end
166
- doc.write(out_file, validate: false, incremental: incremental)
166
+ doc.write(out_file, validate: false, compact: false, incremental: incremental)
167
167
  end
168
168
  end
169
169
 
@@ -183,35 +183,35 @@ module HexaPDF
183
183
  options.separator("")
184
184
  options.separator("Optimization options:")
185
185
  options.on("--[no-]compact", "Delete unnecessary PDF objects (default: " \
186
- "#{@out_options.compact})") do |c|
187
- @out_options.compact = c
186
+ "#{@out_options[:compact]})") do |c|
187
+ @out_options[:compact] = c
188
188
  end
189
189
  options.on("--object-streams MODE", [:generate, :preserve, :delete],
190
190
  "Handling of object streams (either generate, preserve or delete; " \
191
- "default: #{@out_options.object_streams})") do |os|
192
- @out_options.object_streams = os
191
+ "default: #{@out_options[:object_streams]})") do |os|
192
+ @out_options[:object_streams] = os
193
193
  end
194
194
  options.on("--xref-streams MODE", [:generate, :preserve, :delete],
195
195
  "Handling of cross-reference streams (either generate, preserve or delete; " \
196
- "default: #{@out_options.xref_streams})") do |x|
197
- @out_options.xref_streams = x
196
+ "default: #{@out_options[:xref_streams]})") do |x|
197
+ @out_options[:xref_streams] = x
198
198
  end
199
199
  options.on("--streams MODE", [:compress, :preserve, :uncompress],
200
200
  "Handling of stream data (either compress, preserve or uncompress; default: " \
201
- "#{@out_options.streams})") do |streams|
202
- @out_options.streams = streams
201
+ "#{@out_options[:streams]})") do |streams|
202
+ @out_options[:streams] = streams
203
203
  end
204
204
  options.on("--[no-]compress-pages", "Recompress page content streams (may take a long " \
205
- "time; default: #{@out_options.compress_pages})") do |c|
206
- @out_options.compress_pages = c
205
+ "time; default: #{@out_options[:compress_pages]})") do |c|
206
+ @out_options[:compress_pages] = c
207
207
  end
208
208
  options.on("--[no-]prune-page-resources", "Prunes unused objects from the page resources " \
209
- "(may take a long time; default: #{@out_options.prune_page_resources})") do |c|
210
- @out_options.prune_page_resources = c
209
+ "(may take a long time; default: #{@out_options[:prune_page_resources]})") do |c|
210
+ @out_options[:prune_page_resources] = c
211
211
  end
212
212
  options.on("--[no-]optimize-fonts", "Optimize embedded font files; " \
213
- "default: #{@out_options.optimize_fonts})") do |o|
214
- @out_options.optimize_fonts = o
213
+ "default: #{@out_options[:optimize_fonts]})") do |o|
214
+ @out_options[:optimize_fonts] = o
215
215
  end
216
216
  end
217
217
 
@@ -222,37 +222,37 @@ module HexaPDF
222
222
  options.separator("")
223
223
  options.separator("Encryption options:")
224
224
  options.on("--decrypt", "Remove any encryption") do
225
- @out_options.encryption = :remove
225
+ @out_options[:encryption] = :remove
226
226
  end
227
227
  options.on("--encrypt", "Encrypt the output file") do
228
- @out_options.encryption = :add
228
+ @out_options[:encryption] = :add
229
229
  end
230
230
  options.on("--owner-password PASSWORD", String, "The owner password to be set on the " \
231
231
  "output file (use - for reading from standard input)") do |pwd|
232
- @out_options.encryption = :add
233
- @out_options.enc_owner_pwd = (pwd == '-' ? read_password("Owner password") : pwd)
232
+ @out_options[:encryption] = :add
233
+ @out_options[:enc_owner_pwd] = (pwd == '-' ? read_password("Owner password") : pwd)
234
234
  end
235
235
  options.on("--user-password PASSWORD", String, "The user password to be set on the " \
236
236
  "output file (use - for reading from standard input)") do |pwd|
237
- @out_options.encryption = :add
238
- @out_options.enc_user_pwd = (pwd == '-' ? read_password("User password") : pwd)
237
+ @out_options[:encryption] = :add
238
+ @out_options[:enc_user_pwd] = (pwd == '-' ? read_password("User password") : pwd)
239
239
  end
240
240
  options.on("--algorithm ALGORITHM", [:aes, :arc4],
241
241
  "The encryption algorithm: aes or arc4 (default: " \
242
- "#{@out_options.enc_algorithm})") do |a|
243
- @out_options.encryption = :add
244
- @out_options.enc_algorithm = a
242
+ "#{@out_options[:enc_algorithm]})") do |a|
243
+ @out_options[:encryption] = :add
244
+ @out_options[:enc_algorithm] = a
245
245
  end
246
246
  options.on("--key-length BITS", Integer,
247
247
  "The encryption key length in bits (default: " \
248
- "#{@out_options.enc_key_length})") do |i|
249
- @out_options.encryption = :add
250
- @out_options.enc_key_length = i
248
+ "#{@out_options[:enc_key_length]})") do |i|
249
+ @out_options[:encryption] = :add
250
+ @out_options[:enc_key_length] = i
251
251
  end
252
252
  options.on("--force-V4",
253
253
  "Force use of encryption version 4 if key length=128 and algorithm=arc4") do
254
- @out_options.encryption = :add
255
- @out_options.enc_force_v4 = true
254
+ @out_options[:encryption] = :add
255
+ @out_options[:enc_force_v4] = true
256
256
  end
257
257
  syms = HexaPDF::Encryption::StandardSecurityHandler::Permissions::SYMBOL_TO_PERMISSION.keys
258
258
  options.on("--permissions PERMS", Array,
@@ -264,8 +264,8 @@ module HexaPDF
264
264
  end
265
265
  perm.to_sym
266
266
  end
267
- @out_options.encryption = :add
268
- @out_options.enc_permissions = perms
267
+ @out_options[:encryption] = :add
268
+ @out_options[:enc_permissions] = perms
269
269
  end
270
270
  end
271
271
 
@@ -273,12 +273,12 @@ module HexaPDF
273
273
  #
274
274
  # See: #define_optimization_options
275
275
  def apply_optimization_options(doc)
276
- doc.task(:optimize, compact: @out_options.compact,
277
- object_streams: @out_options.object_streams,
278
- xref_streams: @out_options.xref_streams,
279
- compress_pages: @out_options.compress_pages,
280
- prune_page_resources: @out_options.prune_page_resources)
281
- if @out_options.streams != :preserve || @out_options.optimize_fonts
276
+ doc.task(:optimize, compact: @out_options[:compact],
277
+ object_streams: @out_options[:object_streams],
278
+ xref_streams: @out_options[:xref_streams],
279
+ compress_pages: @out_options[:compress_pages],
280
+ prune_page_resources: @out_options[:prune_page_resources])
281
+ if @out_options[:streams] != :preserve || @out_options[:optimize_fonts]
282
282
  doc.each do |obj|
283
283
  optimize_stream(obj)
284
284
  optimize_font(obj)
@@ -292,15 +292,15 @@ module HexaPDF
292
292
 
293
293
  # Applies the chosen stream mode to the given object.
294
294
  def optimize_stream(obj)
295
- return if @out_options.streams == :preserve || !obj.respond_to?(:set_filter) ||
295
+ return if @out_options[:streams] == :preserve || !obj.respond_to?(:set_filter) ||
296
296
  Array(obj[:Filter]).any? {|f| IGNORED_FILTERS[f] }
297
297
 
298
- obj.set_filter(@out_options.streams == :compress ? :FlateDecode : nil)
298
+ obj.set_filter(@out_options[:streams] == :compress ? :FlateDecode : nil)
299
299
  end
300
300
 
301
301
  # Optimize the object if it is a font object.
302
302
  def optimize_font(obj)
303
- return unless @out_options.optimize_fonts && obj.kind_of?(HexaPDF::Type::Font) &&
303
+ return unless @out_options[:optimize_fonts] && obj.kind_of?(HexaPDF::Type::Font) &&
304
304
  (obj[:Subtype] == :TrueType ||
305
305
  (obj[:Subtype] == :Type0 && obj.descendant_font[:Subtype] == :CIDFontType2)) &&
306
306
  obj.embedded?
@@ -319,14 +319,14 @@ module HexaPDF
319
319
  #
320
320
  # See: #define_encryption_options
321
321
  def apply_encryption_options(doc)
322
- case @out_options.encryption
322
+ case @out_options[:encryption]
323
323
  when :add
324
- doc.encrypt(algorithm: @out_options.enc_algorithm,
325
- key_length: @out_options.enc_key_length,
326
- force_v4: @out_options.enc_force_v4,
327
- permissions: @out_options.enc_permissions,
328
- owner_password: @out_options.enc_owner_pwd,
329
- user_password: @out_options.enc_user_pwd)
324
+ doc.encrypt(algorithm: @out_options[:enc_algorithm],
325
+ key_length: @out_options[:enc_key_length],
326
+ force_v4: @out_options[:enc_force_v4],
327
+ permissions: @out_options[:enc_permissions],
328
+ owner_password: @out_options[:enc_owner_pwd],
329
+ user_password: @out_options[:enc_user_pwd])
330
330
  when :remove
331
331
  doc.encrypt(name: nil)
332
332
  end
@@ -188,12 +188,20 @@ module HexaPDF
188
188
  end
189
189
  serialize(obj.value, recursive: true) if obj
190
190
 
191
- when 's', 'stream', 'raw', 'raw-stream'
191
+ when 's', 'stream', 'raw', 'raw-stream', 'sd'
192
192
  if (obj = pdf_object_from_string_reference(data.shift) rescue $stderr.puts($!.message)) &&
193
193
  obj.kind_of?(HexaPDF::Stream)
194
- source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
195
- while source.alive? && (stream_data = source.resume)
196
- $stdout.write(stream_data)
194
+ if command == 'sd'
195
+ if obj.respond_to?(:process_contents)
196
+ obj.process_contents(ContentProcessor.new)
197
+ else
198
+ $stderr.puts("Error: The object is not a Form XObject or page")
199
+ end
200
+ else
201
+ source = (command.start_with?('raw') ? obj.stream_source : obj.stream_decoder)
202
+ while source.alive? && (stream_data = source.resume)
203
+ $stdout.write(stream_data)
204
+ end
197
205
  end
198
206
  elsif command_parser.verbosity_info?
199
207
  $stderr.puts("Note: Object has no stream data")
@@ -396,7 +404,7 @@ module HexaPDF
396
404
  io = @doc.revisions.parser.io
397
405
 
398
406
  io.seek(0, IO::SEEK_END)
399
- startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev] } <<
407
+ startxrefs = @doc.revisions.map {|rev| rev.trailer[:Prev].to_i } <<
400
408
  @doc.revisions.parser.startxref_offset <<
401
409
  io.pos
402
410
  startxrefs.sort!
@@ -427,6 +435,7 @@ module HexaPDF
427
435
  ["OID[,GEN] | o[bject] OID[,GEN]", "Print object"],
428
436
  ["r[ecursive] OID[,GEN]", "Print object recursively"],
429
437
  ["s[tream] OID[,GEN]", "Print filtered stream"],
438
+ ["sd OID[,GEN]", "Print the decoded stream of a Form XObject or page"],
430
439
  ["raw[-stream] OID[,GEN]", "Print raw stream"],
431
440
  ["rev[ision] [NUMBER]", "Print or extract revision"],
432
441
  ["x[ref] OID[,GEN]", "Print the cross-reference entry"],
@@ -34,7 +34,6 @@
34
34
  # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
35
  #++
36
36
 
37
- require 'ostruct'
38
37
  require 'hexapdf/cli/command'
39
38
 
40
39
  module HexaPDF
@@ -53,11 +53,11 @@ module HexaPDF
53
53
  EOF
54
54
 
55
55
  @password = nil
56
- @out_options.compact = true
57
- @out_options.xref_streams = :generate
58
- @out_options.object_streams = :generate
59
- @out_options.streams = :compress
60
- @out_options.optimize_fonts = true
56
+ @out_options[:compact] = true
57
+ @out_options[:xref_streams] = :generate
58
+ @out_options[:object_streams] = :generate
59
+ @out_options[:streams] = :compress
60
+ @out_options[:optimize_fonts] = true
61
61
 
62
62
  options.on("--password PASSWORD", "-p", String,
63
63
  "The password for decryption. Use - for reading from standard input.") do |pwd|
@@ -261,6 +261,20 @@ module HexaPDF
261
261
  @document.layout.style(name, base: base, **properties)
262
262
  end
263
263
 
264
+ # Returns +true+ if a style with the given +name+ exists, else +false+.
265
+ #
266
+ # See HexaPDF::Document::Layout#style for details; this method is just a thin wrapper around
267
+ # that method.
268
+ #
269
+ # Example:
270
+ #
271
+ # composer.style(:header, font: 'Helvetica')
272
+ # composer.style?(:header) # => true
273
+ # composer.style?(:paragraph) # => false
274
+ def style?(name)
275
+ @document.layout.style?(name)
276
+ end
277
+
264
278
  # :call-seq:
265
279
  # composer.styles -> styles
266
280
  # composer.styles(**mapping) -> styles
@@ -224,6 +224,21 @@ module HexaPDF
224
224
  # acro_form.text_field.default_width::
225
225
  # A number specifying the default width of AcroForm text fields which should be auto-sized.
226
226
  #
227
+ # acro_form.text_field.on_max_len_exceeded::
228
+ # Callback hook when the value of a text field exceeds the set maximum length.
229
+ #
230
+ # The value needs to be an object that responds to \#call(field, value) where +field+ is the
231
+ # AcroForm text field on which the value is set and +value+ is the invalid value. The returned
232
+ # value is used instead of the invalid value.
233
+ #
234
+ # The default implementation raises an error.
235
+ #
236
+ # annotation.appearance_generator::
237
+ # The class that should be used for generating appearances for annotations. If the value is a
238
+ # String, it should contain the name of a constant to such a class.
239
+ #
240
+ # See HexaPDF::Type::Annotations::AppearanceGenerator
241
+ #
227
242
  # debug::
228
243
  # If set to +true+, enables debug output.
229
244
  #
@@ -502,6 +517,10 @@ module HexaPDF
502
517
  "#{field.concrete_field_type} field named '#{field.full_field_name}'"
503
518
  end,
504
519
  'acro_form.text_field.default_width' => 100,
520
+ 'acro_form.text_field.on_max_len_exceeded' => proc do |field, value|
521
+ raise HexaPDF::Error, "Value exceeds maximum allowed length of #{field[:MaxLen]}"
522
+ end,
523
+ 'annotation.appearance_generator' => 'HexaPDF::Type::Annotations::AppearanceGenerator',
505
524
  'debug' => false,
506
525
  'document.auto_decrypt' => true,
507
526
  'document.on_invalid_string' => proc do |str|
@@ -690,6 +709,7 @@ module HexaPDF
690
709
  XXAcroFormField: 'HexaPDF::Type::AcroForm::Field',
691
710
  XXAppearanceDictionary: 'HexaPDF::Type::Annotation::AppearanceDictionary',
692
711
  Border: 'HexaPDF::Type::Annotation::Border',
712
+ XXBorderEffect: 'HexaPDF::Type::Annotation::BorderEffect',
693
713
  SigFieldLock: 'HexaPDF::Type::AcroForm::SignatureField::LockDictionary',
694
714
  SV: 'HexaPDF::Type::AcroForm::SignatureField::SeedValueDictionary',
695
715
  SVCert: 'HexaPDF::Type::AcroForm::SignatureField::CertificateSeedValueDictionary',
@@ -746,6 +766,9 @@ module HexaPDF
746
766
  Text: 'HexaPDF::Type::Annotations::Text',
747
767
  Link: 'HexaPDF::Type::Annotations::Link',
748
768
  Widget: 'HexaPDF::Type::Annotations::Widget',
769
+ Line: 'HexaPDF::Type::Annotations::Line',
770
+ Square: 'HexaPDF::Type::Annotations::Square',
771
+ Circle: 'HexaPDF::Type::Annotations::Circle',
749
772
  XML: 'HexaPDF::Type::Metadata',
750
773
  GTS_PDFX: 'HexaPDF::Type::OutputIntent',
751
774
  GTS_PDFA1: 'HexaPDF::Type::OutputIntent',
@@ -774,6 +797,9 @@ module HexaPDF
774
797
  Text: 'HexaPDF::Type::Annotations::Text',
775
798
  Link: 'HexaPDF::Type::Annotations::Link',
776
799
  Widget: 'HexaPDF::Type::Annotations::Widget',
800
+ Line: 'HexaPDF::Type::Annotations::Line',
801
+ Square: 'HexaPDF::Type::Annotations::Square',
802
+ Circle: 'HexaPDF::Type::Annotations::Circle',
777
803
  },
778
804
  XXAcroFormField: {
779
805
  Tx: 'HexaPDF::Type::AcroForm::TextField',
@@ -255,7 +255,7 @@ module HexaPDF
255
255
  array.inject(0) {|m, n| m < 0 ? m : (n < 0 ? -1 : m + n) } <= 0)
256
256
  raise ArgumentError, "Invalid line dash pattern: #{array.inspect} #{phase.inspect}"
257
257
  end
258
- @array = array.freeze
258
+ @array = array
259
259
  @phase = phase
260
260
  end
261
261
 
@@ -108,7 +108,7 @@ module HexaPDF
108
108
  # +type+::
109
109
  # The type can either be :cms when creating standard PDF CMS signatures or :pades when
110
110
  # creating PAdES compatible signatures. PAdES signatures are part of PDF 2.0.
111
- def create(data, type: :cms, &block) # :yield: digested_data
111
+ def create(data, type: :cms, &block) # :yield: digest_algorithm, hashed_data
112
112
  signed_attrs = create_signed_attrs(data, signing_time: (type == :cms))
113
113
  signature = digest_and_sign_data(set(*signed_attrs.value).to_der, &block)
114
114
  unsigned_attrs = create_unsigned_attrs(signature)