hexapdf 0.47.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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