pdf_ravager 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ pkg/
3
+ *.gem
4
+ .bundle
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-19mode
7
+ # uncomment this line if your project needs to run something other than `rake`:
8
+ script: bundle exec rake test
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ platforms :jruby do
6
+ # Necessary for `rake release` on JRuby
7
+ gem "jruby-openssl", "~> 0.7.7"
8
+ end
9
+
10
+ group :test do
11
+ # Formats and colorizes test output
12
+ gem "turn", "~> 0.9.6", :require => false
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Abe Voelker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # PDF Ravager [![Build Status](https://secure.travis-ci.org/abevoelker/pdf_ravager.png)](http://travis-ci.org/abevoelker/pdf_ravager)
2
+
3
+ Provides a simple DSL for easily filling out AcroForms PDF or XFA documents.
4
+
5
+ ## Description
6
+
7
+ This library uses a combination of a simple DSL and a minimal façade over the
8
+ last free version of the iText library to aid in filling out AcroForms PDF or
9
+ XFA documents.
10
+
11
+ ## Synopsis
12
+
13
+ ```ruby
14
+ require 'pdf_ravager'
15
+
16
+ data = {:name => 'Bob', :gender => 'm', :relation => 'Uncle' }
17
+
18
+ info = pdf do
19
+ text 'name', data[:name]
20
+ text 'name_stylized', "<b>#{data[:name]}</b>", :rich => true
21
+ radio_group 'sex' do
22
+ fill 'male' if data[:gender] == 'm'
23
+ fill 'female' if data[:gender] == 'f'
24
+ end
25
+ check 'related' if data[:relation]
26
+ checkbox_group 'relation' do
27
+ case data[:relation]
28
+ when 'Mom', 'Dad'
29
+ check 'parent'
30
+ when 'Brother', 'Sister'
31
+ check 'sibling'
32
+ else
33
+ check 'other'
34
+ end
35
+ end
36
+ end
37
+
38
+ info.ravage '/tmp/info.pdf', :out_file => '/tmp/info_filled.pdf'
39
+ # if you'd like the populated form to be read-only:
40
+ info.ravage '/tmp/info.pdf', :out_file => '/tmp/info_filled.pdf', :read_only => true
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Field Names
46
+ To query and modify a form's field names, use a tool such as Adobe
47
+ LiveCycle.
48
+
49
+ ### Rich Text
50
+ Rich text is specific to XFA forms. To understand how it should be used,
51
+ see the "Rich Text Reference" section of [Adobe's XFA standard][1].
52
+ Rich Text is defined there as a subset of
53
+ XHTML and CSS which uses some custom restrictions and extensions by
54
+ Adobe. The minimum XHTML and CSS elements that a standards-compliant
55
+ XFA processor (e.g. Adobe Reader) must support are also listed there
56
+ and can be used as a guide.
57
+
58
+ **Note**: Rich text values are not HTML-escaped or sanitized in any
59
+ way. It is suggested that you call `CGI.escape_html` on user-supplied
60
+ input.
61
+
62
+ ### Checkbox Groups
63
+ Because there is no such thing as a "checkbox group," the
64
+ `checkbox_group` syntax is simply syntactic sugar for calling
65
+ `check` with the group name and a `.` prepended to the name. For
66
+ example,
67
+
68
+ ```ruby
69
+ checkbox_group 'relation' do
70
+ check 'parent'
71
+ end
72
+ ```
73
+
74
+ is equivalent to
75
+
76
+ ```ruby
77
+ check 'relation.parent'
78
+ ```
79
+
80
+ ## Copyright
81
+
82
+ Copyright (c) 2012 Abe Voelker. Released under the terms of the
83
+ MIT license. See LICENSE for details.
84
+
85
+ [1]: http://partners.adobe.com/public/developer/xml/index_arch.html
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+ require 'rake/testtask'
3
+ require 'bundler'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << ["lib", "spec"]
7
+ t.pattern = "spec/*_spec.rb"
8
+ end
9
+
10
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,3 @@
1
+ require 'pdf_ravager/version'
2
+ require 'pdf_ravager/pdf'
3
+ require 'pdf_ravager/ravager' if RUBY_PLATFORM =~ /java/
@@ -0,0 +1,105 @@
1
+ require 'json'
2
+ require 'pdf_ravager/ravager' if RUBY_PLATFORM =~ /java/
3
+
4
+ module PDFRavager
5
+ class PDF
6
+ attr_reader :name, :fields
7
+
8
+ def initialize(name=nil, opts={})
9
+ @name = name if name
10
+ @fields = opts[:fields] || []
11
+ end
12
+
13
+ def text(name, value, opts={})
14
+ if opts.empty?
15
+ @fields << {:name => name, :value => value, :type => :text}
16
+ else
17
+ @fields << {:name => name, :value => value, :type => :text, :options => opts}
18
+ end
19
+ end
20
+
21
+ def check(name, opts={})
22
+ @fields << {:name => name, :value => true, :type => :checkbox}
23
+ end
24
+
25
+ def radio_group(gname, &blk)
26
+ fields = []
27
+ # TODO: replace w/ singleton method?
28
+ PDF.instance_eval do
29
+ send(:define_method, :fill) do |name, opts={}|
30
+ fields << {:name => gname, :value => name, :type => :radio}
31
+ end
32
+ blk.call
33
+ send(:undef_method, :fill)
34
+ end
35
+
36
+ @fields += fields
37
+ end
38
+
39
+ def checkbox_group(gname, &blk)
40
+ # TODO: replace w/ singleton method?
41
+ PDF.instance_eval do
42
+ alias_method :__check_original__, :check
43
+ send(:define_method, :check) do |name, opts={}|
44
+ __check_original__("#{gname}.#{name}", opts)
45
+ end
46
+ blk.call
47
+ # restore check method back to normal
48
+ alias_method :check, :__check_original__
49
+ send(:undef_method, :__check_original__)
50
+ end
51
+ end
52
+
53
+ if RUBY_PLATFORM =~ /java/
54
+ def ravage(file, opts={})
55
+ PDFRavager::Ravager.open(opts.merge(:in_file => file)) do |pdf|
56
+ @fields.each do |f|
57
+ value = if f[:type] == :checkbox
58
+ !!f[:value] ? '1' : '0' # Checkbox default string values
59
+ else
60
+ f[:value]
61
+ end
62
+ pdf.set_field_value(f[:name], value, f[:type], f[:options])
63
+ end
64
+ end
65
+ end
66
+ else
67
+ def ravage(file, opts={})
68
+ raise "You can only ravage .pdfs using JRuby, not #{RUBY_PLATFORM}!"
69
+ end
70
+ end
71
+
72
+ def ==(other)
73
+ self.name == other.name && self.fields == other.fields
74
+ end
75
+
76
+ def to_json(*args)
77
+ {
78
+ "json_class" => self.class.name,
79
+ "data" => {"name" => @name, "fields" => @fields }
80
+ }.to_json(*args)
81
+ end
82
+
83
+ def self.json_create(obj)
84
+ fields = obj["data"]["fields"].map do |f|
85
+ # symbolize the root keys
86
+ f = f.inject({}){|h,(k,v)| h[k.to_sym] = v; h}
87
+ f[:type] = f[:type].to_sym if f[:type]
88
+ # symbolize the :options keys
89
+ if f[:options]
90
+ f[:options] = f[:options].inject({}){|h,(k,v)| h[k.to_sym] = v; h}
91
+ end
92
+ f
93
+ end
94
+ o = new(obj["data"]["name"], :fields => fields)
95
+ end
96
+ end
97
+ end
98
+
99
+ module Kernel
100
+ def pdf(name=nil, opts={}, &blk)
101
+ r = PDFRavager::PDF.new(name, opts)
102
+ r.instance_eval(&blk)
103
+ r
104
+ end
105
+ end
@@ -0,0 +1,126 @@
1
+ require 'java'
2
+ require 'nokogiri'
3
+ require File.dirname(__FILE__) + '/../../vendor/iText-4.2.0'
4
+
5
+ java_import "com.lowagie.text.pdf.AcroFields"
6
+ java_import "com.lowagie.text.pdf.PdfArray"
7
+ java_import "com.lowagie.text.pdf.PdfDictionary"
8
+ java_import "com.lowagie.text.pdf.PdfName"
9
+ java_import "com.lowagie.text.pdf.PdfObject"
10
+ java_import "com.lowagie.text.pdf.PdfReader"
11
+ java_import "com.lowagie.text.pdf.PdfStamper"
12
+ java_import "com.lowagie.text.pdf.PdfStream"
13
+ java_import "com.lowagie.text.pdf.PdfWriter"
14
+ java_import "com.lowagie.text.pdf.XfaForm"
15
+ java_import "com.lowagie.text.pdf.XfdfReader"
16
+
17
+ module PDFRavager
18
+ class Ravager
19
+ private_class_method :new
20
+
21
+ def self.open(opts={}, &block)
22
+ opts = {:in_file => opts} if opts.is_a? String
23
+ out = if opts[:out_file]
24
+ java.io.FileOutputStream.new(opts[:out_file])
25
+ else
26
+ java.io.ByteArrayOutputStream.new
27
+ end
28
+ raise "You must pass a block" unless block_given?
29
+ ravager = new(opts.merge({:out => out}))
30
+ yield ravager
31
+ ravager.destroy
32
+ out
33
+ end
34
+
35
+ def set_field_value(name, value, type=nil, options={})
36
+ return set_rich_text_field(name, value) if options && options[:rich]
37
+ begin
38
+ # First try AcroForms method of setting value
39
+ @afields.setField(SOM.short_name(name), value)
40
+ rescue java.lang.NullPointerException
41
+ # If the AcroForms method doesn't work, we'll set the XDP
42
+ # Note: the double-load is to work around a Nokogiri bug I found:
43
+ # https://github.com/sparklemotion/nokogiri/issues/781
44
+ doc = Nokogiri::XML(Nokogiri::XML::Document.wrap(@xfa.getDomDocument).to_xml)
45
+ doc.xpath("//*[local-name()='field'][@name='#{name}']").each do |node|
46
+ # Create an XML node in the XDP like this: "<value><text>#{value}</text></value>"
47
+ Nokogiri::XML::Builder.with(node) do |xml|
48
+ xml.value_ {
49
+ xml.text_ {
50
+ xml.text value
51
+ }
52
+ }
53
+ end
54
+ end
55
+ @xfa.setDomDocument(doc.to_java)
56
+ @xfa.setChanged(true)
57
+ end
58
+ end
59
+
60
+ def destroy
61
+ read_only! if @opts[:read_only]
62
+ @stamper.close
63
+ end
64
+
65
+ private
66
+
67
+ def initialize(opts={})
68
+ @opts = opts
69
+ @reader = PdfReader.new(opts[:in_file])
70
+ @stamper = PdfStamper.new(@reader, opts[:out])
71
+ @afields = @stamper.getAcroFields
72
+ @xfa = @afields.getXfa
73
+ @som = @xfa.getDatasetsSom
74
+ @som_template = @xfa.getTemplateSom
75
+ @type = @xfa.isXfaPresent ? :xfa : :acro_forms
76
+ if @type == :xfa
77
+ @xfa_type = @afields.getFields.empty? ? :dynamic : :static
78
+ end
79
+ end
80
+
81
+ def set_rich_text_field(name, value)
82
+ doc = Nokogiri::XML(Nokogiri::XML::Document.wrap(@xfa.getDomDocument).to_xml)
83
+ doc.xpath("//*[local-name()='field'][@name='#{name}']").each do |node|
84
+ Nokogiri::XML::Builder.with(node) do |xml|
85
+ xml.value_ do
86
+ xml.exData('contentType' => 'text/html') do
87
+ xml.body_('xmlns' => "http://www.w3.org/1999/xhtml", 'xmlns:xfa' => "http://www.xfa.org/schema/xfa-data/1.0/") do
88
+ xml << value # Note: this value is not sanitized/escaped!
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ @xfa.setDomDocument(doc.to_java)
95
+ @xfa.setChanged(true)
96
+ end
97
+
98
+ def read_only!
99
+ case @type
100
+ when :acro_forms
101
+ @stamper.setFormFlattening(true)
102
+ when :xfa
103
+ if @xfa_type == :static
104
+ @stamper.setFormFlattening(true)
105
+ else
106
+ doc = Nokogiri::XML(Nokogiri::XML::Document.wrap(@xfa.getDomDocument).to_xml)
107
+ doc.xpath("//*[local-name()='field']").each do |node|
108
+ node["access"] = "readOnly"
109
+ end
110
+ @xfa.setDomDocument(doc.to_java)
111
+ @xfa.setChanged(true)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ class SOM
118
+ def self.short_name(str)
119
+ XfaForm::Xml2Som.getShortName(self.escape(str))
120
+ end
121
+
122
+ def self.escape(str)
123
+ XfaForm::Xml2Som.escapeSom(str) # just does: str.gsub(/\./) { '\\.' }
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,3 @@
1
+ module PDFRavager
2
+ VERSION = "0.0.7"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/pdf_ravager/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "pdf_ravager"
6
+ s.version = PDFRavager::VERSION
7
+ s.authors = ['Abe Voelker']
8
+ s.email = 'abe@abevoelker.com'
9
+ s.homepage = 'https://github.com/abevoelker/pdf_ravager'
10
+ s.summary = %q{DSL to aid filling out AcroForms PDF and XFA documents}
11
+ s.description = %q{DSL to aid filling out AcroForms PDF and XFA documents}
12
+
13
+ s.add_dependency "json"
14
+ s.add_dependency "nokogiri"
15
+ s.add_development_dependency "bundler", "~> 1.0"
16
+ s.add_development_dependency "minitest", "~> 4.1"
17
+ s.add_development_dependency "rspec", "~> 2.11"
18
+ s.add_development_dependency "rake", "~> 0.9"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end
data/spec/pdf_spec.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'pdf_ravager/pdf'
3
+
4
+ class TestPDF < MiniTest::Unit::TestCase
5
+ def setup
6
+ @pdf = pdf 'foo.pdf' do
7
+ text 'text', 'foo'
8
+ text 'rich_text', '<b>foo</b>', :rich => true
9
+ check 'checkbox'
10
+ checkbox_group 'checkbox_group' do
11
+ check 'foo'
12
+ end
13
+ radio_group 'radio_group' do
14
+ fill 'foo'
15
+ end
16
+ end
17
+
18
+ @pdf_from_json = JSON.parse(@pdf.to_json)
19
+ end
20
+
21
+ def test_that_name_is_set
22
+ assert_equal @pdf.name, 'foo.pdf'
23
+ end
24
+
25
+ def test_that_text_is_set
26
+ assert_includes @pdf.fields, {:name => 'text', :value => 'foo', :type => :text}
27
+ end
28
+
29
+ def test_that_rich_text_is_set
30
+ assert_includes @pdf.fields, {:name => 'rich_text', :value => '<b>foo</b>', :type => :text, :options => {:rich => true}}
31
+ end
32
+
33
+ def test_that_checkbox_is_set
34
+ assert_includes @pdf.fields, {:name => 'checkbox', :value => true, :type => :checkbox}
35
+ end
36
+
37
+ def test_that_checkbox_group_is_set
38
+ assert_includes @pdf.fields, {:name => 'checkbox_group.foo', :value => true, :type => :checkbox}
39
+ end
40
+
41
+ def test_that_radio_group_is_set
42
+ assert_includes @pdf.fields, {:name => 'radio_group', :value => 'foo', :type => :radio}
43
+ end
44
+
45
+ def test_json_serialization
46
+ assert_equal @pdf, @pdf_from_json
47
+ end
48
+ end
@@ -0,0 +1 @@
1
+ require 'turn/autorun'
Binary file
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdf_ravager
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.7
6
+ platform: ruby
7
+ authors:
8
+ - Abe Voelker
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ none: false
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ none: false
28
+ prerelease: false
29
+ type: :runtime
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ version_requirements: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ! '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ none: false
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ none: false
44
+ prerelease: false
45
+ type: :runtime
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ version_requirements: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: '1.0'
53
+ none: false
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ version: '1.0'
59
+ none: false
60
+ prerelease: false
61
+ type: :development
62
+ - !ruby/object:Gem::Dependency
63
+ name: minitest
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '4.1'
69
+ none: false
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ version: '4.1'
75
+ none: false
76
+ prerelease: false
77
+ type: :development
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ~>
83
+ - !ruby/object:Gem::Version
84
+ version: '2.11'
85
+ none: false
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: '2.11'
91
+ none: false
92
+ prerelease: false
93
+ type: :development
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ~>
99
+ - !ruby/object:Gem::Version
100
+ version: '0.9'
101
+ none: false
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ~>
105
+ - !ruby/object:Gem::Version
106
+ version: '0.9'
107
+ none: false
108
+ prerelease: false
109
+ type: :development
110
+ description: DSL to aid filling out AcroForms PDF and XFA documents
111
+ email: abe@abevoelker.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - .gitignore
117
+ - .travis.yml
118
+ - Gemfile
119
+ - LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - lib/pdf_ravager.rb
123
+ - lib/pdf_ravager/pdf.rb
124
+ - lib/pdf_ravager/ravager.rb
125
+ - lib/pdf_ravager/version.rb
126
+ - pdf_ravager.gemspec
127
+ - spec/pdf_spec.rb
128
+ - spec/spec_helper.rb
129
+ - vendor/iText-4.2.0.jar
130
+ homepage: https://github.com/abevoelker/pdf_ravager
131
+ licenses: []
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ none: false
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ none: false
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 1.8.24
151
+ signing_key:
152
+ specification_version: 3
153
+ summary: DSL to aid filling out AcroForms PDF and XFA documents
154
+ test_files: []
155
+ ...