prawn-html 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +37 -4
- data/lib/prawn-html.rb +164 -13
- data/lib/prawn_html/attributes.rb +11 -77
- data/lib/prawn_html/context.rb +15 -0
- data/lib/prawn_html/document_renderer.rb +29 -9
- data/lib/prawn_html/{html_handler.rb → html_parser.rb} +7 -3
- data/lib/prawn_html/tag.rb +2 -1
- data/lib/prawn_html/tags/br.rb +1 -1
- data/lib/prawn_html/tags/hr.rb +11 -3
- data/lib/prawn_html/tags/img.rb +6 -6
- data/lib/prawn_html/tags/li.rb +6 -1
- data/lib/prawn_html/tags/ol.rb +28 -0
- data/lib/prawn_html/utils.rb +90 -0
- data/lib/prawn_html/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c519c9608664ef77b5b4489752f49d29ae4dfda7ee42e43685ee5951524bd1e3
|
4
|
+
data.tar.gz: c768fa73bc5601bec246a963153a3d20f0d6712fa72dea6441a27495dc374cee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b62ed1e07608cdb62829fe50ae708c16d55b1510c49ff8e02eb47442dca105cfeb2b18e751dd5f84e421687ba970fecb3078db904d3fc867af93ecbe67361e07
|
7
|
+
data.tar.gz: 503c65d6d22d496c634634dca3c57eda3e864ce91448ad2c119e0cb89857e7731cdef7fd209a1f48d8bda82ad0dc8eff724e407a957bd4d8323f410385ebc906
|
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Features:
|
|
11
11
|
- custom [data attributes](#data-attributes) for Prawn PDF features;
|
12
12
|
- no extra settings: it just parses an input HTML and outputs to a Prawn PDF document.
|
13
13
|
|
14
|
-
**Notice**: render HTML documents properly is not an easy task, this gem support only some HTML tags and a small set of CSS attributes. If you need more rendering accuracy take a look at other projects like WickedPDF
|
14
|
+
**Notice**: render HTML documents properly is not an easy task, this gem support only some HTML tags and a small set of CSS attributes. If you need more rendering accuracy take a look at other projects like *WickedPDF* or *PDFKit*.
|
15
15
|
|
16
16
|
> [prawn-styled-text](https://github.com/blocknotes/prawn-styled-text) rewritten from scratch, finally!
|
17
17
|
|
@@ -50,34 +50,45 @@ HTML tags:
|
|
50
50
|
- **img**: image
|
51
51
|
- **li**: list item
|
52
52
|
- **mark**: highlight
|
53
|
+
- **ol**: ordered list
|
53
54
|
- **p**: block element
|
54
55
|
- **s**: strike-through
|
55
56
|
- **small**: smaller text
|
56
57
|
- **span**: inline element
|
57
58
|
- **strong**: bold
|
58
59
|
- **u**: underline
|
59
|
-
- **ul**: list
|
60
|
+
- **ul**: unordered list
|
60
61
|
|
61
62
|
CSS attributes (dimensional units are ignored and considered in pixel):
|
62
63
|
|
63
|
-
- **background**: for *mark* tag
|
64
|
-
- **color**:
|
64
|
+
- **background**: for *mark* tag (3/6 hex digits or RGB or color name), ex. `style="background: #FECD08"`
|
65
|
+
- **color**: (3/6 hex digits or RGB or color name) ex. `style="color: #FB1"`
|
65
66
|
- **font-family**: font must be registered, quotes are optional, ex. `style="font-family: Courier"`
|
66
67
|
- **font-size**: ex. `style="font-size: 20px"`
|
67
68
|
- **font-style**: values: *:italic*, ex. `style="font-style: italic"`
|
68
69
|
- **font-weight**: values: *:bold*, ex. `style="font-weight: bold"`
|
69
70
|
- **height**: for *img* tag, ex. `<img src="image.jpg" style="height: 200px"/>`
|
70
71
|
- **href**: for *a* tag, ex. `<a href="http://www.google.com/">Google</a>`
|
72
|
+
- **left**: see *position (absolute)*
|
71
73
|
- **letter-spacing**: ex. `style="letter-spacing: 1.5"`
|
72
74
|
- **line-height**: ex. `style="line-height: 10px"`
|
75
|
+
- **list-style-type**: for *ul*, a string, ex. `style="list-style-type: '- '"`
|
73
76
|
- **margin-bottom**: ex. `style="margin-bottom: 10px"`
|
74
77
|
- **margin-left**: ex. `style="margin-left: 15px"`
|
75
78
|
- **margin-top**: ex. `style="margin-top: 20px"`
|
79
|
+
- **position**: `absolute`, ex. `style="position: absolute; left: 20px; top: 100px"`
|
76
80
|
- **src**: for *img* tag, ex. `<img src="image.jpg"/>`
|
77
81
|
- **text-align**: `left` | `center` | `right` | `justify`, ex. `style="text-align: center"`
|
78
82
|
- **text-decoration**: `underline`, ex. `style="text-decoration: underline"`
|
83
|
+
- **top**: see *position (absolute)*
|
79
84
|
- **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
|
80
85
|
|
86
|
+
For colors, the supported formats are:
|
87
|
+
- 3 hex digits, ex. `color: #FB1`;
|
88
|
+
- 6 hex digits, ex. `color: #abcdef`;
|
89
|
+
- RGB, ex. `color: RGB(64, 0, 128)`;
|
90
|
+
- color name, ex. `color: yellow`.
|
91
|
+
|
81
92
|
## Data attributes
|
82
93
|
|
83
94
|
Some custom data attributes are used to pass options:
|
@@ -113,6 +124,28 @@ Only single CSS selectors and basic ones are supported. Example:
|
|
113
124
|
</html>
|
114
125
|
```
|
115
126
|
|
127
|
+
## Additional notes
|
128
|
+
|
129
|
+
### Rails: generate PDF on the fly
|
130
|
+
|
131
|
+
Sample controller's action to create a PDF from Rails:
|
132
|
+
|
133
|
+
```rb
|
134
|
+
class SomeController < ApplicationController
|
135
|
+
def sample_action
|
136
|
+
respond_to do |format|
|
137
|
+
format.pdf do
|
138
|
+
pdf = Prawn::Document.new
|
139
|
+
PrawnHtml.append_html(pdf, '<h1 style="text-align: center">Just a test</h1>')
|
140
|
+
send_data(pdf.render, filename: 'sample.pdf', type: 'application/pdf')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
More details in this blogpost: [generate PDF from HTML](https://www.blocknot.es/2021-08-20-rails-generate-pdf-from-html/)
|
148
|
+
|
116
149
|
## Do you like it? Star it!
|
117
150
|
|
118
151
|
If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
|
data/lib/prawn-html.rb
CHANGED
@@ -1,25 +1,176 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'oga'
|
4
3
|
require 'prawn'
|
5
4
|
|
6
|
-
require 'prawn_html/tag'
|
7
|
-
Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
|
8
|
-
|
9
|
-
Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
|
10
|
-
|
11
|
-
require 'prawn_html/attributes'
|
12
|
-
require 'prawn_html/context'
|
13
|
-
require 'prawn_html/document_renderer'
|
14
|
-
require 'prawn_html/html_handler'
|
15
|
-
|
16
5
|
module PrawnHtml
|
17
6
|
PX = 0.66 # conversion constant for pixel sixes
|
18
7
|
|
8
|
+
COLORS = {
|
9
|
+
'aliceblue' => 'f0f8ff',
|
10
|
+
'antiquewhite' => 'faebd7',
|
11
|
+
'aqua' => '00ffff',
|
12
|
+
'aquamarine' => '7fffd4',
|
13
|
+
'azure' => 'f0ffff',
|
14
|
+
'beige' => 'f5f5dc',
|
15
|
+
'bisque' => 'ffe4c4',
|
16
|
+
'black' => '000000',
|
17
|
+
'blanchedalmond' => 'ffebcd',
|
18
|
+
'blue' => '0000ff',
|
19
|
+
'blueviolet' => '8a2be2',
|
20
|
+
'brown' => 'a52a2a',
|
21
|
+
'burlywood' => 'deb887',
|
22
|
+
'cadetblue' => '5f9ea0',
|
23
|
+
'chartreuse' => '7fff00',
|
24
|
+
'chocolate' => 'd2691e',
|
25
|
+
'coral' => 'ff7f50',
|
26
|
+
'cornflowerblue' => '6495ed',
|
27
|
+
'cornsilk' => 'fff8dc',
|
28
|
+
'crimson' => 'dc143c',
|
29
|
+
'cyan' => '00ffff',
|
30
|
+
'darkblue' => '00008b',
|
31
|
+
'darkcyan' => '008b8b',
|
32
|
+
'darkgoldenrod' => 'b8860b',
|
33
|
+
'darkgray' => 'a9a9a9',
|
34
|
+
'darkgreen' => '006400',
|
35
|
+
'darkgrey' => 'a9a9a9',
|
36
|
+
'darkkhaki' => 'bdb76b',
|
37
|
+
'darkmagenta' => '8b008b',
|
38
|
+
'darkolivegreen' => '556b2f',
|
39
|
+
'darkorange' => 'ff8c00',
|
40
|
+
'darkorchid' => '9932cc',
|
41
|
+
'darkred' => '8b0000',
|
42
|
+
'darksalmon' => 'e9967a',
|
43
|
+
'darkseagreen' => '8fbc8f',
|
44
|
+
'darkslateblue' => '483d8b',
|
45
|
+
'darkslategray' => '2f4f4f',
|
46
|
+
'darkslategrey' => '2f4f4f',
|
47
|
+
'darkturquoise' => '00ced1',
|
48
|
+
'darkviolet' => '9400d3',
|
49
|
+
'deeppink' => 'ff1493',
|
50
|
+
'deepskyblue' => '00bfff',
|
51
|
+
'dimgray' => '696969',
|
52
|
+
'dimgrey' => '696969',
|
53
|
+
'dodgerblue' => '1e90ff',
|
54
|
+
'firebrick' => 'b22222',
|
55
|
+
'floralwhite' => 'fffaf0',
|
56
|
+
'forestgreen' => '228b22',
|
57
|
+
'fuchsia' => 'ff00ff',
|
58
|
+
'gainsboro' => 'dcdcdc',
|
59
|
+
'ghostwhite' => 'f8f8ff',
|
60
|
+
'gold' => 'ffd700',
|
61
|
+
'goldenrod' => 'daa520',
|
62
|
+
'gray' => '808080',
|
63
|
+
'green' => '008000',
|
64
|
+
'greenyellow' => 'adff2f',
|
65
|
+
'grey' => '808080',
|
66
|
+
'honeydew' => 'f0fff0',
|
67
|
+
'hotpink' => 'ff69b4',
|
68
|
+
'indianred' => 'cd5c5c',
|
69
|
+
'indigo' => '4b0082',
|
70
|
+
'ivory' => 'fffff0',
|
71
|
+
'khaki' => 'f0e68c',
|
72
|
+
'lavender' => 'e6e6fa',
|
73
|
+
'lavenderblush' => 'fff0f5',
|
74
|
+
'lawngreen' => '7cfc00',
|
75
|
+
'lemonchiffon' => 'fffacd',
|
76
|
+
'lightblue' => 'add8e6',
|
77
|
+
'lightcoral' => 'f08080',
|
78
|
+
'lightcyan' => 'e0ffff',
|
79
|
+
'lightgoldenrodyellow' => 'fafad2',
|
80
|
+
'lightgray' => 'd3d3d3',
|
81
|
+
'lightgreen' => '90ee90',
|
82
|
+
'lightgrey' => 'd3d3d3',
|
83
|
+
'lightpink' => 'ffb6c1',
|
84
|
+
'lightsalmon' => 'ffa07a',
|
85
|
+
'lightseagreen' => '20b2aa',
|
86
|
+
'lightskyblue' => '87cefa',
|
87
|
+
'lightslategray' => '778899',
|
88
|
+
'lightslategrey' => '778899',
|
89
|
+
'lightsteelblue' => 'b0c4de',
|
90
|
+
'lightyellow' => 'ffffe0',
|
91
|
+
'lime' => '00ff00',
|
92
|
+
'limegreen' => '32cd32',
|
93
|
+
'linen' => 'faf0e6',
|
94
|
+
'magenta' => 'ff00ff',
|
95
|
+
'maroon' => '800000',
|
96
|
+
'mediumaquamarine' => '66cdaa',
|
97
|
+
'mediumblue' => '0000cd',
|
98
|
+
'mediumorchid' => 'ba55d3',
|
99
|
+
'mediumpurple' => '9370db',
|
100
|
+
'mediumseagreen' => '3cb371',
|
101
|
+
'mediumslateblue' => '7b68ee',
|
102
|
+
'mediumspringgreen' => '00fa9a',
|
103
|
+
'mediumturquoise' => '48d1cc',
|
104
|
+
'mediumvioletred' => 'c71585',
|
105
|
+
'midnightblue' => '191970',
|
106
|
+
'mintcream' => 'f5fffa',
|
107
|
+
'mistyrose' => 'ffe4e1',
|
108
|
+
'moccasin' => 'ffe4b5',
|
109
|
+
'navajowhite' => 'ffdead',
|
110
|
+
'navy' => '000080',
|
111
|
+
'oldlace' => 'fdf5e6',
|
112
|
+
'olive' => '808000',
|
113
|
+
'olivedrab' => '6b8e23',
|
114
|
+
'orange' => 'ffa500',
|
115
|
+
'orangered' => 'ff4500',
|
116
|
+
'orchid' => 'da70d6',
|
117
|
+
'palegoldenrod' => 'eee8aa',
|
118
|
+
'palegreen' => '98fb98',
|
119
|
+
'paleturquoise' => 'afeeee',
|
120
|
+
'palevioletred' => 'db7093',
|
121
|
+
'papayawhip' => 'ffefd5',
|
122
|
+
'peachpuff' => 'ffdab9',
|
123
|
+
'peru' => 'cd853f',
|
124
|
+
'pink' => 'ffc0cb',
|
125
|
+
'plum' => 'dda0dd',
|
126
|
+
'powderblue' => 'b0e0e6',
|
127
|
+
'purple' => '800080',
|
128
|
+
'rebeccapurple' => '663399',
|
129
|
+
'red' => 'ff0000',
|
130
|
+
'rosybrown' => 'bc8f8f',
|
131
|
+
'royalblue' => '4169e1',
|
132
|
+
'saddlebrown' => '8b4513',
|
133
|
+
'salmon' => 'fa8072',
|
134
|
+
'sandybrown' => 'f4a460',
|
135
|
+
'seagreen' => '2e8b57',
|
136
|
+
'seashell' => 'fff5ee',
|
137
|
+
'sienna' => 'a0522d',
|
138
|
+
'silver' => 'c0c0c0',
|
139
|
+
'skyblue' => '87ceeb',
|
140
|
+
'slateblue' => '6a5acd',
|
141
|
+
'slategray' => '708090',
|
142
|
+
'slategrey' => '708090',
|
143
|
+
'snow' => 'fffafa',
|
144
|
+
'springgreen' => '00ff7f',
|
145
|
+
'steelblue' => '4682b4',
|
146
|
+
'tan' => 'd2b48c',
|
147
|
+
'teal' => '008080',
|
148
|
+
'thistle' => 'd8bfd8',
|
149
|
+
'tomato' => 'ff6347',
|
150
|
+
'turquoise' => '40e0d0',
|
151
|
+
'violet' => 'ee82ee',
|
152
|
+
'wheat' => 'f5deb3',
|
153
|
+
'white' => 'ffffff',
|
154
|
+
'whitesmoke' => 'f5f5f5',
|
155
|
+
'yellow' => 'ffff00',
|
156
|
+
'yellowgreen' => '9acd32'
|
157
|
+
}.freeze
|
158
|
+
|
19
159
|
def append_html(pdf, html)
|
20
|
-
|
21
|
-
|
160
|
+
html_parser = PrawnHtml::HtmlParser.new(pdf)
|
161
|
+
html_parser.process(html)
|
22
162
|
end
|
23
163
|
|
24
164
|
module_function :append_html
|
25
165
|
end
|
166
|
+
|
167
|
+
require 'prawn_html/utils'
|
168
|
+
|
169
|
+
require 'prawn_html/tag'
|
170
|
+
Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
|
171
|
+
Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
|
172
|
+
|
173
|
+
require 'prawn_html/attributes'
|
174
|
+
require 'prawn_html/context'
|
175
|
+
require 'prawn_html/document_renderer'
|
176
|
+
require 'prawn_html/html_parser'
|
@@ -7,23 +7,24 @@ module PrawnHtml
|
|
7
7
|
attr_reader :styles
|
8
8
|
|
9
9
|
STYLES_APPLY = {
|
10
|
-
block: %i[align leading margin_left padding_left],
|
10
|
+
block: %i[align leading left margin_left padding_left position top],
|
11
11
|
tag_close: %i[margin_bottom padding_bottom],
|
12
12
|
tag_open: %i[margin_top padding_top],
|
13
|
-
text_node: %i[background callback character_spacing color font link size styles]
|
13
|
+
text_node: %i[background callback character_spacing color font link list_style_type size styles]
|
14
14
|
}.freeze
|
15
15
|
|
16
16
|
STYLES_LIST = {
|
17
17
|
# text node styles
|
18
18
|
'background' => { key: :background, set: :convert_color },
|
19
|
-
'callback' => { key: :callback, set: :
|
19
|
+
'callback' => { key: :callback, set: :copy_value },
|
20
20
|
'color' => { key: :color, set: :convert_color },
|
21
21
|
'font-family' => { key: :font, set: :unquote },
|
22
22
|
'font-size' => { key: :size, set: :convert_size },
|
23
23
|
'font-style' => { key: :styles, set: :append_symbol },
|
24
24
|
'font-weight' => { key: :styles, set: :append_symbol },
|
25
|
-
'href' => { key: :link, set: :
|
25
|
+
'href' => { key: :link, set: :copy_value },
|
26
26
|
'letter-spacing' => { key: :character_spacing, set: :convert_float },
|
27
|
+
'list-style-type' => { key: :list_style_type, set: :unquote },
|
27
28
|
'text-decoration' => { key: :styles, set: :append_symbol },
|
28
29
|
# tag opening styles
|
29
30
|
'margin-top' => { key: :margin_top, set: :convert_size },
|
@@ -32,10 +33,13 @@ module PrawnHtml
|
|
32
33
|
'margin-bottom' => { key: :margin_bottom, set: :convert_size },
|
33
34
|
'padding-bottom' => { key: :padding_bottom, set: :convert_size },
|
34
35
|
# block styles
|
36
|
+
'left' => { key: :left, set: :convert_size },
|
35
37
|
'line-height' => { key: :leading, set: :convert_size },
|
36
38
|
'margin-left' => { key: :margin_left, set: :convert_size },
|
37
39
|
'padding-left' => { key: :padding_left, set: :convert_size },
|
38
|
-
'
|
40
|
+
'position' => { key: :position, set: :convert_symbol },
|
41
|
+
'text-align' => { key: :align, set: :convert_symbol },
|
42
|
+
'top' => { key: :top, set: :convert_size }
|
39
43
|
}.freeze
|
40
44
|
|
41
45
|
STYLES_MERGE = %i[margin_left padding_left].freeze
|
@@ -78,65 +82,6 @@ module PrawnHtml
|
|
78
82
|
end
|
79
83
|
|
80
84
|
class << self
|
81
|
-
# Converts a color string
|
82
|
-
#
|
83
|
-
# @param value [String] HTML string color
|
84
|
-
#
|
85
|
-
# @return [String] adjusted string color
|
86
|
-
def convert_color(value)
|
87
|
-
val = value&.downcase || +''
|
88
|
-
val.gsub!(/[^a-f0-9]/, '')
|
89
|
-
return val unless val.size == 3
|
90
|
-
|
91
|
-
a, b, c = val.chars
|
92
|
-
a * 2 + b * 2 + c * 2
|
93
|
-
end
|
94
|
-
|
95
|
-
# Converts a decimal number string
|
96
|
-
#
|
97
|
-
# @param value [String] string decimal
|
98
|
-
#
|
99
|
-
# @return [Float] converted and rounded float number
|
100
|
-
def convert_float(value)
|
101
|
-
val = value&.gsub(/[^0-9.]/, '') || ''
|
102
|
-
val.to_f.round(4)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Converts a size string
|
106
|
-
#
|
107
|
-
# @param value [String] size string
|
108
|
-
# @param container_size [Numeric] container size
|
109
|
-
#
|
110
|
-
# @return [Float] converted and rounded size
|
111
|
-
def convert_size(value, container_size = nil)
|
112
|
-
val = value&.gsub(/[^0-9.]/, '') || ''
|
113
|
-
val =
|
114
|
-
if container_size && value.include?('%')
|
115
|
-
val.to_f * container_size * 0.01
|
116
|
-
else
|
117
|
-
val.to_f * PX
|
118
|
-
end
|
119
|
-
val.round(4)
|
120
|
-
end
|
121
|
-
|
122
|
-
# Converts a string to symbol
|
123
|
-
#
|
124
|
-
# @param value [String] string
|
125
|
-
#
|
126
|
-
# @return [Symbol] symbol
|
127
|
-
def convert_symbol(value)
|
128
|
-
value.to_sym if value && !value.match?(/\A\s*\Z/)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Copy a value without conversion
|
132
|
-
#
|
133
|
-
# @param value
|
134
|
-
#
|
135
|
-
# @return value
|
136
|
-
def copy(value)
|
137
|
-
value
|
138
|
-
end
|
139
|
-
|
140
85
|
# Merges attributes
|
141
86
|
#
|
142
87
|
# @param attributes [Hash] target attributes hash
|
@@ -160,17 +105,6 @@ module PrawnHtml
|
|
160
105
|
def parse_styles(styles)
|
161
106
|
(styles || '').scan(/\s*([^:;]+)\s*:\s*([^;]+)\s*/).to_h
|
162
107
|
end
|
163
|
-
|
164
|
-
# Unquotes a string
|
165
|
-
#
|
166
|
-
# @param value [String] string
|
167
|
-
#
|
168
|
-
# @return [String] string without quotes at the beginning/ending
|
169
|
-
def unquote(value)
|
170
|
-
(value&.strip || +'').tap do |val|
|
171
|
-
val.gsub!(/\A['"]|["']\Z/, '')
|
172
|
-
end
|
173
|
-
end
|
174
108
|
end
|
175
109
|
|
176
110
|
private
|
@@ -179,9 +113,9 @@ module PrawnHtml
|
|
179
113
|
return unless rule
|
180
114
|
|
181
115
|
if rule[:set] == :append_symbol
|
182
|
-
(result[rule[:key]] ||= []) <<
|
116
|
+
(result[rule[:key]] ||= []) << Utils.convert_symbol(value)
|
183
117
|
else
|
184
|
-
result[rule[:key]] =
|
118
|
+
result[rule[:key]] = Utils.send(rule[:set], value)
|
185
119
|
end
|
186
120
|
end
|
187
121
|
end
|
data/lib/prawn_html/context.rb
CHANGED
@@ -13,6 +13,21 @@ module PrawnHtml
|
|
13
13
|
@last_text_node = false
|
14
14
|
end
|
15
15
|
|
16
|
+
# Add an element to the context
|
17
|
+
#
|
18
|
+
# Set the parent for the previous element in the chain.
|
19
|
+
# Run `on_context_add` callback method on the added element.
|
20
|
+
#
|
21
|
+
# @param element [Tag] the element to add
|
22
|
+
#
|
23
|
+
# @return [Context] the context updated
|
24
|
+
def add(element)
|
25
|
+
element.parent = last
|
26
|
+
push(element)
|
27
|
+
element.on_context_add(self) if element.respond_to?(:on_context_add)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
16
31
|
# Evaluate before content
|
17
32
|
#
|
18
33
|
# @return [String] before content string
|
@@ -19,9 +19,11 @@ module PrawnHtml
|
|
19
19
|
#
|
20
20
|
# @param styles [Hash] styles hash with CSS selectors as keys and rules as values
|
21
21
|
def assign_document_styles(styles)
|
22
|
-
@document_styles
|
23
|
-
|
24
|
-
|
22
|
+
@document_styles.merge!(
|
23
|
+
styles.transform_values do |style_rules|
|
24
|
+
Attributes.new(style: style_rules).styles
|
25
|
+
end
|
26
|
+
)
|
25
27
|
end
|
26
28
|
|
27
29
|
# On tag close callback
|
@@ -68,8 +70,7 @@ module PrawnHtml
|
|
68
70
|
def render
|
69
71
|
return if buffer.empty?
|
70
72
|
|
71
|
-
|
72
|
-
output_content(buffer.dup, options)
|
73
|
+
output_content(buffer.dup, context.block_styles)
|
73
74
|
buffer.clear
|
74
75
|
context.last_margin = 0
|
75
76
|
end
|
@@ -83,7 +84,7 @@ module PrawnHtml
|
|
83
84
|
def setup_element(element)
|
84
85
|
add_space_if_needed unless render_if_needed(element)
|
85
86
|
apply_tag_open_styles(element)
|
86
|
-
context.
|
87
|
+
context.add(element)
|
87
88
|
element.custom_render(pdf, context) if element.respond_to?(:custom_render)
|
88
89
|
end
|
89
90
|
|
@@ -112,11 +113,30 @@ module PrawnHtml
|
|
112
113
|
pdf.move_down(move_down) if move_down > 0
|
113
114
|
end
|
114
115
|
|
115
|
-
def output_content(buffer,
|
116
|
+
def output_content(buffer, block_styles)
|
116
117
|
buffer.each { |item| item[:callback] = item[:callback].new(pdf, item) if item[:callback] }
|
117
|
-
left_indent =
|
118
|
+
left_indent = block_styles[:margin_left].to_f + block_styles[:padding_left].to_f
|
119
|
+
options = block_styles.slice(:align, :leading, :mode, :padding_left)
|
118
120
|
options[:indent_paragraphs] = left_indent if left_indent > 0
|
119
|
-
|
121
|
+
formatted_text(buffer, options, bounding_box: bounds(block_styles))
|
122
|
+
end
|
123
|
+
|
124
|
+
def formatted_text(buffer, options, bounding_box: nil)
|
125
|
+
return pdf.formatted_text(buffer, options) unless bounding_box
|
126
|
+
|
127
|
+
current_y = pdf.cursor
|
128
|
+
pdf.bounding_box(*bounding_box) do
|
129
|
+
pdf.formatted_text(buffer, options)
|
130
|
+
end
|
131
|
+
pdf.move_cursor_to(current_y)
|
132
|
+
end
|
133
|
+
|
134
|
+
def bounds(block_styles)
|
135
|
+
return unless block_styles[:position] == :absolute
|
136
|
+
|
137
|
+
y = pdf.bounds.height - (block_styles[:top] || 0)
|
138
|
+
w = pdf.bounds.width - (block_styles[:left] || 0)
|
139
|
+
[[block_styles[:left] || 0, y], { width: w }]
|
120
140
|
end
|
121
141
|
end
|
122
142
|
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'oga'
|
4
|
+
|
3
5
|
module PrawnHtml
|
4
|
-
class
|
5
|
-
# Init the
|
6
|
+
class HtmlParser
|
7
|
+
# Init the HtmlParser
|
6
8
|
#
|
7
9
|
# @param pdf [Prawn::Document] Target Prawn PDF document
|
8
10
|
def initialize(pdf)
|
@@ -44,7 +46,7 @@ module PrawnHtml
|
|
44
46
|
def init_element(node)
|
45
47
|
node.name.downcase.to_sym.tap do |tag_name|
|
46
48
|
@processing = true if tag_name == :body
|
47
|
-
renderer.assign_document_styles(extract_styles(node.text)) if tag_name == :style
|
49
|
+
renderer.assign_document_styles(extract_styles(node.text)) if tag_name == :style
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
@@ -63,4 +65,6 @@ module PrawnHtml
|
|
63
65
|
@processing = false if element.tag == :body
|
64
66
|
end
|
65
67
|
end
|
68
|
+
|
69
|
+
HtmlHandler = HtmlParser
|
66
70
|
end
|
data/lib/prawn_html/tag.rb
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
module PrawnHtml
|
4
4
|
class Tag
|
5
|
-
TAG_CLASSES = %w[A B Body Br Del Div H Hr I Img Li Mark P Small Span U Ul].freeze
|
5
|
+
TAG_CLASSES = %w[A B Body Br Del Div H Hr I Img Li Mark Ol P Small Span U Ul].freeze
|
6
6
|
|
7
|
+
attr_accessor :parent
|
7
8
|
attr_reader :attrs, :tag
|
8
9
|
|
9
10
|
# Init the Tag
|
data/lib/prawn_html/tags/br.rb
CHANGED
data/lib/prawn_html/tags/hr.rb
CHANGED
@@ -14,9 +14,9 @@ module PrawnHtml
|
|
14
14
|
|
15
15
|
def custom_render(pdf, _context)
|
16
16
|
dash = parse_dash_value(attrs.data['dash']) if attrs.data.include?('dash')
|
17
|
-
pdf.dash
|
18
|
-
|
19
|
-
|
17
|
+
around_stroke(pdf, old_color: pdf.stroke_color, new_color: attrs.styles[:color], dash: dash) do
|
18
|
+
pdf.stroke_horizontal_rule
|
19
|
+
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def tag_styles
|
@@ -28,6 +28,14 @@ module PrawnHtml
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
+
def around_stroke(pdf, old_color:, new_color:, dash:)
|
32
|
+
pdf.dash(dash) if dash
|
33
|
+
pdf.stroke_color = new_color if new_color
|
34
|
+
yield
|
35
|
+
pdf.stroke_color = old_color if new_color
|
36
|
+
pdf.undash if dash
|
37
|
+
end
|
38
|
+
|
31
39
|
def parse_dash_value(dash_string)
|
32
40
|
if dash_string.match? /\A\d+\Z/
|
33
41
|
dash_string.to_i
|
data/lib/prawn_html/tags/img.rb
CHANGED
@@ -10,19 +10,19 @@ module PrawnHtml
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def custom_render(pdf, context)
|
13
|
-
|
13
|
+
parsed_styles = Attributes.parse_styles(attrs.style)
|
14
14
|
block_styles = context.block_styles
|
15
|
-
evaluated_styles = evaluate_styles(pdf, block_styles.merge(
|
15
|
+
evaluated_styles = evaluate_styles(pdf, block_styles.merge(parsed_styles))
|
16
16
|
pdf.image(@attrs.src, evaluated_styles) if @attrs.src
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
def evaluate_styles(pdf,
|
21
|
+
def evaluate_styles(pdf, img_styles)
|
22
22
|
{}.tap do |result|
|
23
|
-
result[:width] =
|
24
|
-
result[:height] =
|
25
|
-
result[:position] =
|
23
|
+
result[:width] = Utils.convert_size(img_styles['width'], pdf.bounds.width) if img_styles.include?('width')
|
24
|
+
result[:height] = Utils.convert_size(img_styles['height'], pdf.bounds.height) if img_styles.include?('height')
|
25
|
+
result[:position] = img_styles[:align] if %i[left center right].include?(img_styles[:align])
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
data/lib/prawn_html/tags/li.rb
CHANGED
@@ -9,9 +9,14 @@ module PrawnHtml
|
|
9
9
|
true
|
10
10
|
end
|
11
11
|
|
12
|
+
def on_context_add(_context)
|
13
|
+
@counter = (parent.counter += 1) if parent.is_a? Ol
|
14
|
+
@symbol = parent.styles[:list_style_type] || '•' if parent.is_a? Ul
|
15
|
+
end
|
16
|
+
|
12
17
|
def tag_styles
|
13
18
|
{
|
14
|
-
before_content:
|
19
|
+
before_content: @counter ? "#{@counter}. " : "#{@symbol} "
|
15
20
|
}
|
16
21
|
end
|
17
22
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrawnHtml
|
4
|
+
module Tags
|
5
|
+
class Ol < Tag
|
6
|
+
ELEMENTS = [:ol].freeze
|
7
|
+
|
8
|
+
MARGIN_LEFT = 25
|
9
|
+
|
10
|
+
attr_accessor :counter
|
11
|
+
|
12
|
+
def initialize(*_args)
|
13
|
+
super
|
14
|
+
@counter = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def block?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def tag_styles
|
22
|
+
@tag_styles ||= {
|
23
|
+
'margin-left' => MARGIN_LEFT.to_s,
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrawnHtml
|
4
|
+
module Utils
|
5
|
+
# Converts a color string
|
6
|
+
#
|
7
|
+
# Supported formats:
|
8
|
+
# - 3 hex digits, ex. `color: #FB1`;
|
9
|
+
# - 6 hex digits, ex. `color: #abcdef`;
|
10
|
+
# - RGB, ex. `color: RGB(64, 0, 128)`;
|
11
|
+
# - color name, ex. `color: red`.
|
12
|
+
#
|
13
|
+
# @param value [String] HTML string color
|
14
|
+
#
|
15
|
+
# @return [String] adjusted string color or nil if value is invalid
|
16
|
+
def convert_color(value)
|
17
|
+
val = value.to_s.strip.downcase
|
18
|
+
return Regexp.last_match[1] if val.match /\A#([a-f0-9]{6})\Z/ # rubocop:disable Performance/RedundantMatch
|
19
|
+
|
20
|
+
if val.match /\A#([a-f0-9]{3})\Z/ # rubocop:disable Performance/RedundantMatch
|
21
|
+
r, g, b = Regexp.last_match[1].chars
|
22
|
+
return r * 2 + g * 2 + b * 2
|
23
|
+
end
|
24
|
+
if val.match /\Argb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\Z/ # rubocop:disable Performance/RedundantMatch
|
25
|
+
r, g, b = Regexp.last_match[1..3].map { |v| v.to_i.to_s(16) }
|
26
|
+
return "#{r.rjust(2, '0')}#{g.rjust(2, '0')}#{b.rjust(2, '0')}"
|
27
|
+
end
|
28
|
+
|
29
|
+
COLORS[val]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Converts a decimal number string
|
33
|
+
#
|
34
|
+
# @param value [String] string decimal
|
35
|
+
#
|
36
|
+
# @return [Float] converted and rounded float number
|
37
|
+
def convert_float(value)
|
38
|
+
val = value&.gsub(/[^0-9.]/, '') || ''
|
39
|
+
val.to_f.round(4)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Converts a size string
|
43
|
+
#
|
44
|
+
# @param value [String] size string
|
45
|
+
# @param container_size [Numeric] container size
|
46
|
+
#
|
47
|
+
# @return [Float] converted and rounded size
|
48
|
+
def convert_size(value, container_size = nil)
|
49
|
+
val = value&.gsub(/[^0-9.]/, '') || ''
|
50
|
+
val =
|
51
|
+
if container_size && value.include?('%')
|
52
|
+
val.to_f * container_size * 0.01
|
53
|
+
else
|
54
|
+
val.to_f * PX
|
55
|
+
end
|
56
|
+
val.round(4)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Converts a string to symbol
|
60
|
+
#
|
61
|
+
# @param value [String] string
|
62
|
+
#
|
63
|
+
# @return [Symbol] symbol
|
64
|
+
def convert_symbol(value)
|
65
|
+
value.to_sym if value && !value.match?(/\A\s*\Z/)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Copy a value without conversion
|
69
|
+
#
|
70
|
+
# @param value
|
71
|
+
#
|
72
|
+
# @return value
|
73
|
+
def copy_value(value)
|
74
|
+
value
|
75
|
+
end
|
76
|
+
|
77
|
+
# Unquotes a string
|
78
|
+
#
|
79
|
+
# @param value [String] string
|
80
|
+
#
|
81
|
+
# @return [String] string without quotes at the beginning/ending
|
82
|
+
def unquote(value)
|
83
|
+
(value&.strip || +'').tap do |val|
|
84
|
+
val.gsub!(/\A['"]|["']\Z/, '')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module_function :convert_color, :convert_float, :convert_size, :convert_symbol, :copy_value, :unquote
|
89
|
+
end
|
90
|
+
end
|
data/lib/prawn_html/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prawn-html
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mattia Roccoberton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-08-
|
11
|
+
date: 2021-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oga
|
@@ -52,7 +52,7 @@ files:
|
|
52
52
|
- lib/prawn_html/callbacks/strike_through.rb
|
53
53
|
- lib/prawn_html/context.rb
|
54
54
|
- lib/prawn_html/document_renderer.rb
|
55
|
-
- lib/prawn_html/
|
55
|
+
- lib/prawn_html/html_parser.rb
|
56
56
|
- lib/prawn_html/tag.rb
|
57
57
|
- lib/prawn_html/tags/a.rb
|
58
58
|
- lib/prawn_html/tags/b.rb
|
@@ -66,11 +66,13 @@ files:
|
|
66
66
|
- lib/prawn_html/tags/img.rb
|
67
67
|
- lib/prawn_html/tags/li.rb
|
68
68
|
- lib/prawn_html/tags/mark.rb
|
69
|
+
- lib/prawn_html/tags/ol.rb
|
69
70
|
- lib/prawn_html/tags/p.rb
|
70
71
|
- lib/prawn_html/tags/small.rb
|
71
72
|
- lib/prawn_html/tags/span.rb
|
72
73
|
- lib/prawn_html/tags/u.rb
|
73
74
|
- lib/prawn_html/tags/ul.rb
|
75
|
+
- lib/prawn_html/utils.rb
|
74
76
|
- lib/prawn_html/version.rb
|
75
77
|
homepage: https://github.com/blocknotes/prawn-html
|
76
78
|
licenses:
|