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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +144 -0
- data/RELEASE.md +52 -33
- data/data/pdfrb/afm/Courier-Bold.afm +342 -342
- data/data/pdfrb/afm/Courier-BoldOblique.afm +342 -342
- data/data/pdfrb/afm/Courier-Oblique.afm +342 -342
- data/data/pdfrb/afm/Courier.afm +342 -342
- data/data/pdfrb/afm/Helvetica-Bold.afm +2827 -2827
- data/data/pdfrb/afm/Helvetica-BoldOblique.afm +2827 -2827
- data/data/pdfrb/afm/Helvetica-Oblique.afm +3051 -3051
- data/data/pdfrb/afm/Helvetica.afm +3051 -3051
- data/data/pdfrb/afm/Symbol.afm +213 -213
- data/data/pdfrb/afm/Times-Bold.afm +2588 -2588
- data/data/pdfrb/afm/Times-BoldItalic.afm +2384 -2384
- data/data/pdfrb/afm/Times-Italic.afm +2667 -2667
- data/data/pdfrb/afm/Times-Roman.afm +2419 -2419
- data/data/pdfrb/afm/ZapfDingbats.afm +225 -225
- data/data/pdfrb/layout/hyphenation_en.txt +408 -0
- data/lib/pdfrb/color/default_profile.rb +213 -0
- data/lib/pdfrb/color/icc_validator.rb +69 -0
- data/lib/pdfrb/color.rb +2 -0
- data/lib/pdfrb/conformance/ltv.rb +112 -0
- data/lib/pdfrb/conformance/pades.rb +198 -0
- data/lib/pdfrb/conformance/pdf_2_af.rb +185 -0
- data/lib/pdfrb/conformance/pdf_a.rb +123 -0
- data/lib/pdfrb/conformance/pdf_a4_deep.rb +94 -0
- data/lib/pdfrb/conformance/pdf_ua2_deep.rb +93 -0
- data/lib/pdfrb/conformance/pdf_ua_tagging_deep.rb +125 -0
- data/lib/pdfrb/conformance/pdf_vt.rb +128 -0
- data/lib/pdfrb/conformance/pdf_x.rb +66 -1
- data/lib/pdfrb/conformance/tagged_pdf.rb +182 -0
- data/lib/pdfrb/conformance.rb +8 -0
- data/lib/pdfrb/content/canvas.rb +137 -0
- data/lib/pdfrb/content/operators/inline_image.rb +53 -0
- data/lib/pdfrb/content/operators.rb +1 -0
- data/lib/pdfrb/content/parser.rb +82 -0
- data/lib/pdfrb/digital_signature/timestamp_client.rb +86 -0
- data/lib/pdfrb/digital_signature.rb +1 -0
- data/lib/pdfrb/document.rb +29 -0
- data/lib/pdfrb/encryption/public_key_security_handler.rb +146 -0
- data/lib/pdfrb/encryption/standard_security_handler.rb +98 -3
- data/lib/pdfrb/encryption/v5_writer.rb +108 -0
- data/lib/pdfrb/encryption.rb +3 -0
- data/lib/pdfrb/font_loader/type3.rb +65 -0
- data/lib/pdfrb/font_loader.rb +1 -0
- data/lib/pdfrb/image_loader/gif.rb +246 -0
- data/lib/pdfrb/image_loader/png.rb +76 -19
- data/lib/pdfrb/image_loader/tiff.rb +257 -0
- data/lib/pdfrb/image_loader.rb +2 -0
- data/lib/pdfrb/layout/font_fallback.rb +203 -0
- data/lib/pdfrb/layout/hyphenation.rb +120 -0
- data/lib/pdfrb/layout/justification_kashidas.rb +72 -0
- data/lib/pdfrb/layout/multi_cell_text_layout.rb +65 -0
- data/lib/pdfrb/layout/multi_page_table_box.rb +155 -0
- data/lib/pdfrb/layout/polygon_frame.rb +106 -0
- data/lib/pdfrb/layout/table_box.rb +154 -23
- data/lib/pdfrb/layout/text_shaper.rb +129 -0
- data/lib/pdfrb/layout.rb +7 -0
- data/lib/pdfrb/model/type/page_piece_info.rb +58 -0
- data/lib/pdfrb/model/type.rb +3 -0
- data/lib/pdfrb/source/linearization_reader.rb +76 -0
- data/lib/pdfrb/source/recovery.rb +65 -1
- data/lib/pdfrb/source/tokenizer.rb +21 -0
- data/lib/pdfrb/source.rb +1 -0
- data/lib/pdfrb/task/regenerate_appearances.rb +98 -0
- data/lib/pdfrb/task/thumbnail.rb +133 -0
- data/lib/pdfrb/task.rb +2 -0
- data/lib/pdfrb/version.rb +1 -1
- 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)
|