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,8 @@
1
+ module PdfTempura
2
+ end
3
+
4
+ require_relative "pdf_tempura/version"
5
+ require_relative "pdf_tempura/render"
6
+ require_relative "pdf_tempura/renderer"
7
+ require_relative 'pdf_tempura/extensions/hash/stringify_keys'
8
+ require_relative 'pdf_tempura/document'
@@ -0,0 +1,121 @@
1
+ module PdfTempura
2
+ class Document
3
+
4
+ class << self
5
+
6
+ def template_file_path
7
+ @template_file_path
8
+ end
9
+
10
+ def template(path)
11
+ raise ArgumentError, "Template path must be a string." unless path.is_a?(String)
12
+ @template_file_path = path
13
+ end
14
+
15
+ def pages
16
+ @pages ||= []
17
+ end
18
+
19
+ def repeatable
20
+ @repeatable = true
21
+ end
22
+
23
+ def repeatable_option
24
+ @repeatable || false
25
+ end
26
+
27
+ def page(page_number, &block)
28
+ page = Page.new(page_number)
29
+ pages << page
30
+ page.instance_eval(&block)
31
+ nil
32
+ end
33
+
34
+ def debug_options
35
+ @debug_options ||= []
36
+ end
37
+
38
+ def debug(*options)
39
+ debug_options.concat options
40
+ end
41
+
42
+ end
43
+
44
+ def initialize(data = {})
45
+ load_data(data)
46
+ end
47
+
48
+ def pages
49
+ @pages ||= []
50
+ end
51
+
52
+ def render(&block)
53
+ new_renderer.render(&block)
54
+ end
55
+
56
+ private
57
+
58
+ def repeatable
59
+ self.class.repeatable_option
60
+ end
61
+
62
+ def new_renderer
63
+ PdfTempura::Renderer.new(
64
+ self.class.template_file_path,
65
+ self.pages,
66
+ {
67
+ debug: self.class.debug_options,
68
+ repeatable: self.class.repeatable_option,
69
+ template_page_count: class_pages.count
70
+ }
71
+ )
72
+ end
73
+
74
+ def generate_pages_from_data(data)
75
+ data.each_with_index do |page_data,number|
76
+ page = class_pages[number % class_pages.count ]
77
+
78
+ self.pages << page.dup.tap{ |new_page|
79
+ new_page.data = page_data || {}
80
+ }
81
+ end
82
+
83
+ generate_missing_pages
84
+ end
85
+
86
+ def class_pages
87
+ self.class.pages
88
+ end
89
+
90
+ def generate_missing_pages
91
+ if pages.count % class_pages.count != 0
92
+ [(pages.count % class_pages.count ) .. class_pages.count].each do |number|
93
+ page = class_pages[number]
94
+ self.pages << page.dup
95
+ end
96
+ end
97
+ end
98
+
99
+ def load_data(data)
100
+ if !repeatable && data.keys.count > self.class.pages.count
101
+ raise ArgumentError.new("There are more pages in the data than pages defined. Use 'repeatable' to repeat template pages in the document class.")
102
+ end
103
+
104
+ keys = data.keys.select{|key| key.kind_of?(Numeric) || key.to_i.to_s == key.to_s}.sort {|key1,key2| key1.to_i <=> key2.to_i}
105
+ data_for_pages = data.values_at(*keys)
106
+ generate_pages_from_data(data_for_pages)
107
+ end
108
+
109
+ end
110
+ end
111
+
112
+ require_relative 'document/validation'
113
+ require_relative 'document/default_commands'
114
+ require_relative 'document/page'
115
+ require_relative 'document/field/base'
116
+ require_relative 'document/character_field'
117
+ require_relative 'document/text_field'
118
+ require_relative 'document/checkbox_field'
119
+ require_relative 'document/table'
120
+ require_relative 'document/boxed_characters'
121
+ require_relative 'document/field_set'
@@ -0,0 +1,70 @@
1
+ module PdfTempura
2
+ class Document::BoxedCharacters < Document::Field::Base
3
+
4
+ attr_reader :box_width, :box_spacing, :groups, :truncate, :text_options, :padding
5
+ attr_accessor :coordinates
6
+ alias :truncate? :truncate
7
+
8
+ validates :box_width, required: true, type: Numeric
9
+ validates :box_spacing, required: true, type: Numeric
10
+ validates :truncate, boolean: true
11
+
12
+ def initialize(name, coordinates, height, options = {}, &block)
13
+ @groups = []
14
+
15
+ super name, coordinates, [0, height], options
16
+
17
+ instance_eval(&block) if block_given?
18
+ end
19
+
20
+ def characters(characters)
21
+ @groups << CharacterGroup.new(characters)
22
+ end
23
+
24
+ def space(width)
25
+ @groups << SpaceGroup.new(width)
26
+ end
27
+
28
+ def supported_characters
29
+ @groups.map(&:characters).inject(:+)
30
+ end
31
+
32
+ def fields
33
+ @fields ||= generate_text_fields
34
+ end
35
+
36
+ def width
37
+ groups.inject(0){ |sum,group| sum + group.width(box_width, box_spacing) }
38
+ end
39
+
40
+ def dimensions
41
+ [width, @dimensions[1]]
42
+ end
43
+
44
+ private
45
+
46
+ def generate_text_fields
47
+ [].tap do |fields|
48
+ groups.inject(self.x) do |x, group|
49
+ group.each_supported_character do
50
+ fields << Document::CharacterField.new(name, [x,y], [box_width,height], text_options)
51
+ x+= box_width + box_spacing
52
+ end
53
+
54
+ x + group.spacing - (group.characters > 0 ? box_spacing : 0)
55
+ end
56
+ end
57
+ end
58
+
59
+ def load_options(options)
60
+ @box_width = options["box_width"]
61
+ @box_spacing = options["box_spacing"]
62
+ @truncate = options["truncate"] || false
63
+ @text_options = options.reject { |key,v| ["box_width", "box_spacing", "truncate"].include?(key) }
64
+ @padding = [0,0,0,0]
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+ require_relative 'boxed_characters/groups'
@@ -0,0 +1,53 @@
1
+ module PdfTempura
2
+ class Document::BoxedCharacters::CharacterGroup
3
+ include Document::Validation
4
+
5
+ attr_reader :characters
6
+
7
+ validates :characters, type: Numeric
8
+
9
+ def initialize(characters)
10
+ @characters = characters
11
+ validate!
12
+ end
13
+
14
+ def spacing
15
+ 0
16
+ end
17
+
18
+ def each_supported_character(&block)
19
+ characters.times{ yield }
20
+ end
21
+
22
+ def width(box_width, spacing)
23
+ box_width * characters + spacing * (characters - 1)
24
+ end
25
+
26
+ end
27
+
28
+ class Document::BoxedCharacters::SpaceGroup
29
+ include Document::Validation
30
+
31
+ attr_reader :spacing
32
+
33
+ validates :spacing, type: Numeric
34
+
35
+ def initialize(units)
36
+ @spacing = units
37
+ validate!
38
+ end
39
+
40
+ def characters
41
+ 0
42
+ end
43
+
44
+ def each_supported_character(&block)
45
+ #we support no characters, nein!
46
+ end
47
+
48
+ def width(ignored,ignored2)
49
+ spacing
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ module PdfTempura
2
+ class Document::CharacterField < Document::Field::Base
3
+
4
+ attr_reader :default_value, :font_name, :font_size, :bold, :italic, :padding,
5
+ :alignment, :multi_line, :valign, :leading
6
+
7
+ alias_method :bold?, :bold
8
+ alias_method :italic?, :italic
9
+ alias_method :multi_line?, :multi_line
10
+
11
+ validates :font_name, type: String
12
+ validates :font_size, type: Numeric
13
+ validates :bold, inclusion: [true, false]
14
+ validates :italic, inclusion: [true, false]
15
+ validates :alignment, inclusion: ["left", "right", "center"]
16
+ validates :multi_line, inclusion: [true, false]
17
+ validates :padding, type: Array, inner_type: Numeric, count: 4
18
+ validates :default_value, type: String
19
+
20
+ private
21
+
22
+ def load_options(options)
23
+ @default_value = options["default_value"] || ""
24
+ @font_name = options["font_name"] || "Helvetica"
25
+ @font_size = options["font_size"] || 10
26
+ @bold = options["bold"] || false
27
+ @italic = options["italic"] || false
28
+ @bold = options["bold"] || false
29
+ @padding = options["padding"] || [0,0,0,0]
30
+ @alignment = "center"
31
+ @multi_line = false
32
+ @valign = "center"
33
+ @leader = 0
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ module PdfTempura
2
+ class Document::CheckboxField < Document::Field::Base
3
+
4
+ attr_reader :default_value, :padding
5
+
6
+ validates :default_value, inclusion: [true, false]
7
+ validates :padding, type: Array, inner_type: Numeric, count: 4
8
+
9
+ def load_options(options)
10
+ @default_value = options["default_value"] || false
11
+ @padding = options["padding"] || [1,1,1,1]
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module PdfTempura
2
+ module Document::DefaultCommands
3
+
4
+ def self.included(base)
5
+ base.send(:attr_accessor, :fields)
6
+ end
7
+
8
+ def text_field(name, coordinates, dimensions, options = {})
9
+ fields << Document::TextField.new(name, coordinates, dimensions, @options.merge(options))
10
+ end
11
+
12
+ def checkbox_field(name, coordinates, dimensions, options = {})
13
+ fields << Document::CheckboxField.new(name, coordinates, dimensions, @options.merge(options))
14
+ end
15
+
16
+ def table(name, coordinates, options = {}, &block)
17
+ fields << Document::Table.new(name, coordinates, @options.merge(options), &block)
18
+ end
19
+
20
+ def boxed_characters(name, coordinates, height, options = {}, &block)
21
+ fields << Document::BoxedCharacters.new(name, coordinates, height, @options.merge(options), &block)
22
+ end
23
+
24
+ def field_set(name, options = {}, &block)
25
+ fields << Document::FieldSet.new(name, @options.merge(options), &block)
26
+ end
27
+
28
+ def with_default_options(options = {}, &block)
29
+ previous_options = @options
30
+
31
+ begin
32
+ @options = @options.merge(options)
33
+ instance_eval(&block)
34
+ ensure
35
+ @options = previous_options
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,55 @@
1
+ module PdfTempura
2
+ module Document::Field
3
+ class Base
4
+ include Document::Validation
5
+
6
+ def initialize(name, coordinates, dimensions, options = {})
7
+ @name = name.is_a?(Symbol) ? name.to_s : name
8
+ @coordinates = coordinates
9
+ @dimensions = dimensions
10
+
11
+ convert_options_hash(options)
12
+ load_options(options)
13
+
14
+ validate!
15
+ end
16
+
17
+ attr_reader :name, :coordinates, :dimensions
18
+
19
+ validates :name, type: String
20
+ validates :coordinates, type: Array, inner_type: Numeric, count: 2
21
+ validates :dimensions, type: Array, inner_type: Numeric, count: 2
22
+
23
+ def x
24
+ coordinates.first
25
+ end
26
+
27
+ def y
28
+ coordinates.last
29
+ end
30
+
31
+ def width
32
+ dimensions.first
33
+ end
34
+
35
+ def height
36
+ dimensions.last
37
+ end
38
+
39
+ private
40
+
41
+ def load_options(options)
42
+ raise NotImplementedError, "Implement 'load_options' in your subclass."
43
+ end
44
+
45
+ def convert_options_hash(options)
46
+ if options.is_a?(Hash)
47
+ options.extend(Extensions::Hash::StringifyKeys).stringify_keys!
48
+ else
49
+ raise ArgumentError, "Options must be a hash."
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ module PdfTempura
2
+ class Document::FieldSet
3
+ include Document::Validation
4
+ include Document::DefaultCommands
5
+
6
+ attr_reader :name
7
+
8
+ validates :name, type: String
9
+
10
+ def initialize(name, options = {}, &block)
11
+ @name = name
12
+ @fields = []
13
+ @options = options
14
+
15
+ instance_eval(&block) if block_given?
16
+ validate!
17
+ end
18
+
19
+ def coordinates
20
+ [x,y]
21
+ end
22
+
23
+ def dimensions
24
+ [width,height]
25
+ end
26
+
27
+ def x
28
+ fields.map(&:x).min || 0
29
+ end
30
+
31
+ def y
32
+ fields.map(&:y).max || 0
33
+ end
34
+
35
+ def width
36
+ return 0 if fields.empty?
37
+ fields.map{ |field| field.x + field.width }.max - x
38
+ end
39
+
40
+ def height
41
+ return 0 if fields.empty?
42
+ y - fields.map{ |field| field.y - field.height }.min
43
+ end
44
+
45
+ def padding
46
+ [0,0,0,0]
47
+ end
48
+
49
+ end
50
+ end
51
+