preflight 0.0.1

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 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
+