prawn-html 0.2.0 → 0.3.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 +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:
|