proforma-prawn-renderer 1.0.0.pre.alpha

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.
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require_relative 'renderer'
11
+
12
+ module Proforma
13
+ class PrawnRenderer
14
+ # This class understands how to ender a Proforma::Modeling::Pane component.
15
+ class PaneRenderer < Renderer
16
+ def render(pane)
17
+ pdf.table(
18
+ make_rows(pane.columns),
19
+ column_widths: make_column_widths(pane.columns),
20
+ width: total_width
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def make_rows_shell(columns)
27
+ total_columns = columns.length * 2
28
+ total_rows = columns.map(&:line_count).max
29
+
30
+ (0...total_rows).map { Array.new(total_columns) }
31
+ end
32
+
33
+ def value_cell_padding(column_index, total_columns)
34
+ if column_index < total_columns - 1
35
+ [2, 20, 2, 2]
36
+ else
37
+ [2, 0, 2, 2]
38
+ end
39
+ end
40
+
41
+ def value_cell_style(column, column_index, total_columns)
42
+ base_value_cell_style.merge(
43
+ align: column.align,
44
+ padding: value_cell_padding(column_index, total_columns)
45
+ )
46
+ end
47
+
48
+ def make_rows(columns)
49
+ rows = make_rows_shell(columns)
50
+
51
+ columns.each_with_index do |column, column_index|
52
+ value_cell_style = value_cell_style(column, column_index, columns.length)
53
+
54
+ populate_lines(column.lines, rows, column_index, value_cell_style)
55
+ end
56
+
57
+ rows
58
+ end
59
+
60
+ def populate_lines(lines, rows, column_index, value_cell_style)
61
+ label_cell_index = column_index * 2
62
+ value_cell_index = column_index * 2 + 1
63
+
64
+ lines.each_with_index do |line, line_index|
65
+ label = line.label
66
+ value = line.value
67
+ rows[line_index][label_cell_index] = pdf.make_cell(label, base_label_cell_style)
68
+ rows[line_index][value_cell_index] = pdf.make_cell(value, value_cell_style)
69
+ end
70
+ end
71
+
72
+ def make_column_widths(columns)
73
+ column_widths = {}
74
+
75
+ columns.each_with_index do |column, index|
76
+ label_width = column.label_width
77
+ next unless label_width
78
+
79
+ column_widths[index * 2] = calculate_width(label_width)
80
+ end
81
+
82
+ column_widths
83
+ end
84
+
85
+ def base_value_cell_style
86
+ @base_value_cell_style ||= {
87
+ border_width: 0,
88
+ font: options.font_name,
89
+ padding: [2, 0, 2, 2],
90
+ size: options.text_font_size
91
+ }
92
+ end
93
+
94
+ def base_label_cell_style
95
+ @base_label_cell_style ||= base_value_cell_style.merge(
96
+ padding: [2, 2, 2, 0],
97
+ font_style: options.bold_font_style
98
+ )
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Proforma
11
+ class PrawnRenderer
12
+ # The base class that contains common functionality across all sub-rendering components.
13
+ class Renderer
14
+ def initialize(pdf, options)
15
+ @options = options
16
+ @pdf = pdf
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :options, :pdf
22
+
23
+ def total_width
24
+ pdf.bounds.width
25
+ end
26
+
27
+ def calculate_width(percentage)
28
+ return 0 unless percentage
29
+
30
+ total_width * (percentage.to_f / 100)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require_relative 'renderer'
11
+
12
+ module Proforma
13
+ class PrawnRenderer
14
+ # This class understands how to ender a Proforma::Modeling::Separator component.
15
+ class SeparatorRenderer < Renderer
16
+ WIDTH = 0.5
17
+
18
+ def render(_separator)
19
+ pdf.line_width(WIDTH)
20
+ pdf.stroke_horizontal_rule
21
+ pdf.move_down(5)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require_relative 'renderer'
11
+
12
+ module Proforma
13
+ class PrawnRenderer
14
+ # This class understands how to ender a Proforma::Modeling::Separator component.
15
+ class SpacerRenderer < Renderer
16
+ AMOUNT = 15
17
+
18
+ def render(_separator)
19
+ pdf.move_down(AMOUNT)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require_relative 'renderer'
11
+
12
+ module Proforma
13
+ class PrawnRenderer
14
+ # This class understands how to ender a Proforma::Modeling::Table component.
15
+ class TableRenderer < Renderer
16
+ def render(table)
17
+ row_for_widths = table.header.rows.first
18
+ column_widths = row_for_widths ? make_column_widths(row_for_widths) : {}
19
+
20
+ pdf.table(
21
+ make_all_rows(table),
22
+ column_widths: column_widths,
23
+ width: total_width
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def make_all_rows(table)
30
+ make_rows(table.header, header_cell_style) +
31
+ make_rows(table.body, body_cell_style) +
32
+ make_rows(table.footer, footer_cell_style)
33
+ end
34
+
35
+ def make_rows(section, cell_style)
36
+ section.rows.map do |row|
37
+ row.cells.map do |cell|
38
+ pdf.make_cell(cell.text.to_s, cell_style)
39
+ end
40
+ end
41
+ end
42
+
43
+ def make_column_widths(row)
44
+ column_widths = {}
45
+
46
+ row.cells.each_with_index do |cell, index|
47
+ next unless cell.width
48
+
49
+ column_widths[index] = calculate_width(cell.width)
50
+ end
51
+
52
+ column_widths
53
+ end
54
+
55
+ def cell_style
56
+ @cell_style ||= {
57
+ border_width: 0.5,
58
+ font: options.font_name,
59
+ padding: 3,
60
+ size: options.text_font_size
61
+ }
62
+ end
63
+
64
+ def body_cell_style
65
+ @body_cell_style ||= cell_style.merge(
66
+ borders: %i[top bottom]
67
+ )
68
+ end
69
+
70
+ def header_cell_style
71
+ @header_cell_style ||= cell_style.merge(
72
+ background_color: 'D3D3D3',
73
+ borders: [],
74
+ font_style: options.bold_font_style
75
+ )
76
+ end
77
+
78
+ def footer_cell_style
79
+ @footer_cell_style ||= cell_style.merge(
80
+ background_color: 'F4F4F4',
81
+ borders: [],
82
+ font_style: options.bold_font_style
83
+ )
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require_relative 'renderer'
11
+
12
+ module Proforma
13
+ class PrawnRenderer
14
+ # This class understands how to ender a Proforma::Modeling::Text component.
15
+ class TextRenderer < Renderer
16
+ def render(text)
17
+ pdf.text(
18
+ text.value.to_s,
19
+ font: options.font_name,
20
+ size: options.text_font_size
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Proforma
11
+ class PrawnRenderer
12
+ VERSION = '1.0.0-alpha'
13
+ end
14
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'prawn'
11
+ require 'prawn/table'
12
+ require 'proforma'
13
+
14
+ require_relative 'prawn_renderer/renderer'
15
+ require_relative 'prawn_renderer/banner_renderer'
16
+ require_relative 'prawn_renderer/header_renderer'
17
+ require_relative 'prawn_renderer/options'
18
+ require_relative 'prawn_renderer/pane_renderer'
19
+ require_relative 'prawn_renderer/separator_renderer'
20
+ require_relative 'prawn_renderer/spacer_renderer'
21
+ require_relative 'prawn_renderer/table_renderer'
22
+ require_relative 'prawn_renderer/text_renderer'
23
+
24
+ module Proforma
25
+ # This main class to use as a Proforma renderer.
26
+ class PrawnRenderer
27
+ EXTENSION = '.pdf'
28
+
29
+ RENDERERS = {
30
+ Modeling::Banner => BannerRenderer,
31
+ Modeling::Header => HeaderRenderer,
32
+ Modeling::Pane => PaneRenderer,
33
+ Modeling::Separator => SeparatorRenderer,
34
+ Modeling::Spacer => SpacerRenderer,
35
+ Modeling::Table => TableRenderer,
36
+ Modeling::Text => TextRenderer
37
+ }.freeze
38
+
39
+ private_constant :RENDERERS
40
+
41
+ attr_reader :options
42
+
43
+ def initialize(options = Options.new)
44
+ @options = options || Options.new
45
+
46
+ clear
47
+ end
48
+
49
+ def render(prototype)
50
+ clear
51
+
52
+ render_children(prototype.children)
53
+
54
+ Proforma::Document.new(
55
+ contents: pdf.render,
56
+ extension: EXTENSION,
57
+ title: prototype.title
58
+ )
59
+ end
60
+
61
+ private
62
+
63
+ attr_reader :pdf, :renderers
64
+
65
+ def clear
66
+ @pdf = Prawn::Document.new
67
+
68
+ @renderers = RENDERERS.each_with_object({}) do |(model_class, renderer_class), hash|
69
+ hash[model_class] = renderer_class.new(pdf, options)
70
+ end
71
+ end
72
+
73
+ def render_children(children)
74
+ children.each do |child|
75
+ raise ArgumentError, "Cannot render: #{child.class.name}" unless renderable?(child)
76
+
77
+ renderer(child).render(child)
78
+ end
79
+ end
80
+
81
+ def renderable?(child)
82
+ renderers.key?(child.class)
83
+ end
84
+
85
+ def renderer(child)
86
+ renderers[child.class]
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/proforma/prawn_renderer/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'proforma-prawn-renderer'
7
+ s.version = Proforma::PrawnRenderer::VERSION
8
+ s.summary = 'Proforma renderer plugin for generating PDFs using Prawn'
9
+
10
+ s.description = <<-DESCRIPTION
11
+
12
+ DESCRIPTION
13
+
14
+ s.authors = ['Matthew Ruggio']
15
+ s.email = ['mruggio@bluemarblepayroll.com']
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.homepage = 'https://github.com/bluemarblepayroll/proforma-prawn-renderer'
20
+ s.license = 'MIT'
21
+
22
+ s.required_ruby_version = '>= 2.3.8'
23
+
24
+ s.add_dependency('prawn', '~>2')
25
+ s.add_dependency('prawn-table', '~>0')
26
+
27
+ s.add_development_dependency('guard-rspec', '~>4.7')
28
+ s.add_development_dependency('pdf-inspector', '~>1')
29
+ s.add_development_dependency('proforma', '>=1.0.0-alpha')
30
+ s.add_development_dependency('pry', '~>0')
31
+ s.add_development_dependency('rspec', '~> 3.8')
32
+ s.add_development_dependency('rubocop', '~>0.63.1')
33
+ s.add_development_dependency('simplecov', '~>0.16.1')
34
+ s.add_development_dependency('simplecov-console', '~>0.4.2')
35
+ end
@@ -0,0 +1,69 @@
1
+ data:
2
+ - id: 1
3
+ first: Will
4
+ last: Smith
5
+ username: will_smith
6
+ login_count: 9219
7
+ phone_numbers:
8
+ - type: cell
9
+ number: 555-444-3333
10
+ - type: fax
11
+ number: 333-444-2222
12
+ - id: 2
13
+ first: Martin
14
+ last: Lawrence
15
+ username: mlaw10
16
+ login_count: 8077
17
+ phone_numbers:
18
+ - type: home
19
+ number: 999-888-7777
20
+ - id: 3
21
+ first: Will
22
+ last: Ferrell
23
+ username: will.ferrell
24
+ login_count: 190890
25
+ phone_numbers:
26
+
27
+ template:
28
+ split: true
29
+ title: User Details
30
+ children:
31
+ - type: Header
32
+ value: User Details
33
+ - type: Separator
34
+ - type: Text
35
+ value: 'The following is user details'
36
+ - type: Pane
37
+ columns:
38
+ - label_width: 20.0
39
+ lines:
40
+ - label: ID Number
41
+ value: $:id
42
+ - label_width: 20.0
43
+ lines:
44
+ - label: First Name
45
+ value: $:first
46
+ - label_width: 20.0
47
+ lines:
48
+ - label: Last Name
49
+ value: $:last
50
+ - type: Spacer
51
+ - type: Header
52
+ value: Phone Numbers
53
+ - type: DataTable
54
+ property: phone_numbers
55
+ empty_message: No phone numbers.
56
+ columns:
57
+ - header: Type
58
+ body: $:type
59
+ - header: Number
60
+ body: $:number
61
+ - type: Spacer
62
+
63
+ strings:
64
+ - User Details The following is user details ID Number 1 First Name Will
65
+ Last Name Smith Phone Numbers Type Number cell 555-444-3333 fax 333-444-2222
66
+ - User Details The following is user details ID Number 2 First Name
67
+ Martin Last Name Lawrence Phone Numbers Type Number home 999-888-7777
68
+ - User Details The following is user details ID Number 3
69
+ First Name Will Last Name Ferrell Phone Numbers No phone numbers.
@@ -0,0 +1,124 @@
1
+ data:
2
+ users:
3
+ - id: 1
4
+ first: Will
5
+ last: Smith
6
+ username: will_smith
7
+ login_count: 9219
8
+ phone_numbers:
9
+ - type: cell
10
+ number: 555-444-3333
11
+ - type: fax
12
+ number: 333-444-2222
13
+ - id: 2
14
+ first: Martin
15
+ last: Lawrence
16
+ username: mlaw10
17
+ login_count: 8077
18
+ phone_numbers:
19
+ - type: home
20
+ number: 999-888-7777
21
+ - id: 3
22
+ first: Will
23
+ last: Ferrell
24
+ username: will.ferrell
25
+ login_count: 190890
26
+ phone_numbers:
27
+
28
+ renderer: !ruby/object:Proforma::PrawnRenderer
29
+ options: !ruby/object:Proforma::PrawnRenderer::Options
30
+ bold_font_style: :bold
31
+ font_name: Helvetica
32
+ header_font_size: 12
33
+ text_font_size: 9
34
+
35
+ template: !ruby/object:Proforma::Template
36
+ output: :collection
37
+ title: User List
38
+ children:
39
+ - type: Banner
40
+ details: "555 N. Michigan Ave.\nChicago, IL 62626\n555-555-5555 ext. 55555"
41
+ image: logo
42
+ image_width: 60
43
+ image_height: 48
44
+ title: Some Random System
45
+ - type: Spacer
46
+ - type: Header
47
+ value: User List
48
+ - type: Separator
49
+ - type: Text
50
+ value: The following is a list of users
51
+ - type: Spacer
52
+ - type: DataTable
53
+ property: users
54
+ aggregators:
55
+ - property: login_count
56
+ function: sum
57
+ name: login_count_sum
58
+ - property: login_count
59
+ function: ave
60
+ name: login_count_ave
61
+ columns:
62
+ - header: ID Number
63
+ body: $:id
64
+ footer: Login Count Ave.
65
+ width: 20
66
+ - header: First Name
67
+ body: $:first
68
+ footer: $:login_count_ave
69
+ width: 20
70
+ - header: Last Name
71
+ body: $:last
72
+ width: 20
73
+ - header: Username
74
+ body: $:username
75
+ footer: Login Count Sum
76
+ width: 20
77
+ - header: Login Count
78
+ footer: $:login_count_sum
79
+ body: $:login_count
80
+ width: 20
81
+ - type: Grouping
82
+ property: users
83
+ children:
84
+ - type: Spacer
85
+ - type: Grouping
86
+ children:
87
+ - type: Header
88
+ value: 'User Details'
89
+ - type: Separator
90
+ - type: Pane
91
+ columns:
92
+ - label_width: 25.0
93
+ lines:
94
+ - label: ID Number
95
+ value: $:id
96
+ - label: First Name
97
+ value: $:first
98
+ - label: Last Name
99
+ value: $:last
100
+ - type: Spacer
101
+ - type: Header
102
+ value: Phone Numbers
103
+ - type: DataTable
104
+ property: phone_numbers
105
+ empty_message: No phone numbers.
106
+ columns:
107
+ - header: Type
108
+ body: $:type
109
+ - header: Number
110
+ body: $:number
111
+ - type: Spacer
112
+
113
+ strings:
114
+ - >-
115
+ Some Random System 555 N. Michigan Ave. Chicago, IL 62626 555-555-5555 ext. 55555
116
+ User List The following is a list of users ID Number First Name Last Name Username
117
+ Login Count
118
+ 1 Will Smith will_smith 9219 2 Martin Lawrence mlaw10 8077
119
+ 3 Will Ferrell will.ferrell 190890
120
+ Login Count Ave. 69395.333333333333333 333 Login Count Sum 208186.0
121
+ User Details ID Number 1 First Name Will Last Name Smith Phone Numbers Type Number
122
+ cell 555-444-3333 fax 333-444-2222 User Details ID Number 2 First Name Martin Last Name
123
+ Lawrence Phone Numbers Type Number home 999-888-7777 User Details ID Number 3 First Name
124
+ Will Last Name Ferrell Phone Numbers No phone numbers.
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe ::Proforma::PrawnRenderer do
13
+ let(:snapshot_dir) { File.join('spec', 'fixtures', 'snapshots', '*.yml') }
14
+
15
+ let(:snapshot_filenames) { Dir[snapshot_dir] }
16
+
17
+ it 'should process each snapshot successfully' do
18
+ snapshot_filenames.each do |file|
19
+ contents = yaml_read(file)
20
+
21
+ documents = Proforma.render(
22
+ contents['data'],
23
+ contents['template'],
24
+ evaluator: contents['evaluator'] || Proforma::HashEvaluator.new,
25
+ renderer: contents['renderer'] || Proforma::PrawnRenderer.new
26
+ )
27
+
28
+ documents.each_with_index do |document, index|
29
+ expected_strings = contents['strings'][index]
30
+
31
+ actual_strings = pdf_strings(document.contents)
32
+
33
+ expect(actual_strings).to eq(expected_strings)
34
+ end
35
+ end
36
+ end
37
+
38
+ specify 'Proforma Rendering Example' do
39
+ data = [
40
+ { id: 1, name: 'Chicago Bulls' },
41
+ { id: 2, name: 'Indiana Pacers' },
42
+ { id: 3, name: 'Boston Celtics' }
43
+ ]
44
+
45
+ template = {
46
+ title: 'nba_team_list',
47
+ children: [
48
+ { type: 'Header', value: 'NBA Teams' },
49
+ { type: 'Separator' },
50
+ {
51
+ type: 'DataTable',
52
+ columns: [
53
+ { header: 'Team ID #', body: '$:id' },
54
+ { header: 'Team Name', body: '$:name' }
55
+ ]
56
+ }
57
+ ]
58
+ }
59
+
60
+ actual_documents = Proforma.render(data, template, renderer: Proforma::PrawnRenderer.new)
61
+
62
+ actual_document = actual_documents.first
63
+
64
+ expected_strings = 'NBA Teams Team ID # Team Name 1'\
65
+ ' Chicago Bulls 2 Indiana Pacers 3 Boston Celtics'
66
+
67
+ expect(pdf_strings(actual_document.contents)).to eq(expected_strings)
68
+ expect(actual_document.title).to eq('nba_team_list')
69
+ expect(actual_document.extension).to eq('.pdf')
70
+ end
71
+ end