pdf_ravager 0.0.7

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