pdf_utils 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "pdf_utils"
5
+ gemspec.summary = "PdfUtils abstracts a lot of well working UNIX tools for PDF files"
6
+ gemspec.description = <<-DESC
7
+ Requires xpdf, pdftk, swftools/pdf2swf and imagemagick.
8
+ You can check their functionality by running `$ rake check_system_dependencies´.
9
+ DESC
10
+ gemspec.email = "l.rieder@gmail.com"
11
+ gemspec.homepage = "http://github.com/Overbryd/pdf_utils"
12
+ gemspec.authors = ["Lukas Rieder", "Andreas Korth"]
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: gem install jeweler"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+
21
+ desc "Run all examples"
22
+ Spec::Rake::SpecTask.new('spec') do |t|
23
+ t.spec_opts = ['--colour', '--format specdoc']
24
+ t.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ task :check_system_dependencies do
28
+ load File.join(File.dirname(__FILE__), 'script', 'check_system_dependencies')
29
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,29 @@
1
+ require 'tmpdir'
2
+
3
+ module InTmpdir
4
+
5
+ private
6
+
7
+ def in_tmpdir(dirname='tmp')
8
+ dirname = tmp_file_name(dirname)
9
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
10
+ begin
11
+ yield(dirname)
12
+ ensure
13
+ FileUtils.rm_rf(dirname) if File.exists?(dirname)
14
+ end
15
+ end
16
+
17
+ def with_tmpfile(filename='tmp')
18
+ filename = tmp_file_name(filename)
19
+ begin
20
+ yield(filename)
21
+ ensure
22
+ FileUtils.rm_rf(filename) if File.exists?(filename)
23
+ end
24
+ end
25
+
26
+ def tmp_file_name(name='tmp')
27
+ File.join(Dir.tmpdir, [$$.to_s(16), object_id.to_s(16), name].join('_'))
28
+ end
29
+ end
@@ -0,0 +1,175 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'prawn'
4
+ require 'pdf/reader'
5
+
6
+ require 'in_tempdir'
7
+ require 'pdf_utils/info'
8
+ require 'pdf_utils/color'
9
+
10
+ module PdfUtils
11
+
12
+ class << self
13
+
14
+ include InTmpdir
15
+
16
+ def objects(filename, filter={})
17
+ uncompress(filename) do |uncompressed|
18
+ PDF::Hash.new(uncompressed).values.select do |obj|
19
+ if obj.kind_of?(Hash)
20
+ if obj.has_key?(:Contents) && obj[:Contents].kind_of?(String)
21
+ obj[:Contents] = Iconv.conv('utf-8', 'utf-16', obj[:Contents])
22
+ end
23
+ filter.detect {|k, v| obj[k] == v }
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ def annotations(filename)
30
+ objects(filename, :Type => :Annot)
31
+ end
32
+
33
+ def info(filename)
34
+ PdfUtils::Info.new(filename)
35
+ end
36
+
37
+ def toc(filename)
38
+ toc = []
39
+ if meta_data = run_command(:pdftk, "#{filename} dump_data")
40
+ entry = nil
41
+ meta_data.scan(/^Bookmark(\w+): (.*)$/) do |(key, value)|
42
+ case key
43
+ when "Title"
44
+ entry = [value]
45
+ when "Level"
46
+ entry << value.to_i
47
+ when "PageNumber"
48
+ if value.to_i > 0
49
+ entry << value.to_i
50
+ toc << entry
51
+ end
52
+ end
53
+ end
54
+ end
55
+ toc
56
+ end
57
+
58
+ def to_text(filename, target = nil)
59
+ run_command(:pdftotext, "-layout -enc UTF-8 #{filename} #{target || '-'}", !!target)
60
+ end
61
+
62
+ def to_swf(source, target)
63
+ run_command(:pdf2swf, "-T 9 -f -s transparent -s detectspaces #{source} -o #{target}")
64
+ end
65
+
66
+ def uncompress(source, target=nil, &block)
67
+ target ||= tmp_file_name("uncompress")
68
+ run_command(:pdftk, "#{source} output #{target} uncompress")
69
+ if block_given?
70
+ begin
71
+ yield(target)
72
+ ensure
73
+ FileUtils.rm(target)
74
+ end
75
+ end
76
+ end
77
+
78
+ def slice(source, from_page, to_page, target)
79
+ run_command(:pdftk, "#{source} cat #{from_page}-#{to_page} output #{target}")
80
+ end
81
+
82
+ def slice!(source, from_page, to_page)
83
+ target = tmp_file_name("slice")
84
+ slice(source, from_page, to_page, target)
85
+ FileUtils.mv(target, source)
86
+ end
87
+
88
+ def burst(source, target)
89
+ run_command(:pdftk, "#{source} burst output #{target}")
90
+ end
91
+
92
+ def cat(source, target)
93
+ run_command(:pdftk, "#{source} cat output #{target}")
94
+ end
95
+
96
+ def watermark(source, target, options = {}, &block)
97
+ raise ArgumentError.new("No block given") unless block_given?
98
+ options[:page_size] ||= info(source).page_dimensions
99
+ with_tmpfile("watermark") do |watermarked|
100
+ Prawn::Document.generate(watermarked, options, &block)
101
+ run_command(:pdftk, "#{source} background #{watermarked} output #{target}")
102
+ end
103
+ end
104
+
105
+ def watermark!(source, options = {}, &block)
106
+ target = tmp_file_name("watermark_merge")
107
+ watermark(source, target, options, &block)
108
+ FileUtils.mv(target, source)
109
+ end
110
+
111
+ def annotate(source, annotations, target, options = {})
112
+ options[:page_size] ||= info(source).page_dimensions
113
+ with_tmpfile("annotate") do |annotated|
114
+ Prawn::Document.generate(annotated, options) do |pdf|
115
+ annotations.each do |annotation|
116
+ pdf.annotate(annotation)
117
+ end
118
+ end
119
+ run_command(:pdftk, "#{annotated} background #{source} output #{target}")
120
+ end
121
+ end
122
+
123
+ def annotate!(source, annotations, options = {})
124
+ target = tmp_file_name("annotate_merge")
125
+ annotate(source, annotations, target, options)
126
+ FileUtils.mv(target, source)
127
+ end
128
+
129
+ THUMBNAIL_DEFAULTS = { :density => 150, :size => '50%', :format => 'jpg', :quality => 85, :target => nil, :page => 1 }.freeze
130
+
131
+ def thumbnail(source, options = {})
132
+ options.assert_valid_keys(THUMBNAIL_DEFAULTS.keys)
133
+
134
+ options = THUMBNAIL_DEFAULTS.merge(options)
135
+ target = options[:target] || source.sub(/(\.\w+)?$/, ".#{options[:format]}")
136
+ source = "#{source}[#{options[:page].to_i-1}]"
137
+
138
+ run_command(:convert, [
139
+ '-density' , options[:density],
140
+ source,
141
+ '-colorspace' , :rgb,
142
+ '-thumbnail' , options[:size],
143
+ '-quality' , options[:quality],
144
+ target
145
+ ].join(' '))
146
+ return target
147
+ end
148
+
149
+ SNAPSHOT_DEFAULTS = { :density => 150, :compress => 'JPEG', :quality => 85 }.freeze
150
+
151
+ def snapshot(source, target, options = {})
152
+ options.assert_valid_keys(SNAPSHOT_DEFAULTS.keys + [:page])
153
+ page_number = (options.delete(:page) || 1).to_i
154
+ source = "#{source}[#{page_number-1}]"
155
+ options = SNAPSHOT_DEFAULTS.merge(options).map{ |opt,val| "-#{opt} #{val}" } << "#{source} #{target}"
156
+ run_command(:convert, options)
157
+ end
158
+
159
+ def snapshot!(source, options = {})
160
+ snapshot(source, source, options)
161
+ end
162
+
163
+ def run_command(command, args, reroute_errors = true)
164
+ command = [command, args]
165
+ command << '2>&1' if reroute_errors
166
+ command = command.join(' ')
167
+ output = `#{command}`
168
+ if $?.success?
169
+ output
170
+ else
171
+ raise RuntimeError.new("Command failed: #{command}\n#{output}")
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,27 @@
1
+ module PdfUtils
2
+ class Color
3
+ def initialize(*args)
4
+ if args.first.is_a?(String)
5
+ @r, @g, @b = args.first.scan(/../).map{ |rgb| rgb.to_i(16) }
6
+ elsif args.size == 3
7
+ if args.any? {|arg| arg.kind_of?(Float) }
8
+ @r, @g, @b = args.map {|v| (v * 255).to_i }
9
+ else
10
+ @r, @g, @b = args
11
+ end
12
+ end
13
+ end
14
+
15
+ def to_pdf
16
+ to_rgb.map{ |v| v / 255.0 }
17
+ end
18
+
19
+ def to_hex
20
+ to_rgb.inject(''){ |hex, v| hex << "%02x" % v }
21
+ end
22
+
23
+ def to_rgb
24
+ [@r, @g, @b]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ module PdfUtils
2
+ class Info
3
+ attr_reader :pdf_version, :title, :author, :subject, :keywords, :creator, :producer, :tagged, :optimized, :encrypted
4
+ attr_reader :file_size, :mod_date, :creation_date, :pages, :page_size, :page_format
5
+ alias_method :tagged?, :tagged
6
+ alias_method :optimized?, :optimized
7
+ alias_method :encrypted?, :encrypted
8
+ alias_method :modification_date, :mod_date
9
+
10
+ def initialize(filename)
11
+ raise ArgumentError.new("File does not exist: #{filename}") unless File.exist?(filename)
12
+ info = {}
13
+ PdfUtils::run_command(:pdfinfo, filename).scan(/^([^:]+): +(.*?)?$/) do |property|
14
+ info.store(*property)
15
+ end
16
+ @pdf_version = info["PDF version"]
17
+ @title = info["Title"]
18
+ @subject = info["Subject"]
19
+ @keywords = info["Keywords"]
20
+ @author = info["Author"]
21
+ @creator = info["Creator"]
22
+ @producer = info["Producer"]
23
+ @pages = info["Pages"].to_i
24
+ @creation_date = Time.parse(info["CreationDate"]) rescue nil
25
+ @mod_date = Time.parse(info["ModDate"]) rescue nil
26
+ @page_size = info["Page size"] =~ /^([\d.]+) x ([\d.]+)/ && [$1.to_f, $2.to_f]
27
+ @page_format = info["Page size"] =~ /\(([^(]+)\)$/ && $1
28
+ @file_size = info["File size"] =~ /^(\d+) bytes$/ && $1.to_i
29
+ @optimized = info["Optimized"] == 'yes'
30
+ @tagged = info["Tagged"] == 'yes'
31
+ @encrypted = info["Encrypted"] == 'yes'
32
+ end
33
+
34
+ def page_dimensions
35
+ [page_width, page_height]
36
+ end
37
+
38
+ def page_width
39
+ @page_size[0]
40
+ end
41
+
42
+ def page_height
43
+ @page_size[1]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.join(File.dirname(__FILE__), '../lib'))
3
+ require 'pdf_utils'
4
+
5
+ tmpdir = File.join(Dir.tmpdir, "#{$$}-dependency_checks")
6
+ FileUtils.mkdir(tmpdir) rescue nil
7
+
8
+ root = File.join(File.dirname(__FILE__), '../')
9
+ checkfile = File.join(tmpdir, 'check.pdf')
10
+ FileUtils.cp(File.join(root, 'spec/fixtures/marketing.pdf'), checkfile)
11
+
12
+ def check(what, &block)
13
+ print "Checking #{what}... "
14
+ yield
15
+ if $?.success?
16
+ puts "passed."
17
+ else
18
+ puts "failed."
19
+ end
20
+ end
21
+
22
+ check 'pdf info' do
23
+ PdfUtils::info(checkfile)
24
+ end
25
+
26
+ check 'pdf table of contents' do
27
+ PdfUtils::toc(checkfile)
28
+ end
29
+
30
+ check 'pdf to text' do
31
+ PdfUtils::to_text(checkfile)
32
+ end
33
+
34
+ check 'pdf uncompress' do
35
+ PdfUtils::uncompress(checkfile)
36
+ end
37
+
38
+ check 'pdf to swf' do
39
+ PdfUtils::to_swf(checkfile, File.join(tmpdir, 'check.swf'))
40
+ end
41
+
42
+ check 'pdf slicing' do
43
+ PdfUtils::slice(checkfile, 1, 2, File.join(tmpdir, 'sliced.pdf'))
44
+ end
45
+
46
+ check 'pdf bursting' do
47
+ PdfUtils::burst(checkfile, File.join(tmpdir, 'burst-%04d.pdf'))
48
+ end
49
+
50
+ check 'pdf background' do
51
+ PdfUtils::watermark(checkfile, File.join(tmpdir, 'watermarked.pdf'), :margin => 0) do |page|
52
+ page.text 'I am watermarked'
53
+ end
54
+ end
55
+
56
+ check 'pdf to cover' do
57
+ PdfUtils::thumbnail(checkfile, :target => File.join(tmpdir, 'cover.jpg'))
58
+ end
59
+
60
+ check 'pdf to snapshot' do
61
+ PdfUtils::snapshot(checkfile, File.join(tmpdir, 'snapshot.pdf'))
62
+ end
Binary file
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe PdfUtils::Color do
4
+ it "should convert to pdf color" do
5
+ PdfUtils::Color.new(255, 255, 0).to_pdf.should eql([1.0, 1.0, 0.0])
6
+ end
7
+
8
+ it "should convert to hex color" do
9
+ PdfUtils::Color.new(0, 1.0, 0).to_hex.should eql('00ff00')
10
+ end
11
+
12
+ it "should convert to rgb values" do
13
+ PdfUtils::Color.new('ff0000').to_rgb.should eql([255, 0, 0])
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe PdfUtils::Info do
4
+ it "should extract meta information from the pdf" do
5
+ @info = PdfUtils::Info.new(fixture_file_path('marketing.pdf'))
6
+
7
+ @info.tagged .should eql(false)
8
+ @info.keywords .should eql("")
9
+ @info.page_size .should eql([595.0, 842.0])
10
+ @info.producer .should eql("Mac OS X 10.5.6 Quartz PDFContext")
11
+ @info.optimized .should eql(false)
12
+ @info.title .should eql("marketing")
13
+ @info.mod_date .should eql(Time.parse('2010/01/12 17:34:51 +0100'))
14
+ @info.creator .should eql("Preview")
15
+ @info.file_size .should eql(33179)
16
+ @info.pdf_version .should eql("1.4")
17
+ @info.creation_date .should eql(Time.parse('2009/02/04 13:42:55 +0100'))
18
+ @info.encrypted .should eql(false)
19
+ @info.author .should eql("Alexander Lang")
20
+ @info.page_format .should eql("A4")
21
+ @info.pages .should eql(3)
22
+ @info.subject .should eql("")
23
+ end
24
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+
3
+ describe PdfUtils do
4
+
5
+ describe "toc" do
6
+
7
+ it "should return an array of toc entries" do
8
+ PdfUtils.should_receive(:run_command).with(:pdftk, "stub.pdf dump_data").and_return(
9
+ "BookmarkTitle: Content\nBookmarkLevel: 1\nBookmarkPageNumber: 3\nBookmarkTitle: Intro\nBookmarkLevel: 2\nBookmarkPageNumber: 4")
10
+ PdfUtils::toc('stub.pdf').should eql([["Content",1,3], ["Intro",2,4]])
11
+ end
12
+
13
+ it "should not add an toc entry with page number < 1" do
14
+ PdfUtils.stub(:run_command => "BookmarkTitle: Content\nBookmarkLevel: 1\nBookmarkPageNumber: 0")
15
+ PdfUtils::toc('stub.pdf').should be_empty
16
+ end
17
+
18
+ it "should be empty if the document has no toc data" do
19
+ PdfUtils.should_receive(:run_command).with(:pdftk, "stub.pdf dump_data").and_return(
20
+ "InfoKey: Creator\nInfoValue: The Pragmatic Bookshelf")
21
+ PdfUtils::toc('stub.pdf').entries.should be_empty
22
+ end
23
+ end
24
+
25
+ describe "slice" do
26
+
27
+ it "should slice into a new pdf" do
28
+ sliced_path = Tempfile.new('sliced').path
29
+ PdfUtils::slice(fixture_file_path('marketing.pdf'), 2, 3, sliced_path)
30
+ PdfUtils::info(sliced_path).pages.should eql(2)
31
+ end
32
+
33
+ it "should slice the given file" do
34
+ sliced_path = duplicate_fixture_file('marketing.pdf').path
35
+ PdfUtils::slice!(sliced_path, 2, 3)
36
+ PdfUtils::info(sliced_path).pages.should eql(2)
37
+ end
38
+
39
+ it "should raise an error if pdftk fails to slice the pdf" do
40
+ lambda {
41
+ PdfUtils::slice('does/not/exist.pdf', 2, 3, 'does/not/exist/either.pdf')
42
+ }.should raise_error(RuntimeError)
43
+ end
44
+ end
45
+
46
+ describe "watermark" do
47
+
48
+ before :each do
49
+ @pdf_path = fixture_file_path('marketing.pdf')
50
+ end
51
+
52
+ it "should watermark into a new pdf" do
53
+ watermarked_path = Tempfile.new('watermarked').path
54
+ PdfUtils::watermark(@pdf_path, watermarked_path) do |pdf|
55
+ pdf.text "WATERMARKED PDF", :align => :center, :size => 8
56
+ end
57
+ pdf_text = PdfUtils::to_text(watermarked_path)
58
+ pdf_text.should include('WATERMARKED PDF')
59
+ pdf_text.should include('Beltz Verlag Weinheim')
60
+ end
61
+
62
+ it "should watermark the given file" do
63
+ watermarked_path = duplicate_fixture_file('marketing.pdf').path
64
+ PdfUtils::watermark!(watermarked_path) do |pdf|
65
+ pdf.text "WATERMARKED PDF", :align => :center, :size => 8
66
+ end
67
+ pdf_text = PdfUtils::to_text(watermarked_path)
68
+ pdf_text.should include('WATERMARKED PDF')
69
+ pdf_text.should include('Beltz Verlag Weinheim')
70
+ end
71
+
72
+ it "should pass options to the watermark pdf" do
73
+ Prawn::Document.should_receive(:generate).with(anything, :page_size => [25, 25])
74
+ PdfUtils::watermark(@pdf_path, Tempfile.new('target').path, :page_size => [25, 25]) {}
75
+ end
76
+ end
77
+
78
+ describe "annotate" do
79
+
80
+ before :each do
81
+ @pdf_path = fixture_file_path('page.pdf')
82
+ @annotations = [{
83
+ :Type => :Annot,
84
+ :Subtype => :Text,
85
+ :Name => :Comment,
86
+ :Rect => [10, 10, 34, 34],
87
+ :Contents => 'Dies ist eine Notiz.',
88
+ :C => PdfUtils::Color.new('fdaa00').to_pdf,
89
+ :M => Time.now,
90
+ :F => 4
91
+ }]
92
+ end
93
+
94
+ it "should annotate into a new pdf" do
95
+ annotated_path = Tempfile.new('annotated').path
96
+ PdfUtils::annotate(@pdf_path, @annotations, annotated_path)
97
+ annotations = PdfUtils::annotations(annotated_path)
98
+ annotations.should have(1).item
99
+ annotations.first[:Contents].should eql('Dies ist eine Notiz.')
100
+ end
101
+ end
102
+
103
+ describe "thumbnail" do
104
+
105
+ it "should create a thumbnail of the given page" do
106
+ source = fixture_file_path('marketing.pdf')
107
+ target = File.join(Dir.tmpdir, 'marketing.png')
108
+ FileUtils.rm(target) if File.exists?(target)
109
+ PdfUtils::thumbnail(source, :page => '3', :target => target)
110
+ File.should be_exists(target)
111
+ end
112
+ end
113
+
114
+ describe "snapshot" do
115
+
116
+ it "should rasterize the given page" do
117
+ path = duplicate_fixture_file('marketing.pdf').path
118
+ original_info = PdfUtils::info(path)
119
+
120
+ PdfUtils::snapshot!(path, :page => 2)
121
+
122
+ info = PdfUtils::info(path)
123
+ info.pages.should eql(1)
124
+ info.page_width. should be_close(original_info.page_width , 1.0)
125
+ info.page_height.should be_close(original_info.page_height, 1.0)
126
+ info.page_format.should eql(original_info.page_format)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,20 @@
1
+ require "rubygems"
2
+ require 'test/unit'
3
+ require "spec"
4
+ require 'tempfile'
5
+
6
+ require 'pdf_utils'
7
+
8
+ def fixture_file_path(file)
9
+ File.join(File.dirname(__FILE__), 'fixtures', file)
10
+ end
11
+
12
+ def fixture_file(file)
13
+ File.open(fixture_file_path(file))
14
+ end
15
+
16
+ def duplicate_fixture_file(file)
17
+ tempfile = Tempfile.new(File.basename(file))
18
+ FileUtils.cp_r(fixture_file_path(file), tempfile.path)
19
+ tempfile
20
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdf_utils
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Lukas Rieder
13
+ - Andreas Korth
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-03 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: " Requires xpdf, pdftk, swftools/pdf2swf and imagemagick.\n You can check their functionality by running `$ rake check_system_dependencies\xC2\xB4.\n"
23
+ email: l.rieder@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - Rakefile
32
+ - VERSION
33
+ - lib/in_tempdir.rb
34
+ - lib/pdf_utils.rb
35
+ - lib/pdf_utils/color.rb
36
+ - lib/pdf_utils/info.rb
37
+ - script/check_system_dependencies
38
+ - spec/fixtures/marketing.pdf
39
+ - spec/fixtures/page.pdf
40
+ - spec/pdf_utils/color_spec.rb
41
+ - spec/pdf_utils/info_spec.rb
42
+ - spec/pdf_utils_spec.rb
43
+ - spec/spec_helper.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/Overbryd/pdf_utils
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.3.6
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: PdfUtils abstracts a lot of well working UNIX tools for PDF files
74
+ test_files:
75
+ - spec/pdf_utils/color_spec.rb
76
+ - spec/pdf_utils/info_spec.rb
77
+ - spec/pdf_utils_spec.rb
78
+ - spec/spec_helper.rb