pdf_tempura 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +342 -0
  8. data/Rakefile +1 -0
  9. data/example/my_pdf.rb +37 -0
  10. data/lib/pdf_tempura.rb +8 -0
  11. data/lib/pdf_tempura/document.rb +121 -0
  12. data/lib/pdf_tempura/document/boxed_characters.rb +70 -0
  13. data/lib/pdf_tempura/document/boxed_characters/groups.rb +53 -0
  14. data/lib/pdf_tempura/document/character_field.rb +37 -0
  15. data/lib/pdf_tempura/document/checkbox_field.rb +15 -0
  16. data/lib/pdf_tempura/document/default_commands.rb +40 -0
  17. data/lib/pdf_tempura/document/field/base.rb +55 -0
  18. data/lib/pdf_tempura/document/field_set.rb +51 -0
  19. data/lib/pdf_tempura/document/page.rb +33 -0
  20. data/lib/pdf_tempura/document/table.rb +95 -0
  21. data/lib/pdf_tempura/document/table/boxed_character_column.rb +20 -0
  22. data/lib/pdf_tempura/document/table/checkbox_column.rb +9 -0
  23. data/lib/pdf_tempura/document/table/column.rb +18 -0
  24. data/lib/pdf_tempura/document/table/spacer.rb +13 -0
  25. data/lib/pdf_tempura/document/table/text_column.rb +9 -0
  26. data/lib/pdf_tempura/document/text_field.rb +18 -0
  27. data/lib/pdf_tempura/document/validation.rb +79 -0
  28. data/lib/pdf_tempura/extensions/hash/stringify_keys.rb +35 -0
  29. data/lib/pdf_tempura/render.rb +18 -0
  30. data/lib/pdf_tempura/render/boxed_characters.rb +42 -0
  31. data/lib/pdf_tempura/render/character_field.rb +49 -0
  32. data/lib/pdf_tempura/render/checkbox_field.rb +35 -0
  33. data/lib/pdf_tempura/render/debug.rb +16 -0
  34. data/lib/pdf_tempura/render/debug/annotation/base.rb +83 -0
  35. data/lib/pdf_tempura/render/debug/character_field_annotation.rb +13 -0
  36. data/lib/pdf_tempura/render/debug/checkbox_field_annotation.rb +24 -0
  37. data/lib/pdf_tempura/render/debug/field_set_annotation.rb +23 -0
  38. data/lib/pdf_tempura/render/debug/grid.rb +59 -0
  39. data/lib/pdf_tempura/render/debug/outside_annotation.rb +42 -0
  40. data/lib/pdf_tempura/render/debug/table_annotation.rb +19 -0
  41. data/lib/pdf_tempura/render/debug/text_field_annotation.rb +42 -0
  42. data/lib/pdf_tempura/render/field.rb +40 -0
  43. data/lib/pdf_tempura/render/field_bounds.rb +26 -0
  44. data/lib/pdf_tempura/render/field_data_mapper.rb +15 -0
  45. data/lib/pdf_tempura/render/field_set.rb +31 -0
  46. data/lib/pdf_tempura/render/option_access.rb +21 -0
  47. data/lib/pdf_tempura/render/page.rb +23 -0
  48. data/lib/pdf_tempura/render/table.rb +35 -0
  49. data/lib/pdf_tempura/render/text_field.rb +13 -0
  50. data/lib/pdf_tempura/renderer.rb +39 -0
  51. data/lib/pdf_tempura/version.rb +3 -0
  52. data/pdf_tempura.gemspec +27 -0
  53. data/spec/assets/sample_pdf_form.odg +0 -0
  54. data/spec/assets/sample_pdf_form.pdf +0 -0
  55. data/spec/integration_spec.rb +88 -0
  56. data/spec/lib/pdf_tempura/document/boxed_characters_spec.rb +125 -0
  57. data/spec/lib/pdf_tempura/document/checkbox_field_spec.rb +54 -0
  58. data/spec/lib/pdf_tempura/document/field_common.rb +12 -0
  59. data/spec/lib/pdf_tempura/document/field_set_spec.rb +38 -0
  60. data/spec/lib/pdf_tempura/document/page_spec.rb +57 -0
  61. data/spec/lib/pdf_tempura/document/table_spec.rb +161 -0
  62. data/spec/lib/pdf_tempura/document/text_field_spec.rb +195 -0
  63. data/spec/lib/pdf_tempura/document_spec.rb +131 -0
  64. data/spec/lib/pdf_tempura/extensions/hash/stringify_keys_spec.rb +42 -0
  65. data/spec/lib/pdf_tempura/render/boxed_characters_spec.rb +68 -0
  66. data/spec/lib/pdf_tempura/render/checkbox_field_spec.rb +39 -0
  67. data/spec/lib/pdf_tempura/render/debug/annotation_renderer/base_spec.rb +45 -0
  68. data/spec/lib/pdf_tempura/render/debug/checkbox_field_annotation_spec.rb +45 -0
  69. data/spec/lib/pdf_tempura/render/debug/grid_spec.rb +15 -0
  70. data/spec/lib/pdf_tempura/render/debug/text_field_annotation_spec.rb +46 -0
  71. data/spec/lib/pdf_tempura/render/field_data_mapper_spec.rb +31 -0
  72. data/spec/lib/pdf_tempura/render/field_set_spec.rb +41 -0
  73. data/spec/lib/pdf_tempura/render/field_spec.rb +37 -0
  74. data/spec/lib/pdf_tempura/render/page_spec.rb +77 -0
  75. data/spec/lib/pdf_tempura/render/table_spec.rb +44 -0
  76. data/spec/lib/pdf_tempura/render/text_field_spec.rb +39 -0
  77. data/spec/lib/pdf_tempura/renderer_spec.rb +79 -0
  78. data/spec/spec_helper.rb +29 -0
  79. data/spec/support/shared_examples/field_examples.rb +265 -0
  80. metadata +219 -0
@@ -0,0 +1,31 @@
1
+ module PdfTempura
2
+ module Render
3
+ class FieldSet
4
+ include OptionAccess
5
+
6
+ def initialize(set, data, options = {})
7
+ @set = set
8
+ @data = data
9
+ @options = options
10
+ end
11
+
12
+ def render(pdf)
13
+ render_debug_annotation(pdf) if draw_outlines?
14
+
15
+ pairs = Render::FieldDataMapper.map(@set.fields, @data)
16
+
17
+ pairs.each do |(field, value)|
18
+ Render::Field.generate(field, value, @options).render(pdf)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def render_debug_annotation(pdf)
25
+ Debug::FieldSetAnnotation.new(@set).render(pdf)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,21 @@
1
+ module PdfTempura
2
+ module Render
3
+ module OptionAccess
4
+
5
+ private
6
+
7
+ def debug_options
8
+ @options[:debug].kind_of?(Array) ? @options[:debug] : []
9
+ end
10
+
11
+ def draw_grid?
12
+ debug_options.include?(:grid)
13
+ end
14
+
15
+ def draw_outlines?
16
+ debug_options.include?(:outlines)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module PdfTempura
2
+ module Render
3
+ class Page
4
+ include OptionAccess
5
+
6
+ def initialize(page, options = {})
7
+ @page = page
8
+ @options = options
9
+ end
10
+
11
+ def render(pdf)
12
+ Render::Debug::Grid.new.render(pdf) if draw_grid?
13
+
14
+ pairs = Render::FieldDataMapper.map(@page.fields, @page.data)
15
+
16
+ pairs.each do |(field, value)|
17
+ Render::Field.generate(field, value, @options).render(pdf)
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module PdfTempura
2
+ module Render
3
+ class Table
4
+ include OptionAccess
5
+ include FieldBounds
6
+
7
+ def initialize(table, values, options = {})
8
+ @table = table
9
+ @options = options
10
+ @values = values || []
11
+
12
+ unless @values.respond_to?(:each)
13
+ raise ArgumentError.new("Expected value passed to table to be an array but it isn't.")
14
+ end
15
+ end
16
+
17
+ def render(pdf)
18
+ render_debug_annotation(pdf) if draw_outlines?
19
+
20
+ @table.fields_for(@values) do |field,value|
21
+ Render::Field.generate(field, value, @options).render(pdf)
22
+ end
23
+
24
+ end
25
+
26
+ private
27
+
28
+ def render_debug_annotation(pdf)
29
+ Debug::TableAnnotation.new(@table).render(pdf)
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,13 @@
1
+ module PdfTempura
2
+ module Render
3
+ class TextField < CharacterField
4
+
5
+ private
6
+
7
+ def render_debug_annotation(pdf)
8
+ Debug::TextFieldAnnotation.new(@field).render(pdf)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'tempfile'
2
+ require 'prawn'
3
+
4
+ module PdfTempura
5
+ class Renderer
6
+
7
+ def initialize(template_path, pages, options = {})
8
+ @template_path = template_path
9
+ @pages = pages
10
+ @options = options
11
+ @template_page_count = options[:template_page_count] || @pages.count
12
+ end
13
+
14
+ def render_into(pdf)
15
+ @pages.to_enum.with_index(0).each do |page, index|
16
+ pdf.start_new_page template: @template_path, template_page: ((index % @template_page_count) + 1)
17
+ Render::Page.new(page,@options).render(pdf)
18
+ end
19
+ end
20
+
21
+ def render
22
+ tempfile = Tempfile.new(["render",".pdf"],:encoding => 'ascii-8bit')
23
+
24
+ begin
25
+ pdf = Prawn::Document.new(skip_page_creation: true, margin: 0)
26
+
27
+ render_into(pdf)
28
+
29
+ tempfile.write pdf.render
30
+ tempfile.rewind
31
+
32
+ yield tempfile
33
+ ensure
34
+ tempfile.unlink
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module PdfTempura
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pdf_tempura/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pdf_tempura"
8
+ spec.version = PdfTempura::VERSION
9
+ spec.authors = ["Dane Natoli"]
10
+ spec.email = ["dnatoli@payrollhero.com"]
11
+ spec.description = %q{A gem for overlaying text and other fields onto PDF templates using Prawn.}
12
+ spec.summary = %q{A gem for overlaying text and other fields onto PDF templates using Prawn.}
13
+ spec.homepage = "https://github.com/payrollhero/pdf_tempura"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "prawn", "~> 0.12.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "debugger"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ require_relative '../example/my_pdf'
3
+
4
+ describe PdfTempura do
5
+
6
+ describe "integration" do
7
+
8
+ let(:data) do
9
+ {
10
+ 1 => {
11
+ "name" => (["John Doe"] * 10).join(' '),
12
+ "email" => "john@doe.com",
13
+ "accept" => true,
14
+ "pin" => "2233322",
15
+ "reason" => "
16
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ultrices tortor leo. Nunc convallis erat vitae lorem elementum, vel blandit odio scelerisque. Maecenas a cursus nunc. Duis lectus velit, porta eget rhoncus ut, posuere eu nisl. Maecenas imperdiet eget sem sit amet pellentesque. Mauris eu sodales est. Mauris quis quam eu nisl luctus scelerisque sit amet faucibus urna. Morbi sagittis, ligula quis ullamcorper tempus, ante mi gravida urna, ac facilisis tortor nisl ac turpis.
17
+
18
+ Nam adipiscing urna mauris, sed mollis enim malesuada non. Praesent non tellus blandit, rhoncus elit non, semper sem. Nam mollis tristique velit vehicula dignissim. Donec adipiscing, odio a ornare ultricies, tortor dolor condimentum sem, sed consectetur mi lorem in nunc. Aliquam at accumsan urna, ut dapibus dolor. In bibendum vitae velit quis tristique. Duis cursus pulvinar arcu, in mollis augue eleifend eget. Nam et est non ipsum congue placerat euismod eu justo. Aenean facilisis eu mi sed condimentum. Maecenas posuere lorem lectus, eu condimentum justo mattis ut. In fringilla feugiat tortor nec elementum.
19
+ ",
20
+ },
21
+ 2 => {
22
+ "form" => {
23
+ "id" => "123",
24
+ },
25
+ "table" => [
26
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
27
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
28
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
29
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
30
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
31
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
32
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
33
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
34
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
35
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
36
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
37
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
38
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
39
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
40
+ ]
41
+ },
42
+ 3 => {
43
+ "name" => (["John Doe"] * 10).join(' '),
44
+ "email" => "john@doe.com",
45
+ "accept" => true,
46
+ "pin" => "2233322",
47
+ "reason" => "
48
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ultrices tortor leo. Nunc convallis erat vitae lorem elementum, vel blandit odio scelerisque. Maecenas a cursus nunc. Duis lectus velit, porta eget rhoncus ut, posuere eu nisl. Maecenas imperdiet eget sem sit amet pellentesque. Mauris eu sodales est. Mauris quis quam eu nisl luctus scelerisque sit amet faucibus urna. Morbi sagittis, ligula quis ullamcorper tempus, ante mi gravida urna, ac facilisis tortor nisl ac turpis.
49
+
50
+ Nam adipiscing urna mauris, sed mollis enim malesuada non. Praesent non tellus blandit, rhoncus elit non, semper sem. Nam mollis tristique velit vehicula dignissim. Donec adipiscing, odio a ornare ultricies, tortor dolor condimentum sem, sed consectetur mi lorem in nunc. Aliquam at accumsan urna, ut dapibus dolor. In bibendum vitae velit quis tristique. Duis cursus pulvinar arcu, in mollis augue eleifend eget. Nam et est non ipsum congue placerat euismod eu justo. Aenean facilisis eu mi sed condimentum. Maecenas posuere lorem lectus, eu condimentum justo mattis ut. In fringilla feugiat tortor nec elementum.
51
+ ",
52
+ },
53
+ 4 => {
54
+ "form" => {
55
+ "id" => "123",
56
+ },
57
+ "table" => [
58
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
59
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
60
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
61
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
62
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
63
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
64
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
65
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
66
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
67
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
68
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
69
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
70
+ {"id" => "1","name" => "one", "email" => "one@theone.com"},
71
+ {"id" => "2","name" => "two", "email" => "two@theone.com"},
72
+ ]
73
+ }
74
+ }
75
+ end
76
+
77
+ example do
78
+ FileUtils.mkdir_p("tmp")
79
+ MyPdf.new(data).render do |file|
80
+ File.open("tmp/integration.pdf", "w") do |fh|
81
+ fh.write(file.read)
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ describe PdfTempura::Document::BoxedCharacters do
4
+
5
+ context "valid options" do
6
+ let(:options) {{box_width: 10, box_spacing: 0}}
7
+ let(:height){ 100 }
8
+ subject{ described_class.new(name, coordinates, height,options) { characters 20} }
9
+ it_behaves_like "a document field"
10
+ end
11
+
12
+ let(:options) {{box_width: 10, box_spacing: 1}}
13
+ let(:name){ :text_field }
14
+ let(:coordinates){ [0, 0] }
15
+ let(:height){ 10 }
16
+
17
+ describe ".characters" do
18
+ let(:subject) {described_class.new(name, coordinates, height, options)}
19
+
20
+ context "when passed valid number of characters" do
21
+ it "adds supported characters" do
22
+ subject.characters 5
23
+ subject.supported_characters.should == 5
24
+ subject.characters 2
25
+ subject.supported_characters.should == 7
26
+ end
27
+ end
28
+
29
+ context "when passed invalid number of characters" do
30
+ it "fails to add characters" do
31
+ expect{
32
+ subject.characters "a"
33
+ }.to raise_error ArgumentError, "Characters must be of type Numeric."
34
+ end
35
+ end
36
+ end
37
+
38
+ describe ".space" do
39
+ let(:subject) {described_class.new(name, coordinates, height, options)}
40
+
41
+ context "when passed a valid length" do
42
+ it "adds space groups" do
43
+ subject.space 2
44
+ subject.groups.count.should == 1
45
+ subject.supported_characters.should == 0
46
+ subject.space 3
47
+ subject.groups.count.should == 2
48
+ subject.supported_characters.should == 0
49
+ end
50
+ end
51
+
52
+ context "when passed an invalid length" do
53
+ it "fails with an error" do
54
+ expect{
55
+ subject.space "a"
56
+ }.to raise_error ArgumentError, "Spacing must be of type Numeric."
57
+ end
58
+ end
59
+ end
60
+
61
+ describe ".fields" do
62
+ let(:options) {{box_width: 10, box_spacing: 1}}
63
+ let(:subject) do
64
+ described_class.new(name,coordinates,height,options) do
65
+ characters 2
66
+ space 2
67
+ characters 2
68
+ end
69
+ end
70
+
71
+ it "returns correct number of fields at correct coordinates" do
72
+ PdfTempura::Document::CharacterField.tap do |it|
73
+ it.should_receive(:new).with(name.to_s, [0,0],[10,10], {}).and_call_original
74
+ it.should_receive(:new).with(name.to_s, [11,0],[10,10], {}).and_call_original
75
+ it.should_receive(:new).with(name.to_s, [23,0],[10,10], {}).and_call_original
76
+ it.should_receive(:new).with(name.to_s, [34,0],[10,10], {}).and_call_original
77
+ end
78
+ subject.fields.count.should == 4
79
+ end
80
+ end
81
+
82
+ describe "validation" do
83
+ context "when passed valid attributes" do
84
+ it "returns a field object" do
85
+ described_class.new(name, coordinates, height, options).should be_a(described_class)
86
+ end
87
+ it "should yield" do
88
+ expect { |b| described_class.new(name, coordinates, height, options,&b)}.to yield_control
89
+ end
90
+ end
91
+
92
+ context "when passed invalid options" do
93
+ context "without required options" do
94
+ let(:options){ {} }
95
+
96
+ it "throws an error" do
97
+ expect{
98
+ described_class.new(name, coordinates, height, options)
99
+ }.to raise_error ArgumentError, "Box_width is required."
100
+ end
101
+ end
102
+
103
+ context "with box spacing and width not numeric" do
104
+ let(:options){ {box_width: "foo",box_spacing: "bar"} }
105
+
106
+ it "throws an error" do
107
+ expect{
108
+ described_class.new(name, coordinates, height, options)
109
+ }.to raise_error ArgumentError, "Box_width must be of type Numeric."
110
+ end
111
+ end
112
+
113
+ context "with bad truncate option" do
114
+ let(:options){ {box_width: 1,box_spacing: 2, truncate: "foo"} }
115
+
116
+ it "throws an error" do
117
+ expect{
118
+ described_class.new(name, coordinates, height, options)
119
+ }.to raise_error ArgumentError, "Truncate must be of type boolean."
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe PdfTempura::Document::CheckboxField do
4
+
5
+ it_behaves_like "a document field"
6
+
7
+ let(:name){ :checkbox_field }
8
+ let(:coordinates){ [10, 20] }
9
+ let(:dimensions){ [10, 10] }
10
+ let(:default_value){ true }
11
+ let(:padding){ [1,1,1,1]}
12
+ let(:options){ { default_value: default_value, padding: padding} }
13
+
14
+ subject{ described_class.new(name, coordinates, dimensions, options) }
15
+
16
+ its(:default_value){ should be_true }
17
+
18
+ describe "defaults" do
19
+ subject{ described_class.new(name, coordinates, dimensions) }
20
+
21
+ its(:default_value){ should be_false }
22
+ end
23
+
24
+ describe "validation" do
25
+ context "when passed valid attributes" do
26
+ it "returns a field object" do
27
+ described_class.new(name, coordinates, dimensions, options).should be_a(described_class)
28
+ end
29
+ end
30
+
31
+ context "when passed invalid options" do
32
+ context "default_value" do
33
+ let(:default_value){ 3 }
34
+
35
+ it "throws an error" do
36
+ expect{
37
+ described_class.new(name, coordinates, dimensions, options)
38
+ }.to raise_error ArgumentError, "Default_value must be one of the following values: true, false."
39
+ end
40
+ end
41
+
42
+ context "padding" do
43
+ let(:padding){[3,2,1]}
44
+
45
+ it "throws an error" do
46
+ expect{
47
+ described_class.new(name, coordinates, dimensions, options)
48
+ }.to raise_error ArgumentError, "Padding must contain 4 values."
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ end