preflight 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ v0.0.1 (27th March 2011)
2
+ * initial release
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
1
+ = Preflight
2
+
3
+ Check your PDF files meet the standard you require.
4
+
5
+ The full PDF spec is a beast, and there are any number of ways of producing a
6
+ valid PDF. Receivers of PDF files may need to ensure the files they're sent
7
+ meet a particular set of requirements, be it PDF/X, PDF/A or just "images must
8
+ be 300 dpi or greater". These checks are called preflight.
9
+
10
+ There's expensive software around that can preflight for you, but it's often
11
+ difficult to script and you know, expensive.
12
+
13
+ This may not check as comprehensively as the official preflight tools from Adobe
14
+ and friends, but hopefully it'll get you most of the way with less stress.
15
+
16
+ == Installation
17
+
18
+ gem install preflight
19
+
20
+ == Usage
21
+
22
+ == Standard Profile ( PDF/X-1a )
23
+
24
+ require "preflight"
25
+
26
+ preflight = Preflight::Profiles::PDFX1A.new
27
+
28
+ puts preflight.check("somefile.pdf").inspect
29
+
30
+ File.open("somefile.pdf", "rb") do |file|
31
+ puts preflight.check(file).inspect
32
+ end
33
+
34
+ == Custom Profile
35
+
36
+ require "preflight"
37
+
38
+ class MyPreflight
39
+ include Preflight::Profile
40
+
41
+ profile_name "simple-pdf"
42
+
43
+ rule Preflight::Rules::MaxVersion, 1.4
44
+ rule Preflight::Rules::NoEncryption
45
+ rule Preflight::Rules::DocumentId
46
+ end
47
+
48
+ preflight = MyPreflight
49
+
50
+ puts preflight.check("somefile.pdf").inspect
51
+
52
+ == Extend A Profile
53
+
54
+ require "preflight"
55
+
56
+ class MyPreflight
57
+ include Preflight::Profile
58
+
59
+ profile_name "simple-pdf"
60
+
61
+ import Preflight::Profiles::PDFX1A
62
+
63
+ rule Preflight::Rules::MaxVersion, 1.4
64
+ rule Preflight::Rules::NoEncryption
65
+ rule Preflight::Rules::DocumentId
66
+ end
67
+
68
+ preflight = MyPreflight
69
+
70
+ puts preflight.check("somefile.pdf").inspect
71
+
72
+ == Status
73
+
74
+ This library is in an early stage of development. Use at your own risk.
75
+
76
+ == Compatability
77
+
78
+ This is pure ruby should run on most ruby VMs. I develop on MRI 1.9.2.
79
+
80
+ == Further Reading
81
+
82
+ * http://chneukirchen.github.com/rps/
83
+ * http://en.wikipedia.org/wiki/PDF/A
84
+ * http://en.wikipedia.org/wiki/PDF/X
85
+ * http://www.planetpdf.com/planetpdf/pdfs/pdfx3_draft4.pdf
86
+ * http://www.gwg.org/doc_pdfxfaq.phtml
87
+ * http://www.pdfxreport.com/lib/exe/fetch.php?id=en%3Adownloads&cache=cache&media=en:technote_pdfx_checks.pdf
data/bin/is_pdfx_1a ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'preflight'
7
+ rescue LoadError
8
+ require 'bundler'
9
+ Bundler.setup
10
+ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib/"
11
+ require 'preflight'
12
+ end
13
+
14
+ filename = ARGV.shift
15
+ preflight = Preflight::Profiles::PDFX1A.new
16
+ messages = preflight.check(filename)
17
+
18
+ messages.each do |msg|
19
+ $stderr.puts msg
20
+ end
21
+
22
+ exit messages.size
data/lib/preflight.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'pdf/reader'
2
+
3
+ require 'preflight/measurements'
4
+ require 'preflight/profile'
5
+
6
+ require 'preflight/rules'
7
+ require 'preflight/profiles'
8
+
9
+ module Preflight
10
+ VERSION = "0.0.1"
11
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ #
3
+
4
+ require 'bigdecimal'
5
+
6
+ module Preflight
7
+ module Measurements
8
+
9
+ private
10
+
11
+ # convert inches to standard PDF points
12
+ #
13
+ def in2pt(inch)
14
+ return inch * BigDecimal.new("72")
15
+ end
16
+
17
+ # convert mm to standard PDF points
18
+ #
19
+ def mm2pt(mm)
20
+ return mm * (BigDecimal.new("72") / BigDecimal.new("24.4"))
21
+ end
22
+
23
+ # convert mm to standard PDF points
24
+ #
25
+ def cm2pt(cm)
26
+ return cm * mm2pt(10)
27
+ end
28
+
29
+ # convert standard PDF points to inches
30
+ #
31
+ def pt2in(pt)
32
+ return pt / in2pt(1)
33
+ end
34
+
35
+ # convert standard PDF points to mm
36
+ #
37
+ def pt2mm(pt)
38
+ return pt / mm2pt(1)
39
+ end
40
+
41
+ # convert standard PDF points to cm
42
+ #
43
+ def pt2cm(pt)
44
+ return pt / cm2pt(1)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,101 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+
5
+ # base functionality for all profiles.
6
+ #
7
+ module Profile
8
+
9
+ def self.included(base) # :nodoc:
10
+ base.class_eval do
11
+ extend Preflight::Profile::ClassMethods
12
+ include InstanceMethods
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def profile_name(str)
18
+ @profile_name = str
19
+ end
20
+
21
+ def import(profile)
22
+ profile.rules.each do |array|
23
+ rules << array.flatten
24
+ end
25
+ end
26
+
27
+ def rule(*args)
28
+ rules << args.flatten
29
+ end
30
+
31
+ def rules
32
+ @rules ||= []
33
+ end
34
+
35
+ end
36
+
37
+ module InstanceMethods
38
+ def check(input)
39
+ if File.file?(input)
40
+ check_filename(input)
41
+ elsif input.is_a?(IO)
42
+ check_io(input)
43
+ else
44
+ raise ArgumentError, "input must be a string with a filename or an IO object"
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def check_filename(filename)
51
+ File.open(filename, "rb") do |file|
52
+ return check_io(file)
53
+ end
54
+ end
55
+
56
+ def check_io(io)
57
+ check_receivers(io) + check_hash(io)
58
+ end
59
+
60
+ # TODO: this is nasty, we parse the full file once for each receiver.
61
+ # PDF::Reader needs to be updated to support multiple receivers
62
+ #
63
+ def check_receivers(io)
64
+ receiver_rules.map { |rec|
65
+ begin
66
+ PDF::Reader.new.parse(io, rec)
67
+ rec.messages
68
+ rescue PDF::Reader::UnsupportedFeatureError
69
+ nil
70
+ end
71
+ }.flatten.compact
72
+ end
73
+
74
+ def check_hash(io)
75
+ ohash = PDF::Reader::ObjectHash.new(io)
76
+
77
+ hash_rules.map { |chk|
78
+ chk.messages(ohash)
79
+ }.flatten.compact
80
+ end
81
+
82
+ def hash_rules
83
+ self.class.rules.select { |arr|
84
+ arr.first.rule_type == :hash
85
+ }.map { |arr|
86
+ klass = arr[0]
87
+ klass.new(*arr[1,10])
88
+ }
89
+ end
90
+
91
+ def receiver_rules
92
+ self.class.rules.select { |arr|
93
+ arr.first.rule_type == :receiver
94
+ }.map { |arr|
95
+ klass = arr[0]
96
+ klass.new(*arr[1,10])
97
+ }
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,2 @@
1
+ require 'preflight/profiles/pdfa1a'
2
+ require 'preflight/profiles/pdfx1a'
@@ -0,0 +1,17 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Profiles
5
+ class PDFA1A
6
+ include Preflight::Profile
7
+
8
+ profile_name "pdfa-1a"
9
+
10
+ # hard failures of the pdfx/1a spec
11
+ rule Preflight::Rules::CompressionAlgorithms, :CCITTFaxDecode, :DCTDecode, :FlateDecode, :RunLengthDecode
12
+ rule Preflight::Rules::NoEncryption
13
+ rule Preflight::Rules::OnlyEmbeddedFonts
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Profiles
5
+ class PDFX1A
6
+ include Preflight::Profile
7
+
8
+ profile_name "pdfx-1a"
9
+
10
+ rule Preflight::Rules::MatchInfoEntries, {:GTS_PDFXVersion => /\APDF\/X/,
11
+ :GTS_PDFXConformance => /\APDF\/X-1a/}
12
+ rule Preflight::Rules::RootHasKeys, :OutputIntents
13
+ rule Preflight::Rules::InfoHasKeys, :Title, :CreationDate, :ModDate
14
+ rule Preflight::Rules::InfoSpecifiesTrapping
15
+ rule Preflight::Rules::CompressionAlgorithms, :CCITTFaxDecode, :DCTDecode, :FlateDecode, :RunLengthDecode
16
+ rule Preflight::Rules::DocumentId
17
+ rule Preflight::Rules::NoEncryption
18
+ rule Preflight::Rules::OnlyEmbeddedFonts
19
+ rule Preflight::Rules::BoxNesting
20
+ rule Preflight::Rules::MaxVersion, 1.4
21
+ rule Preflight::Rules::PrintBoxes
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ require 'preflight/rules/box_nesting'
2
+ require 'preflight/rules/compression_algorithms'
3
+ require 'preflight/rules/document_id'
4
+ require 'preflight/rules/info_has_keys'
5
+ require 'preflight/rules/info_specifies_trapping'
6
+ require 'preflight/rules/match_info_entries'
7
+ require 'preflight/rules/max_version'
8
+ require 'preflight/rules/min_ppi'
9
+ require 'preflight/rules/no_encryption'
10
+ require 'preflight/rules/no_font_subsets'
11
+ require 'preflight/rules/no_proprietary_fonts'
12
+ require 'preflight/rules/only_embedded_fonts'
13
+ require 'preflight/rules/print_boxes'
14
+ require 'preflight/rules/root_has_keys'
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # For each page MediaBox must be the biggest box, followed by the
7
+ # BleedBox or ArtBox, followed by the TrimBox.
8
+ #
9
+ class BoxNesting
10
+ attr_reader :messages
11
+
12
+ def initialize
13
+ @messages = []
14
+ @page_num = 0
15
+ end
16
+
17
+ def self.rule_type
18
+ :receiver
19
+ end
20
+
21
+ def begin_page(hash = {})
22
+ @page_num += 1
23
+
24
+ media = hash[:MediaBox]
25
+ bleed = hash[:BleedBox]
26
+ trim = hash[:TrimBox]
27
+ art = hash[:ArtBox]
28
+
29
+ if media && bleed && (bleed[2] > media[2] || bleed[3] > media[3])
30
+ @messages << "BleedBox must be smaller than MediaBox (page #{@page_num})"
31
+ elsif trim && bleed && (trim[2] > bleed[2] || trim[3] > bleed[3])
32
+ @messages << "TrimBox must be smaller than BleedBox (page #{@page_num})"
33
+ elsif art && bleed && (art[2] > bleed[2] || art[3] > bleed[3])
34
+ @messages << "ArtBox must be smaller than BleedBox (page #{@page_num})"
35
+ elsif trim && media && (trim[2] > media[2] || trim[3] > media[3])
36
+ @messages << "TrimBox must be smaller than MediaBox (page #{@page_num})"
37
+ elsif art && media && (art[2] > media[2] || art[3] > media[3])
38
+ @messages << "ArtBox must be smaller than MediaBox (page #{@page_num})"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # check a file only uses permitted compression algorithms
7
+ #
8
+ class CompressionAlgorithms
9
+
10
+ def initialize(*algorithms)
11
+ @algorithms = algorithms.flatten
12
+ end
13
+
14
+ def self.rule_type
15
+ :hash
16
+ end
17
+
18
+ def messages(ohash)
19
+ algorithms = banned_algorithms(ohash)
20
+
21
+ if algorithms.size > 0
22
+ ["File uses excluded compression algorithm (#{algorithms.join(", ")})"]
23
+ else
24
+ []
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def banned_algorithms(ohash)
31
+ array = []
32
+ ohash.each do |key, obj|
33
+ if obj.is_a?(PDF::Reader::Stream)
34
+ filters = [obj.hash[:Filter]].flatten.compact
35
+ array += (filters - @algorithms)
36
+ end
37
+ end
38
+ array.uniq
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+ class DocumentId
6
+
7
+ def self.rule_type
8
+ :hash
9
+ end
10
+
11
+ def messages(ohash)
12
+ if ohash.trailer[:ID].nil?
13
+ ["Document ID missing"]
14
+ else
15
+ []
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # ensure the info dict has the specified keys
7
+ class InfoHasKeys
8
+
9
+ def initialize(*keys)
10
+ @keys = keys.flatten
11
+ end
12
+
13
+ def self.rule_type
14
+ :hash
15
+ end
16
+
17
+ def messages(ohash)
18
+ info = ohash.object(ohash.trailer[:Info])
19
+ missing = @keys - info.keys
20
+ missing.map { |key|
21
+ "Info dict missing required key #{key}"
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+ class InfoSpecifiesTrapping
6
+
7
+ def self.rule_type
8
+ :hash
9
+ end
10
+
11
+ def messages(ohash)
12
+ info = ohash.object(ohash.trailer[:Info])
13
+
14
+ if !info.has_key?(:Trapped)
15
+ [ "Info dict does not specify Trapped" ]
16
+ elsif info[:Trapped] != :True && info[:Trapped] != :False
17
+ [ "Trapped value of Info dict must be True or False" ]
18
+ else
19
+ []
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+ class MatchInfoEntries
6
+
7
+ def initialize(matches = {})
8
+ @matches = matches
9
+ end
10
+
11
+ def self.rule_type
12
+ :hash
13
+ end
14
+
15
+ def messages(ohash)
16
+ array = []
17
+ info = ohash.object(ohash.trailer[:Info])
18
+ @matches.each do |key, regexp|
19
+ if !info.has_key?(key)
20
+ array << "Info dict missing required key #{key}"
21
+ elsif !info[key].to_s.match(regexp)
22
+ array << "value of Info entry #{key} doesn't match (#{info[key]} != #{regexp})"
23
+ end
24
+ end
25
+ array
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+ # ensure the PDF version of the file under review is not more recent
6
+ # than desired
7
+ class MaxVersion
8
+
9
+ def initialize(max_version)
10
+ @max_version = max_version.to_f
11
+ end
12
+
13
+ def self.rule_type
14
+ :hash
15
+ end
16
+
17
+ def messages(ohash)
18
+ if ohash.pdf_version > @max_version
19
+ ["PDF version should be #{@max_version} or lower (value: #{ohash.pdf_version})"]
20
+ else
21
+ []
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,68 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # For high quality prints, you generally want raster images to be
7
+ # AT LEAST 300 points-per-inch (ppi). 600 is better, 1200 better again.
8
+ #
9
+ class MinPpi
10
+ include Preflight::Measurements
11
+
12
+ attr_reader :messages
13
+
14
+ def initialize(min_ppi)
15
+ @min_ppi = min_ppi.to_i
16
+ @messages = []
17
+ @last_matrix = []
18
+ @page_num = 0
19
+ end
20
+
21
+ def self.rule_type
22
+ :receiver
23
+ end
24
+
25
+ # store sample width and height for each image on the current page
26
+ #
27
+ def resource_xobject(label, stream)
28
+ return unless stream.hash[:Subtype] == :Image
29
+
30
+ @images[label] = [
31
+ stream.hash[:Width],
32
+ stream.hash[:Height]
33
+ ]
34
+ end
35
+
36
+ # track the most recent matrix transform.
37
+ #
38
+ # TODO: This needs to be smarter at tracking the graphics state stack
39
+ #
40
+ def concatenate_matrix(*args)
41
+ @last_matrix = args
42
+ end
43
+
44
+ # As each image is drawn on the canvas, determine the amount of device
45
+ # space it's being crammed into and therefore the PPI.
46
+ #
47
+ def invoke_xobject(label)
48
+ sample_w, sample_h = *@images[label]
49
+ device_w = pt2in(@last_matrix[0])
50
+ device_h = pt2in(@last_matrix[3])
51
+
52
+ horizontal_ppi = (sample_w / device_w).round(3)
53
+ vertical_ppi = (sample_h / device_h).round(3)
54
+
55
+ if horizontal_ppi < @min_ppi || vertical_ppi < @min_ppi
56
+ @messages << "Image with low PPI/DPI on page #{@page_num} (h:#{horizontal_ppi} v:#{vertical_ppi})"
57
+ end
58
+ end
59
+
60
+ # start fresh on every page
61
+ #
62
+ def begin_page(hash = {})
63
+ @images = {}
64
+ @page_num += 1
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+ class NoEncryption
6
+
7
+ def self.rule_type
8
+ :hash
9
+ end
10
+
11
+ def messages(ohash)
12
+ if ohash.trailer[:Encrypt]
13
+ ["File is encrypted"]
14
+ else
15
+ []
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # check a file has no font subsets. Subsets are handy and valid
7
+ # in standards like PDFX/1a, but they make it hard to edit a
8
+ # file
9
+ #
10
+ class NoFontSubsets
11
+
12
+ def self.rule_type
13
+ :hash
14
+ end
15
+
16
+ def messages(ohash)
17
+ array = []
18
+ ohash.each do |key, obj|
19
+ next unless obj.is_a?(::Hash) && obj[:Type] == :Font
20
+ if subset?(obj)
21
+ array << "Font #{obj[:BaseFont]} is a partial subset"
22
+ end
23
+ end
24
+ array
25
+ end
26
+
27
+ private
28
+
29
+ def subset?(font)
30
+ font[:BaseFont] && font[:BaseFont].match(/.+\+.+/)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ # coding: utf-8
2
+
3
+ require 'ttfunk'
4
+
5
+ module Preflight
6
+ module Rules
7
+
8
+ # check a file has no proprietary fonts. They look nice, but we can't
9
+ # print the damn things.
10
+ #
11
+ class NoProprietaryFonts
12
+
13
+ def self.rule_type
14
+ :hash
15
+ end
16
+
17
+ def messages(ohash)
18
+ array = []
19
+ ohash.each do |key, obj|
20
+ next unless obj.is_a?(::Hash) && obj[:Type] == :Font
21
+ if proprietary?(ohash, obj[:FontDescriptor])
22
+ array << "Font #{obj[:BaseFont]} is proprietary"
23
+ end
24
+ end
25
+ array
26
+ end
27
+
28
+ private
29
+
30
+ def proprietary?(ohash, descriptor)
31
+ descriptor = ohash.object(descriptor)
32
+
33
+ return false if descriptor.nil?
34
+ raise ArgumentError, 'expected a FontDescriptor hash' unless descriptor[:Type] == :FontDescriptor
35
+
36
+ if descriptor.has_key?(:FontFile)
37
+ # TODO embedded type 1 font
38
+ false
39
+ elsif descriptor.has_key?(:FontFile2) && ttf_proprietary?(ohash, descriptor[:FontFile2])
40
+ true
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ def ttf_proprietary?(ohash, font_file)
47
+ font_file = ohash.object(font_file)
48
+ ttf = TTFunk::File.new(font_file.unfiltered_data)
49
+ #puts ttf.name.strings
50
+ false
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # check a file only uses embedded fonts
7
+ #
8
+ class OnlyEmbeddedFonts
9
+
10
+ def self.rule_type
11
+ :hash
12
+ end
13
+
14
+ def messages(ohash)
15
+ array = []
16
+ ohash.each do |key, obj|
17
+ next unless obj.is_a?(::Hash) && obj[:Type] == :Font
18
+ if !embedded?(ohash, obj)
19
+ array << "Font #{obj[:BaseFont]} is not embedded"
20
+ end
21
+ end
22
+ array
23
+ end
24
+
25
+ private
26
+
27
+ def embedded?(ohash, font)
28
+ if font.has_key?(:FontDescriptor)
29
+ descriptor = ohash.object(font[:FontDescriptor])
30
+ descriptor.has_key?(:FontFile) || descriptor.has_key?(:FontFile2) || descriptor.has_key?(:FontFile3)
31
+ elsif font[:Subtype] == :Type0
32
+ descendants = ohash.object(font[:DescendantFonts])
33
+ descendants.all? { |f|
34
+ f = ohash.object(f)
35
+ embedded?(ohash, f)
36
+ }
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # For PDFX/1a, every page must have MediaBox, plus either ArtBox or
7
+ # TrimBox
8
+ #
9
+ class PrintBoxes
10
+ attr_reader :messages
11
+
12
+ def initialize
13
+ @messages = []
14
+ @page_num = 0
15
+ end
16
+
17
+ def self.rule_type
18
+ :receiver
19
+ end
20
+
21
+ def begin_page(hash = {})
22
+ @page_num += 1
23
+
24
+ if hash[:MediaBox].nil?
25
+ @messages << "every page must have a MediaBox (page #{@page_num})"
26
+ elsif hash[:ArtBox].nil? && hash[:TrimBox].nil?
27
+ @messages << "every page must have either an ArtBox or a TrimBox (page #{@page_num})"
28
+ elsif hash[:ArtBox] && hash[:TrimBox]
29
+ @messages << "no page can have both ArtBox and TrimBox - TrimBox is preferred (page #{@page_num})"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ module Preflight
4
+ module Rules
5
+
6
+ # ensure the root dict has the specified keys
7
+ class RootHasKeys
8
+
9
+ def initialize(*keys)
10
+ @keys = keys.flatten
11
+ end
12
+
13
+ def self.rule_type
14
+ :hash
15
+ end
16
+
17
+ def messages(ohash)
18
+ root = ohash.object(ohash.trailer[:Root])
19
+ missing = @keys - root.keys
20
+ missing.map { |key|
21
+ "Root dict missing required key #{key}"
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: preflight
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - James Healy
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-28 00:00:00 +11:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: pdf-reader
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 9
31
+ version: "0.9"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :development
46
+ version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: roodi
49
+ prerelease: false
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id003
60
+ - !ruby/object:Gem::Dependency
61
+ name: rspec
62
+ prerelease: false
63
+ requirement: &id004 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ segments:
69
+ - 2
70
+ - 0
71
+ version: "2.0"
72
+ type: :development
73
+ version_requirements: *id004
74
+ description: Provides a programatic way to check a PDF file conforms to standards like PDF-X/1a
75
+ email:
76
+ - james@yob.id.au
77
+ executables:
78
+ - is_pdfx_1a
79
+ extensions: []
80
+
81
+ extra_rdoc_files: []
82
+
83
+ files:
84
+ - lib/preflight/profiles/pdfx1a.rb
85
+ - lib/preflight/profiles/pdfa1a.rb
86
+ - lib/preflight/profile.rb
87
+ - lib/preflight/rules/root_has_keys.rb
88
+ - lib/preflight/rules/min_ppi.rb
89
+ - lib/preflight/rules/print_boxes.rb
90
+ - lib/preflight/rules/info_specifies_trapping.rb
91
+ - lib/preflight/rules/only_embedded_fonts.rb
92
+ - lib/preflight/rules/compression_algorithms.rb
93
+ - lib/preflight/rules/no_encryption.rb
94
+ - lib/preflight/rules/no_font_subsets.rb
95
+ - lib/preflight/rules/match_info_entries.rb
96
+ - lib/preflight/rules/max_version.rb
97
+ - lib/preflight/rules/no_proprietary_fonts.rb
98
+ - lib/preflight/rules/info_has_keys.rb
99
+ - lib/preflight/rules/document_id.rb
100
+ - lib/preflight/rules/box_nesting.rb
101
+ - lib/preflight/measurements.rb
102
+ - lib/preflight/rules.rb
103
+ - lib/preflight/profiles.rb
104
+ - lib/preflight.rb
105
+ - bin/is_pdfx_1a
106
+ - README.rdoc
107
+ - CHANGELOG
108
+ has_rdoc: true
109
+ homepage: http://github.com/yob/pdf-preflight
110
+ licenses: []
111
+
112
+ post_install_message:
113
+ rdoc_options:
114
+ - --title
115
+ - PDF::Preflight
116
+ - --line-numbers
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ segments:
133
+ - 1
134
+ - 3
135
+ - 2
136
+ version: 1.3.2
137
+ requirements: []
138
+
139
+ rubyforge_project:
140
+ rubygems_version: 1.3.7
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Check PDF files conform to various standards
144
+ test_files: []
145
+