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,33 @@
1
+ module PdfTempura
2
+ class Document::Page
3
+ include Document::Validation
4
+ include Document::DefaultCommands
5
+
6
+ def initialize(page_number, options = {})
7
+ @number = page_number
8
+ @fields = []
9
+ @options = options
10
+
11
+ validate!
12
+ end
13
+
14
+ attr_reader :number
15
+
16
+ validates :number, type: Numeric
17
+
18
+ def ==(other)
19
+ self.number == other.number
20
+ end
21
+
22
+ def data
23
+ @data ||= {}
24
+ end
25
+
26
+ def data=(data)
27
+ @data = data.extend(Extensions::Hash::StringifyKeys).stringify_keys!
28
+ end
29
+
30
+ end
31
+ end
32
+
33
+ require_relative "default_commands"
@@ -0,0 +1,95 @@
1
+ module PdfTempura
2
+ class Document::Table < Document::Field::Base
3
+
4
+ attr_accessor :columns,:name,:row_height,:padding,:cell_padding,:row_count
5
+
6
+ validates :row_height, type: Numeric
7
+ validates :row_count, type: Numeric
8
+ validates :padding, type: Array, inner_type: Numeric, count: 4
9
+ validates :cell_padding, type: Numeric
10
+
11
+ def initialize(name,coordinates,options = {},&block)
12
+ super name, coordinates, [0,0], options
13
+
14
+ @columns = []
15
+
16
+ instance_eval(&block) if block_given?
17
+ end
18
+
19
+ def width
20
+ table_width + padding_width
21
+ end
22
+
23
+ def height
24
+ @height ||= row_height * row_count
25
+ end
26
+
27
+ def row_height
28
+ @row_height ||= height.to_f / row_count.to_f
29
+ end
30
+
31
+ def text_column(name, width, options = {})
32
+ columns << Document::Table::TextColumn.new(name, width, row_height, @options.merge(options))
33
+ end
34
+
35
+ def checkbox_column(name, width, options = {})
36
+ columns << Document::Table::CheckboxColumn.new(name, width, row_height, @options.merge(options))
37
+ end
38
+
39
+ def boxed_character_column(name, options = {}, &block)
40
+ columns << Document::Table::BoxedCharacterColumn.new(name, row_height, @options.merge(options), &block)
41
+ end
42
+
43
+ def space(width)
44
+ columns << Document::Table::Spacer.new(width, row_height)
45
+ end
46
+
47
+ def fields_for(values, &block)
48
+ values.inject(self.y) do |y, value_hash|
49
+ generate_columns(y, value_hash, &block)
50
+ y - row_height
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def generate_columns(y, values)
57
+ columns.inject(self.x) do |x, column|
58
+ yield column.field_at([x,y]), values[column.name] if column.generates_field?
59
+ x + column.width + cell_padding
60
+ end
61
+ end
62
+
63
+ def table_width
64
+ columns.map(&:width).inject(0, :+)
65
+ end
66
+
67
+ def padding_width
68
+ (columns.any?) ? cell_padding * (columns.count - 1) : 0
69
+ end
70
+
71
+ def validate_height
72
+ unless row_count && (@row_height || @height)
73
+ raise ArgumentError.new("You must pass number_of_rows and either height or row_height")
74
+ end
75
+ end
76
+
77
+ def load_options(options)
78
+ @height = options["height"]
79
+ @row_height = options["row_height"]
80
+ @row_count = options["number_of_rows"]
81
+ @padding = options["padding"] || [0,0,0,0]
82
+ @cell_padding = options["cell_padding"] || 0
83
+ @options = options
84
+
85
+ validate_height
86
+ end
87
+
88
+ end
89
+ end
90
+
91
+ require_relative 'table/column'
92
+ require_relative 'table/text_column'
93
+ require_relative 'table/checkbox_column'
94
+ require_relative 'table/spacer'
95
+ require_relative 'table/boxed_character_column'
@@ -0,0 +1,20 @@
1
+ module PdfTempura
2
+ class Document::Table::BoxedCharacterColumn < Document::Table::Column
3
+
4
+ def initialize(name, height, options = {}, &block)
5
+ @name = name
6
+ @height = height
7
+ @options = options
8
+ @block = block if block_given?
9
+ end
10
+
11
+ def width
12
+ field_at([0,0]).width
13
+ end
14
+
15
+ def field_at(coordinates)
16
+ PdfTempura::Document::BoxedCharacters.new(@name, coordinates, @height, @options, &@block)
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module PdfTempura
2
+ class Document::Table::CheckboxColumn < Document::Table::Column
3
+
4
+ def field_at(coordinates)
5
+ Document::CheckboxField.new(name, coordinates, [width,height], options)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module PdfTempura
2
+ class Document::Table::Column
3
+
4
+ attr_reader :name, :width, :options, :height
5
+
6
+ def initialize(name, width, height, options = {})
7
+ @name = name
8
+ @width = width
9
+ @height = height
10
+ @options = options
11
+ end
12
+
13
+ def generates_field?
14
+ true
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module PdfTempura
2
+ class Document::Table::Spacer < Document::Table::Column
3
+
4
+ def initialize(width, height)
5
+ super nil, width, height
6
+ end
7
+
8
+ def generates_field?
9
+ false
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module PdfTempura
2
+ class Document::Table::TextColumn < Document::Table::Column
3
+
4
+ def field_at(coordinates)
5
+ PdfTempura::Document::TextField.new(name, coordinates, [width, height], options)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'character_field'
2
+
3
+ module PdfTempura
4
+ class Document::TextField < Document::CharacterField
5
+
6
+ private
7
+
8
+ def load_options(options)
9
+ super
10
+
11
+ @alignment = (options["alignment"] || "left").to_s
12
+ @multi_line = options["multi_line"] || false
13
+ @valign = options["valign"] || "center"
14
+ @leading = options["leading"] || 0
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+ module PdfTempura
2
+ module Document::Validation
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def validations
11
+ @validations ||= (superclass.respond_to?(:validations) ? superclass.validations.dup : {})
12
+ end
13
+
14
+ private
15
+
16
+ def validates(name, validation_options)
17
+ if self.instance_methods.include?(name)
18
+ validations[name] = validation_options
19
+ else
20
+ raise NoMethodError, "Can't validate undefined method '#{name}'."
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ private
27
+
28
+ def validate!
29
+ self.class.validations.each do |method, validation_options|
30
+ validation_options.each do |validation_type, values|
31
+ find_validator(validation_type).new.validate(self, method, values)
32
+ end
33
+ end
34
+ end
35
+
36
+ def find_validator(validation_type)
37
+ validator_name = validation_type.to_s.split("_").push("validator").map(&:capitalize).join
38
+ Document::Validation.const_get(validator_name)
39
+ end
40
+
41
+ class RequiredValidator
42
+ def validate(object, method, not_used)
43
+ raise ArgumentError, "#{method.capitalize} is required." unless object.send(method)
44
+ end
45
+ end
46
+
47
+ class BooleanValidator
48
+ def validate(object, method, not_used)
49
+ raise ArgumentError, "#{method.capitalize} must be of type boolean." unless object.send(method).is_a?(TrueClass) || object.send(method).is_a?(FalseClass)
50
+ end
51
+ end
52
+
53
+ class InclusionValidator
54
+ def validate(object, method, values)
55
+ raise ArgumentError, "#{method.capitalize} must be one of the following values: #{values.join(", ")}." unless values.include?(object.send(method))
56
+ end
57
+ end
58
+
59
+ class TypeValidator
60
+ def validate(object, method, type)
61
+ raise ArgumentError, "#{method.capitalize} must be of type #{type.inspect}." unless object.send(method).is_a?(type)
62
+ end
63
+ end
64
+
65
+ class InnerTypeValidator
66
+ def validate(object, method, type)
67
+ raise ArgumentError, "#{method.capitalize} must contain only #{type.inspect} values." unless object.send(method).all?{ |inner| inner.is_a?(type) }
68
+ end
69
+ end
70
+
71
+ class CountValidator
72
+ def validate(object, method, count)
73
+ count_objects = object.send(method)
74
+ raise ArgumentError, "#{method.capitalize} must contain #{count} values." unless count_objects.respond_to?(:count) && count_objects.count == count
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,35 @@
1
+ module PdfTempura
2
+ module Extensions
3
+ module Hash
4
+ module StringifyKeys
5
+
6
+ def stringify_keys
7
+ dup.extend(StringifyKeys).stringify_keys!
8
+ end
9
+
10
+ def stringify_keys!
11
+ keys.each do |k|
12
+ stringify_keys_recursively!(self[k])
13
+ self[k.to_s] = self.delete(k)
14
+ end
15
+
16
+ self
17
+ end
18
+
19
+ private
20
+
21
+ def stringify_keys_recursively!(object)
22
+ object.extend(StringifyKeys) if object.is_a?(::Hash)
23
+
24
+ if object.respond_to?(:stringify_keys!)
25
+ object.stringify_keys!
26
+ else
27
+ object.each{ |o| stringify_keys_recursively!(o) } if object.is_a?(::Array)
28
+ object
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ module PdfTempura
2
+ module Render
3
+
4
+ end
5
+ end
6
+
7
+ require_relative 'render/option_access'
8
+ require_relative 'render/field_bounds'
9
+ require_relative 'render/debug'
10
+ require_relative 'render/field'
11
+ require_relative 'render/character_field'
12
+ require_relative 'render/text_field'
13
+ require_relative 'render/boxed_characters'
14
+ require_relative 'render/checkbox_field'
15
+ require_relative 'render/field_data_mapper'
16
+ require_relative 'render/page'
17
+ require_relative 'render/table'
18
+ require_relative 'render/field_set'
@@ -0,0 +1,42 @@
1
+ module PdfTempura
2
+ module Render
3
+ class BoxedCharacters
4
+ include OptionAccess
5
+ include FieldBounds
6
+
7
+ attr_accessor :field,:value
8
+
9
+ def initialize(field, value, options = {})
10
+ @field = field
11
+ @options = options
12
+ @value = value ? value.dup : ""
13
+
14
+ validate_value_length!
15
+ end
16
+
17
+ def render(pdf)
18
+ characters = value.chars.to_a
19
+
20
+ field.fields.each do |field|
21
+ break if characters.empty?
22
+ Field::generate(field, characters.shift, @options).render(pdf)
23
+ end
24
+
25
+ render_debug_annotation(pdf) if draw_outlines?
26
+ end
27
+
28
+ private
29
+
30
+ def validate_value_length!
31
+ if value && !field.truncate? && value.length > field.supported_characters
32
+ raise ArgumentError.new("Data for #{field.name} must be exactly #{field.supported_characters} characters or use the truncate option.")
33
+ end
34
+ end
35
+
36
+ def render_debug_annotation(pdf)
37
+ Debug::OutsideAnnotation.new(field).render(pdf)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ module PdfTempura
2
+ module Render
3
+ class CharacterField < Field
4
+
5
+ private
6
+
7
+ def set_styling(pdf)
8
+ pdf.fill_color = "000000"
9
+ pdf.font @field.font_name, style: font_style
10
+ end
11
+
12
+ def font_style
13
+ if @field.bold? && @field.italic?
14
+ :bold_italic
15
+ elsif @field.bold?
16
+ :bold
17
+ elsif @field.italic?
18
+ :italic
19
+ else
20
+ :normal
21
+ end
22
+ end
23
+
24
+ def render_field(pdf)
25
+ field_bounds_box(pdf) do
26
+ padding_bounds_box(pdf) do
27
+ pdf.text_box(@value.to_s, field_options)
28
+ end
29
+ end
30
+ end
31
+
32
+ def field_options
33
+ {
34
+ valign: @field.valign.to_sym,
35
+ align: @field.alignment.to_sym,
36
+ single_line: !@field.multi_line?,
37
+ overflow: :shrink_to_fit,
38
+ size: @field.font_size,
39
+ leading: @field.leading
40
+ }
41
+ end
42
+
43
+ def render_debug_annotation(pdf)
44
+ Debug::CharacterFieldAnnotation.new(@field).render(pdf)
45
+ end
46
+
47
+ end
48
+ end
49
+ end