preflight 0.1.1 → 0.2.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.
- data/CHANGELOG +8 -1
- data/README.rdoc +21 -4
- data/lib/preflight.rb +1 -0
- data/lib/preflight/issue.rb +35 -0
- data/lib/preflight/profile.rb +38 -16
- data/lib/preflight/profiles/pdfa1a.rb +0 -1
- data/lib/preflight/profiles/pdfx1a.rb +4 -3
- data/lib/preflight/rules.rb +17 -2
- data/lib/preflight/rules/box_nesting.rb +35 -23
- data/lib/preflight/rules/compression_algorithms.rb +14 -4
- data/lib/preflight/rules/consistent_boxes.rb +63 -0
- data/lib/preflight/rules/cropbox_matches_mediabox.rb +38 -0
- data/lib/preflight/rules/document_id.rb +14 -2
- data/lib/preflight/rules/info_has_keys.rb +15 -3
- data/lib/preflight/rules/info_specifies_trapping.rb +16 -3
- data/lib/preflight/rules/match_info_entries.rb +17 -3
- data/lib/preflight/rules/max_ink_density.rb +69 -0
- data/lib/preflight/rules/max_version.rb +14 -2
- data/lib/preflight/rules/mediabox_at_origin.rb +42 -0
- data/lib/preflight/rules/min_bleed.rb +171 -0
- data/lib/preflight/rules/min_ppi.rb +54 -116
- data/lib/preflight/rules/no_cmyk.rb +113 -0
- data/lib/preflight/rules/no_filespecs.rb +15 -5
- data/lib/preflight/rules/no_font_subsets.rb +15 -6
- data/lib/preflight/rules/no_gray.rb +105 -0
- data/lib/preflight/rules/no_page_rotation.rb +36 -0
- data/lib/preflight/rules/no_private_data.rb +37 -0
- data/lib/preflight/rules/no_registration_black.rb +102 -0
- data/lib/preflight/rules/no_rgb.rb +112 -0
- data/lib/preflight/rules/no_separation.rb +85 -0
- data/lib/preflight/rules/no_transparency.rb +90 -0
- data/lib/preflight/rules/only_embedded_fonts.rb +28 -14
- data/lib/preflight/rules/output_intent_for_pdfx.rb +14 -2
- data/lib/preflight/rules/page_box_height.rb +88 -0
- data/lib/preflight/rules/page_box_size.rb +106 -0
- data/lib/preflight/rules/page_box_width.rb +88 -0
- data/lib/preflight/rules/page_count.rb +87 -0
- data/lib/preflight/rules/pdfx_output_intent_has_keys.rb +12 -2
- data/lib/preflight/rules/print_boxes.rb +21 -19
- data/lib/preflight/rules/root_has_keys.rb +15 -3
- metadata +97 -113
- data/lib/preflight/rules/no_encryption.rb +0 -16
- data/lib/preflight/rules/no_proprietary_fonts.rb +0 -50
data/CHANGELOG
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
v0.
|
1
|
+
v0.2.0 (25th March 2012)
|
2
|
+
- BREAKING API CHANGES FROM 0.1.1 release
|
3
|
+
- Detected issues are reported with an Issue object instead of a simple string
|
4
|
+
- new rules
|
5
|
+
- bug fixes and improvements to existing rules
|
6
|
+
- improved documentation
|
7
|
+
|
8
|
+
v0.1.1 (6th July 2011)
|
2
9
|
- bump required version of PDF::Reader to take advantage of a more
|
3
10
|
efficient API
|
4
11
|
|
data/README.rdoc
CHANGED
@@ -13,6 +13,10 @@ difficult to script and you know, expensive.
|
|
13
13
|
This may not check as comprehensively as the official preflight tools from Adobe
|
14
14
|
and friends, but hopefully it'll get you most of the way with less stress.
|
15
15
|
|
16
|
+
Unlike some preflight tools, this library doesn't attempt to fix any issues. It
|
17
|
+
only reports them. That may change one day, but for now you will need to fix
|
18
|
+
any issues that are detected by using some other tool.
|
19
|
+
|
16
20
|
== Installation
|
17
21
|
|
18
22
|
gem install preflight
|
@@ -77,9 +81,8 @@ Use an existing rule set as the base for a larger set of rules.
|
|
77
81
|
|
78
82
|
=== Adding Rules to a Profile Instance
|
79
83
|
|
80
|
-
|
81
|
-
rules
|
82
|
-
dynamic conditions.
|
84
|
+
Dynamically add rules to a profile instance. Useful when the required set of
|
85
|
+
rules depends on dynamic conditions like user input or database entries.
|
83
86
|
|
84
87
|
require "preflight"
|
85
88
|
|
@@ -88,6 +91,21 @@ dynamic conditions.
|
|
88
91
|
|
89
92
|
puts preflight.check("somefile.pdf").inspect
|
90
93
|
|
94
|
+
=== Available Rules
|
95
|
+
|
96
|
+
All rules shipped with this gem are in the Preflight::Rules namespace. Explore
|
97
|
+
the classes and related documentation in there to see what's available.
|
98
|
+
|
99
|
+
If you need a rule that we don't ship it is perfectly valid to build your own.
|
100
|
+
A profile will accept any class that behaves appropriately. If you think your
|
101
|
+
new rule may be of general interest please send me a pull request so I can
|
102
|
+
include it in the next release.
|
103
|
+
|
104
|
+
I'm yet to document the API that a rule class must follow. There are two
|
105
|
+
types of rule, each designed to check a different aspect of the target file
|
106
|
+
- raw PDF objects and individual pages. At this stage your best option when
|
107
|
+
writing your own rule is to copy an existing rule as a starting point.
|
108
|
+
|
91
109
|
== Status
|
92
110
|
|
93
111
|
This library is in an early stage of development. Use at your own risk.
|
@@ -98,7 +116,6 @@ This is pure ruby should run on most ruby VMs. I develop on MRI 1.9.2.
|
|
98
116
|
|
99
117
|
== Further Reading
|
100
118
|
|
101
|
-
* http://chneukirchen.github.com/rps/
|
102
119
|
* http://en.wikipedia.org/wiki/PDF/A
|
103
120
|
* http://en.wikipedia.org/wiki/PDF/X
|
104
121
|
* http://www.planetpdf.com/planetpdf/pdfs/pdfx3_draft4.pdf
|
data/lib/preflight.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Preflight
|
4
|
+
class Issue
|
5
|
+
|
6
|
+
attr_reader :description, :rule, :attributes
|
7
|
+
|
8
|
+
def initialize(description, rule, attributes = {})
|
9
|
+
@description = description
|
10
|
+
if rule.is_a?(Class)
|
11
|
+
@rule = rule.to_s.to_sym
|
12
|
+
else
|
13
|
+
@rule = rule.class.to_s.to_sym
|
14
|
+
end
|
15
|
+
@attributes = attributes || {}
|
16
|
+
|
17
|
+
attach_attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
@description
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def attach_attributes
|
27
|
+
singleton = class << self; self end
|
28
|
+
|
29
|
+
@attributes.each do |key, value|
|
30
|
+
singleton.send(:define_method, key, lambda { value })
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/preflight/profile.rb
CHANGED
@@ -36,6 +36,8 @@ module Preflight
|
|
36
36
|
|
37
37
|
module InstanceMethods
|
38
38
|
def check(input)
|
39
|
+
valid_rules?
|
40
|
+
|
39
41
|
if File.file?(input)
|
40
42
|
check_filename(input)
|
41
43
|
elsif input.is_a?(IO)
|
@@ -58,7 +60,12 @@ module Preflight
|
|
58
60
|
end
|
59
61
|
|
60
62
|
def check_io(io)
|
61
|
-
|
63
|
+
PDF::Reader.open(io) do |reader|
|
64
|
+
raise PDF::Reader::EncryptedPDFError if reader.objects.encrypted?
|
65
|
+
check_pages(reader) + check_hash(reader)
|
66
|
+
end
|
67
|
+
rescue PDF::Reader::EncryptedPDFError
|
68
|
+
["Can't preflight an encrypted PDF"]
|
62
69
|
end
|
63
70
|
|
64
71
|
def instance_rules
|
@@ -69,38 +76,53 @@ module Preflight
|
|
69
76
|
self.class.rules + instance_rules
|
70
77
|
end
|
71
78
|
|
72
|
-
def
|
73
|
-
|
79
|
+
def check_hash(reader)
|
80
|
+
hash_rules.map { |chk|
|
81
|
+
chk.check_hash(reader.objects)
|
82
|
+
}.flatten.compact
|
83
|
+
rescue PDF::Reader::UnsupportedFeatureError
|
84
|
+
[]
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_pages(reader)
|
88
|
+
rules_array = page_rules
|
89
|
+
issues = []
|
90
|
+
|
74
91
|
begin
|
75
|
-
|
92
|
+
reader.pages.each do |page|
|
93
|
+
page.walk(*rules_array)
|
94
|
+
issues += rules_array.map(&:issues).flatten.compact
|
95
|
+
end
|
76
96
|
rescue PDF::Reader::UnsupportedFeatureError
|
77
97
|
nil
|
78
98
|
end
|
79
|
-
|
99
|
+
issues
|
80
100
|
end
|
81
101
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
102
|
+
# ensure all rules follow the prescribed API
|
103
|
+
#
|
104
|
+
def valid_rules?
|
105
|
+
invalid_rules = all_rules.reject { |arr|
|
106
|
+
arr.first.instance_methods.map(&:to_sym).include?(:check_hash) ||
|
107
|
+
arr.first.instance_methods.map(&:to_sym).include?(:issues)
|
108
|
+
}
|
109
|
+
if invalid_rules.size > 0
|
110
|
+
raise "The following rules are invalid: #{invalid_rules.join(", ")}. Preflight rules MUST respond to either check_hash() or issues()."
|
111
|
+
end
|
88
112
|
end
|
89
113
|
|
90
114
|
def hash_rules
|
91
115
|
all_rules.select { |arr|
|
92
|
-
|
93
|
-
meth && meth.arity == 1
|
116
|
+
arr.first.instance_methods.map(&:to_sym).include?(:check_hash)
|
94
117
|
}.map { |arr|
|
95
118
|
klass = arr[0]
|
96
119
|
klass.new(*arr[1,10])
|
97
120
|
}
|
98
121
|
end
|
99
122
|
|
100
|
-
def
|
123
|
+
def page_rules
|
101
124
|
all_rules.select { |arr|
|
102
|
-
|
103
|
-
meth && meth.arity == 0
|
125
|
+
arr.first.instance_methods.map(&:to_sym).include?(:issues)
|
104
126
|
}.map { |arr|
|
105
127
|
klass = arr[0]
|
106
128
|
klass.new(*arr[1,10])
|
@@ -12,16 +12,17 @@ module Preflight
|
|
12
12
|
rule Preflight::Rules::RootHasKeys, :OutputIntents
|
13
13
|
rule Preflight::Rules::InfoHasKeys, :Title, :CreationDate, :ModDate
|
14
14
|
rule Preflight::Rules::InfoSpecifiesTrapping
|
15
|
-
rule Preflight::Rules::CompressionAlgorithms, :CCITTFaxDecode, :DCTDecode, :FlateDecode, :RunLengthDecode
|
15
|
+
rule Preflight::Rules::CompressionAlgorithms, :ASCII85Decode, :CCITTFaxDecode, :DCTDecode, :FlateDecode, :RunLengthDecode
|
16
16
|
rule Preflight::Rules::DocumentId
|
17
|
-
rule Preflight::Rules::NoEncryption
|
18
17
|
rule Preflight::Rules::NoFilespecs
|
18
|
+
rule Preflight::Rules::NoTransparency
|
19
19
|
rule Preflight::Rules::OnlyEmbeddedFonts
|
20
20
|
rule Preflight::Rules::BoxNesting
|
21
|
-
rule Preflight::Rules::MaxVersion, 1.
|
21
|
+
rule Preflight::Rules::MaxVersion, 1.3
|
22
22
|
rule Preflight::Rules::PrintBoxes
|
23
23
|
rule Preflight::Rules::OutputIntentForPdfx
|
24
24
|
rule Preflight::Rules::PdfxOutputIntentHasKeys, :OutputConditionIdentifier, :Info
|
25
|
+
rule Preflight::Rules::NoRgb
|
25
26
|
|
26
27
|
end
|
27
28
|
end
|
data/lib/preflight/rules.rb
CHANGED
@@ -1,17 +1,32 @@
|
|
1
1
|
require 'preflight/rules/box_nesting'
|
2
2
|
require 'preflight/rules/compression_algorithms'
|
3
|
+
require 'preflight/rules/consistent_boxes'
|
4
|
+
require 'preflight/rules/cropbox_matches_mediabox'
|
3
5
|
require 'preflight/rules/document_id'
|
4
6
|
require 'preflight/rules/info_has_keys'
|
5
7
|
require 'preflight/rules/info_specifies_trapping'
|
6
8
|
require 'preflight/rules/match_info_entries'
|
9
|
+
require 'preflight/rules/max_ink_density'
|
7
10
|
require 'preflight/rules/max_version'
|
11
|
+
require 'preflight/rules/mediabox_at_origin'
|
12
|
+
require 'preflight/rules/min_bleed'
|
8
13
|
require 'preflight/rules/min_ppi'
|
9
|
-
require 'preflight/rules/
|
14
|
+
require 'preflight/rules/no_cmyk'
|
10
15
|
require 'preflight/rules/no_filespecs'
|
11
16
|
require 'preflight/rules/no_font_subsets'
|
12
|
-
require 'preflight/rules/
|
17
|
+
require 'preflight/rules/no_gray'
|
18
|
+
require 'preflight/rules/no_page_rotation'
|
19
|
+
require 'preflight/rules/no_registration_black'
|
20
|
+
require 'preflight/rules/no_rgb'
|
21
|
+
require 'preflight/rules/no_private_data'
|
22
|
+
require 'preflight/rules/no_separation'
|
23
|
+
require 'preflight/rules/no_transparency'
|
13
24
|
require 'preflight/rules/only_embedded_fonts'
|
14
25
|
require 'preflight/rules/output_intent_for_pdfx'
|
26
|
+
require 'preflight/rules/page_count'
|
27
|
+
require 'preflight/rules/page_box_height'
|
28
|
+
require 'preflight/rules/page_box_width'
|
29
|
+
require 'preflight/rules/page_box_size'
|
15
30
|
require 'preflight/rules/pdfx_output_intent_has_keys'
|
16
31
|
require 'preflight/rules/print_boxes'
|
17
32
|
require 'preflight/rules/root_has_keys'
|
@@ -6,38 +6,50 @@ module Preflight
|
|
6
6
|
# For each page MediaBox must be the biggest box, followed by the
|
7
7
|
# BleedBox or ArtBox, followed by the TrimBox.
|
8
8
|
#
|
9
|
+
# Boxes may be omitted, but if they're provided they must be correctly nested.
|
10
|
+
#
|
11
|
+
# Arguments: none
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
#
|
15
|
+
# class MyPreflight
|
16
|
+
# include Preflight::Profile
|
17
|
+
#
|
18
|
+
# rule Preflight::Rules::BoxNesting
|
19
|
+
# end
|
20
|
+
#
|
9
21
|
class BoxNesting
|
10
|
-
attr_reader :messages
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@messages = []
|
14
|
-
@page_num = 0
|
15
|
-
@parent = {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def begin_page_container(hash = {})
|
19
|
-
@parent.merge!(hash)
|
20
|
-
end
|
21
22
|
|
22
|
-
|
23
|
-
@page_num += 1
|
24
|
-
hash = @parent.merge(hash)
|
23
|
+
attr_reader :issues
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
def page=(page)
|
26
|
+
media = page.attributes[:MediaBox]
|
27
|
+
bleed = page.attributes[:BleedBox]
|
28
|
+
trim = page.attributes[:TrimBox]
|
29
|
+
art = page.attributes[:ArtBox]
|
30
30
|
|
31
31
|
if media && bleed && (bleed[2] > media[2] || bleed[3] > media[3])
|
32
|
-
@
|
32
|
+
@issues = [Issue.new("BleedBox must be smaller than MediaBox", self, :page => page.number,
|
33
|
+
:large_box => "BleedBox",
|
34
|
+
:small_box => "MediaBox")]
|
33
35
|
elsif trim && bleed && (trim[2] > bleed[2] || trim[3] > bleed[3])
|
34
|
-
@
|
36
|
+
@issues = [Issue.new("TrimBox must be smaller than BleedBox", self, :page => page.number,
|
37
|
+
:large_box => "TrimBox",
|
38
|
+
:small_box => "BleedBox")]
|
35
39
|
elsif art && bleed && (art[2] > bleed[2] || art[3] > bleed[3])
|
36
|
-
@
|
40
|
+
@issues = [Issue.new("ArtBox must be smaller than BleedBox", self, :page => page.number,
|
41
|
+
:large_box => "ArtBox",
|
42
|
+
:small_box => "BleedBox")]
|
37
43
|
elsif trim && media && (trim[2] > media[2] || trim[3] > media[3])
|
38
|
-
@
|
44
|
+
@issues = [Issue.new("TrimBox must be smaller than MediaBox", self, :page => page.number,
|
45
|
+
:large_box => "TrimBox",
|
46
|
+
:small_box => "MediaBox")]
|
39
47
|
elsif art && media && (art[2] > media[2] || art[3] > media[3])
|
40
|
-
@
|
48
|
+
@issues = [Issue.new("ArtBox must be smaller than MediaBox", self, :page => page.number,
|
49
|
+
:large_box => "ArtBox",
|
50
|
+
:small_box => "MediaBox")]
|
51
|
+
else
|
52
|
+
@issues = []
|
41
53
|
end
|
42
54
|
end
|
43
55
|
end
|
@@ -3,19 +3,29 @@
|
|
3
3
|
module Preflight
|
4
4
|
module Rules
|
5
5
|
|
6
|
-
# check a file
|
6
|
+
# check a file doesn't use unwanted compression algorithms
|
7
|
+
#
|
8
|
+
# Arguments: a list of permitted compression algorithms.
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# class MyPreflight
|
13
|
+
# include Preflight::Profile
|
14
|
+
#
|
15
|
+
# rule Preflight::Rules::CompressionAlgorithms, :CCITTFaxDecode, :DCTDecode, :FlateDecode
|
16
|
+
# end
|
7
17
|
#
|
8
18
|
class CompressionAlgorithms
|
9
19
|
|
10
20
|
def initialize(*algorithms)
|
11
|
-
@algorithms = algorithms.flatten
|
21
|
+
@algorithms = algorithms.flatten
|
12
22
|
end
|
13
23
|
|
14
|
-
def
|
24
|
+
def check_hash(ohash)
|
15
25
|
algorithms = banned_algorithms(ohash)
|
16
26
|
|
17
27
|
if algorithms.size > 0
|
18
|
-
["File uses excluded compression algorithm
|
28
|
+
[Issue.new("File uses excluded compression algorithm", self, :algorithms => algorithms)]
|
19
29
|
else
|
20
30
|
[]
|
21
31
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Preflight
|
4
|
+
module Rules
|
5
|
+
|
6
|
+
# Every page should have identical page boxes
|
7
|
+
#
|
8
|
+
# Arguments: none
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# class MyPreflight
|
13
|
+
# include Preflight::Profile
|
14
|
+
#
|
15
|
+
# rule Preflight::Rules::BoxNesting
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class ConsistentBoxes
|
19
|
+
|
20
|
+
# each page box MUST be within .03 PDF points of the same box
|
21
|
+
# on all other pages
|
22
|
+
TOLERANCE = (BigDecimal.new("-0.03")..BigDecimal.new("0.03"))
|
23
|
+
|
24
|
+
attr_reader :issues
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@boxes = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def page=(page)
|
31
|
+
@issues = []
|
32
|
+
dict = page.attributes
|
33
|
+
|
34
|
+
@boxes[:MediaBox] ||= dict[:MediaBox]
|
35
|
+
@boxes[:CropBox] ||= dict[:CropBox]
|
36
|
+
@boxes[:BleedBox] ||= dict[:BleedBox]
|
37
|
+
@boxes[:TrimBox] ||= dict[:TrimBox]
|
38
|
+
@boxes[:ArtBox] ||= dict[:ArtBox]
|
39
|
+
|
40
|
+
%w(MediaBox CropBox BleedBox TrimBox ArtBox).map(&:to_sym).each do |box_type|
|
41
|
+
unless subtract_all(@boxes[box_type], dict[box_type]).all? { |diff| TOLERANCE.include?(diff) }
|
42
|
+
@issues << Issue.new("#{box_type} must be consistent across every page", self, :page => page.number,
|
43
|
+
:inconsistent_box => box_type)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def subtract_all(one, two)
|
51
|
+
one ||= [0, 0, 0, 0]
|
52
|
+
two ||= [0, 0, 0, 0]
|
53
|
+
|
54
|
+
[
|
55
|
+
one[0] - two[0],
|
56
|
+
one[1] - two[1],
|
57
|
+
one[2] - two[2],
|
58
|
+
one[3] - two[3]
|
59
|
+
]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Preflight
|
4
|
+
module Rules
|
5
|
+
|
6
|
+
# Every page should have a CropBox that matches the MediaBox
|
7
|
+
#
|
8
|
+
# Arguments: none
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# class MyPreflight
|
13
|
+
# include Preflight::Profile
|
14
|
+
#
|
15
|
+
# rule Preflight::Rules::CropboxMatchesMediabox
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class CropboxMatchesMediabox
|
19
|
+
|
20
|
+
attr_reader :issues
|
21
|
+
|
22
|
+
def page=(page)
|
23
|
+
@issues = []
|
24
|
+
dict = page.attributes
|
25
|
+
|
26
|
+
if dict[:CropBox] && round_off(dict[:CropBox]) != round_off(dict[:MediaBox])
|
27
|
+
@issues << Issue.new("CropBox must match MediaBox", self, :page => page.number)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def round_off(*arr)
|
34
|
+
arr.flatten.compact.map { |n| BigDecimal.new(n.to_s).round(2) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|