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