pdf 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/.claude/execution/00-overview.md +121 -0
- data/.claude/execution/01-core.md +324 -0
- data/.claude/execution/02-renderer.md +237 -0
- data/.claude/execution/03-components.md +551 -0
- data/.claude/execution/04-builders.md +322 -0
- data/.claude/execution/05-view-layout.md +362 -0
- data/.claude/execution/06-evaluators.md +494 -0
- data/.claude/execution/07-entry-extensibility.md +435 -0
- data/.claude/execution/08-integration-tests.md +978 -0
- data/Rakefile +7 -3
- data/examples/01_basic_invoice.rb +139 -0
- data/examples/02_report_with_layout.rb +266 -0
- data/examples/03_inherited_views.rb +318 -0
- data/examples/04_conditional_content.rb +421 -0
- data/examples/05_custom_components.rb +442 -0
- data/examples/README.md +123 -0
- data/lib/pdf/blueprint.rb +50 -0
- data/lib/pdf/builders/content_builder.rb +96 -0
- data/lib/pdf/builders/footer_builder.rb +24 -0
- data/lib/pdf/builders/header_builder.rb +31 -0
- data/lib/pdf/component.rb +43 -0
- data/lib/pdf/components/alert.rb +42 -0
- data/lib/pdf/components/context.rb +16 -0
- data/lib/pdf/components/date.rb +28 -0
- data/lib/pdf/components/heading.rb +12 -0
- data/lib/pdf/components/hr.rb +12 -0
- data/lib/pdf/components/logo.rb +15 -0
- data/lib/pdf/components/paragraph.rb +12 -0
- data/lib/pdf/components/qr_code.rb +38 -0
- data/lib/pdf/components/spacer.rb +11 -0
- data/lib/pdf/components/span.rb +12 -0
- data/lib/pdf/components/subtitle.rb +12 -0
- data/lib/pdf/components/table.rb +48 -0
- data/lib/pdf/components/title.rb +12 -0
- data/lib/pdf/content_evaluator.rb +218 -0
- data/lib/pdf/dynamic_components.rb +17 -0
- data/lib/pdf/footer_evaluator.rb +66 -0
- data/lib/pdf/header_evaluator.rb +56 -0
- data/lib/pdf/layout.rb +61 -0
- data/lib/pdf/renderer.rb +153 -0
- data/lib/pdf/resolver.rb +36 -0
- data/lib/pdf/version.rb +1 -1
- data/lib/pdf/view.rb +113 -0
- data/lib/pdf.rb +74 -1
- metadata +127 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdf
|
|
4
|
+
class HeaderEvaluator
|
|
5
|
+
def initialize(context, renderer)
|
|
6
|
+
@context = context
|
|
7
|
+
@renderer = renderer
|
|
8
|
+
@resolver = Resolver.new(context)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def evaluate(blueprint)
|
|
12
|
+
blueprint.elements.each { |el| evaluate_element(el) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def evaluate_element(element)
|
|
18
|
+
type = element[:type]
|
|
19
|
+
args = element[:args]
|
|
20
|
+
options = element[:options]
|
|
21
|
+
|
|
22
|
+
case type
|
|
23
|
+
when :logo
|
|
24
|
+
component(:logo).render(args.first, **options)
|
|
25
|
+
|
|
26
|
+
when :qr_code
|
|
27
|
+
# QR code generates from data, not file path
|
|
28
|
+
data = @resolver.resolve(options[:data])
|
|
29
|
+
component(:qr_code).render(data: data, **options.except(:data))
|
|
30
|
+
|
|
31
|
+
when :context
|
|
32
|
+
lines = @resolver.resolve(args.first)
|
|
33
|
+
component(:context).render(lines, **options)
|
|
34
|
+
|
|
35
|
+
when :header_text
|
|
36
|
+
content = @resolver.resolve(args.first)
|
|
37
|
+
component(:paragraph).render(content, **options)
|
|
38
|
+
|
|
39
|
+
else
|
|
40
|
+
render_custom_component(type, args, options)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_custom_component(type, args, options)
|
|
45
|
+
return unless Pdf.component_registered?(type)
|
|
46
|
+
|
|
47
|
+
comp = component(type)
|
|
48
|
+
resolved_args = args.map { |a| @resolver.resolve(a) }
|
|
49
|
+
comp.render(*resolved_args, **options)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def component(type)
|
|
53
|
+
Pdf.component(type).new(@renderer.pdf)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/pdf/layout.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdf
|
|
4
|
+
class Layout
|
|
5
|
+
class << self
|
|
6
|
+
def inherited(subclass)
|
|
7
|
+
subclass.instance_variable_set(:@header_blueprint, nil)
|
|
8
|
+
subclass.instance_variable_set(:@footer_blueprint, nil)
|
|
9
|
+
subclass.instance_variable_set(:@margins_config, Pdf::DEFAULT_MARGINS.dup)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def header(&block)
|
|
13
|
+
builder = Builders::HeaderBuilder.new
|
|
14
|
+
builder.instance_eval(&block)
|
|
15
|
+
@header_blueprint = builder.blueprint
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def footer(&block)
|
|
19
|
+
builder = Builders::FooterBuilder.new
|
|
20
|
+
builder.instance_eval(&block)
|
|
21
|
+
@footer_blueprint = builder.blueprint
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def margins(**options)
|
|
25
|
+
@margins_config = Pdf::DEFAULT_MARGINS.merge(options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_reader :header_blueprint, :footer_blueprint
|
|
29
|
+
|
|
30
|
+
def margins_config
|
|
31
|
+
@margins_config || Pdf::DEFAULT_MARGINS
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :view
|
|
36
|
+
|
|
37
|
+
def initialize(view)
|
|
38
|
+
@view = view
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def render(renderer)
|
|
42
|
+
renderer.setup(margins: self.class.margins_config)
|
|
43
|
+
|
|
44
|
+
render_header(renderer) if self.class.header_blueprint
|
|
45
|
+
view.render_content(renderer)
|
|
46
|
+
setup_footer(renderer) if self.class.footer_blueprint
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def render_header(renderer)
|
|
52
|
+
evaluator = HeaderEvaluator.new(view, renderer)
|
|
53
|
+
evaluator.evaluate(self.class.header_blueprint)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def setup_footer(renderer)
|
|
57
|
+
evaluator = FooterEvaluator.new(view, renderer)
|
|
58
|
+
evaluator.setup(self.class.footer_blueprint)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/pdf/renderer.rb
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prawn"
|
|
4
|
+
require "prawn/table"
|
|
5
|
+
|
|
6
|
+
module Pdf
|
|
7
|
+
# Default margins used by both Renderer and Layout
|
|
8
|
+
DEFAULT_MARGINS = { top: 72, right: 36, bottom: 72, left: 36 }.freeze
|
|
9
|
+
|
|
10
|
+
class Renderer
|
|
11
|
+
attr_reader :pdf
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@pdf = nil
|
|
15
|
+
@footer_callback = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def setup(margins: {})
|
|
19
|
+
m = Pdf::DEFAULT_MARGINS.merge(margins)
|
|
20
|
+
@pdf = Prawn::Document.new(
|
|
21
|
+
margin: [m[:top], m[:right], m[:bottom], m[:left]]
|
|
22
|
+
)
|
|
23
|
+
setup_fonts
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def finalize
|
|
28
|
+
apply_footer if @footer_callback
|
|
29
|
+
@pdf.render
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# --- Primitives ---
|
|
33
|
+
|
|
34
|
+
def text(content, **options)
|
|
35
|
+
@pdf.text content.to_s, **options
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def text_box(content, at:, **options)
|
|
39
|
+
@pdf.text_box content.to_s, at: at, **options
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def move_down(amount)
|
|
43
|
+
@pdf.move_down amount
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def stroke_horizontal_rule
|
|
47
|
+
@pdf.stroke_horizontal_rule
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def start_new_page
|
|
51
|
+
@pdf.start_new_page
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cursor
|
|
55
|
+
@pdf.cursor
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def bounds
|
|
59
|
+
@pdf.bounds
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def bounding_box(point, **options, &block)
|
|
63
|
+
@pdf.bounding_box(point, **options, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def canvas(&block)
|
|
67
|
+
@pdf.canvas(&block)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def float(&block)
|
|
71
|
+
@pdf.float(&block)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Image embedding - raises if file not found (no silent failures)
|
|
75
|
+
def image(path, **options)
|
|
76
|
+
raise Pdf::FileNotFoundError, "Image not found: #{path}" unless File.exist?(path)
|
|
77
|
+
|
|
78
|
+
@pdf.image path, **options
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Image from string data (for generated images like QR codes)
|
|
82
|
+
def image_data(data, **options)
|
|
83
|
+
@pdf.image StringIO.new(data), **options
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fill_color(color)
|
|
87
|
+
@pdf.fill_color color
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def stroke_color(color)
|
|
91
|
+
@pdf.stroke_color color
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def line_width(width)
|
|
95
|
+
@pdf.line_width width
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def fill_and_stroke_rounded_rectangle(point, width, height, radius)
|
|
99
|
+
@pdf.fill_and_stroke_rounded_rectangle point, width, height, radius
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def draw_text(content, at:, **options)
|
|
103
|
+
@pdf.draw_text content.to_s, at: at, **options
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def width_of(content, **options)
|
|
107
|
+
@pdf.width_of content.to_s, **options
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def height_of(content, **options)
|
|
111
|
+
@pdf.height_of content.to_s, **options
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def table(data, **options, &block)
|
|
115
|
+
@pdf.table(data, **options, &block)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def page_number
|
|
119
|
+
@pdf.page_number
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def page_count
|
|
123
|
+
@pdf.page_count
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def repeat(page_filter, options = {}, &block)
|
|
127
|
+
@pdf.repeat(page_filter, options, &block)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# --- Escape hatch for direct Prawn access ---
|
|
131
|
+
|
|
132
|
+
def raw(&block)
|
|
133
|
+
yield @pdf
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# --- Footer setup (deferred) ---
|
|
137
|
+
|
|
138
|
+
def setup_footer(&block)
|
|
139
|
+
@footer_callback = block
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def setup_fonts
|
|
145
|
+
# Use Helvetica by default (built into Prawn)
|
|
146
|
+
# Users can override in their app
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def apply_footer
|
|
150
|
+
@footer_callback&.call(@pdf)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/pdf/resolver.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdf
|
|
4
|
+
class ResolutionError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Resolver
|
|
7
|
+
def initialize(context)
|
|
8
|
+
@context = context
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def resolve(value)
|
|
12
|
+
case value
|
|
13
|
+
when Symbol then resolve_symbol(value)
|
|
14
|
+
when Proc then @context.instance_exec(&value)
|
|
15
|
+
when Array then value.map { |v| resolve(v) }
|
|
16
|
+
when Hash then value.transform_values { |v| resolve(v) }
|
|
17
|
+
else value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def resolve_symbol(sym)
|
|
24
|
+
if @context.respond_to?(sym, true)
|
|
25
|
+
return @context.send(sym)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if @context.respond_to?(:data) && @context.data.is_a?(Hash)
|
|
29
|
+
return @context.data[sym] if @context.data.key?(sym)
|
|
30
|
+
return @context.data[sym.to_s] if @context.data.key?(sym.to_s)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
raise ResolutionError, "Cannot resolve :#{sym}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/pdf/version.rb
CHANGED
data/lib/pdf/view.rb
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pdf
|
|
4
|
+
class View
|
|
5
|
+
class << self
|
|
6
|
+
def inherited(subclass)
|
|
7
|
+
# CRITICAL: Inherit parent's blueprint via dup, not empty blueprint
|
|
8
|
+
# This allows child views to extend parent views
|
|
9
|
+
parent_blueprint = @blueprint || Blueprint.new
|
|
10
|
+
subclass.instance_variable_set(:@blueprint, parent_blueprint.dup)
|
|
11
|
+
subclass.instance_variable_set(:@content_builder, nil)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def blueprint
|
|
15
|
+
@blueprint ||= Blueprint.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def layout(klass)
|
|
19
|
+
blueprint.set_layout(klass)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# DSL methods - delegate to ContentBuilder
|
|
23
|
+
%i[title subtitle date heading paragraph span hr spacer alert table page_break].each do |method|
|
|
24
|
+
define_method(method) do |*args, **options, &block|
|
|
25
|
+
content_builder.send(method, *args, **options, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def section(title_text, **options, &block)
|
|
30
|
+
content_builder.section(title_text, **options, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def each(source, **options, &block)
|
|
34
|
+
content_builder.each(source, **options, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def partial(method_name)
|
|
38
|
+
content_builder.partial(method_name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def page_break_if(threshold:)
|
|
42
|
+
content_builder.page_break_if(threshold: threshold)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render_if(condition, &block)
|
|
46
|
+
content_builder.render_if(condition, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def render_unless(condition, &block)
|
|
50
|
+
content_builder.render_unless(condition, &block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def raw(&block)
|
|
54
|
+
content_builder.raw(&block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Dynamic component support via DynamicComponents pattern
|
|
58
|
+
def method_missing(name, *args, **options, &block)
|
|
59
|
+
if Pdf.component_registered?(name)
|
|
60
|
+
content_builder.send(name, *args, **options, &block)
|
|
61
|
+
else
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def respond_to_missing?(name, include_private = false)
|
|
67
|
+
Pdf.component_registered?(name) || super
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def content_builder
|
|
73
|
+
@content_builder ||= Builders::ContentBuilder.new(blueprint)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attr_reader :data
|
|
78
|
+
|
|
79
|
+
def initialize(data = {})
|
|
80
|
+
@data = data.is_a?(Hash) ? symbolize_keys(data) : data
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_pdf
|
|
84
|
+
renderer = Renderer.new
|
|
85
|
+
layout_class = self.class.blueprint.layout_class
|
|
86
|
+
|
|
87
|
+
if layout_class
|
|
88
|
+
layout = layout_class.new(self)
|
|
89
|
+
layout.render(renderer)
|
|
90
|
+
else
|
|
91
|
+
renderer.setup
|
|
92
|
+
render_content(renderer)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
renderer.finalize
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_file(path)
|
|
99
|
+
File.binwrite(path, to_pdf)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def render_content(renderer)
|
|
103
|
+
evaluator = ContentEvaluator.new(self, renderer)
|
|
104
|
+
evaluator.evaluate(self.class.blueprint)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def symbolize_keys(hash)
|
|
110
|
+
hash.transform_keys { |k| k.is_a?(String) ? k.to_sym : k }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/pdf.rb
CHANGED
|
@@ -2,7 +2,80 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "pdf/version"
|
|
4
4
|
|
|
5
|
+
# Core
|
|
6
|
+
require_relative "pdf/blueprint"
|
|
7
|
+
require_relative "pdf/resolver"
|
|
8
|
+
require_relative "pdf/component"
|
|
9
|
+
require_relative "pdf/dynamic_components"
|
|
10
|
+
require_relative "pdf/renderer"
|
|
11
|
+
|
|
12
|
+
# Components
|
|
13
|
+
require_relative "pdf/components/title"
|
|
14
|
+
require_relative "pdf/components/subtitle"
|
|
15
|
+
require_relative "pdf/components/heading"
|
|
16
|
+
require_relative "pdf/components/paragraph"
|
|
17
|
+
require_relative "pdf/components/span"
|
|
18
|
+
require_relative "pdf/components/date"
|
|
19
|
+
require_relative "pdf/components/hr"
|
|
20
|
+
require_relative "pdf/components/spacer"
|
|
21
|
+
require_relative "pdf/components/alert"
|
|
22
|
+
require_relative "pdf/components/table"
|
|
23
|
+
require_relative "pdf/components/logo"
|
|
24
|
+
require_relative "pdf/components/qr_code"
|
|
25
|
+
require_relative "pdf/components/context"
|
|
26
|
+
|
|
27
|
+
# Builders
|
|
28
|
+
require_relative "pdf/builders/content_builder"
|
|
29
|
+
require_relative "pdf/builders/header_builder"
|
|
30
|
+
require_relative "pdf/builders/footer_builder"
|
|
31
|
+
|
|
32
|
+
# Evaluators
|
|
33
|
+
require_relative "pdf/content_evaluator"
|
|
34
|
+
require_relative "pdf/header_evaluator"
|
|
35
|
+
require_relative "pdf/footer_evaluator"
|
|
36
|
+
|
|
37
|
+
# View & Layout
|
|
38
|
+
require_relative "pdf/view"
|
|
39
|
+
require_relative "pdf/layout"
|
|
40
|
+
|
|
5
41
|
module Pdf
|
|
6
42
|
class Error < StandardError; end
|
|
7
|
-
|
|
43
|
+
class FileNotFoundError < Error; end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
def components
|
|
47
|
+
@components ||= {}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def register_component(name, klass)
|
|
51
|
+
unless klass < Component
|
|
52
|
+
raise ArgumentError, "#{klass} must inherit from Pdf::Component"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
components[name.to_sym] = klass
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def component(name)
|
|
59
|
+
components[name.to_sym] || raise(Error, "Unknown component: #{name}")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def component_registered?(name)
|
|
63
|
+
components.key?(name.to_sym)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Register built-in components
|
|
68
|
+
register_component :title, Components::Title
|
|
69
|
+
register_component :subtitle, Components::Subtitle
|
|
70
|
+
register_component :heading, Components::Heading
|
|
71
|
+
register_component :paragraph, Components::Paragraph
|
|
72
|
+
register_component :span, Components::Span
|
|
73
|
+
register_component :date, Components::Date
|
|
74
|
+
register_component :hr, Components::Hr
|
|
75
|
+
register_component :spacer, Components::Spacer
|
|
76
|
+
register_component :alert, Components::Alert
|
|
77
|
+
register_component :table, Components::Table
|
|
78
|
+
register_component :logo, Components::Logo
|
|
79
|
+
register_component :qr_code, Components::QrCode
|
|
80
|
+
register_component :context, Components::Context
|
|
8
81
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pdf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben
|
|
@@ -9,7 +9,91 @@ autorequire:
|
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2025-12-15 00:00:00.000000000 Z
|
|
12
|
-
dependencies:
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: prawn
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.4'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: prawn-table
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.2'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rqrcode
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: minitest
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '5.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rake
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '13.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '13.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rubocop
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.21'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.21'
|
|
13
97
|
description: A Ruby library for generating and manipulating PDF documents
|
|
14
98
|
email:
|
|
15
99
|
- ben@dee.mx
|
|
@@ -17,6 +101,15 @@ executables: []
|
|
|
17
101
|
extensions: []
|
|
18
102
|
extra_rdoc_files: []
|
|
19
103
|
files:
|
|
104
|
+
- ".claude/execution/00-overview.md"
|
|
105
|
+
- ".claude/execution/01-core.md"
|
|
106
|
+
- ".claude/execution/02-renderer.md"
|
|
107
|
+
- ".claude/execution/03-components.md"
|
|
108
|
+
- ".claude/execution/04-builders.md"
|
|
109
|
+
- ".claude/execution/05-view-layout.md"
|
|
110
|
+
- ".claude/execution/06-evaluators.md"
|
|
111
|
+
- ".claude/execution/07-entry-extensibility.md"
|
|
112
|
+
- ".claude/execution/08-integration-tests.md"
|
|
20
113
|
- ".rspec"
|
|
21
114
|
- ".rubocop.yml"
|
|
22
115
|
- CHANGELOG.md
|
|
@@ -24,8 +117,40 @@ files:
|
|
|
24
117
|
- LICENSE.txt
|
|
25
118
|
- README.md
|
|
26
119
|
- Rakefile
|
|
120
|
+
- examples/01_basic_invoice.rb
|
|
121
|
+
- examples/02_report_with_layout.rb
|
|
122
|
+
- examples/03_inherited_views.rb
|
|
123
|
+
- examples/04_conditional_content.rb
|
|
124
|
+
- examples/05_custom_components.rb
|
|
125
|
+
- examples/README.md
|
|
27
126
|
- lib/pdf.rb
|
|
127
|
+
- lib/pdf/blueprint.rb
|
|
128
|
+
- lib/pdf/builders/content_builder.rb
|
|
129
|
+
- lib/pdf/builders/footer_builder.rb
|
|
130
|
+
- lib/pdf/builders/header_builder.rb
|
|
131
|
+
- lib/pdf/component.rb
|
|
132
|
+
- lib/pdf/components/alert.rb
|
|
133
|
+
- lib/pdf/components/context.rb
|
|
134
|
+
- lib/pdf/components/date.rb
|
|
135
|
+
- lib/pdf/components/heading.rb
|
|
136
|
+
- lib/pdf/components/hr.rb
|
|
137
|
+
- lib/pdf/components/logo.rb
|
|
138
|
+
- lib/pdf/components/paragraph.rb
|
|
139
|
+
- lib/pdf/components/qr_code.rb
|
|
140
|
+
- lib/pdf/components/spacer.rb
|
|
141
|
+
- lib/pdf/components/span.rb
|
|
142
|
+
- lib/pdf/components/subtitle.rb
|
|
143
|
+
- lib/pdf/components/table.rb
|
|
144
|
+
- lib/pdf/components/title.rb
|
|
145
|
+
- lib/pdf/content_evaluator.rb
|
|
146
|
+
- lib/pdf/dynamic_components.rb
|
|
147
|
+
- lib/pdf/footer_evaluator.rb
|
|
148
|
+
- lib/pdf/header_evaluator.rb
|
|
149
|
+
- lib/pdf/layout.rb
|
|
150
|
+
- lib/pdf/renderer.rb
|
|
151
|
+
- lib/pdf/resolver.rb
|
|
28
152
|
- lib/pdf/version.rb
|
|
153
|
+
- lib/pdf/view.rb
|
|
29
154
|
- sig/pdf.rbs
|
|
30
155
|
homepage: https://github.com/getnvoi/pdf
|
|
31
156
|
licenses:
|