proforma-prawn-renderer 1.0.0.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -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