pdf_tempura 0.0.2

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