hexapdf 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/CONTRIBUTERS +1 -1
  4. data/Rakefile +35 -50
  5. data/VERSION +1 -1
  6. data/lib/hexapdf/cli.rb +4 -0
  7. data/lib/hexapdf/cli/command.rb +6 -2
  8. data/lib/hexapdf/cli/image2pdf.rb +141 -0
  9. data/lib/hexapdf/cli/info.rb +1 -1
  10. data/lib/hexapdf/cli/inspect.rb +32 -2
  11. data/lib/hexapdf/cli/modify.rb +1 -1
  12. data/lib/hexapdf/cli/optimize.rb +1 -1
  13. data/lib/hexapdf/cli/watermark.rb +130 -0
  14. data/lib/hexapdf/composer.rb +2 -2
  15. data/lib/hexapdf/configuration.rb +7 -1
  16. data/lib/hexapdf/content/canvas.rb +2 -2
  17. data/lib/hexapdf/content/graphic_object/arc.rb +2 -2
  18. data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -2
  19. data/lib/hexapdf/content/graphic_object/geom2d.rb +1 -1
  20. data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
  21. data/lib/hexapdf/dictionary.rb +11 -3
  22. data/lib/hexapdf/dictionary_fields.rb +32 -3
  23. data/lib/hexapdf/document.rb +7 -3
  24. data/lib/hexapdf/document/files.rb +1 -1
  25. data/lib/hexapdf/document/fonts.rb +21 -1
  26. data/lib/hexapdf/document/pages.rb +2 -2
  27. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -2
  28. data/lib/hexapdf/font/cmap/parser.rb +1 -1
  29. data/lib/hexapdf/font/true_type/table/head.rb +2 -2
  30. data/lib/hexapdf/font/true_type/table/os2.rb +4 -4
  31. data/lib/hexapdf/font/true_type_wrapper.rb +16 -16
  32. data/lib/hexapdf/font/type1_wrapper.rb +16 -16
  33. data/lib/hexapdf/font_loader.rb +2 -0
  34. data/lib/hexapdf/font_loader/from_configuration.rb +5 -0
  35. data/lib/hexapdf/font_loader/standard14.rb +5 -0
  36. data/lib/hexapdf/image_loader/png.rb +1 -1
  37. data/lib/hexapdf/layout/box.rb +2 -2
  38. data/lib/hexapdf/layout/image_box.rb +1 -1
  39. data/lib/hexapdf/layout/style.rb +50 -24
  40. data/lib/hexapdf/layout/text_box.rb +1 -1
  41. data/lib/hexapdf/layout/text_fragment.rb +2 -2
  42. data/lib/hexapdf/layout/text_layouter.rb +14 -10
  43. data/lib/hexapdf/name_tree_node.rb +3 -3
  44. data/lib/hexapdf/number_tree_node.rb +3 -3
  45. data/lib/hexapdf/pdf_array.rb +207 -0
  46. data/lib/hexapdf/rectangle.rb +12 -12
  47. data/lib/hexapdf/serializer.rb +1 -1
  48. data/lib/hexapdf/stream.rb +6 -4
  49. data/lib/hexapdf/task/optimize.rb +3 -3
  50. data/lib/hexapdf/type.rb +2 -0
  51. data/lib/hexapdf/type/acro_form.rb +51 -0
  52. data/lib/hexapdf/type/acro_form/field.rb +129 -0
  53. data/lib/hexapdf/type/acro_form/form.rb +124 -0
  54. data/lib/hexapdf/type/action.rb +1 -1
  55. data/lib/hexapdf/type/actions/go_to.rb +1 -1
  56. data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
  57. data/lib/hexapdf/type/actions/launch.rb +1 -1
  58. data/lib/hexapdf/type/annotation.rb +2 -2
  59. data/lib/hexapdf/type/annotations.rb +1 -0
  60. data/lib/hexapdf/type/annotations/link.rb +4 -15
  61. data/lib/hexapdf/type/annotations/markup_annotation.rb +2 -1
  62. data/lib/hexapdf/type/annotations/text.rb +3 -6
  63. data/lib/hexapdf/type/annotations/widget.rb +90 -0
  64. data/lib/hexapdf/type/catalog.rb +12 -9
  65. data/lib/hexapdf/type/cid_font.rb +3 -3
  66. data/lib/hexapdf/type/file_specification.rb +2 -2
  67. data/lib/hexapdf/type/font_descriptor.rb +5 -2
  68. data/lib/hexapdf/type/font_simple.rb +1 -1
  69. data/lib/hexapdf/type/font_type0.rb +1 -1
  70. data/lib/hexapdf/type/font_type3.rb +1 -1
  71. data/lib/hexapdf/type/form.rb +2 -2
  72. data/lib/hexapdf/type/graphics_state_parameter.rb +11 -6
  73. data/lib/hexapdf/type/icon_fit.rb +58 -0
  74. data/lib/hexapdf/type/image.rb +14 -8
  75. data/lib/hexapdf/type/info.rb +2 -1
  76. data/lib/hexapdf/type/page.rb +4 -4
  77. data/lib/hexapdf/type/page_tree_node.rb +3 -7
  78. data/lib/hexapdf/type/resources.rb +1 -1
  79. data/lib/hexapdf/type/trailer.rb +4 -4
  80. data/lib/hexapdf/type/viewer_preferences.rb +7 -4
  81. data/lib/hexapdf/type/xref_stream.rb +2 -2
  82. data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
  83. data/lib/hexapdf/version.rb +1 -1
  84. data/man/man1/hexapdf.1 +77 -8
  85. data/test/hexapdf/content/test_canvas.rb +2 -2
  86. data/test/hexapdf/content/test_processor.rb +3 -3
  87. data/test/hexapdf/document/test_files.rb +4 -4
  88. data/test/hexapdf/document/test_fonts.rb +13 -1
  89. data/test/hexapdf/document/test_images.rb +6 -6
  90. data/test/hexapdf/document/test_pages.rb +8 -8
  91. data/test/hexapdf/encryption/test_security_handler.rb +7 -7
  92. data/test/hexapdf/encryption/test_standard_security_handler.rb +5 -5
  93. data/test/hexapdf/font/test_true_type_wrapper.rb +2 -2
  94. data/test/hexapdf/font_loader/test_from_configuration.rb +4 -0
  95. data/test/hexapdf/font_loader/test_standard14.rb +10 -0
  96. data/test/hexapdf/image_loader/test_jpeg.rb +1 -1
  97. data/test/hexapdf/image_loader/test_png.rb +3 -3
  98. data/test/hexapdf/layout/test_box.rb +2 -2
  99. data/test/hexapdf/layout/test_frame.rb +1 -1
  100. data/test/hexapdf/layout/test_image_box.rb +1 -1
  101. data/test/hexapdf/layout/test_style.rb +18 -13
  102. data/test/hexapdf/layout/test_text_box.rb +1 -1
  103. data/test/hexapdf/layout/test_text_layouter.rb +11 -6
  104. data/test/hexapdf/task/test_dereference.rb +2 -2
  105. data/test/hexapdf/task/test_optimize.rb +11 -11
  106. data/test/hexapdf/test_composer.rb +1 -1
  107. data/test/hexapdf/test_dictionary.rb +10 -2
  108. data/test/hexapdf/test_dictionary_fields.rb +27 -3
  109. data/test/hexapdf/test_document.rb +16 -15
  110. data/test/hexapdf/test_importer.rb +4 -4
  111. data/test/hexapdf/test_object.rb +1 -1
  112. data/test/hexapdf/test_pdf_array.rb +162 -0
  113. data/test/hexapdf/test_rectangle.rb +3 -5
  114. data/test/hexapdf/test_serializer.rb +1 -1
  115. data/test/hexapdf/test_stream.rb +1 -0
  116. data/test/hexapdf/test_writer.rb +3 -3
  117. data/test/hexapdf/type/acro_form/test_field.rb +85 -0
  118. data/test/hexapdf/type/acro_form/test_form.rb +69 -0
  119. data/test/hexapdf/type/annotations/test_text.rb +2 -6
  120. data/test/hexapdf/type/annotations/test_widget.rb +24 -0
  121. data/test/hexapdf/type/test_annotation.rb +1 -1
  122. data/test/hexapdf/type/test_catalog.rb +1 -1
  123. data/test/hexapdf/type/test_cid_font.rb +3 -3
  124. data/test/hexapdf/type/test_font.rb +2 -2
  125. data/test/hexapdf/type/test_font_descriptor.rb +2 -1
  126. data/test/hexapdf/type/test_font_simple.rb +3 -3
  127. data/test/hexapdf/type/test_font_true_type.rb +6 -6
  128. data/test/hexapdf/type/test_font_type0.rb +5 -5
  129. data/test/hexapdf/type/test_font_type1.rb +8 -8
  130. data/test/hexapdf/type/test_font_type3.rb +4 -4
  131. data/test/hexapdf/type/test_image.rb +16 -12
  132. data/test/hexapdf/type/test_page.rb +11 -11
  133. data/test/hexapdf/type/test_page_tree_node.rb +20 -20
  134. data/test/hexapdf/type/test_resources.rb +6 -6
  135. data/test/hexapdf/type/test_trailer.rb +5 -2
  136. data/test/hexapdf/type/test_xref_stream.rb +1 -0
  137. data/test/hexapdf/utils/test_sorted_tree_node.rb +35 -35
  138. metadata +23 -7
  139. data/test/hexapdf/type/annotations/test_link.rb +0 -19
@@ -0,0 +1,130 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2019 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/cli/command'
38
+
39
+ module HexaPDF
40
+ module CLI
41
+
42
+ # Uses one or more pages of one PDF and underlays/overlays it/them onto another.
43
+ class Watermark < Command
44
+
45
+ def initialize #:nodoc:
46
+ super('watermark', takes_commands: false)
47
+ short_desc("Put one or more PDF pages onto another PDF")
48
+ long_desc(<<~EOF)
49
+ This command uses one ore more pages from a PDF file and applies them as background or
50
+ stamp on another PDF file.
51
+
52
+ If multiple pages are selected from the watermark PDF, the --repeat option can be used to
53
+ specify how they should be applied: 'last' (the default) will only repeat the last
54
+ watermark page whereas 'all' will cyclically repeat all watermark pages.
55
+ EOF
56
+
57
+ options.on("-w", "--watermark-file FILE", "The PDF used as watermark") do |watermark_file|
58
+ @watermark_file = watermark_file
59
+ end
60
+ options.on("-i", "--pages PAGES", "The pages of the watermark file that should be used " \
61
+ "(default: 1)") do |pages|
62
+ @pages = pages
63
+ end
64
+ options.on("-r", "--repeat REPEAT_MODE", [:last, :all],
65
+ "Specifies how the watermark pages should be repeated. Either last or " \
66
+ "all (default: last)") do |repeat|
67
+ @repeat = repeat
68
+ end
69
+ options.on("-t", "--type WATERMARK_TYPE", [:background, :stamp],
70
+ "Specifies how the watermark is applied: background applies it below the page " \
71
+ "contents and stamp applies it above. Default: background") do |type|
72
+ @type = (type == :background ? :underlay : :overlay)
73
+ end
74
+ options.on("--password PASSWORD", "-p", String,
75
+ "The password for decrypting the input PDF. Use - for reading from " \
76
+ "standard input.") do |pwd|
77
+ @password = (pwd == '-' ? read_password : pwd)
78
+ end
79
+ define_optimization_options
80
+ define_encryption_options
81
+
82
+ @watermark_file = nil
83
+ @pages = "1"
84
+ @repeat = :last
85
+ @type = :underlay
86
+ @password = nil
87
+ end
88
+
89
+ def execute(in_file, out_file) #:nodoc:
90
+ maybe_raise_on_existing_file(out_file)
91
+ watermark = HexaPDF::Document.open(@watermark_file)
92
+ indices = page_index_generator(watermark)
93
+ xobject_map = {}
94
+ with_document(in_file, password: @password, out_file: out_file) do |doc|
95
+ doc.pages.each do |page|
96
+ index = indices.next
97
+ xobject = xobject_map[index] ||= doc.import(watermark.pages[index].to_form_xobject)
98
+ pw = page.box(:media).width.to_f
99
+ ph = page.box(:media).height.to_f
100
+ xw = xobject.width.to_f
101
+ xh = xobject.height.to_f
102
+ canvas = page.canvas(type: @type)
103
+ ratio = [pw / xw, ph / xh].min
104
+ xw, xh = xw * ratio, xh * ratio
105
+ x, y = (pw - xw) / 2, (ph - xh) / 2
106
+ canvas.xobject(xobject, at: [x, y], width: xw, height: xh)
107
+ end
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ # Returns an Enumerator instance that returns the indices of the watermark pages that should
114
+ # be used.
115
+ def page_index_generator(watermark)
116
+ pages = parse_pages_specification(@pages, watermark.pages.count)
117
+ Enumerator.new do |y|
118
+ loop do
119
+ pages.each {|index, _rotation| y << index }
120
+ if @repeat == :last
121
+ y << pages.last[0] while true
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
@@ -93,7 +93,7 @@ module HexaPDF
93
93
  # ...
94
94
  # end
95
95
  def self.create(output, **options, &block)
96
- new(options, &block).write(output)
96
+ new(**options, &block).write(output)
97
97
  end
98
98
 
99
99
  # The PDF document that is created.
@@ -295,7 +295,7 @@ module HexaPDF
295
295
  # options to make it work in all cases.
296
296
  def update_style(style, options = {})
297
297
  style ||= base_style
298
- style = style.dup.update(options) unless options.empty?
298
+ style = style.dup.update(**options) unless options.empty?
299
299
  style.font(base_style.font) unless style.font?
300
300
  style.font(@document.fonts.add(style.font)) unless style.font.respond_to?(:dict)
301
301
  style
@@ -199,7 +199,7 @@ module HexaPDF
199
199
  # {"font_name": {variant: file_name, variant2: file_name2, ...}, ...}
200
200
  #
201
201
  # Once a font is registered in this way, the font name together with a variant name can be used
202
- # with the HexaPDF::Document::Fonts#load method to load the font.
202
+ # with the HexaPDF::Document::Fonts#add method to load the font.
203
203
  #
204
204
  # For best compatibility, the following variant names should be used:
205
205
  #
@@ -447,6 +447,10 @@ module HexaPDF
447
447
  Action: 'HexaPDF::Type::Action',
448
448
  XXLaunchActionWinParameters: 'HexaPDF::Type::Actions::Launch::WinParameters',
449
449
  Annot: 'HexaPDF::Type::Annotation',
450
+ XXAppearanceCharacteristics: 'HexaPDF::Type::Annotations::Widget::AppearanceCharacteristics',
451
+ XXIconFit: 'HexaPDF::Type::IconFit',
452
+ XXAcroForm: 'HexaPDF::Type::AcroForm::Form',
453
+ XXAcroFormField: 'HexaPDF::Type::AcroForm::Field',
450
454
  },
451
455
  'object.subtype_map' => {
452
456
  nil => {
@@ -463,6 +467,7 @@ module HexaPDF
463
467
  URI: 'HexaPDF::Type::Actions::URI',
464
468
  Text: 'HexaPDF::Type::Annotations::Text',
465
469
  Link: 'HexaPDF::Type::Annotations::Link',
470
+ Widget: 'HexaPDF::Type::Annotations::Widget',
466
471
  },
467
472
  XObject: {
468
473
  Image: 'HexaPDF::Type::Image',
@@ -485,6 +490,7 @@ module HexaPDF
485
490
  Annot: {
486
491
  Text: 'HexaPDF::Type::Annotations::Text',
487
492
  Link: 'HexaPDF::Type::Annotations::Link',
493
+ Widget: 'HexaPDF::Type::Annotations::Widget',
488
494
  },
489
495
  })
490
496
 
@@ -1088,7 +1088,7 @@ module HexaPDF
1088
1088
  unless obj.respond_to?(:configure)
1089
1089
  obj = context.document.config.constantize('graphic_object.map', obj)
1090
1090
  end
1091
- obj = obj.configure(options) unless options.empty? && obj.respond_to?(:draw)
1091
+ obj = obj.configure(**options) unless options.empty? && obj.respond_to?(:draw)
1092
1092
  obj
1093
1093
  end
1094
1094
 
@@ -1607,7 +1607,7 @@ module HexaPDF
1607
1607
  # See: PDF1.7 s9.2.2
1608
1608
  def font(name = nil, size: nil, **options)
1609
1609
  if name
1610
- @font = (name.respond_to?(:dict) ? name : context.document.fonts.add(name, options))
1610
+ @font = (name.respond_to?(:dict) ? name : context.document.fonts.add(name, **options))
1611
1611
  if size
1612
1612
  font_size(size)
1613
1613
  else
@@ -54,7 +54,7 @@ module HexaPDF
54
54
  #
55
55
  # See #configure for the allowed keyword arguments.
56
56
  def self.configure(**kwargs)
57
- new.configure(kwargs)
57
+ new.configure(**kwargs)
58
58
  end
59
59
 
60
60
  # The maximal number of curves used for approximating a complete ellipse.
@@ -162,7 +162,7 @@ module HexaPDF
162
162
  def draw(canvas, move_to_start: true)
163
163
  @max_curves = canvas.context.document.config['graphic_object.arc.max_curves']
164
164
  canvas.move_to(*start_point) if move_to_start
165
- curves.each {|curve| canvas.curve_to(*curve) }
165
+ curves.each {|x, y, hash| canvas.curve_to(x, y, **hash) }
166
166
  end
167
167
 
168
168
  # Returns an array of arrays that contain the points for the Bezier curves which are used
@@ -54,7 +54,7 @@ module HexaPDF
54
54
  #
55
55
  # See #configure for the allowed keyword arguments.
56
56
  def self.configure(**kwargs)
57
- new.configure(kwargs)
57
+ new.configure(**kwargs)
58
58
  end
59
59
 
60
60
  # x-coordinate of endpoint
@@ -133,7 +133,7 @@ module HexaPDF
133
133
  canvas.line_to(@x, @y)
134
134
  else
135
135
  values = compute_arc_values(x1, y1)
136
- arc = canvas.graphic_object(:arc, values)
136
+ arc = canvas.graphic_object(:arc, **values)
137
137
  arc.draw(canvas, move_to_start: false)
138
138
  end
139
139
  end
@@ -50,7 +50,7 @@ module HexaPDF
50
50
  #
51
51
  # See #configure for the allowed keyword arguments.
52
52
  def self.configure(**kwargs)
53
- new.configure(kwargs)
53
+ new.configure(**kwargs)
54
54
  end
55
55
 
56
56
  # The Geom2D object that should be drawn
@@ -62,7 +62,7 @@ module HexaPDF
62
62
  #
63
63
  # See #configure for the allowed keyword arguments.
64
64
  def self.configure(**kwargs)
65
- new.configure(kwargs)
65
+ new.configure(**kwargs)
66
66
  end
67
67
 
68
68
  # x-coordinate of center point
@@ -70,7 +70,7 @@ module HexaPDF
70
70
  # String:: String (for text strings), PDFByteString (for binary strings)
71
71
  # Date:: PDFDate
72
72
  # Name:: Symbol
73
- # Array:: Array
73
+ # Array:: PDFArray or Array
74
74
  # Dictionary:: Dictionary (or any subclass) or Hash
75
75
  # Stream:: Stream (or any subclass)
76
76
  # Null:: NilClass
@@ -93,11 +93,14 @@ module HexaPDF
93
93
  # to be an indirect object (+true+), a direct object (+false+) or if it doesn't
94
94
  # matter (unspecified or +nil+).
95
95
  #
96
+ # allowed_values:: An array of allowed values for this field.
97
+ #
96
98
  # version:: Specifies the minimum version of the PDF specification needed for this value.
97
99
  def self.define_field(name, type:, required: false, default: nil, indirect: nil,
98
- version: '1.2')
100
+ allowed_values: nil, version: '1.2')
99
101
  @fields ||= {}
100
- @fields[name] = Field.new(type, required, default, indirect, version)
102
+ @fields[name] = Field.new(type, required: required, default: default, indirect: indirect,
103
+ allowed_values: allowed_values, version: version)
101
104
  end
102
105
 
103
106
  # Returns the field entry for the given field name.
@@ -296,6 +299,11 @@ module HexaPDF
296
299
  end
297
300
  end
298
301
 
302
+ # Check the value of the field against the allowed values.
303
+ if field.allowed_values && !field.allowed_values.include?(obj)
304
+ yield("Field #{name} does not contain an allowed value")
305
+ end
306
+
299
307
  # Check if field value needs to be (in)direct
300
308
  unless field.indirect.nil?
301
309
  obj = value[name] # we need the unwrapped object!
@@ -37,6 +37,7 @@
37
37
  require 'time'
38
38
  require 'date'
39
39
  require 'hexapdf/object'
40
+ require 'hexapdf/pdf_array'
40
41
  require 'hexapdf/rectangle'
41
42
  require 'hexapdf/configuration'
42
43
  require 'hexapdf/utils/pdf_doc_encoding'
@@ -101,6 +102,10 @@ module HexaPDF
101
102
  # needs to be a direct object or +nil+ if it can be either.
102
103
  attr_reader :indirect
103
104
 
105
+ # Returns an array with the allowed values for this field, or +nil+ if the values are not
106
+ # constrained.
107
+ attr_reader :allowed_values
108
+
104
109
  # Returns the PDF version that is required for this field.
105
110
  attr_reader :version
106
111
 
@@ -108,10 +113,12 @@ module HexaPDF
108
113
  #
109
114
  # Depending on the +type+ entry an appropriate field converter object is chosen from the
110
115
  # available converters.
111
- def initialize(type, required = false, default = nil, indirect = nil, version = nil)
116
+ def initialize(type, required: false, default: nil, indirect: nil, allowed_values: nil,
117
+ version: nil)
112
118
  @type = [type].flatten
113
119
  @type_mapped = false
114
120
  @required, @default, @indirect, @version = required, default, indirect, version
121
+ @allowed_values = allowed_values && [allowed_values].flatten
115
122
  @converters = @type.map {|t| self.class.converter_for(t) }.compact
116
123
  end
117
124
 
@@ -199,6 +206,27 @@ module HexaPDF
199
206
 
200
207
  end
201
208
 
209
+ # Converter module for fields of type PDFArray.
210
+ module ArrayConverter
211
+
212
+ # This converter is usable if the +type+ is PDFArray.
213
+ def self.usable_for?(type)
214
+ type == PDFArray
215
+ end
216
+
217
+ # PDFArray fields can also contain simple arrays.
218
+ def self.additional_types
219
+ Array
220
+ end
221
+
222
+ # Wraps a given array in the PDFArray class. Otherwise returns +nil+.
223
+ def self.convert(data, _type, document)
224
+ return unless data.kind_of?(Array)
225
+ document.wrap(data, type: PDFArray)
226
+ end
227
+
228
+ end
229
+
202
230
  # Converter module for string fields to automatically convert a string into UTF-8 encoding.
203
231
  module StringConverter
204
232
 
@@ -329,8 +357,9 @@ module HexaPDF
329
357
 
330
358
  end
331
359
 
332
- Field.converters.replace([FileSpecificationConverter, DictionaryConverter, StringConverter,
333
- PDFByteStringConverter, DateConverter, RectangleConverter])
360
+ Field.converters.replace([FileSpecificationConverter, DictionaryConverter, ArrayConverter,
361
+ StringConverter, PDFByteStringConverter, DateConverter,
362
+ RectangleConverter])
334
363
 
335
364
  end
336
365
 
@@ -40,6 +40,7 @@ require 'hexapdf/content'
40
40
  require 'hexapdf/configuration'
41
41
  require 'hexapdf/reference'
42
42
  require 'hexapdf/object'
43
+ require 'hexapdf/pdf_array'
43
44
  require 'hexapdf/stream'
44
45
  require 'hexapdf/revisions'
45
46
  require 'hexapdf/type'
@@ -135,7 +136,7 @@ module HexaPDF
135
136
 
136
137
  @revisions = Revisions.from_io(self, io)
137
138
  @security_handler = if encrypted? && @config['document.auto_decrypt']
138
- Encryption::SecurityHandler.set_up_decryption(self, decryption_opts)
139
+ Encryption::SecurityHandler.set_up_decryption(self, **decryption_opts)
139
140
  else
140
141
  nil
141
142
  end
@@ -199,7 +200,7 @@ module HexaPDF
199
200
  # If the +revision+ option is +:current+, the current revision is used. Otherwise +revision+
200
201
  # should be a revision index.
201
202
  def add(obj, revision: :current, **wrap_opts)
202
- obj = wrap(obj, wrap_opts) unless obj.kind_of?(HexaPDF::Object)
203
+ obj = wrap(obj, **wrap_opts) unless obj.kind_of?(HexaPDF::Object)
203
204
 
204
205
  revision = (revision == :current ? @revisions.current : @revisions.revision(revision))
205
206
  if revision.nil?
@@ -294,7 +295,8 @@ module HexaPDF
294
295
  # prevent invalid mappings when only partial knowledge (:Type key is missing) is available.
295
296
  #
296
297
  # * If there is no valid class after the above steps, HexaPDF::Stream is used if a stream is
297
- # given, HexaPDF::Dictionary if the given objecct is a hash or else HexaPDF::Object is used.
298
+ # given, HexaPDF::Dictionary if the given object is a hash, HexaPDF::PDFArray if it is an
299
+ # array or else HexaPDF::Object is used.
298
300
  #
299
301
  # Options:
300
302
  #
@@ -346,6 +348,8 @@ module HexaPDF
346
348
  HexaPDF::Stream
347
349
  elsif data.value.kind_of?(Hash)
348
350
  HexaPDF::Dictionary
351
+ elsif data.value.kind_of?(Array)
352
+ HexaPDF::PDFArray
349
353
  else
350
354
  HexaPDF::Object
351
355
  end
@@ -83,7 +83,7 @@ module HexaPDF
83
83
  raise ArgumentError, "The name argument is mandatory when given an IO object"
84
84
  end
85
85
 
86
- spec = @document.add(Type: :Filespec)
86
+ spec = @document.add({Type: :Filespec})
87
87
  spec.path = name
88
88
  spec[:Desc] = description if description
89
89
  spec.embed(file_or_io, name: name, register: true) if embed || !file_or_io.kind_of?(String)
@@ -70,11 +70,31 @@ module HexaPDF
70
70
  if font
71
71
  @loaded_fonts_cache[[name, options]] = font
72
72
  else
73
+ font_list = configured_fonts.sort.map do |font_name, variants|
74
+ "#{font_name} (#{variants.join(', ')})"
75
+ end.join(', ')
73
76
  raise HexaPDF::Error, "The requested font '#{name}' in variant '#{options[:variant]}' " \
74
- "couldn't be found"
77
+ "couldn't be found. Configured fonts: #{font_list}"
75
78
  end
76
79
  end
77
80
 
81
+ # Returns a hash of the form 'font_name => [variants, ...]' with all the fonts that are
82
+ # configured. These fonts can be added to the document by using the #add method.
83
+ def configured_fonts
84
+ result = {}
85
+ each_font_loader do |loader|
86
+ next unless loader.respond_to?(:available_fonts)
87
+ loader.available_fonts(@document).each do |name, variants|
88
+ if result.key?(name)
89
+ result[name].concat(variants).uniq!
90
+ else
91
+ result[name] = variants
92
+ end
93
+ end
94
+ end
95
+ result
96
+ end
97
+
78
98
  private
79
99
 
80
100
  # :call-seq: