prawn-html 0.3.0 → 0.5.0
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/README.md +8 -2
- data/lib/prawn-html.rb +9 -5
- data/lib/prawn_html/attributes.rb +45 -33
- data/lib/prawn_html/callbacks/background.rb +19 -0
- data/lib/prawn_html/callbacks/strike_through.rb +4 -4
- data/lib/prawn_html/context.rb +22 -11
- data/lib/prawn_html/document_renderer.rb +56 -45
- data/lib/prawn_html/html_parser.rb +38 -13
- data/lib/prawn_html/pdf_wrapper.rb +141 -0
- data/lib/prawn_html/tag.rb +18 -23
- data/lib/prawn_html/tags/a.rb +7 -1
- data/lib/prawn_html/tags/b.rb +1 -3
- data/lib/prawn_html/tags/blockquote.rb +25 -0
- data/lib/prawn_html/tags/body.rb +4 -0
- data/lib/prawn_html/tags/br.rb +3 -4
- data/lib/prawn_html/tags/code.rb +13 -0
- data/lib/prawn_html/tags/del.rb +1 -3
- data/lib/prawn_html/tags/h.rb +21 -21
- data/lib/prawn_html/tags/hr.rb +6 -16
- data/lib/prawn_html/tags/i.rb +1 -3
- data/lib/prawn_html/tags/img.rb +6 -5
- data/lib/prawn_html/tags/li.rb +20 -7
- data/lib/prawn_html/tags/mark.rb +1 -3
- data/lib/prawn_html/tags/ol.rb +20 -5
- data/lib/prawn_html/tags/p.rb +6 -6
- data/lib/prawn_html/tags/pre.rb +25 -0
- data/lib/prawn_html/tags/sub.rb +13 -0
- data/lib/prawn_html/tags/sup.rb +13 -0
- data/lib/prawn_html/tags/u.rb +1 -3
- data/lib/prawn_html/tags/ul.rb +23 -4
- data/lib/prawn_html/utils.rb +48 -11
- data/lib/prawn_html/version.rb +1 -1
- metadata +9 -3
- data/lib/prawn_html/callbacks/highlight.rb +0 -21
@@ -4,30 +4,38 @@ require 'oga'
|
|
4
4
|
|
5
5
|
module PrawnHtml
|
6
6
|
class HtmlParser
|
7
|
+
REGEXP_STYLES = /\s*([^{\s]+)\s*{\s*([^}]*?)\s*}/m.freeze
|
8
|
+
|
7
9
|
# Init the HtmlParser
|
8
10
|
#
|
9
|
-
# @param
|
10
|
-
|
11
|
+
# @param renderer [DocumentRenderer] document renderer
|
12
|
+
# @param ignore_content_tags [Array] array of tags (symbols) to skip their contents while preparing the PDF document
|
13
|
+
def initialize(renderer, ignore_content_tags: %i[script style])
|
11
14
|
@processing = false
|
12
|
-
@
|
15
|
+
@ignore = false
|
16
|
+
@ignore_content_tags = ignore_content_tags
|
17
|
+
@renderer = renderer
|
18
|
+
@styles = {}
|
13
19
|
end
|
14
20
|
|
15
|
-
# Processes HTML and renders it
|
21
|
+
# Processes HTML and renders it
|
16
22
|
#
|
17
23
|
# @param html [String] The HTML content to process
|
18
24
|
def process(html)
|
19
25
|
@processing = !html.include?('<body')
|
20
|
-
|
21
|
-
traverse_nodes(
|
26
|
+
@document = Oga.parse_html(html)
|
27
|
+
traverse_nodes(document.children)
|
22
28
|
renderer.flush
|
23
29
|
end
|
24
30
|
|
25
31
|
private
|
26
32
|
|
27
|
-
attr_reader :processing, :renderer
|
33
|
+
attr_reader :document, :ignore, :processing, :renderer, :styles
|
28
34
|
|
29
35
|
def traverse_nodes(nodes)
|
30
36
|
nodes.each do |node|
|
37
|
+
next if node.is_a?(Oga::XML::Comment)
|
38
|
+
|
31
39
|
element = node_open(node)
|
32
40
|
traverse_nodes(node.children) if node.children.any?
|
33
41
|
node_close(element) if element
|
@@ -37,21 +45,27 @@ module PrawnHtml
|
|
37
45
|
def node_open(node)
|
38
46
|
tag = node.is_a?(Oga::XML::Element) && init_element(node)
|
39
47
|
return unless processing
|
48
|
+
return IgnoredTag.new(tag) if ignore
|
40
49
|
return renderer.on_text_node(node.text) unless tag
|
41
50
|
|
42
|
-
attributes
|
43
|
-
renderer.on_tag_open(tag, attributes)
|
51
|
+
renderer.on_tag_open(tag, attributes: prepare_attributes(node), element_styles: styles[node])
|
44
52
|
end
|
45
53
|
|
46
54
|
def init_element(node)
|
47
55
|
node.name.downcase.to_sym.tap do |tag_name|
|
48
56
|
@processing = true if tag_name == :body
|
49
|
-
|
57
|
+
@ignore = true if @processing && @ignore_content_tags.include?(tag_name)
|
58
|
+
process_styles(node.text) if tag_name == :style
|
50
59
|
end
|
51
60
|
end
|
52
61
|
|
53
|
-
def
|
54
|
-
|
62
|
+
def process_styles(text_styles)
|
63
|
+
hash_styles = text_styles.scan(REGEXP_STYLES).to_h
|
64
|
+
hash_styles.each do |selector, rule|
|
65
|
+
document.css(selector).each do |node|
|
66
|
+
styles[node] = rule
|
67
|
+
end
|
68
|
+
end
|
55
69
|
end
|
56
70
|
|
57
71
|
def prepare_attributes(node)
|
@@ -61,10 +75,21 @@ module PrawnHtml
|
|
61
75
|
end
|
62
76
|
|
63
77
|
def node_close(element)
|
64
|
-
|
78
|
+
if processing
|
79
|
+
renderer.on_tag_close(element) unless ignore
|
80
|
+
@ignore = false if ignore && @ignore_content_tags.include?(element.tag)
|
81
|
+
end
|
65
82
|
@processing = false if element.tag == :body
|
66
83
|
end
|
67
84
|
end
|
68
85
|
|
86
|
+
class IgnoredTag
|
87
|
+
attr_accessor :tag
|
88
|
+
|
89
|
+
def initialize(tag_name)
|
90
|
+
@tag = tag_name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
69
94
|
HtmlHandler = HtmlParser
|
70
95
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module PrawnHtml
|
6
|
+
class PdfWrapper
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@pdf, :start_new_page
|
10
|
+
|
11
|
+
# Wrapper for Prawn PDF Document
|
12
|
+
#
|
13
|
+
# @param pdf_document [Prawn::Document] PDF document to wrap
|
14
|
+
def initialize(pdf_document)
|
15
|
+
@pdf = pdf_document
|
16
|
+
end
|
17
|
+
|
18
|
+
# Advance the cursor
|
19
|
+
#
|
20
|
+
# @param move_down [Float] Quantity to advance (move down)
|
21
|
+
def advance_cursor(move_down)
|
22
|
+
return if !move_down || move_down == 0
|
23
|
+
|
24
|
+
pdf.move_down(move_down)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Calculate the height of a buffer of items
|
28
|
+
#
|
29
|
+
# @param buffer [Array] Buffer of items
|
30
|
+
# @param options [Hash] Output options
|
31
|
+
#
|
32
|
+
# @return [Float] calculated height
|
33
|
+
def calc_buffer_height(buffer, options)
|
34
|
+
pdf.height_of_formatted(buffer, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Calculate the width of a buffer of items
|
38
|
+
#
|
39
|
+
# @param buffer [Array] Buffer of items
|
40
|
+
#
|
41
|
+
# @return [Float] calculated width
|
42
|
+
def calc_buffer_width(buffer)
|
43
|
+
width = 0
|
44
|
+
buffer.each do |item|
|
45
|
+
font_family = item[:font] || pdf.font.name
|
46
|
+
pdf.font(font_family, size: item[:size] || pdf.font_size) do
|
47
|
+
width += pdf.width_of(item[:text], inline_format: true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
width
|
51
|
+
end
|
52
|
+
|
53
|
+
# Height of the page
|
54
|
+
#
|
55
|
+
# @return [Float] height
|
56
|
+
def page_height
|
57
|
+
pdf.bounds.height
|
58
|
+
end
|
59
|
+
|
60
|
+
# Width of the page
|
61
|
+
#
|
62
|
+
# @return [Float] width
|
63
|
+
def page_width
|
64
|
+
pdf.bounds.width
|
65
|
+
end
|
66
|
+
|
67
|
+
# Draw a rectangle
|
68
|
+
#
|
69
|
+
# @param x [Float] left position of the rectangle
|
70
|
+
# @param y [Float] top position of the rectangle
|
71
|
+
# @param width [Float] width of the rectangle
|
72
|
+
# @param height [Float] height of the rectangle
|
73
|
+
# @param color [String] fill color
|
74
|
+
def draw_rectangle(x:, y:, width:, height:, color:)
|
75
|
+
current_fill_color = pdf.fill_color
|
76
|
+
pdf.fill_color = color
|
77
|
+
pdf.fill_rectangle([y, x], width, height)
|
78
|
+
pdf.fill_color = current_fill_color
|
79
|
+
end
|
80
|
+
|
81
|
+
# Horizontal line
|
82
|
+
#
|
83
|
+
# @param color [String] line color
|
84
|
+
# @param dash [Integer|Array] integer or array of integer with dash options
|
85
|
+
def horizontal_rule(color:, dash:)
|
86
|
+
current_color = pdf.stroke_color
|
87
|
+
pdf.dash(dash) if dash
|
88
|
+
pdf.stroke_color = color if color
|
89
|
+
pdf.stroke_horizontal_rule
|
90
|
+
pdf.stroke_color = current_color if color
|
91
|
+
pdf.undash if dash
|
92
|
+
end
|
93
|
+
|
94
|
+
# Image
|
95
|
+
#
|
96
|
+
# @param src [String] image source path
|
97
|
+
# @param options [Hash] hash of options
|
98
|
+
def image(src, options = {})
|
99
|
+
return unless src
|
100
|
+
|
101
|
+
pdf.image(src, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Output to the PDF document
|
105
|
+
#
|
106
|
+
# @param buffer [Array] array of text items
|
107
|
+
# @param options [Hash] hash of options
|
108
|
+
# @param bounding_box [Array] bounding box arguments, if bounded
|
109
|
+
def puts(buffer, options, bounding_box: nil, left_indent: 0)
|
110
|
+
return output_buffer(buffer, options, left_indent: left_indent) unless bounding_box
|
111
|
+
|
112
|
+
current_y = pdf.cursor
|
113
|
+
pdf.bounding_box(*bounding_box) do
|
114
|
+
output_buffer(buffer, options, left_indent: left_indent)
|
115
|
+
end
|
116
|
+
pdf.move_cursor_to(current_y)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Underline
|
120
|
+
#
|
121
|
+
# @param x1 [Float] left position of the line
|
122
|
+
# @param x2 [Float] right position of the line
|
123
|
+
# @param y [Float] vertical position of the line
|
124
|
+
def underline(x1:, x2:, y:)
|
125
|
+
pdf.stroke do
|
126
|
+
pdf.line [x1, y], [x2, y]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
attr_reader :pdf
|
133
|
+
|
134
|
+
def output_buffer(buffer, options, left_indent:)
|
135
|
+
formatted_text = proc { pdf.formatted_text(buffer, options) }
|
136
|
+
return formatted_text.call if left_indent == 0
|
137
|
+
|
138
|
+
pdf.indent(left_indent, 0, &formatted_text)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/prawn_html/tag.rb
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
module PrawnHtml
|
4
4
|
class Tag
|
5
|
-
|
5
|
+
CALLBACKS = {
|
6
|
+
'Background' => Callbacks::Background,
|
7
|
+
'StrikeThrough' => Callbacks::StrikeThrough
|
8
|
+
}.freeze
|
9
|
+
TAG_CLASSES = %w[A B Blockquote Body Br Code Del Div H Hr I Img Li Mark Ol P Pre Small Span Sub Sup U Ul].freeze
|
6
10
|
|
7
11
|
attr_accessor :parent
|
8
12
|
attr_reader :attrs, :tag
|
@@ -11,12 +15,11 @@ module PrawnHtml
|
|
11
15
|
#
|
12
16
|
# @param tag [Symbol] tag name
|
13
17
|
# @param attributes [Hash] hash of element attributes
|
14
|
-
# @param
|
15
|
-
def initialize(tag, attributes
|
18
|
+
# @param options [Hash] options (container width/height/etc.)
|
19
|
+
def initialize(tag, attributes: {}, options: {})
|
16
20
|
@tag = tag
|
17
|
-
|
21
|
+
@options = options
|
18
22
|
@attrs = Attributes.new(attributes)
|
19
|
-
process_styles(document_styles, element_styles)
|
20
23
|
end
|
21
24
|
|
22
25
|
# Is a block tag?
|
@@ -35,6 +38,15 @@ module PrawnHtml
|
|
35
38
|
block_styles
|
36
39
|
end
|
37
40
|
|
41
|
+
# Process tag styles
|
42
|
+
#
|
43
|
+
# @param element_styles [String] extra styles to apply to the element
|
44
|
+
def process_styles(element_styles: nil)
|
45
|
+
attrs.merge_text_styles!(tag_styles, options: options) if respond_to?(:tag_styles)
|
46
|
+
attrs.merge_text_styles!(element_styles, options: options) if element_styles
|
47
|
+
attrs.merge_text_styles!(attrs.style, options: options)
|
48
|
+
end
|
49
|
+
|
38
50
|
# Styles to apply on tag closing
|
39
51
|
#
|
40
52
|
# @return [Hash] hash of styles to apply
|
@@ -74,23 +86,6 @@ module PrawnHtml
|
|
74
86
|
|
75
87
|
private
|
76
88
|
|
77
|
-
|
78
|
-
selectors = [
|
79
|
-
tag.to_s,
|
80
|
-
attrs['class'] ? ".#{attrs['class']}" : nil,
|
81
|
-
attrs['id'] ? "##{attrs['id']}" : nil
|
82
|
-
].compact!
|
83
|
-
document_styles.each_with_object({}) do |(sel, attributes), res|
|
84
|
-
res.merge!(attributes) if selectors.include?(sel)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def process_styles(document_styles, element_styles)
|
89
|
-
attrs.merge_styles!(attrs.process_styles(tag_styles)) if respond_to?(:tag_styles)
|
90
|
-
doc_styles = evaluate_document_styles(document_styles)
|
91
|
-
attrs.merge_styles!(doc_styles)
|
92
|
-
el_styles = Attributes.parse_styles(element_styles)
|
93
|
-
attrs.merge_styles!(attrs.process_styles(el_styles)) if el_styles
|
94
|
-
end
|
89
|
+
attr_reader :options
|
95
90
|
end
|
96
91
|
end
|
data/lib/prawn_html/tags/a.rb
CHANGED
data/lib/prawn_html/tags/b.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrawnHtml
|
4
|
+
module Tags
|
5
|
+
class Blockquote < Tag
|
6
|
+
ELEMENTS = [:blockquote].freeze
|
7
|
+
|
8
|
+
MARGIN_BOTTOM = 12.7
|
9
|
+
MARGIN_LEFT = 40.4
|
10
|
+
MARGIN_TOP = 12.7
|
11
|
+
|
12
|
+
def block?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def tag_styles
|
17
|
+
<<~STYLES
|
18
|
+
margin-bottom: #{MARGIN_BOTTOM}px;
|
19
|
+
margin-left: #{MARGIN_LEFT}px;
|
20
|
+
margin-top: #{MARGIN_TOP}px;
|
21
|
+
STYLES
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/prawn_html/tags/body.rb
CHANGED
data/lib/prawn_html/tags/br.rb
CHANGED
@@ -5,17 +5,16 @@ module PrawnHtml
|
|
5
5
|
class Br < Tag
|
6
6
|
ELEMENTS = [:br].freeze
|
7
7
|
|
8
|
-
BR_SPACING =
|
8
|
+
BR_SPACING = Utils.convert_size('17')
|
9
9
|
|
10
10
|
def block?
|
11
11
|
true
|
12
12
|
end
|
13
13
|
|
14
14
|
def custom_render(pdf, context)
|
15
|
-
return if context.last_text_node
|
15
|
+
return if context.last_text_node || context.previous_tag != :br
|
16
16
|
|
17
|
-
|
18
|
-
pdf.move_down(@spacing)
|
17
|
+
pdf.advance_cursor(BR_SPACING)
|
19
18
|
end
|
20
19
|
end
|
21
20
|
end
|
data/lib/prawn_html/tags/del.rb
CHANGED
data/lib/prawn_html/tags/h.rb
CHANGED
@@ -6,30 +6,30 @@ module PrawnHtml
|
|
6
6
|
ELEMENTS = [:h1, :h2, :h3, :h4, :h5, :h6].freeze
|
7
7
|
|
8
8
|
MARGINS_TOP = {
|
9
|
-
h1: 25
|
9
|
+
h1: 25,
|
10
10
|
h2: 20.5,
|
11
|
-
h3:
|
12
|
-
h4:
|
11
|
+
h3: 18,
|
12
|
+
h4: 21.2,
|
13
13
|
h5: 21.2,
|
14
|
-
h6:
|
14
|
+
h6: 22.8
|
15
15
|
}.freeze
|
16
16
|
|
17
17
|
MARGINS_BOTTOM = {
|
18
|
-
h1:
|
19
|
-
h2:
|
20
|
-
h3:
|
21
|
-
h4:
|
22
|
-
h5:
|
23
|
-
h6:
|
18
|
+
h1: 15.8,
|
19
|
+
h2: 15.8,
|
20
|
+
h3: 15.8,
|
21
|
+
h4: 20,
|
22
|
+
h5: 21.4,
|
23
|
+
h6: 24.8
|
24
24
|
}.freeze
|
25
25
|
|
26
26
|
SIZES = {
|
27
|
-
h1: 31,
|
28
|
-
h2:
|
29
|
-
h3: 18.
|
30
|
-
h4:
|
27
|
+
h1: 31.5,
|
28
|
+
h2: 24,
|
29
|
+
h3: 18.7,
|
30
|
+
h4: 15.7,
|
31
31
|
h5: 13,
|
32
|
-
h6: 10.
|
32
|
+
h6: 10.8
|
33
33
|
}.freeze
|
34
34
|
|
35
35
|
def block?
|
@@ -37,12 +37,12 @@ module PrawnHtml
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def tag_styles
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
<<~STYLES
|
41
|
+
font-size: #{SIZES[tag]}px;
|
42
|
+
font-weight: bold;
|
43
|
+
margin-bottom: #{MARGINS_BOTTOM[tag]}px;
|
44
|
+
margin-top: #{MARGINS_TOP[tag]}px;
|
45
|
+
STYLES
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|