prawn-markup 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile.lock +21 -23
- data/README.md +3 -3
- data/lib/prawn/markup/builders/table_builder.rb +1 -0
- data/lib/prawn/markup/processor.rb +5 -2
- data/lib/prawn/markup/processor/blocks.rb +148 -0
- data/lib/prawn/markup/processor/lists.rb +4 -0
- data/lib/prawn/markup/processor/tables.rb +1 -0
- data/lib/prawn/markup/processor/text.rb +28 -123
- data/lib/prawn/markup/support/normalizer.rb +2 -1
- data/lib/prawn/markup/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b195d6ff3dfeacfc8e8112f17323bfdc8981b955a994862d9609b07184dd229f
|
4
|
+
data.tar.gz: 33113b2d2db4aedae4eb7c412f65d7d2400d89f4dc94f30d9f4a700ea6598164
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 912de36400986c54d00f4ca94fa5e37207236e79152490370278f2c11895da5e75041b1bd528e6c179c11b145723a7ee1a4fdee8da05f384f4054d711a589117
|
7
|
+
data.tar.gz: 3f03640c1d82f6a46f2d5c74e9fcb8ff16287a0b83ad6a6e273f55ba03f2d4a716e0a549e7e6c9b64551062522e04ae2108678b048163fb21b5554a327c6b6fa
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
prawn-markup (0.
|
4
|
+
prawn-markup (0.2.0)
|
5
5
|
nokogiri
|
6
6
|
prawn
|
7
7
|
prawn-table
|
@@ -14,63 +14,61 @@ GEM
|
|
14
14
|
ast (2.4.0)
|
15
15
|
byebug (10.0.2)
|
16
16
|
diff-lcs (1.3)
|
17
|
-
docile (1.3.
|
17
|
+
docile (1.3.2)
|
18
18
|
hashery (2.1.2)
|
19
|
-
jaro_winkler (1.5.
|
20
|
-
json (2.
|
21
|
-
mini_portile2 (2.
|
22
|
-
nokogiri (1.
|
23
|
-
mini_portile2 (~> 2.
|
24
|
-
parallel (1.
|
25
|
-
parser (2.
|
19
|
+
jaro_winkler (1.5.3)
|
20
|
+
json (2.2.0)
|
21
|
+
mini_portile2 (2.4.0)
|
22
|
+
nokogiri (1.9.1)
|
23
|
+
mini_portile2 (~> 2.4.0)
|
24
|
+
parallel (1.17.0)
|
25
|
+
parser (2.6.3.0)
|
26
26
|
ast (~> 2.4.0)
|
27
27
|
pdf-core (0.7.0)
|
28
28
|
pdf-inspector (1.3.0)
|
29
29
|
pdf-reader (>= 1.0, < 3.0.a)
|
30
|
-
pdf-reader (2.
|
30
|
+
pdf-reader (2.2.0)
|
31
31
|
Ascii85 (~> 1.0.0)
|
32
32
|
afm (~> 0.2.1)
|
33
33
|
hashery (~> 2.0)
|
34
34
|
ruby-rc4
|
35
35
|
ttfunk
|
36
|
-
powerpack (0.1.2)
|
37
36
|
prawn (2.2.2)
|
38
37
|
pdf-core (~> 0.7.0)
|
39
38
|
ttfunk (~> 1.5)
|
40
39
|
prawn-table (0.2.2)
|
41
40
|
prawn (>= 1.3.0, < 3.0.0)
|
42
41
|
rainbow (3.0.0)
|
43
|
-
rake (12.3.
|
42
|
+
rake (12.3.2)
|
44
43
|
rspec (3.8.0)
|
45
44
|
rspec-core (~> 3.8.0)
|
46
45
|
rspec-expectations (~> 3.8.0)
|
47
46
|
rspec-mocks (~> 3.8.0)
|
48
|
-
rspec-core (3.8.
|
47
|
+
rspec-core (3.8.2)
|
49
48
|
rspec-support (~> 3.8.0)
|
50
|
-
rspec-expectations (3.8.
|
49
|
+
rspec-expectations (3.8.4)
|
51
50
|
diff-lcs (>= 1.2.0, < 2.0)
|
52
51
|
rspec-support (~> 3.8.0)
|
53
|
-
rspec-mocks (3.8.
|
52
|
+
rspec-mocks (3.8.1)
|
54
53
|
diff-lcs (>= 1.2.0, < 2.0)
|
55
54
|
rspec-support (~> 3.8.0)
|
56
|
-
rspec-support (3.8.
|
57
|
-
rubocop (0.
|
55
|
+
rspec-support (3.8.2)
|
56
|
+
rubocop (0.68.1)
|
58
57
|
jaro_winkler (~> 1.5.1)
|
59
58
|
parallel (~> 1.10)
|
60
59
|
parser (>= 2.5, != 2.5.1.1)
|
61
|
-
powerpack (~> 0.1)
|
62
60
|
rainbow (>= 2.2.2, < 4.0)
|
63
61
|
ruby-progressbar (~> 1.7)
|
64
|
-
unicode-display_width (
|
65
|
-
ruby-progressbar (1.10.
|
62
|
+
unicode-display_width (>= 1.4.0, < 1.6)
|
63
|
+
ruby-progressbar (1.10.1)
|
66
64
|
ruby-rc4 (0.1.5)
|
67
|
-
simplecov (0.
|
65
|
+
simplecov (0.17.0)
|
68
66
|
docile (~> 1.1)
|
69
67
|
json (>= 1.8, < 3)
|
70
68
|
simplecov-html (~> 0.10.0)
|
71
69
|
simplecov-html (0.10.2)
|
72
70
|
ttfunk (1.5.1)
|
73
|
-
unicode-display_width (1.
|
71
|
+
unicode-display_width (1.5.0)
|
74
72
|
|
75
73
|
PLATFORMS
|
76
74
|
ruby
|
@@ -86,4 +84,4 @@ DEPENDENCIES
|
|
86
84
|
simplecov
|
87
85
|
|
88
86
|
BUNDLED WITH
|
89
|
-
1.
|
87
|
+
1.17.3
|
data/README.md
CHANGED
@@ -74,15 +74,15 @@ A callable gets the URL of the IFrame as an argument. Defaults to ignore iframes
|
|
74
74
|
|
75
75
|
This gem parses the given HTML and layouts the following elements in a vertical order:
|
76
76
|
|
77
|
-
* Text
|
78
|
-
* Text semantics: `b`, `strong`, `i`, `em`, `u`, `
|
77
|
+
* Text blocks: `p`, `div`, `ol`, `ul`, `li`, `hr`, `br`
|
78
|
+
* Text semantics: `a`, `b`, `strong`, `i`, `em`, `u`, `s`, `del`, `sub`, `sup`
|
79
79
|
* Headings: `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
|
80
80
|
* Tables: `table`, `tr`, `td`, `th`
|
81
81
|
* Media: `img`, `iframe`
|
82
82
|
|
83
83
|
All other elements are ignored, their content is added to the parent element. With a few exceptions, no CSS is processed. One exception is the `width` property of `img`, `td` and `th`, which may contain values in `cm`, `mm`, `px`, `pt`, `%` or `auto`.
|
84
84
|
|
85
|
-
If no explicit loader is given (see above), images are loaded from `http(s)` addresses
|
85
|
+
If no explicit loader is given (see above), images are loaded from `http(s)` addresses or may be contained in the `src` attribute as base64 encoded data URIs. Prawn only supports `PNG` and `JPG`.
|
86
86
|
|
87
87
|
|
88
88
|
## Development
|
@@ -21,12 +21,14 @@ module Prawn
|
|
21
21
|
self.logger = defined?(Rails) ? Rails.logger : nil
|
22
22
|
|
23
23
|
require 'prawn/markup/processor/text'
|
24
|
+
require 'prawn/markup/processor/blocks'
|
24
25
|
require 'prawn/markup/processor/headings'
|
25
26
|
require 'prawn/markup/processor/images'
|
26
27
|
require 'prawn/markup/processor/tables'
|
27
28
|
require 'prawn/markup/processor/lists'
|
28
29
|
|
29
30
|
prepend Prawn::Markup::Processor::Text
|
31
|
+
prepend Prawn::Markup::Processor::Blocks
|
30
32
|
prepend Prawn::Markup::Processor::Headings
|
31
33
|
prepend Prawn::Markup::Processor::Images
|
32
34
|
prepend Prawn::Markup::Processor::Tables
|
@@ -39,6 +41,7 @@ module Prawn
|
|
39
41
|
|
40
42
|
def parse(html)
|
41
43
|
return if html.to_s.strip.empty?
|
44
|
+
|
42
45
|
reset
|
43
46
|
html = Prawn::Markup::Normalizer.new(html).normalize
|
44
47
|
Nokogiri::HTML::SAX::Parser.new(self).parse(html) { |ctx| ctx.recovery = true }
|
@@ -62,11 +65,11 @@ module Prawn
|
|
62
65
|
end
|
63
66
|
|
64
67
|
def error(string)
|
65
|
-
logger.
|
68
|
+
logger.info('SAX parsing error: ' + string.strip) if logger
|
66
69
|
end
|
67
70
|
|
68
71
|
def warning(string)
|
69
|
-
logger.
|
72
|
+
logger.info('SAX parsing warning: ' + string.strip) if logger
|
70
73
|
end
|
71
74
|
|
72
75
|
private
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Prawn
|
2
|
+
module Markup
|
3
|
+
module Processor::Blocks
|
4
|
+
def self.prepended(base)
|
5
|
+
base.known_elements.push('p', 'br', 'div', 'hr')
|
6
|
+
end
|
7
|
+
|
8
|
+
def start_br
|
9
|
+
append_text("\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
def start_p
|
13
|
+
handle_text_element
|
14
|
+
end
|
15
|
+
|
16
|
+
def end_p
|
17
|
+
if inside_container?
|
18
|
+
append_new_line
|
19
|
+
append_text("\n")
|
20
|
+
else
|
21
|
+
add_paragraph
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_div
|
26
|
+
handle_text_element
|
27
|
+
end
|
28
|
+
|
29
|
+
def end_div
|
30
|
+
handle_text_element
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_hr
|
34
|
+
return if inside_container?
|
35
|
+
|
36
|
+
put_bottom_margin(nil)
|
37
|
+
add_current_text
|
38
|
+
pdf.move_down(hr_vertical_margin_top)
|
39
|
+
pdf.stroke_horizontal_rule
|
40
|
+
pdf.move_down(hr_vertical_margin_bottom)
|
41
|
+
end
|
42
|
+
|
43
|
+
def end_document
|
44
|
+
add_current_text
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def handle_text_element
|
50
|
+
if inside_container?
|
51
|
+
append_new_line
|
52
|
+
else
|
53
|
+
add_current_text
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def append_new_line
|
58
|
+
append_text("\n") if buffered_text? && text_buffer[-1] != "\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_paragraph
|
62
|
+
text = dump_text
|
63
|
+
text.gsub!(/[^\n]/, '') if text.strip.empty?
|
64
|
+
unless text.empty?
|
65
|
+
add_bottom_margin
|
66
|
+
add_formatted_text(text, text_options)
|
67
|
+
put_bottom_margin(text_margin_bottom)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_current_text(options = text_options)
|
72
|
+
add_bottom_margin
|
73
|
+
return unless buffered_text?
|
74
|
+
|
75
|
+
string = dump_text
|
76
|
+
string.strip!
|
77
|
+
add_formatted_text(string, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_bottom_margin
|
81
|
+
if @bottom_margin
|
82
|
+
pdf.move_down(@bottom_margin)
|
83
|
+
@bottom_margin = nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_formatted_text(string, options = text_options)
|
88
|
+
with_font(options) do
|
89
|
+
pdf.text(string, options)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_font(options)
|
94
|
+
pdf.font(options[:font] || pdf.font.family,
|
95
|
+
size: options[:size],
|
96
|
+
style: options[:style]) do
|
97
|
+
return yield
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def hr_vertical_margin_top
|
102
|
+
@hr_vertical_margin_top ||=
|
103
|
+
(text_options[:size] || pdf.font_size) / 2.0
|
104
|
+
end
|
105
|
+
|
106
|
+
def hr_vertical_margin_bottom
|
107
|
+
@hr_vertical_margin_bottom ||= with_font(text_options) do
|
108
|
+
hr_vertical_margin_top +
|
109
|
+
pdf.font.descender +
|
110
|
+
text_leading -
|
111
|
+
pdf.line_width
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def reset
|
116
|
+
super
|
117
|
+
text_margin_bottom # pre-calculate
|
118
|
+
end
|
119
|
+
|
120
|
+
def text_margin_bottom
|
121
|
+
options[:text] ||= {}
|
122
|
+
options[:text][:margin_bottom] ||= default_text_margin_bottom
|
123
|
+
end
|
124
|
+
|
125
|
+
def default_text_margin_bottom
|
126
|
+
with_font(text_options) do
|
127
|
+
pdf.font.line_gap +
|
128
|
+
pdf.font.descender +
|
129
|
+
text_leading
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def text_leading
|
134
|
+
text_options[:leading] || pdf.default_leading
|
135
|
+
end
|
136
|
+
|
137
|
+
def text_options
|
138
|
+
@text_options ||= HashMerger.deep(default_text_options, options[:text] || {})
|
139
|
+
end
|
140
|
+
|
141
|
+
def default_text_options
|
142
|
+
{
|
143
|
+
inline_format: true
|
144
|
+
}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -32,10 +32,14 @@ module Prawn
|
|
32
32
|
alias end_ul end_list
|
33
33
|
|
34
34
|
def start_li
|
35
|
+
return unless inside_container?
|
36
|
+
|
35
37
|
current_list.items << Elements::Item.new
|
36
38
|
end
|
37
39
|
|
38
40
|
def end_li
|
41
|
+
return unless inside_container?
|
42
|
+
|
39
43
|
add_cell_text_node(current_list_item)
|
40
44
|
end
|
41
45
|
|
@@ -2,41 +2,21 @@ module Prawn
|
|
2
2
|
module Markup
|
3
3
|
module Processor::Text
|
4
4
|
def self.prepended(base)
|
5
|
-
base.known_elements.push(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
append_text("\n")
|
10
|
-
end
|
11
|
-
|
12
|
-
def start_p
|
13
|
-
handle_text_element
|
14
|
-
end
|
15
|
-
|
16
|
-
def end_p
|
17
|
-
if inside_container?
|
18
|
-
append_new_line
|
19
|
-
append_text("\n")
|
20
|
-
else
|
21
|
-
add_paragraph
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def start_div
|
26
|
-
handle_text_element
|
27
|
-
end
|
28
|
-
|
29
|
-
def end_div
|
30
|
-
handle_text_element
|
5
|
+
base.known_elements.push(
|
6
|
+
'a', 'b', 'strong', 'i', 'em', 'u', 'strikethrough', 'strike', 's', 'del',
|
7
|
+
'sub', 'sup'
|
8
|
+
)
|
31
9
|
end
|
32
10
|
|
33
11
|
def start_a
|
34
12
|
append_text("<link href=\"#{current_attrs['href']}\">")
|
35
13
|
end
|
14
|
+
alias start_link start_a
|
36
15
|
|
37
16
|
def end_a
|
38
17
|
append_text('</link>')
|
39
18
|
end
|
19
|
+
alias end_link end_a
|
40
20
|
|
41
21
|
def start_b
|
42
22
|
append_text('<b>')
|
@@ -58,119 +38,44 @@ module Prawn
|
|
58
38
|
end
|
59
39
|
alias end_em end_i
|
60
40
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
put_bottom_margin(nil)
|
65
|
-
add_current_text
|
66
|
-
pdf.move_down(hr_vertical_margin_top)
|
67
|
-
pdf.stroke_horizontal_rule
|
68
|
-
pdf.move_down(hr_vertical_margin_bottom)
|
69
|
-
end
|
70
|
-
|
71
|
-
def end_document
|
72
|
-
add_current_text
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def handle_text_element
|
78
|
-
if inside_container?
|
79
|
-
append_new_line
|
80
|
-
else
|
81
|
-
add_current_text
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def append_new_line
|
86
|
-
append_text("\n") if buffered_text? && text_buffer[-1] != "\n"
|
87
|
-
end
|
88
|
-
|
89
|
-
def add_paragraph
|
90
|
-
text = dump_text
|
91
|
-
text.gsub!(/[^\n]/, '') if text.strip.empty?
|
92
|
-
unless text.empty?
|
93
|
-
add_bottom_margin
|
94
|
-
add_formatted_text(text, text_options)
|
95
|
-
put_bottom_margin(text_margin_bottom)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def add_current_text(options = text_options)
|
100
|
-
add_bottom_margin
|
101
|
-
return unless buffered_text?
|
102
|
-
|
103
|
-
string = dump_text
|
104
|
-
string.strip!
|
105
|
-
add_formatted_text(string, options)
|
41
|
+
def start_u
|
42
|
+
append_text('<u>')
|
106
43
|
end
|
107
44
|
|
108
|
-
def
|
109
|
-
|
110
|
-
pdf.move_down(@bottom_margin)
|
111
|
-
@bottom_margin = nil
|
112
|
-
end
|
45
|
+
def end_u
|
46
|
+
append_text('</u>')
|
113
47
|
end
|
114
48
|
|
115
|
-
def
|
116
|
-
|
117
|
-
pdf.text(string, options)
|
118
|
-
end
|
49
|
+
def start_strikethrough
|
50
|
+
append_text('<strikethrough>')
|
119
51
|
end
|
52
|
+
alias start_s start_strikethrough
|
53
|
+
alias start_strike start_strikethrough
|
54
|
+
alias start_del start_strikethrough
|
120
55
|
|
121
|
-
def
|
122
|
-
|
123
|
-
size: options[:size],
|
124
|
-
style: options[:style]) do
|
125
|
-
return yield
|
126
|
-
end
|
56
|
+
def end_strikethrough
|
57
|
+
append_text('</strikethrough>')
|
127
58
|
end
|
59
|
+
alias end_s end_strikethrough
|
60
|
+
alias end_strike end_strikethrough
|
61
|
+
alias end_del end_strikethrough
|
128
62
|
|
129
|
-
def
|
130
|
-
|
131
|
-
(text_options[:size] || pdf.font_size) / 2.0
|
63
|
+
def start_sub
|
64
|
+
append_text('<sub>')
|
132
65
|
end
|
133
66
|
|
134
|
-
def
|
135
|
-
|
136
|
-
hr_vertical_margin_top +
|
137
|
-
pdf.font.descender +
|
138
|
-
text_leading -
|
139
|
-
pdf.line_width
|
140
|
-
end
|
67
|
+
def end_sub
|
68
|
+
append_text('</sub>')
|
141
69
|
end
|
142
70
|
|
143
|
-
def
|
144
|
-
|
145
|
-
text_margin_bottom # pre-calculate
|
71
|
+
def start_sup
|
72
|
+
append_text('<sup>')
|
146
73
|
end
|
147
74
|
|
148
|
-
def
|
149
|
-
|
150
|
-
options[:text][:margin_bottom] ||= default_text_margin_bottom
|
75
|
+
def end_sup
|
76
|
+
append_text('</sup>')
|
151
77
|
end
|
152
78
|
|
153
|
-
def default_text_margin_bottom
|
154
|
-
with_font(text_options) do
|
155
|
-
pdf.font.line_gap +
|
156
|
-
pdf.font.descender +
|
157
|
-
text_leading
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def text_leading
|
162
|
-
text_options[:leading] || pdf.default_leading
|
163
|
-
end
|
164
|
-
|
165
|
-
def text_options
|
166
|
-
@text_options ||= HashMerger.deep(default_text_options, options[:text] || {})
|
167
|
-
end
|
168
|
-
|
169
|
-
def default_text_options
|
170
|
-
{
|
171
|
-
inline_format: true
|
172
|
-
}
|
173
|
-
end
|
174
79
|
end
|
175
80
|
end
|
176
81
|
end
|
@@ -22,7 +22,7 @@ module Prawn
|
|
22
22
|
close_self_closing_elements
|
23
23
|
normalize_spaces
|
24
24
|
replace_html_entities
|
25
|
-
"<
|
25
|
+
"<body>#{html}</body>"
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
@@ -43,6 +43,7 @@ module Prawn
|
|
43
43
|
html.gsub!(/&#{entity};/, string)
|
44
44
|
end
|
45
45
|
end
|
46
|
+
|
46
47
|
end
|
47
48
|
end
|
48
49
|
end
|
data/lib/prawn/markup/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prawn-markup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pascal Zumkehr
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -180,6 +180,7 @@ files:
|
|
180
180
|
- lib/prawn/markup/elements/list.rb
|
181
181
|
- lib/prawn/markup/interface.rb
|
182
182
|
- lib/prawn/markup/processor.rb
|
183
|
+
- lib/prawn/markup/processor/blocks.rb
|
183
184
|
- lib/prawn/markup/processor/headings.rb
|
184
185
|
- lib/prawn/markup/processor/images.rb
|
185
186
|
- lib/prawn/markup/processor/lists.rb
|
@@ -210,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
210
211
|
version: '0'
|
211
212
|
requirements: []
|
212
213
|
rubyforge_project:
|
213
|
-
rubygems_version: 2.
|
214
|
+
rubygems_version: 2.7.8
|
214
215
|
signing_key:
|
215
216
|
specification_version: 4
|
216
217
|
summary: Parse simple HTML markup to include in Prawn PDFs
|