pdfrb 0.6.0 → 0.7.10

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 +144 -0
  3. data/RELEASE.md +52 -33
  4. data/data/pdfrb/afm/Courier-Bold.afm +342 -342
  5. data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
  6. data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
  7. data/data/pdfrb/afm/Courier.afm +342 -342
  8. data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
  9. data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
  10. data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
  11. data/data/pdfrb/afm/Helvetica.afm +3051 -3051
  12. data/data/pdfrb/afm/Symbol.afm +213 -213
  13. data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
  14. data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
  15. data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
  16. data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
  17. data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
  18. data/data/pdfrb/layout/hyphenation_en.txt +408 -0
  19. data/lib/pdfrb/color/default_profile.rb +213 -0
  20. data/lib/pdfrb/color/icc_validator.rb +69 -0
  21. data/lib/pdfrb/color.rb +2 -0
  22. data/lib/pdfrb/conformance/ltv.rb +112 -0
  23. data/lib/pdfrb/conformance/pades.rb +198 -0
  24. data/lib/pdfrb/conformance/pdf_2_af.rb +185 -0
  25. data/lib/pdfrb/conformance/pdf_a.rb +123 -0
  26. data/lib/pdfrb/conformance/pdf_a4_deep.rb +94 -0
  27. data/lib/pdfrb/conformance/pdf_ua2_deep.rb +93 -0
  28. data/lib/pdfrb/conformance/pdf_ua_tagging_deep.rb +125 -0
  29. data/lib/pdfrb/conformance/pdf_vt.rb +128 -0
  30. data/lib/pdfrb/conformance/pdf_x.rb +66 -1
  31. data/lib/pdfrb/conformance/tagged_pdf.rb +182 -0
  32. data/lib/pdfrb/conformance.rb +8 -0
  33. data/lib/pdfrb/content/canvas.rb +137 -0
  34. data/lib/pdfrb/content/operators/inline_image.rb +53 -0
  35. data/lib/pdfrb/content/operators.rb +1 -0
  36. data/lib/pdfrb/content/parser.rb +82 -0
  37. data/lib/pdfrb/digital_signature/timestamp_client.rb +86 -0
  38. data/lib/pdfrb/digital_signature.rb +1 -0
  39. data/lib/pdfrb/document.rb +29 -0
  40. data/lib/pdfrb/encryption/public_key_security_handler.rb +146 -0
  41. data/lib/pdfrb/encryption/standard_security_handler.rb +98 -3
  42. data/lib/pdfrb/encryption/v5_writer.rb +108 -0
  43. data/lib/pdfrb/encryption.rb +3 -0
  44. data/lib/pdfrb/font_loader/type3.rb +65 -0
  45. data/lib/pdfrb/font_loader.rb +1 -0
  46. data/lib/pdfrb/image_loader/gif.rb +246 -0
  47. data/lib/pdfrb/image_loader/png.rb +76 -19
  48. data/lib/pdfrb/image_loader/tiff.rb +257 -0
  49. data/lib/pdfrb/image_loader.rb +2 -0
  50. data/lib/pdfrb/layout/font_fallback.rb +203 -0
  51. data/lib/pdfrb/layout/hyphenation.rb +120 -0
  52. data/lib/pdfrb/layout/justification_kashidas.rb +72 -0
  53. data/lib/pdfrb/layout/multi_cell_text_layout.rb +65 -0
  54. data/lib/pdfrb/layout/multi_page_table_box.rb +155 -0
  55. data/lib/pdfrb/layout/polygon_frame.rb +106 -0
  56. data/lib/pdfrb/layout/table_box.rb +154 -23
  57. data/lib/pdfrb/layout/text_shaper.rb +129 -0
  58. data/lib/pdfrb/layout.rb +7 -0
  59. data/lib/pdfrb/model/type/page_piece_info.rb +58 -0
  60. data/lib/pdfrb/model/type.rb +3 -0
  61. data/lib/pdfrb/source/linearization_reader.rb +76 -0
  62. data/lib/pdfrb/source/recovery.rb +65 -1
  63. data/lib/pdfrb/source/tokenizer.rb +21 -0
  64. data/lib/pdfrb/source.rb +1 -0
  65. data/lib/pdfrb/task/regenerate_appearances.rb +98 -0
  66. data/lib/pdfrb/task/thumbnail.rb +133 -0
  67. data/lib/pdfrb/task.rb +2 -0
  68. data/lib/pdfrb/version.rb +1 -1
  69. metadata +35 -3
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ module Pdfrb
5
+ module Conformance
6
+ # PDF/A-4 deep checks (ISO 19005-4). Builds on the SHARED + A4
7
+ # rules in Pdfrb::Conformance::PdfA by adding additional
8
+ # structural requirements specific to PDF/A-4: PDF 2.0 base,
9
+ # XMP metadata required (not just recommended), /AF entries
10
+ # valid per App Note 002.
11
+ module PdfA4Deep
12
+ module_function
13
+
14
+ RULESET = RuleSet.new("PDF/A-4-deep").tap do |rs|
15
+ rs.register(Rule.new(
16
+ id: "a4deep-1",
17
+ description: "PDF/A-4 requires XMP /Metadata stream",
18
+ severity: :error,
19
+ spec_clause: "ISO 19005-4 6.1",
20
+ check: ->(doc) {
21
+ next nil if doc.catalog[:Metadata]
22
+
23
+ Violation.new(
24
+ rule_id: "a4deep-1",
25
+ message: "PDF/A-4 requires /Catalog/Metadata XMP stream",
26
+ object: "Catalog/Metadata",
27
+ severity: :error,
28
+ spec_clause: "ISO 19005-4 6.1"
29
+ )
30
+ }
31
+ ))
32
+
33
+ rs.register(Rule.new(
34
+ id: "a4deep-2",
35
+ description: "PDF/A-4 should have valid /OutputIntents when colour-managed",
36
+ severity: :warning,
37
+ spec_clause: "ISO 19005-4 6.2.4",
38
+ check: ->(doc) {
39
+ next nil if doc.catalog[:OutputIntents]
40
+
41
+ # Only flag when document has ICCBased color spaces
42
+ has_icc = false
43
+ doc.each_indirect_object do |obj|
44
+ next unless obj.value[:ColorSpace]
45
+
46
+ cs = obj.value[:ColorSpace]
47
+ cs_array = cs.is_a?(::Array) ? cs : [cs]
48
+ if cs_array.any?(:ICCBased)
49
+ has_icc = true
50
+ break
51
+ end
52
+ end
53
+ next nil unless has_icc
54
+
55
+ Violation.new(
56
+ rule_id: "a4deep-2",
57
+ message: "ICC-based color spaces require /OutputIntents in PDF/A-4",
58
+ object: "Catalog/OutputIntents",
59
+ severity: :warning,
60
+ spec_clause: "ISO 19005-4 6.2.4"
61
+ )
62
+ }
63
+ ))
64
+
65
+ rs.register(Rule.new(
66
+ id: "a4deep-3",
67
+ description: "PDF/A-4 forbids EmbeddedFile streams",
68
+ severity: :error,
69
+ spec_clause: "ISO 19005-4 6.4",
70
+ check: ->(doc) {
71
+ vs = []
72
+ doc.each_indirect_object do |obj|
73
+ next unless obj.value[:Type] == :EmbeddedFile
74
+
75
+ vs << Violation.new(
76
+ rule_id: "a4deep-3",
77
+ message: "PDF/A-4 forbids EmbeddedFile streams",
78
+ object: "EmbeddedFile",
79
+ severity: :error,
80
+ spec_clause: "ISO 19005-4 6.4"
81
+ )
82
+ end
83
+ vs.empty? ? nil : vs
84
+ }
85
+ ))
86
+ end
87
+
88
+ def validate(document)
89
+ RULESET.validate(document)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ module Pdfrb
5
+ module Conformance
6
+ # PDF/UA-2 (ISO 14289-2) deep checks. PDF/UA-2 is rooted in
7
+ # PDF 2.0 and adds stricter requirements on top of PDF/UA-1:
8
+ # * PDF version >= 2.0
9
+ # * Structure tree with consistent /RoleMap
10
+ # * Heading hierarchy (H1-H6) without skipping levels
11
+ # * Every Figure, Formula, Table has /Alt or /ActualText
12
+ module PdfUA2Deep
13
+ module_function
14
+
15
+ RULESET = RuleSet.new("PDF/UA-2-deep").tap do |rs|
16
+ rs.register(Rule.new(
17
+ id: "ua2-1",
18
+ description: "PDF/UA-2 requires PDF 2.0",
19
+ severity: :error,
20
+ spec_clause: "ISO 14289-2 4.1",
21
+ check: ->(doc) {
22
+ v = doc.version.to_s
23
+ next nil if v >= "2.0"
24
+
25
+ Violation.new(
26
+ rule_id: "ua2-1",
27
+ message: "PDF/UA-2 requires PDF 2.0 (was #{v})",
28
+ object: "Header",
29
+ severity: :error,
30
+ spec_clause: "ISO 14289-2 4.1"
31
+ )
32
+ }
33
+ ))
34
+
35
+ rs.register(Rule.new(
36
+ id: "ua2-2",
37
+ description: "Heading hierarchy must not skip levels",
38
+ severity: :warning,
39
+ spec_clause: "ISO 14289-2 7.3",
40
+ check: ->(doc) {
41
+ vs = []
42
+ prev_level = 0
43
+ TaggedPdf.walk_structure(doc) do |elem|
44
+ s = elem[:S]&.to_s
45
+ next unless s&.match?(/\AH([1-6])\z/)
46
+
47
+ level = Regexp.last_match(1).to_i
48
+ if level > prev_level + 1 && prev_level.positive?
49
+ vs << Violation.new(
50
+ rule_id: "ua2-2",
51
+ message: "Heading H#{level} jumps from H#{prev_level}",
52
+ object: "StructElem/#{s}",
53
+ severity: :warning,
54
+ spec_clause: "ISO 14289-2 7.3"
55
+ )
56
+ end
57
+ prev_level = level
58
+ end
59
+ vs.empty? ? nil : vs
60
+ }
61
+ ))
62
+
63
+ rs.register(Rule.new(
64
+ id: "ua2-3",
65
+ description: "Formula elements require /Alt or /ActualText",
66
+ severity: :warning,
67
+ spec_clause: "ISO 14289-2 7.9",
68
+ check: ->(doc) {
69
+ vs = []
70
+ TaggedPdf.walk_structure(doc) do |elem|
71
+ next unless elem[:S] == :Formula
72
+ next if elem[:Alt] || elem[:ActualText]
73
+
74
+ vs << Violation.new(
75
+ rule_id: "ua2-3",
76
+ message: "Formula without /Alt or /ActualText",
77
+ object: "StructElem/Formula",
78
+ severity: :warning,
79
+ spec_clause: "ISO 14289-2 7.9"
80
+ )
81
+ end
82
+ vs.empty? ? nil : vs
83
+ }
84
+ ))
85
+ end
86
+
87
+ def validate(document)
88
+ RULESET.validate(document)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ module Pdfrb
5
+ module Conformance
6
+ # PDF/UA-1 deep tagging checks (ISO 14289-1 §7). Extends the
7
+ # baseline PDF/UA rule set with structural-tree traversal rules:
8
+ # * Every /L (list) must contain /LI children
9
+ # * Every /LI must contain /LBody
10
+ # * Every /Table must contain /TR rows
11
+ # * Every /TH and /TD must have /Scope or headers linkage
12
+ # * Every /Figure, /Formula, /Table has /Alt or /ActualText
13
+ module PdfUATaggingDeep
14
+ module_function
15
+
16
+ RULESET = RuleSet.new("PDF/UA-tagging-deep").tap do |rs|
17
+ rs.register(Rule.new(
18
+ id: "tag-deep-1",
19
+ description: "List /L must have /LI children",
20
+ severity: :warning,
21
+ spec_clause: "ISO 14289-1 7.4",
22
+ check: ->(doc) {
23
+ vs = []
24
+ TaggedPdf.walk_structure(doc) do |elem|
25
+ next unless elem[:S] == :L
26
+
27
+ kids = TaggedPdf.structure_kids(elem, doc)
28
+ has_li = kids.any? { |k| k[:S] == :LI }
29
+ next if has_li
30
+
31
+ vs << Violation.new(
32
+ rule_id: "tag-deep-1",
33
+ message: "/L without /LI child",
34
+ object: "StructElem/L",
35
+ severity: :warning,
36
+ spec_clause: "ISO 14289-1 7.4"
37
+ )
38
+ end
39
+ vs.empty? ? nil : vs
40
+ }
41
+ ))
42
+
43
+ rs.register(Rule.new(
44
+ id: "tag-deep-2",
45
+ description: "/LI must have /LBody",
46
+ severity: :warning,
47
+ spec_clause: "ISO 14289-1 7.4",
48
+ check: ->(doc) {
49
+ vs = []
50
+ TaggedPdf.walk_structure(doc) do |elem|
51
+ next unless elem[:S] == :LI
52
+
53
+ kids = TaggedPdf.structure_kids(elem, doc)
54
+ has_body = kids.any? { |k| k[:S] == :LBody }
55
+ next if has_body
56
+
57
+ vs << Violation.new(
58
+ rule_id: "tag-deep-2",
59
+ message: "/LI without /LBody",
60
+ object: "StructElem/LI",
61
+ severity: :warning,
62
+ spec_clause: "ISO 14289-1 7.4"
63
+ )
64
+ end
65
+ vs.empty? ? nil : vs
66
+ }
67
+ ))
68
+
69
+ rs.register(Rule.new(
70
+ id: "tag-deep-3",
71
+ description: "/Table must have /TR rows",
72
+ severity: :warning,
73
+ spec_clause: "ISO 14289-1 7.5",
74
+ check: ->(doc) {
75
+ vs = []
76
+ TaggedPdf.walk_structure(doc) do |elem|
77
+ next unless elem[:S] == :Table
78
+
79
+ kids = TaggedPdf.structure_kids(elem, doc)
80
+ has_tr = kids.any? { |k| k[:S] == :TR }
81
+ next if has_tr
82
+
83
+ vs << Violation.new(
84
+ rule_id: "tag-deep-3",
85
+ message: "/Table without /TR child rows",
86
+ object: "StructElem/Table",
87
+ severity: :warning,
88
+ spec_clause: "ISO 14289-1 7.5"
89
+ )
90
+ end
91
+ vs.empty? ? nil : vs
92
+ }
93
+ ))
94
+
95
+ rs.register(Rule.new(
96
+ id: "tag-deep-4",
97
+ description: "/TH cells should declare /Scope",
98
+ severity: :warning,
99
+ spec_clause: "ISO 14289-1 7.5",
100
+ check: ->(doc) {
101
+ vs = []
102
+ TaggedPdf.walk_structure(doc) do |elem|
103
+ next unless elem[:S] == :TH
104
+ next if elem[:Scope] || elem[:Headers]
105
+
106
+ vs << Violation.new(
107
+ rule_id: "tag-deep-4",
108
+ message: "/TH without /Scope or /Headers",
109
+ object: "StructElem/TH",
110
+ severity: :warning,
111
+ spec_clause: "ISO 14289-1 7.5"
112
+ )
113
+ end
114
+ vs.empty? ? nil : vs
115
+ }
116
+ ))
117
+ end
118
+
119
+ def validate(document)
120
+ RULESET.validate(document)
121
+ end
122
+ end
123
+ end
124
+ end
125
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ module Pdfrb
5
+ module Conformance
6
+ # PDF/VT conformance per ISO 16612-2. PDF/VT is the variable
7
+ # data printing standard for transactional and direct mail
8
+ # workflows. Two conformance levels:
9
+ #
10
+ # PDF/VT-1: rooted in PDF/X-4 (production-ready print environment)
11
+ # PDF/VT-2: rooted in PDF/X-4 but allows external references
12
+ #
13
+ # Both levels require:
14
+ # * /MarkInfo /Marked true (tagged)
15
+ # * /OutputIntents present
16
+ # * /DPartRoot dict on the Catalog (Document Part registry)
17
+ # * All fonts embedded
18
+ # * No encryption
19
+ module PdfVT
20
+ module_function
21
+
22
+ SHARED = RuleSet.new("PDF/VT-shared").tap do |rs|
23
+ rs.register(Rule.new(
24
+ id: "vt-1",
25
+ description: "/MarkInfo /Marked true required",
26
+ severity: :error,
27
+ spec_clause: "ISO 16612-2 6.2.1",
28
+ check: ->(doc) {
29
+ mark = doc.catalog[:MarkInfo]
30
+ mark = doc.object(mark) if mark.is_a?(Pdfrb::Model::Reference)
31
+ next nil if mark && mark[:Marked] == true
32
+
33
+ Violation.new(
34
+ rule_id: "vt-1",
35
+ message: "PDF/VT requires /MarkInfo /Marked true",
36
+ object: "Catalog/MarkInfo",
37
+ severity: :error,
38
+ spec_clause: "ISO 16612-2 6.2.1"
39
+ )
40
+ }
41
+ ))
42
+
43
+ rs.register(Rule.new(
44
+ id: "vt-2",
45
+ description: "OutputIntents required",
46
+ severity: :error,
47
+ spec_clause: "ISO 16612-2 6.2.2",
48
+ check: ->(doc) {
49
+ next nil if doc.catalog[:OutputIntents]
50
+
51
+ Violation.new(
52
+ rule_id: "vt-2",
53
+ message: "PDF/VT requires /OutputIntents",
54
+ object: "Catalog/OutputIntents",
55
+ severity: :error,
56
+ spec_clause: "ISO 16612-2 6.2.2"
57
+ )
58
+ }
59
+ ))
60
+
61
+ rs.register(Rule.new(
62
+ id: "vt-3",
63
+ description: "/DPartRoot required for variable data partitioning",
64
+ severity: :error,
65
+ spec_clause: "ISO 16612-2 6.3",
66
+ check: ->(doc) {
67
+ next nil if doc.catalog[:DPartRoot]
68
+
69
+ Violation.new(
70
+ rule_id: "vt-3",
71
+ message: "PDF/VT requires /DPartRoot on Catalog",
72
+ object: "Catalog/DPartRoot",
73
+ severity: :error,
74
+ spec_clause: "ISO 16612-2 6.3"
75
+ )
76
+ }
77
+ ))
78
+
79
+ rs.register(Rule.new(
80
+ id: "vt-4",
81
+ description: "Encryption is forbidden",
82
+ severity: :error,
83
+ spec_clause: "ISO 16612-2 6.2.4",
84
+ check: ->(doc) {
85
+ next nil unless doc.trailer && doc.trailer[:Encrypt]
86
+
87
+ Violation.new(
88
+ rule_id: "vt-4",
89
+ message: "PDF/VT prohibits encryption",
90
+ object: "Trailer/Encrypt",
91
+ severity: :error,
92
+ spec_clause: "ISO 16612-2 6.2.4"
93
+ )
94
+ }
95
+ ))
96
+ end
97
+
98
+ VT1 = RuleSet.new("PDF/VT-1").tap do |rs|
99
+ SHARED.rules.each { |r| rs.register(r) }
100
+ end
101
+
102
+ VT2 = RuleSet.new("PDF/VT-2").tap do |rs|
103
+ SHARED.rules.each { |r| rs.register(r) }
104
+ rs.register(Rule.new(
105
+ id: "vt2-1",
106
+ description: "PDF/VT-2 permits external OutputIntents references",
107
+ severity: :warning,
108
+ spec_clause: "ISO 16612-2 6.4.2",
109
+ check: ->(_doc) {}
110
+ ))
111
+ end
112
+
113
+ LEVEL_RULESETS = {
114
+ vt1: VT1, vt2: VT2
115
+ }.freeze
116
+
117
+ def profiles
118
+ LEVEL_RULESETS.dup
119
+ end
120
+
121
+ def validate(document, level: :vt1)
122
+ rs = LEVEL_RULESETS[level] || SHARED
123
+ rs.validate(document)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ # rubocop:enable Metrics/BlockLength
@@ -21,7 +21,7 @@ module Pdfrb
21
21
  module_function
22
22
 
23
23
  def profiles
24
- { x1a: X1A, x3: X3, x4: X4 }
24
+ { x1a: X1A, x3: X3, x4: X4, x6: X6 }
25
25
  end
26
26
 
27
27
  def validate(document, level: :x4)
@@ -32,6 +32,7 @@ module Pdfrb
32
32
  case level.to_s
33
33
  when /\Ax1/i then X1A
34
34
  when /\Ax3/i then X3
35
+ when /\Ax6/i then X6
35
36
  else X4
36
37
  end
37
38
  end
@@ -221,6 +222,70 @@ module Pdfrb
221
222
  SHARED.rules.each { |r| rs.register(r) }
222
223
  end
223
224
 
225
+ # X-6 specific rules. Defined as lambdas (not module methods)
226
+ # so they are in scope when X6 is built.
227
+ X6_VERSION_RULE = Rule.new(
228
+ id: "x6-1",
229
+ description: "PDF/X-6 requires PDF 2.0",
230
+ severity: :error,
231
+ spec_clause: "ISO 15930-11 6.1",
232
+ check: ->(doc) {
233
+ v = doc.version.to_s
234
+ next nil if compare_versions(v, "2.0") >= 0
235
+
236
+ Violation.new(
237
+ rule_id: "x6-1",
238
+ message: "PDF/X-6 requires PDF version 2.0 (was #{v})",
239
+ object: "Header",
240
+ severity: :error,
241
+ spec_clause: "ISO 15930-11 6.1"
242
+ )
243
+ }
244
+ )
245
+
246
+ X6_OUTPUT_INTENT_RULE = Rule.new(
247
+ id: "x6-2",
248
+ description: "PDF/X-6 OutputIntents should use /S /GTS_PDFX",
249
+ severity: :warning,
250
+ spec_clause: "ISO 15930-11 6.2.4",
251
+ check: ->(doc) {
252
+ intents = doc.catalog[:OutputIntents]
253
+ intents_array = case intents
254
+ when ::Array then intents
255
+ when Pdfrb::Model::PdfArray then intents.value
256
+ when nil then []
257
+ else [intents]
258
+ end
259
+ has_pdfx = intents_array.any? do |i|
260
+ obj = i.is_a?(Pdfrb::Model::Reference) ? doc.object(i) : i
261
+ obj && obj[:S] == :GTS_PDFX
262
+ end
263
+ next nil if has_pdfx
264
+
265
+ Violation.new(
266
+ rule_id: "x6-2",
267
+ message: "PDF/X-6 OutputIntents should include an entry with /S /GTS_PDFX",
268
+ object: "Catalog/OutputIntents",
269
+ severity: :warning,
270
+ spec_clause: "ISO 15930-11 6.2.4"
271
+ )
272
+ }
273
+ )
274
+
275
+ # X-6: ISO 15930-11 — PDF 2.0 based. Adds the PDF 2.0 base
276
+ # version requirement and PDF/X-4's relaxed rules.
277
+ X6 = RuleSet.new("PDF/X-6").tap do |rs|
278
+ SHARED.rules.each { |r| rs.register(r) }
279
+ rs.register(X6_VERSION_RULE)
280
+ rs.register(X6_OUTPUT_INTENT_RULE)
281
+ end
282
+
283
+ def compare_versions(a, b)
284
+ aa = a.to_s.split(".").map(&:to_i)
285
+ bb = b.to_s.split(".").map(&:to_i)
286
+ (aa <=> bb) || 0
287
+ end
288
+
224
289
  def scan_color_spaces(doc, &block)
225
290
  doc.each_indirect_object do |obj|
226
291
  next unless obj.is_a?(Pdfrb::Model::Cos::Dictionary)