hexapdf 0.47.0 → 1.0.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -16
  3. data/lib/hexapdf/composer.rb +7 -0
  4. data/lib/hexapdf/configuration.rb +2 -0
  5. data/lib/hexapdf/content/parser.rb +3 -1
  6. data/lib/hexapdf/digital_signature/cms_handler.rb +13 -0
  7. data/lib/hexapdf/digital_signature/signature.rb +1 -1
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +1 -0
  9. data/lib/hexapdf/document.rb +14 -3
  10. data/lib/hexapdf/font/cmap/writer.rb +58 -4
  11. data/lib/hexapdf/font/cmap.rb +7 -0
  12. data/lib/hexapdf/font/true_type_wrapper.rb +41 -16
  13. data/lib/hexapdf/layout/text_fragment.rb +2 -1
  14. data/lib/hexapdf/object.rb +1 -1
  15. data/lib/hexapdf/parser.rb +1 -1
  16. data/lib/hexapdf/reference.rb +1 -1
  17. data/lib/hexapdf/task/merge_acro_form.rb +164 -0
  18. data/lib/hexapdf/task.rb +1 -0
  19. data/lib/hexapdf/tokenizer.rb +2 -0
  20. data/lib/hexapdf/type/acro_form/form.rb +14 -27
  21. data/lib/hexapdf/type/acro_form/signature_field.rb +16 -6
  22. data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
  23. data/lib/hexapdf/type/actions/go_to.rb +1 -0
  24. data/lib/hexapdf/type/actions/go_to_r.rb +1 -0
  25. data/lib/hexapdf/type/actions/launch.rb +5 -1
  26. data/lib/hexapdf/type/annotation.rb +6 -1
  27. data/lib/hexapdf/type/annotations/markup_annotation.rb +14 -1
  28. data/lib/hexapdf/type/catalog.rb +3 -0
  29. data/lib/hexapdf/type/cid_font.rb +4 -1
  30. data/lib/hexapdf/type/file_specification.rb +17 -14
  31. data/lib/hexapdf/type/font_descriptor.rb +4 -3
  32. data/lib/hexapdf/type/font_simple.rb +3 -1
  33. data/lib/hexapdf/type/font_true_type.rb +2 -0
  34. data/lib/hexapdf/type/font_type0.rb +1 -1
  35. data/lib/hexapdf/type/font_type1.rb +7 -0
  36. data/lib/hexapdf/type/font_type3.rb +0 -1
  37. data/lib/hexapdf/type/form.rb +5 -2
  38. data/lib/hexapdf/type/graphics_state_parameter.rb +7 -4
  39. data/lib/hexapdf/type/image.rb +8 -4
  40. data/lib/hexapdf/type/info.rb +2 -2
  41. data/lib/hexapdf/type/mark_information.rb +2 -2
  42. data/lib/hexapdf/type/optional_content_configuration.rb +1 -1
  43. data/lib/hexapdf/type/optional_content_membership.rb +1 -1
  44. data/lib/hexapdf/type/page.rb +5 -3
  45. data/lib/hexapdf/type/resources.rb +6 -6
  46. data/lib/hexapdf/type/viewer_preferences.rb +4 -3
  47. data/lib/hexapdf/version.rb +1 -1
  48. data/test/hexapdf/common_tokenizer_tests.rb +5 -0
  49. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +6 -0
  50. data/test/hexapdf/digital_signature/test_cms_handler.rb +12 -7
  51. data/test/hexapdf/digital_signature/test_signature.rb +7 -0
  52. data/test/hexapdf/digital_signature/test_signatures.rb +8 -3
  53. data/test/hexapdf/font/cmap/test_writer.rb +73 -16
  54. data/test/hexapdf/font/test_true_type_wrapper.rb +17 -3
  55. data/test/hexapdf/layout/test_list_box.rb +7 -7
  56. data/test/hexapdf/layout/test_text_fragment.rb +3 -3
  57. data/test/hexapdf/layout/test_text_layouter.rb +4 -2
  58. data/test/hexapdf/task/test_merge_acro_form.rb +104 -0
  59. data/test/hexapdf/test_composer.rb +8 -0
  60. data/test/hexapdf/test_document.rb +9 -0
  61. data/test/hexapdf/test_parser.rb +7 -0
  62. data/test/hexapdf/test_writer.rb +8 -3
  63. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +18 -18
  64. data/test/hexapdf/type/acro_form/test_form.rb +7 -3
  65. data/test/hexapdf/type/actions/test_launch.rb +6 -2
  66. data/test/hexapdf/type/test_font_type1.rb +5 -0
  67. data/test/hexapdf/type/test_form.rb +1 -1
  68. data/test/hexapdf/type/test_page.rb +7 -1
  69. metadata +4 -2
@@ -0,0 +1,164 @@
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-2024 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/serializer'
38
+
39
+ module HexaPDF
40
+ module Task
41
+
42
+ # Task for merging an AcroForm from one PDF into another.
43
+ #
44
+ # It takes care of
45
+ #
46
+ # * adding the fields to the main Type::AcroForm::Form dictionary,
47
+ # * adjusting the field names so that they are unique,
48
+ # * and merging the properties of the main AcroForm dictionary itself and adjusting field
49
+ # information appropriately.
50
+ #
51
+ # Note that the pages with the fields need to be imported already.
52
+ #
53
+ # The steps for using this task are:
54
+ #
55
+ # 1. Import the pages into the target document and add all imported pages to an array
56
+ # 2. Call this task using the created array of pages.
57
+ #
58
+ # Example:
59
+ #
60
+ # pages = doc.pages.map {|page| target.pages.add(target.import(page)) }
61
+ # target.task(:merge_acro_form, source: doc, pages: pages)
62
+ module MergeAcroForm
63
+
64
+ # Performs the necessary steps to merge the AcroForm fields from the +source+ into the target
65
+ # document +doc+.
66
+ #
67
+ # +source+::
68
+ # Specifies the source PDF document the information from which should be merged into the
69
+ # target document.
70
+ #
71
+ # +pages+::
72
+ # An array of pages that were imported from +source+ and contain the widgets of the fields
73
+ # that should be merged.
74
+ def self.call(doc, source:, pages:)
75
+ return unless source.acro_form
76
+
77
+ acro_form = doc.acro_form(create: true)
78
+
79
+ # Determine a unique name for root field and create root field
80
+ import_name = 'merged_' +
81
+ (acro_form.root_fields.select {|field| field[:T] =~ /\Amerged_\d+\z/ }.
82
+ map {|field| field[:T][/\d+/].to_i }.sort.last || 0).succ.to_s
83
+ root_field = doc.add({T: import_name, Kids: []})
84
+ acro_form.root_fields << root_field
85
+
86
+ # Merge the main AcroForm dictionary
87
+ font_name_mapping = merge_form_dictionary(acro_form, source.acro_form, root_field)
88
+ font_name_re = font_name_mapping.keys.map {|name| Regexp.escape(name) }.join('|')
89
+ root_field[:DA] && root_field[:DA].sub!(font_name_re, font_name_mapping)
90
+
91
+ # Process all field widgets of the given pages
92
+ process_calculate_actions = false
93
+ signature_field_seen = false
94
+ pages.each do |page|
95
+ page.each_annotation do |widget|
96
+ next unless widget[:Subtype] == :Widget
97
+ field = widget.form_field
98
+
99
+ # Correct the font name in the default appearance string
100
+ widget[:DA] && widget[:DA].sub!(font_name_re, font_name_mapping)
101
+ field[:DA] && field[:DA].sub!(font_name_re, font_name_mapping)
102
+
103
+ process_calculate_actions = true if field[:AA]&.[](:C)
104
+ signature_field_seen = true if field.field_type == :Sig
105
+
106
+ # Add to the root field
107
+ field = field[:Parent] while field[:Parent]
108
+ if field != root_field
109
+ field[:Parent] = root_field
110
+ root_field[:Kids] << field
111
+ end
112
+ end
113
+ end
114
+
115
+ # Update calculation JavaScript actions with changed field names
116
+ fix_calculate_actions(acro_form, source.acro_form, import_name) if process_calculate_actions
117
+
118
+ # Update signature flags if necessary
119
+ if signature_field_seen && source.acro_form.signature_flag?(:signatures_exist)
120
+ acro_form.signature_flag(:signatures_exist)
121
+ end
122
+ end
123
+
124
+ # Merges the AcroForm +source_form+ into the +target_form+ and returns a mapping of old font
125
+ # names to new ones.
126
+ def self.merge_form_dictionary(target_form, source_form, root_field)
127
+ target_resources = target_form.default_resources
128
+ font_name_mapping = {}
129
+ serializer = HexaPDF::Serializer.new
130
+
131
+ source_form.default_resources[:Font].each do |font_name, value|
132
+ new_name = target_resources.add_font(target_form.document.import(value))
133
+ font_name_mapping[serializer.serialize(font_name)] = serializer.serialize(new_name)
134
+ end
135
+
136
+ root_field[:DA] = target_form.document.import(source_form[:DA])
137
+ root_field[:Q] = target_form.document.import(source_form[:Q])
138
+
139
+ font_name_mapping
140
+ end
141
+
142
+ # Fixes the calculate actions listed in the /CO entry of the main AcroForm dictionary to use
143
+ # the new names of the fields.
144
+ def self.fix_calculate_actions(acro_form, source_form, import_name)
145
+ if source_form[:CO]
146
+ acro_form[:CO] ||= []
147
+ acro_form[:CO].value.concat(acro_form.document.import(source_form[:CO]).value)
148
+ acro_form[:CO].each do |field|
149
+ next unless (action = field[:AA]&.[](:C))
150
+ action[:JS].gsub!(/"(.*?)"/) do |match|
151
+ if source_form.field_by_name($1)
152
+ "\"#{import_name}.#{$1}\""
153
+ else
154
+ match
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+ end
data/lib/hexapdf/task.rb CHANGED
@@ -65,6 +65,7 @@ module HexaPDF
65
65
  autoload(:Optimize, 'hexapdf/task/optimize')
66
66
  autoload(:Dereference, 'hexapdf/task/dereference')
67
67
  autoload(:PDFA, 'hexapdf/task/pdfa')
68
+ autoload(:MergeAcroForm, 'hexapdf/task/merge_acro_form')
68
69
 
69
70
  end
70
71
 
@@ -144,6 +144,8 @@ module HexaPDF
144
144
  elsif byte == 93 # ]
145
145
  @ss.pos += 1
146
146
  TOKEN_ARRAY_END
147
+ elsif byte == 41 # )
148
+ raise HexaPDF::MalformedPDFError.new("Delimiter ')' found at invalid position", pos: pos)
147
149
  elsif byte == 123 || byte == 125 # { }
148
150
  Token.new(@ss.get_byte)
149
151
  elsif byte == 37 # %
@@ -81,6 +81,7 @@ module HexaPDF
81
81
  define_field :CO, type: PDFArray, version: '1.3'
82
82
  define_field :DR, type: :XXResources
83
83
  define_field :DA, type: String
84
+ define_field :Q, type: Integer
84
85
  define_field :XFA, type: [Stream, PDFArray], version: '1.5'
85
86
 
86
87
  bit_field(:signature_flags, {signatures_exist: 0, append_only: 1},
@@ -182,29 +183,18 @@ module HexaPDF
182
183
  # The optional keyword arguments allow setting often used properties of the field:
183
184
  #
184
185
  # +font+::
185
- # The font that should be used for the text of the field. If +font_size+, +font_options+
186
- # or +font_color+ is specified but +font+ isn't, the font Helvetica is used.
187
- #
188
- # If no font is set on the text field, the default font properties of the AcroForm form
189
- # are used. Note that field specific or form specific font properties have to be
190
- # set. Otherwise there might be problems when creating a visual appearance with other
191
- # PDF libraries/viewers.
192
- #
193
- # If HexaPDF is used to create a visual appearance of the field value and neither field
194
- # specific nor form specific font properties are available, the configuration option
195
- # 'acro_form.fallback_default_appearance' defines whether and which field specific font
196
- # properties are set and used.
186
+ # The font that should be used for the text of the field. If not specified, it
187
+ # defaults to Helvetica.
197
188
  #
198
189
  # +font_options+::
199
- # A hash with font options like :variant that should be used.
190
+ # A hash with font options like :variant that should be used. If not specified, it
191
+ # defaults to the empty hash.
200
192
  #
201
193
  # +font_size+::
202
- # The font size that should be used. If +font+, +font_options+ or +font_color+ is
203
- # specified but +font_size+ isn't, font size defaults to 0 (= auto-sizing).
194
+ # The font size that should be used. If not specified, it defaults to 0 (= auto-sizing).
204
195
  #
205
196
  # +font_color+::
206
- # The font color that should be used. If +font+, +font_options+ or +font_size+ is
207
- # specified but +font_color+ isn't, font color defaults to 0 (i.e. black).
197
+ # The font color that should be used. If not specified, it defaults to 0 (i.e. black).
208
198
  #
209
199
  # +align+::
210
200
  # The alignment of the text, either :left, :center or :right.
@@ -445,8 +435,7 @@ module HexaPDF
445
435
 
446
436
  # Returns the dictionary containing the default resources for form field appearance streams.
447
437
  def default_resources
448
- self[:DR] ||= document.wrap({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]},
449
- type: :XXResources)
438
+ self[:DR] ||= document.wrap({}, type: :XXResources)
450
439
  end
451
440
 
452
441
  # Sets the global default appearance string using the provided values or the default values
@@ -532,7 +521,7 @@ module HexaPDF
532
521
  field = Field.wrap(document, field)
533
522
  next unless field && (calculation_action = field[:AA]&.[](:C))
534
523
  result = JavaScriptActions.calculate(self, calculation_action)
535
- field.form_field.field_value = result if result
524
+ field.field_value = result if result
536
525
  end
537
526
  end
538
527
 
@@ -566,13 +555,11 @@ module HexaPDF
566
555
  # Applies the given variable field properties to the field.
567
556
  def apply_variable_text_properties(field, font: nil, font_options: nil, font_size: nil,
568
557
  font_color: nil, align: nil)
569
- if font || font_options || font_size || font_color
570
- field.set_default_appearance_string(font: font || 'Helvetica',
571
- font_options: font_options || {},
572
- font_size: font_size || 0,
573
- font_color: font_color || 0)
574
- end
575
- field.text_alignment(align) if align
558
+ field.set_default_appearance_string(font: font || 'Helvetica',
559
+ font_options: font_options || {},
560
+ font_size: font_size || 0,
561
+ font_color: font_color || 0)
562
+ field.text_alignment(align || :left)
576
563
  end
577
564
 
578
565
  def perform_validation # :nodoc:
@@ -62,8 +62,10 @@ module HexaPDF
62
62
 
63
63
  define_field :Type, type: Symbol, default: type
64
64
  define_field :Action, type: Symbol, required: true,
65
- allowed_values: [:All, :Include, :Exclude]
65
+ allowed_values: [:All, :Include, :Exclude]
66
66
  define_field :Fields, type: PDFArray
67
+ define_field :P, type: Numeric, version: '2.0',
68
+ allowed_values: [1, 2, 3]
67
69
 
68
70
  private
69
71
 
@@ -83,8 +85,8 @@ module HexaPDF
83
85
  # If a flag is set it means that the associated entry is a required constraint. Otherwise it
84
86
  # is optional.
85
87
  #
86
- # The available flags are: filter, sub_filter, v, reasons, legal_attestation, add_rev_info
87
- # and digest_method.
88
+ # The available flags are: filter, sub_filter, v, reasons, legal_attestation, add_rev_info,
89
+ # digest_method, lock_document and appearance_filter.
88
90
  #
89
91
  # See: PDF2.0 s12.7.5.5
90
92
  class SeedValueDictionary < Dictionary
@@ -98,13 +100,16 @@ module HexaPDF
98
100
  define_field :Filter, type: Symbol
99
101
  define_field :SubFilter, type: PDFArray
100
102
  define_field :DigestMethod, type: PDFArray, version: '1.7'
101
- define_field :V, type: Float
103
+ define_field :V, type: Integer
102
104
  define_field :Cert, type: :SVCert
103
105
  define_field :Reasons, type: PDFArray
104
106
  define_field :MDP, type: Dictionary, version: '1.6'
105
107
  define_field :TimeStamp, type: Dictionary, version: '1.6'
106
108
  define_field :LegalAttestation, type: PDFArray, version: '1.6'
107
109
  define_field :AddRevInfo, type: Boolean, version: '1.7'
110
+ define_field :LockDocument, type: Symbol, version: '2.0',
111
+ allowed_values: [:true, :false, :auto]
112
+ define_field :AppearanceFilter, type: String, version: '2.0'
108
113
 
109
114
  ##
110
115
  # :method: flags
@@ -130,7 +135,8 @@ module HexaPDF
130
135
  # all prior flags will be cleared.
131
136
  #
132
137
  bit_field(:flags, {filter: 0, sub_filter: 1, v: 2, reasons: 3, legal_attestation: 4,
133
- add_rev_info: 5, digest_method: 6},
138
+ add_rev_info: 5, digest_method: 6, lock_document: 7,
139
+ appearance_filter: 8},
134
140
  lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
135
141
  value_getter: "self[:Ff]", value_setter: "self[:Ff]")
136
142
 
@@ -155,12 +161,16 @@ module HexaPDF
155
161
  define_field :Type, type: Symbol, default: type
156
162
  define_field :Ff, type: Integer, default: 0
157
163
  define_field :Subject, type: PDFArray
164
+ define_field :SignaturePolicyOID, type: String, version: '2.0'
165
+ define_field :SignaturePolicyHashValue, type: String, version: '2.0'
166
+ define_field :SignaturePolicyHashAlgorithm, type: Symbol, version: '2.0'
167
+ define_field :SignaturePolicyCommitmentType, type: PDFArray, version: '2.0'
158
168
  define_field :SubjectDN, type: PDFArray, version: '1.7'
159
169
  define_field :KeyUsage, type: PDFArray, version: '1.7'
160
170
  define_field :Issuer, type: PDFArray
161
171
  define_field :OID, type: PDFArray
162
172
  define_field :URL, type: String
163
- define_field :URLType, type: Symbol, default: :Browser
173
+ define_field :URLType, type: Symbol, default: :Browser, version: '1.7'
164
174
 
165
175
  ##
166
176
  # :method: flags
@@ -51,7 +51,7 @@ module HexaPDF
51
51
  # See: PDF2.0 s12.7.4.3
52
52
  class VariableTextField < Field
53
53
 
54
- define_field :DA, type: String
54
+ define_field :DA, type: PDFByteString
55
55
  define_field :Q, type: Integer, default: 0, allowed_values: [0, 1, 2]
56
56
  define_field :DS, type: String, version: '1.5'
57
57
  define_field :RV, type: [String, Stream], version: '1.5'
@@ -47,6 +47,7 @@ module HexaPDF
47
47
 
48
48
  define_field :S, type: Symbol, required: true, default: :GoTo
49
49
  define_field :D, type: [Symbol, PDFByteString, PDFArray], required: true
50
+ define_field :SD, type: PDFArray, version: '2.0'
50
51
 
51
52
  end
52
53
 
@@ -48,6 +48,7 @@ module HexaPDF
48
48
  define_field :S, type: Symbol, required: true, default: :GoToR
49
49
  define_field :F, type: :Filespec, required: true
50
50
  define_field :D, type: [Symbol, PDFByteString, PDFArray], required: true
51
+ define_field :SD, type: PDFArray, version: '2.0'
51
52
  define_field :NewWindow, type: Boolean, version: '1.2'
52
53
 
53
54
  end
@@ -60,13 +60,17 @@ module HexaPDF
60
60
  define_field :S, type: Symbol, required: true, default: :Launch
61
61
  define_field :F, type: :Filespec
62
62
  define_field :Win, type: :XXLaunchActionWinParameters
63
+ define_field :Mac, type: ::Object, version: '2.0'
64
+ define_field :Unix, type: ::Object, version: '2.0'
63
65
  define_field :NewWindow, type: Boolean, version: '1.2'
64
66
 
65
67
  private
66
68
 
67
69
  def perform_validation #:nodoc:
68
70
  super
69
- yield("A Launch action needs a target") unless key?(:F) || key?(:Win)
71
+ unless key?(:Win) || key?(:Mac) || key?(:Unix) || key?(:F)
72
+ yield("Launch action key /F required if /Win, /Mac and /Unix are absent")
73
+ end
70
74
  end
71
75
 
72
76
  end
@@ -123,7 +123,7 @@ module HexaPDF
123
123
  define_field :Contents, type: String
124
124
  define_field :P, type: Dictionary, version: '1.3'
125
125
  define_field :NM, type: String, version: '1.4'
126
- define_field :M, type: PDFDate, version: '1.1'
126
+ define_field :M, type: [PDFDate, String], version: '1.1'
127
127
  define_field :F, type: Integer, default: 0, version: '1.1'
128
128
  define_field :AP, type: :XXAppearanceDictionary, version: '1.2'
129
129
  define_field :AS, type: Symbol, version: '1.2'
@@ -131,6 +131,11 @@ module HexaPDF
131
131
  define_field :C, type: PDFArray, version: '1.1'
132
132
  define_field :StructParent, type: Integer, version: '1.3'
133
133
  define_field :OC, type: Dictionary, version: '1.5'
134
+ define_field :AF, type: PDFArray, version: '2.0'
135
+ define_field :ca, type: Numeric, default: 1.0, version: '2.0'
136
+ define_field :CA, type: Numeric, default: 1.0, version: '2.0'
137
+ define_field :BM, type: Symbol, version: '2.0'
138
+ define_field :Lang, type: String, version: '2.0'
134
139
 
135
140
  ##
136
141
  # :method: flags
@@ -46,6 +46,19 @@ module HexaPDF
46
46
  # See: PDF2.0 s12.5.6.2, HexaPDF::Type::Annotation
47
47
  class MarkupAnnotation < Annotation
48
48
 
49
+ # External data dictionary used by some markup annotation types.
50
+ #
51
+ # See: PDF2.0 s12.5.6.2
52
+ class ExData < Dictionary
53
+
54
+ define_type :ExData
55
+
56
+ define_field :Type, type: Symbol, required: true, default: type
57
+ define_field :Subtype, type: Symbol, required: true,
58
+ allowed_values: [:Markup3D, :'3DM', :MarkupGeo]
59
+
60
+ end
61
+
49
62
  define_field :T, type: String, version: '1.1'
50
63
  define_field :Popup, type: :Annot, version: '1.3'
51
64
  define_field :CA, type: Numeric, default: 1.0, version: '1.4'
@@ -56,7 +69,7 @@ module HexaPDF
56
69
  define_field :RT, type: Symbol, default: :R, allowed_values: [:R, :Group],
57
70
  version: '1.6'
58
71
  define_field :IT, type: Symbol, version: '1.6'
59
- define_field :ExData, type: Dictionary, version: '1.7'
72
+ define_field :ExData, type: :ExData, version: '1.7'
60
73
 
61
74
  private
62
75
 
@@ -84,6 +84,9 @@ module HexaPDF
84
84
  define_field :Requirements, type: PDFArray, version: '1.7'
85
85
  define_field :Collection, type: Dictionary, version: '1.7'
86
86
  define_field :NeedsRendering, type: Boolean, version: '1.7'
87
+ define_field :DSS, type: Dictionary, version: '2.0'
88
+ define_field :AF, type: PDFArray, version: '2.0'
89
+ define_field :DPartRoot, type: Dictionary, version: '2.0'
87
90
 
88
91
  # Returns +true+ since catalog objects must always be indirect.
89
92
  def must_be_indirect?
@@ -61,13 +61,16 @@ module HexaPDF
61
61
 
62
62
  DEFAULT_WIDTH = 1000 # :nodoc:
63
63
 
64
+ define_field :Subtype, type: Symbol, required: true,
65
+ allowed_values: [:CIDFontType0, :CIDFontType2]
64
66
  define_field :BaseFont, type: Symbol, required: true
65
67
  define_field :CIDSystemInfo, type: :XXCIDSystemInfo, required: true
66
- define_field :FontDescriptor, type: :FontDescriptor, indirect: true, required: true
68
+ define_field :FontDescriptor, type: :FontDescriptor, required: true
67
69
  define_field :DW, type: Integer, default: DEFAULT_WIDTH
68
70
  define_field :W, type: PDFArray
69
71
  define_field :DW2, type: PDFArray, default: [880, -1100]
70
72
  define_field :W2, type: PDFArray
73
+ define_field :CIDToGIDMap, type: [Symbol, Stream]
71
74
 
72
75
  # Returns the unscaled width of the given CID in glyph units, or 0 if the width for the CID is
73
76
  # missing.
@@ -78,19 +78,22 @@ module HexaPDF
78
78
 
79
79
  define_type :Filespec
80
80
 
81
- define_field :Type, type: Symbol, default: type, required: true
82
- define_field :FS, type: Symbol
83
- define_field :F, type: PDFByteString
84
- define_field :UF, type: String, version: '1.7'
85
- define_field :DOS, type: PDFByteString
86
- define_field :Mac, type: PDFByteString
87
- define_field :Unix, type: PDFByteString
88
- define_field :ID, type: PDFArray
89
- define_field :V, type: Boolean, version: '1.2'
90
- define_field :EF, type: :XXFilespecEFDictionary, version: '1.7'
91
- define_field :RF, type: Dictionary, version: '1.3'
92
- define_field :Desc, type: String, version: '1.6'
93
- define_field :CI, type: Dictionary, version: '1.7'
81
+ define_field :Type, type: Symbol, default: type, required: true
82
+ define_field :FS, type: Symbol
83
+ define_field :F, type: PDFByteString
84
+ define_field :UF, type: String, version: '1.7'
85
+ define_field :DOS, type: PDFByteString
86
+ define_field :Mac, type: PDFByteString
87
+ define_field :Unix, type: PDFByteString
88
+ define_field :ID, type: PDFArray
89
+ define_field :V, type: Boolean, version: '1.2'
90
+ define_field :EF, type: :XXFilespecEFDictionary, version: '1.7'
91
+ define_field :RF, type: Dictionary, version: '1.3'
92
+ define_field :Desc, type: String, version: '1.6'
93
+ define_field :CI, type: Dictionary, version: '1.7'
94
+ define_field :Thumb, type: Stream, version: '2.0'
95
+ define_field :EP, type: Dictionary, version: '2.0'
96
+ define_field :AF, type: Symbol, version: '2.0', default: :Unspecified
94
97
 
95
98
  # Returns +true+ if this file specification references an URL and not a file.
96
99
  def url?
@@ -114,7 +117,7 @@ module HexaPDF
114
117
 
115
118
  # Sets the file specification string to the given filename.
116
119
  #
117
- # Since the /Unix, /Mac and /DOS fields are obsolescent, only the /F and /UF fields are set.
120
+ # Since the /Unix, /Mac and /DOS fields are deprecated, only the /F and /UF fields are set.
118
121
  def path=(filename)
119
122
  self[:UF] = filename
120
123
  self[:F] = filename.b
@@ -57,7 +57,7 @@ module HexaPDF
57
57
  define_field :FontStretch, type: Symbol, version: '1.5',
58
58
  allowed_values: [:UltraCondensed, :ExtraCondensed, :Condensed, :SemiCondensed,
59
59
  :Normal, :SemiExpanded, :Expanded, :ExtraExpanded, :UltraExpanded]
60
- define_field :FontWeight, type: Numeric, version: '1.5'
60
+ define_field :FontWeight, type: Integer, version: '1.5' # also see validation
61
61
  define_field :Flags, type: Integer, required: true
62
62
  define_field :FontBBox, type: Rectangle
63
63
  define_field :ItalicAngle, type: Numeric, required: true
@@ -76,6 +76,7 @@ module HexaPDF
76
76
  define_field :FontFile3, type: Stream, version: '1.2'
77
77
  define_field :CharSet, type: [PDFByteString, String], version: '1.1'
78
78
 
79
+ # From PDF2.0 s9.8.3.1
79
80
  define_field :Style, type: Dictionary
80
81
  define_field :Lang, type: Symbol, version: '1.5'
81
82
  define_field :FD, type: Dictionary
@@ -98,13 +99,13 @@ module HexaPDF
98
99
 
99
100
  font_weight = self[:FontWeight]
100
101
  if font_weight && !ALLOWED_FONT_WEIGHTS.include?(font_weight)
101
- yield("Field FontWeight does not contain an allowed value", true)
102
+ yield("Field FontWeight contains the disallowed value #{font_weight}", true)
102
103
  delete(:FontWeight)
103
104
  end
104
105
 
105
106
  descent = self[:Descent]
106
107
  if descent && descent > 0
107
- yield("The /Descent value needs to be a negative number", true)
108
+ yield("The /Descent value needs to be zero or negative", true)
108
109
  self[:Descent] = -descent
109
110
  end
110
111
  end
@@ -47,10 +47,12 @@ module HexaPDF
47
47
  # See: PDF2.0 s9.6
48
48
  class FontSimple < Font
49
49
 
50
+ # Only the common fields are defined here, the rest in FontType1, FontType3, FontTrueType
51
+ define_field :Name, type: Symbol
50
52
  define_field :FirstChar, type: Integer
51
53
  define_field :LastChar, type: Integer
52
54
  define_field :Widths, type: PDFArray
53
- define_field :FontDescriptor, type: :FontDescriptor, indirect: true
55
+ define_field :FontDescriptor, type: :FontDescriptor
54
56
  define_field :Encoding, type: [Dictionary, Symbol]
55
57
 
56
58
  # Returns the font descriptor. May be +nil+ for a standard 14 font.
@@ -41,6 +41,8 @@ module HexaPDF
41
41
  module Type
42
42
 
43
43
  # Represents a TrueType font.
44
+ #
45
+ # See: PDF2.0 s9.6.3
44
46
  class FontTrueType < FontSimple
45
47
 
46
48
  define_field :Subtype, type: Symbol, required: true, default: :TrueType
@@ -48,7 +48,7 @@ module HexaPDF
48
48
  # Composite fonts also allow for vertical writing mode and support TrueType as well as OpenType
49
49
  # fonts.
50
50
  #
51
- # See: PDF2.0 s9.7
51
+ # See: PDF2.0 s9.7, s9.7.6.1
52
52
  class FontType0 < Font
53
53
 
54
54
  define_field :Subtype, type: Symbol, required: true, default: :Type0
@@ -170,6 +170,8 @@ module HexaPDF
170
170
  end
171
171
  end
172
172
 
173
+ PREDEFINED_ENCODING = [:MacRomanEncoding, :MacExpertEncoding, :WinAnsiEncoding] #:nodoc:
174
+
173
175
  # Validates the Type1 font dictionary.
174
176
  def perform_validation
175
177
  std_font = StandardFonts.standard_font?(self[:BaseFont])
@@ -178,6 +180,11 @@ module HexaPDF
178
180
  if !std_font && self[:FontDescriptor].nil?
179
181
  yield("Required field FontDescriptor is not set", false)
180
182
  end
183
+
184
+ encoding = self[:Encoding]
185
+ if encoding.kind_of?(Symbol) && !PREDEFINED_ENCODING.include?(encoding)
186
+ yield("The /Encoding value '#{encoding}' is invalid", false)
187
+ end
181
188
  end
182
189
 
183
190
  end
@@ -49,7 +49,6 @@ module HexaPDF
49
49
  class FontType3 < FontSimple
50
50
 
51
51
  define_field :Subtype, type: Symbol, required: true, default: :Type3
52
- define_field :Name, type: Symbol
53
52
  define_field :FontBBox, type: Rectangle, required: true
54
53
  define_field :FontMatrix, type: PDFArray, required: true
55
54
  define_field :CharProcs, type: Dictionary, required: true
@@ -89,6 +89,10 @@ module HexaPDF
89
89
  define_field :StructParents, type: Integer, version: '1.3'
90
90
  define_field :OPI, type: Dictionary, version: '1.2'
91
91
  define_field :OC, type: Dictionary, version: '1.5'
92
+ define_field :Name, type: Symbol
93
+ define_field :AF, type: PDFArray, version: '2.0'
94
+ define_field :Measure, type: Dictionary, version: '2.0'
95
+ define_field :PtData, type: Dictionary, version: '2.0'
92
96
 
93
97
  # Returns the path to the PDF file that was used when creating the form object.
94
98
  #
@@ -131,8 +135,7 @@ module HexaPDF
131
135
 
132
136
  # Returns the resource dictionary which is automatically created if it doesn't exist.
133
137
  def resources
134
- self[:Resources] ||= document.wrap({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]},
135
- type: :XXResources)
138
+ self[:Resources] ||= document.wrap({}, type: :XXResources)
136
139
  end
137
140
 
138
141
  # Processes the content stream of the form XObject with the given processor object.
@@ -58,10 +58,10 @@ module HexaPDF
58
58
  define_field :ML, type: Numeric, version: "1.3"
59
59
  define_field :D, type: PDFArray, version: "1.3"
60
60
  define_field :RI, type: Symbol, version: "1.3",
61
- allowed_values: [HexaPDF::Content::RenderingIntent::ABSOLUTE_COLORIMETRIC,
62
- HexaPDF::Content::RenderingIntent::RELATIVE_COLORIMETRIC,
63
- HexaPDF::Content::RenderingIntent::SATURATION,
64
- HexaPDF::Content::RenderingIntent::PERCEPTUAL]
61
+ allowed_values: [HexaPDF::Content::RenderingIntent::ABSOLUTE_COLORIMETRIC,
62
+ HexaPDF::Content::RenderingIntent::RELATIVE_COLORIMETRIC,
63
+ HexaPDF::Content::RenderingIntent::SATURATION,
64
+ HexaPDF::Content::RenderingIntent::PERCEPTUAL]
65
65
  define_field :OP, type: Boolean
66
66
  define_field :op, type: Boolean, version: "1.3"
67
67
  define_field :OPM, type: Integer, version: "1.3"
@@ -82,6 +82,9 @@ module HexaPDF
82
82
  define_field :ca, type: Numeric, version: "1.4"
83
83
  define_field :AIS, type: Boolean, version: "1.4"
84
84
  define_field :TK, type: Boolean, version: "1.4"
85
+ define_field :UseBlackPtComp, type: Symbol, version: "2.0",
86
+ allowed_values: [:OFF, :ON, :Default]
87
+ define_field :HTO, type: PDFArray, version: "2.0"
85
88
 
86
89
  end
87
90