prawn-table-html 0.0.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 +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +236 -0
- data/lib/prawn-html.rb +180 -0
- data/lib/prawn_html/attributes.rb +166 -0
- data/lib/prawn_html/callbacks/background.rb +19 -0
- data/lib/prawn_html/callbacks/strike_through.rb +18 -0
- data/lib/prawn_html/context.rb +100 -0
- data/lib/prawn_html/document_renderer.rb +172 -0
- data/lib/prawn_html/html_parser.rb +104 -0
- data/lib/prawn_html/instance.rb +18 -0
- data/lib/prawn_html/pdf_wrapper.rb +145 -0
- data/lib/prawn_html/tag.rb +93 -0
- data/lib/prawn_html/tags/a.rb +20 -0
- data/lib/prawn_html/tags/b.rb +13 -0
- data/lib/prawn_html/tags/blockquote.rb +25 -0
- data/lib/prawn_html/tags/body.rb +13 -0
- data/lib/prawn_html/tags/br.rb +21 -0
- data/lib/prawn_html/tags/code.rb +13 -0
- data/lib/prawn_html/tags/col.rb +37 -0
- data/lib/prawn_html/tags/colgroup.rb +13 -0
- data/lib/prawn_html/tags/del.rb +13 -0
- data/lib/prawn_html/tags/div.rb +13 -0
- data/lib/prawn_html/tags/h.rb +49 -0
- data/lib/prawn_html/tags/hr.rb +39 -0
- data/lib/prawn_html/tags/i.rb +13 -0
- data/lib/prawn_html/tags/img.rb +31 -0
- data/lib/prawn_html/tags/li.rb +39 -0
- data/lib/prawn_html/tags/mark.rb +13 -0
- data/lib/prawn_html/tags/ol.rb +43 -0
- data/lib/prawn_html/tags/p.rb +23 -0
- data/lib/prawn_html/tags/pre.rb +25 -0
- data/lib/prawn_html/tags/small.rb +15 -0
- data/lib/prawn_html/tags/span.rb +9 -0
- data/lib/prawn_html/tags/sub.rb +13 -0
- data/lib/prawn_html/tags/sup.rb +13 -0
- data/lib/prawn_html/tags/table.rb +53 -0
- data/lib/prawn_html/tags/tbody.rb +13 -0
- data/lib/prawn_html/tags/td.rb +43 -0
- data/lib/prawn_html/tags/th.rb +43 -0
- data/lib/prawn_html/tags/tr.rb +37 -0
- data/lib/prawn_html/tags/u.rb +13 -0
- data/lib/prawn_html/tags/ul.rb +40 -0
- data/lib/prawn_html/utils.rb +139 -0
- data/lib/prawn_html/version.rb +5 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '078e99259e55bc4de98bee9204fa640c4f4aca11964d127d479885938f107ec6'
|
4
|
+
data.tar.gz: 06506e5d430f2260b1ecae80d1862d9bd63f0f63f9624dc2b2672b9d6693c7cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e9f3cc44c133268464ffc98299cd90d9472e1ca5db40ac2b8ea1e3cbc16702c0844220d76de920fe0e39d3dbe97ed42bc2027ca761e0422443044769160f7ce
|
7
|
+
data.tar.gz: ebcede030ba385ce31804769d1286345c6116336cd4fb193c5b89227a1162d7c7b4905ba6f52385cbed696b0644235bc11883f5c5465b60cbb42ef332d8ca83b
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2021 Mattia Roccoberton
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# Prawn HTML
|
2
|
+
[](https://rubygems.org/gems/prawn-html)
|
3
|
+
[](https://rubygems.org/gems/prawn-html)
|
4
|
+
[](https://codeclimate.com/github/blocknotes/prawn-html/maintainability)
|
5
|
+
[](https://github.com/blocknotes/prawn-html/actions/workflows/linters.yml)
|
6
|
+
[](https://github.com/blocknotes/prawn-html/actions/workflows/specs.yml)
|
7
|
+
|
8
|
+
HTML to PDF renderer using [Prawn PDF](https://github.com/prawnpdf/prawn).
|
9
|
+
Nerya's maintained fork with added features.
|
10
|
+
|
11
|
+
Features:
|
12
|
+
- support a [good set](#supported-tags--attributes) of HTML tags and CSS properties;
|
13
|
+
- handle [document styles](#document-styles);
|
14
|
+
- custom [data attributes](#data-attributes) for Prawn PDF features;
|
15
|
+
- no extra settings: it just parses an input HTML and outputs to a Prawn PDF document.
|
16
|
+
|
17
|
+
**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*.
|
18
|
+
|
19
|
+
> [prawn-styled-text](https://github.com/blocknotes/prawn-styled-text) rewritten from scratch, finally!
|
20
|
+
|
21
|
+
Please :star: if you like it.
|
22
|
+
|
23
|
+
## Install
|
24
|
+
|
25
|
+
- Add to your Gemfile: `gem 'prawn-html'` (and execute `bundle`)
|
26
|
+
- Just call `PrawnHtml.append_html` on a `Prawn::Document` instance (see the examples)
|
27
|
+
|
28
|
+
## Examples
|
29
|
+
|
30
|
+
```rb
|
31
|
+
require 'prawn-html'
|
32
|
+
pdf = Prawn::Document.new(page_size: 'A4')
|
33
|
+
PrawnHtml.append_html(pdf, '<h1 style="text-align: center">Just a test</h1>')
|
34
|
+
pdf.render_file('test.pdf')
|
35
|
+
```
|
36
|
+
|
37
|
+
To check some examples with the PDF output see [examples](examples/) folder.
|
38
|
+
|
39
|
+
Alternative form using _PrawnHtml::Instance_ to preserve the context:
|
40
|
+
|
41
|
+
```rb
|
42
|
+
require 'prawn-html'
|
43
|
+
pdf = Prawn::Document.new(page_size: 'A4')
|
44
|
+
phtml = PrawnHtml::Instance.new(pdf)
|
45
|
+
css = <<~CSS
|
46
|
+
h1 { color: green }
|
47
|
+
i { color: red }
|
48
|
+
CSS
|
49
|
+
phtml.append(css: css)
|
50
|
+
phtml.append(html: '<h1>Some <i>HTML</i> before</h1>')
|
51
|
+
pdf.text 'Some Prawn text'
|
52
|
+
phtml.append(html: '<h1>Some <i>HTML</i> after</h1>')
|
53
|
+
pdf.render_file('test.pdf')
|
54
|
+
```
|
55
|
+
|
56
|
+
## Supported tags & attributes
|
57
|
+
|
58
|
+
HTML tags (using MDN definitions):
|
59
|
+
|
60
|
+
- **a**: the Anchor element
|
61
|
+
- **b**: the Bring Attention To element
|
62
|
+
- **blockquote**: the Block Quotation element
|
63
|
+
- **br**: the Line Break element
|
64
|
+
- **code**: the Inline Code element
|
65
|
+
- **col**: the Col table element
|
66
|
+
- **colgroup**: the Colgroup table element
|
67
|
+
- **del**: the Deleted Text element
|
68
|
+
- **div**: the Content Division element
|
69
|
+
- **em**: the Emphasis element
|
70
|
+
- **h1** - **h6**: the HTML Section Heading elements
|
71
|
+
- **hr**: the Thematic Break (Horizontal Rule) element
|
72
|
+
- **i**: the Idiomatic Text element
|
73
|
+
- **ins**: the added text element
|
74
|
+
- **img**: the Image Embed element
|
75
|
+
- **li**: the list item element
|
76
|
+
- **mark**: the Mark Text element
|
77
|
+
- **ol**: the Ordered List element
|
78
|
+
- **p**: the Paragraph element
|
79
|
+
- **pre**: the Preformatted Text element
|
80
|
+
- **s**: the strike-through text element
|
81
|
+
- **small**: the side comment element
|
82
|
+
- **span**: the generic inline element
|
83
|
+
- **strong**: the Strong Importance element
|
84
|
+
- **sub**: the Subscript element
|
85
|
+
- **sup**: the Superscript element
|
86
|
+
- **table**: the Table element
|
87
|
+
- **tbody**: the Table Body element, children of table
|
88
|
+
- **td**: the Table Data element, children of table
|
89
|
+
- **th**: the Table Header element, children of table
|
90
|
+
- **tr**: the Table Row element, children of table
|
91
|
+
- **u**: the Unarticulated Annotation (Underline) element
|
92
|
+
- **ul**: the Unordered List element
|
93
|
+
|
94
|
+
CSS attributes (dimensional units are ignored and considered in pixel):
|
95
|
+
|
96
|
+
- **background**: for *mark* tag (3/6 hex digits or RGB or color name), ex. `style="background: #FECD08"`
|
97
|
+
- **break-after**: go to a new page after some elements, ex. `style="break-after: auto"`
|
98
|
+
- **break-before**: go to a new page before some elements, ex. `style="break-before: auto"`
|
99
|
+
- **color**: (3/6 hex digits or RGB or color name) ex. `style="color: #FB1"`
|
100
|
+
- **font-family**: font must be registered, quotes are optional, ex. `style="font-family: Courier"`
|
101
|
+
- **font-size**: ex. `style="font-size: 20px"`
|
102
|
+
- **font-style**: values: *:italic*, ex. `style="font-style: italic"`
|
103
|
+
- **font-weight**: values: *:bold*, ex. `style="font-weight: bold"`
|
104
|
+
- **height**: for *img* tag, ex. `<img src="image.jpg" style="height: 200px"/>`
|
105
|
+
- **href**: for *a* tag, ex. `<a href="http://www.google.com/">Google</a>`
|
106
|
+
- **left**: see *position (absolute)*
|
107
|
+
- **letter-spacing**: ex. `style="letter-spacing: 1.5"`
|
108
|
+
- **line-height**: ex. `style="line-height: 10px"`
|
109
|
+
- **list-style-type**: for *ul*, a string, ex. `style="list-style-type: '- '"`
|
110
|
+
- **margin-bottom**: ex. `style="margin-bottom: 10px"`
|
111
|
+
- **margin-left**: ex. `style="margin-left: 15px"`
|
112
|
+
- **margin-top**: ex. `style="margin-top: 20px"`
|
113
|
+
- **position**: `absolute`, ex. `style="position: absolute; left: 20px; top: 100px"`
|
114
|
+
- **src**: for *img* tag, ex. `<img src="image.jpg"/>`
|
115
|
+
- **text-align**: `left` | `center` | `right` | `justify`, ex. `style="text-align: center"`
|
116
|
+
- **text-decoration**: `underline`, ex. `style="text-decoration: underline"`
|
117
|
+
- **top**: see *position (absolute)*
|
118
|
+
- **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
|
119
|
+
|
120
|
+
The above attributes supports the `initial` value to reset them to their original value.
|
121
|
+
|
122
|
+
For colors, the supported formats are:
|
123
|
+
- 3 hex digits, ex. `color: #FB1`;
|
124
|
+
- 6 hex digits, ex. `color: #abcdef`;
|
125
|
+
- RGB, ex. `color: RGB(64, 0, 128)`;
|
126
|
+
- color name, ex. `color: yellow`.
|
127
|
+
|
128
|
+
## Data attributes
|
129
|
+
|
130
|
+
Some custom data attributes are used to pass options:
|
131
|
+
|
132
|
+
- **dash**: for *hr* tag, accepts an integer or a list of integers), ex. `data-data="2, 4, 3"`
|
133
|
+
- **mode**: allow to specify the text mode (stroke|fill||fill_stroke), ex. `data-mode="stroke"`
|
134
|
+
- **colwidth**: for *col* tag only, accepts a float value, ex. `colwidth="100"`
|
135
|
+
- **width**: for *td* tag only, accepts a float value, ex. `colwidth="100"`
|
136
|
+
- **colspan**: for *td* tag, accepts an integer value, ex. `colspan="2"`
|
137
|
+
- **rowspan**: for *td* tag, accepts an integer value, ex. `rowspan="2"`
|
138
|
+
|
139
|
+
## Document styles
|
140
|
+
|
141
|
+
You can define document CSS rules inside an _head_ tag. Example:
|
142
|
+
|
143
|
+
```html
|
144
|
+
<!DOCTYPE html>
|
145
|
+
<html>
|
146
|
+
<head>
|
147
|
+
<title>A test</title>
|
148
|
+
<style>
|
149
|
+
body { color: #abbccc }
|
150
|
+
.green {
|
151
|
+
color: #0f0;
|
152
|
+
font-family: Courier;
|
153
|
+
}
|
154
|
+
#test-1 { font-weight: bold }
|
155
|
+
</style>
|
156
|
+
</head>
|
157
|
+
<body>
|
158
|
+
<div class="green">
|
159
|
+
Div content
|
160
|
+
<span id="test-1">Span content</span>
|
161
|
+
</div>
|
162
|
+
</body>
|
163
|
+
</html>
|
164
|
+
```
|
165
|
+
|
166
|
+
## Additional notes
|
167
|
+
|
168
|
+
### Rails: generate PDF on the fly
|
169
|
+
|
170
|
+
Sample controller's action to create a PDF from Rails:
|
171
|
+
|
172
|
+
```rb
|
173
|
+
class SomeController < ApplicationController
|
174
|
+
def sample_action
|
175
|
+
respond_to do |format|
|
176
|
+
format.pdf do
|
177
|
+
pdf = Prawn::Document.new
|
178
|
+
PrawnHtml.append_html(pdf, '<h1 style="text-align: center">Just a test</h1>')
|
179
|
+
send_data(pdf.render, filename: 'sample.pdf', type: 'application/pdf')
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
More details in this blogpost: [generate PDF from HTML](https://www.blocknot.es/2021-08-20-rails-generate-pdf-from-html/)
|
187
|
+
|
188
|
+
### Table support through Prawn::Table
|
189
|
+
|
190
|
+
Prawn HTML partially supports tables through Prawn::Table. It is not a full implementation, but it is a good start.
|
191
|
+
|
192
|
+
Example:
|
193
|
+
|
194
|
+
```rb
|
195
|
+
require 'prawn-html'
|
196
|
+
pdf = Prawn::Document.new(page_size: 'A4')
|
197
|
+
PrawnHtml.append_html(pdf, '<table>
|
198
|
+
<thead>
|
199
|
+
<tr>
|
200
|
+
<th><b>Header 1</b></th>
|
201
|
+
<th><b>Header 2</b></th>
|
202
|
+
</tr>
|
203
|
+
</thead>
|
204
|
+
<tbody>
|
205
|
+
<tr>
|
206
|
+
<td>Cell 1</td>
|
207
|
+
<td>Cell 2</td>
|
208
|
+
</tr>
|
209
|
+
<tr>
|
210
|
+
<td>Cell 3</td>
|
211
|
+
<td>Cell 4</td>
|
212
|
+
</tr>
|
213
|
+
</tbody>
|
214
|
+
</table>')
|
215
|
+
pdf.render_file('test.pdf')
|
216
|
+
```
|
217
|
+
|
218
|
+
This will generate a PDF with a table with two columns and two rows. The table style is fixed, but it accept inline styles in cells.
|
219
|
+
Warning: it doesn't support inline `<p></p>` html tags. However, it is possible to use `<span></span>` tags to style the text.
|
220
|
+
|
221
|
+
It will also support html table attributes, like `width`, `colspan` and `rowspan`. If no width value is provided, the table will be auto-sized.
|
222
|
+
|
223
|
+
## Do you like it? Star it!
|
224
|
+
|
225
|
+
If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
|
226
|
+
|
227
|
+
Or consider offering me a coffee, it's a small thing but it is greatly appreciated: [about me](https://www.blocknot.es/about-me).
|
228
|
+
|
229
|
+
## Contributors
|
230
|
+
|
231
|
+
- [Mattia Roccoberton](https://www.blocknot.es): author
|
232
|
+
- [Anthony Amar](https://github.com/anthony-amar): added table support
|
233
|
+
|
234
|
+
## License
|
235
|
+
|
236
|
+
The gem is available as open-source under the terms of the [MIT](LICENSE.txt).
|
data/lib/prawn-html.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrawnHtml
|
4
|
+
ADJUST_LEADING = { nil => 0.18, 'Courier' => -0.07, 'Helvetica' => -0.17, 'Times-Roman' => 0.03 }.freeze
|
5
|
+
PX = 0.6 # conversion constant for pixel sixes
|
6
|
+
|
7
|
+
COLORS = {
|
8
|
+
'aliceblue' => 'f0f8ff',
|
9
|
+
'antiquewhite' => 'faebd7',
|
10
|
+
'aqua' => '00ffff',
|
11
|
+
'aquamarine' => '7fffd4',
|
12
|
+
'azure' => 'f0ffff',
|
13
|
+
'beige' => 'f5f5dc',
|
14
|
+
'bisque' => 'ffe4c4',
|
15
|
+
'black' => '000000',
|
16
|
+
'blanchedalmond' => 'ffebcd',
|
17
|
+
'blue' => '0000ff',
|
18
|
+
'blueviolet' => '8a2be2',
|
19
|
+
'brown' => 'a52a2a',
|
20
|
+
'burlywood' => 'deb887',
|
21
|
+
'cadetblue' => '5f9ea0',
|
22
|
+
'chartreuse' => '7fff00',
|
23
|
+
'chocolate' => 'd2691e',
|
24
|
+
'coral' => 'ff7f50',
|
25
|
+
'cornflowerblue' => '6495ed',
|
26
|
+
'cornsilk' => 'fff8dc',
|
27
|
+
'crimson' => 'dc143c',
|
28
|
+
'cyan' => '00ffff',
|
29
|
+
'darkblue' => '00008b',
|
30
|
+
'darkcyan' => '008b8b',
|
31
|
+
'darkgoldenrod' => 'b8860b',
|
32
|
+
'darkgray' => 'a9a9a9',
|
33
|
+
'darkgreen' => '006400',
|
34
|
+
'darkgrey' => 'a9a9a9',
|
35
|
+
'darkkhaki' => 'bdb76b',
|
36
|
+
'darkmagenta' => '8b008b',
|
37
|
+
'darkolivegreen' => '556b2f',
|
38
|
+
'darkorange' => 'ff8c00',
|
39
|
+
'darkorchid' => '9932cc',
|
40
|
+
'darkred' => '8b0000',
|
41
|
+
'darksalmon' => 'e9967a',
|
42
|
+
'darkseagreen' => '8fbc8f',
|
43
|
+
'darkslateblue' => '483d8b',
|
44
|
+
'darkslategray' => '2f4f4f',
|
45
|
+
'darkslategrey' => '2f4f4f',
|
46
|
+
'darkturquoise' => '00ced1',
|
47
|
+
'darkviolet' => '9400d3',
|
48
|
+
'deeppink' => 'ff1493',
|
49
|
+
'deepskyblue' => '00bfff',
|
50
|
+
'dimgray' => '696969',
|
51
|
+
'dimgrey' => '696969',
|
52
|
+
'dodgerblue' => '1e90ff',
|
53
|
+
'firebrick' => 'b22222',
|
54
|
+
'floralwhite' => 'fffaf0',
|
55
|
+
'forestgreen' => '228b22',
|
56
|
+
'fuchsia' => 'ff00ff',
|
57
|
+
'gainsboro' => 'dcdcdc',
|
58
|
+
'ghostwhite' => 'f8f8ff',
|
59
|
+
'gold' => 'ffd700',
|
60
|
+
'goldenrod' => 'daa520',
|
61
|
+
'gray' => '808080',
|
62
|
+
'green' => '008000',
|
63
|
+
'greenyellow' => 'adff2f',
|
64
|
+
'grey' => '808080',
|
65
|
+
'honeydew' => 'f0fff0',
|
66
|
+
'hotpink' => 'ff69b4',
|
67
|
+
'indianred' => 'cd5c5c',
|
68
|
+
'indigo' => '4b0082',
|
69
|
+
'ivory' => 'fffff0',
|
70
|
+
'khaki' => 'f0e68c',
|
71
|
+
'lavender' => 'e6e6fa',
|
72
|
+
'lavenderblush' => 'fff0f5',
|
73
|
+
'lawngreen' => '7cfc00',
|
74
|
+
'lemonchiffon' => 'fffacd',
|
75
|
+
'lightblue' => 'add8e6',
|
76
|
+
'lightcoral' => 'f08080',
|
77
|
+
'lightcyan' => 'e0ffff',
|
78
|
+
'lightgoldenrodyellow' => 'fafad2',
|
79
|
+
'lightgray' => 'd3d3d3',
|
80
|
+
'lightgreen' => '90ee90',
|
81
|
+
'lightgrey' => 'd3d3d3',
|
82
|
+
'lightpink' => 'ffb6c1',
|
83
|
+
'lightsalmon' => 'ffa07a',
|
84
|
+
'lightseagreen' => '20b2aa',
|
85
|
+
'lightskyblue' => '87cefa',
|
86
|
+
'lightslategray' => '778899',
|
87
|
+
'lightslategrey' => '778899',
|
88
|
+
'lightsteelblue' => 'b0c4de',
|
89
|
+
'lightyellow' => 'ffffe0',
|
90
|
+
'lime' => '00ff00',
|
91
|
+
'limegreen' => '32cd32',
|
92
|
+
'linen' => 'faf0e6',
|
93
|
+
'magenta' => 'ff00ff',
|
94
|
+
'maroon' => '800000',
|
95
|
+
'mediumaquamarine' => '66cdaa',
|
96
|
+
'mediumblue' => '0000cd',
|
97
|
+
'mediumorchid' => 'ba55d3',
|
98
|
+
'mediumpurple' => '9370db',
|
99
|
+
'mediumseagreen' => '3cb371',
|
100
|
+
'mediumslateblue' => '7b68ee',
|
101
|
+
'mediumspringgreen' => '00fa9a',
|
102
|
+
'mediumturquoise' => '48d1cc',
|
103
|
+
'mediumvioletred' => 'c71585',
|
104
|
+
'midnightblue' => '191970',
|
105
|
+
'mintcream' => 'f5fffa',
|
106
|
+
'mistyrose' => 'ffe4e1',
|
107
|
+
'moccasin' => 'ffe4b5',
|
108
|
+
'navajowhite' => 'ffdead',
|
109
|
+
'navy' => '000080',
|
110
|
+
'oldlace' => 'fdf5e6',
|
111
|
+
'olive' => '808000',
|
112
|
+
'olivedrab' => '6b8e23',
|
113
|
+
'orange' => 'ffa500',
|
114
|
+
'orangered' => 'ff4500',
|
115
|
+
'orchid' => 'da70d6',
|
116
|
+
'palegoldenrod' => 'eee8aa',
|
117
|
+
'palegreen' => '98fb98',
|
118
|
+
'paleturquoise' => 'afeeee',
|
119
|
+
'palevioletred' => 'db7093',
|
120
|
+
'papayawhip' => 'ffefd5',
|
121
|
+
'peachpuff' => 'ffdab9',
|
122
|
+
'peru' => 'cd853f',
|
123
|
+
'pink' => 'ffc0cb',
|
124
|
+
'plum' => 'dda0dd',
|
125
|
+
'powderblue' => 'b0e0e6',
|
126
|
+
'purple' => '800080',
|
127
|
+
'rebeccapurple' => '663399',
|
128
|
+
'red' => 'ff0000',
|
129
|
+
'rosybrown' => 'bc8f8f',
|
130
|
+
'royalblue' => '4169e1',
|
131
|
+
'saddlebrown' => '8b4513',
|
132
|
+
'salmon' => 'fa8072',
|
133
|
+
'sandybrown' => 'f4a460',
|
134
|
+
'seagreen' => '2e8b57',
|
135
|
+
'seashell' => 'fff5ee',
|
136
|
+
'sienna' => 'a0522d',
|
137
|
+
'silver' => 'c0c0c0',
|
138
|
+
'skyblue' => '87ceeb',
|
139
|
+
'slateblue' => '6a5acd',
|
140
|
+
'slategray' => '708090',
|
141
|
+
'slategrey' => '708090',
|
142
|
+
'snow' => 'fffafa',
|
143
|
+
'springgreen' => '00ff7f',
|
144
|
+
'steelblue' => '4682b4',
|
145
|
+
'tan' => 'd2b48c',
|
146
|
+
'teal' => '008080',
|
147
|
+
'thistle' => 'd8bfd8',
|
148
|
+
'tomato' => 'ff6347',
|
149
|
+
'turquoise' => '40e0d0',
|
150
|
+
'violet' => 'ee82ee',
|
151
|
+
'wheat' => 'f5deb3',
|
152
|
+
'white' => 'ffffff',
|
153
|
+
'whitesmoke' => 'f5f5f5',
|
154
|
+
'yellow' => 'ffff00',
|
155
|
+
'yellowgreen' => '9acd32'
|
156
|
+
}.freeze
|
157
|
+
|
158
|
+
def append_html(pdf, html)
|
159
|
+
PrawnHtml::Instance.new(pdf).append(html: html)
|
160
|
+
end
|
161
|
+
|
162
|
+
module_function :append_html
|
163
|
+
end
|
164
|
+
|
165
|
+
require 'prawn'
|
166
|
+
require 'prawn/table'
|
167
|
+
|
168
|
+
require 'prawn_html/utils'
|
169
|
+
|
170
|
+
Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
|
171
|
+
|
172
|
+
require 'prawn_html/tag'
|
173
|
+
Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
|
174
|
+
|
175
|
+
require 'prawn_html/attributes'
|
176
|
+
require 'prawn_html/context'
|
177
|
+
require 'prawn_html/pdf_wrapper'
|
178
|
+
require 'prawn_html/document_renderer'
|
179
|
+
require 'prawn_html/html_parser'
|
180
|
+
require 'prawn_html/instance'
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
module PrawnHtml
|
7
|
+
class Attributes < OpenStruct
|
8
|
+
attr_reader :initial, :styles
|
9
|
+
|
10
|
+
STYLES_APPLY = {
|
11
|
+
block: %i[align bottom leading left margin_left padding_left position right top],
|
12
|
+
tag_close: %i[margin_bottom padding_bottom break_after],
|
13
|
+
tag_open: %i[margin_top padding_top break_before],
|
14
|
+
text_node: %i[callback character_spacing color font link list_style_type size styles white_space]
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
STYLES_LIST = {
|
18
|
+
# text node styles
|
19
|
+
'background' => { key: :callback, set: :callback_background },
|
20
|
+
'color' => { key: :color, set: :convert_color },
|
21
|
+
'font-family' => { key: :font, set: :filter_font_family },
|
22
|
+
'font-size' => { key: :size, set: :convert_size },
|
23
|
+
'font-style' => { key: :styles, set: :append_styles, values: %i[italic] },
|
24
|
+
'font-weight' => { key: :styles, set: :append_styles, values: %i[bold] },
|
25
|
+
'href' => { key: :link, set: :copy_value },
|
26
|
+
'letter-spacing' => { key: :character_spacing, set: :convert_float },
|
27
|
+
'list-style-type' => { key: :list_style_type, set: :unquote },
|
28
|
+
'text-decoration' => { key: :styles, set: :append_styles, values: %i[underline] },
|
29
|
+
'vertical-align' => { key: :styles, set: :append_styles, values: %i[subscript superscript] },
|
30
|
+
'white-space' => { key: :white_space, set: :convert_symbol },
|
31
|
+
# tag opening styles
|
32
|
+
'break-before' => { key: :break_before, set: :convert_symbol },
|
33
|
+
'margin-top' => { key: :margin_top, set: :convert_size },
|
34
|
+
'padding-top' => { key: :padding_top, set: :convert_size },
|
35
|
+
# tag closing styles
|
36
|
+
'break-after' => { key: :break_after, set: :convert_symbol },
|
37
|
+
'margin-bottom' => { key: :margin_bottom, set: :convert_size },
|
38
|
+
'padding-bottom' => { key: :padding_bottom, set: :convert_size },
|
39
|
+
# block styles
|
40
|
+
'bottom' => { key: :bottom, set: :convert_size, options: :height },
|
41
|
+
'left' => { key: :left, set: :convert_size, options: :width },
|
42
|
+
'line-height' => { key: :leading, set: :convert_size },
|
43
|
+
'margin-left' => { key: :margin_left, set: :convert_size },
|
44
|
+
'padding-left' => { key: :padding_left, set: :convert_size },
|
45
|
+
'position' => { key: :position, set: :convert_symbol },
|
46
|
+
'right' => { key: :right, set: :convert_size, options: :width },
|
47
|
+
'text-align' => { key: :align, set: :convert_symbol },
|
48
|
+
'top' => { key: :top, set: :convert_size, options: :height },
|
49
|
+
# special styles
|
50
|
+
'text-decoration-line-through' => { key: :callback, set: :callback_strike_through }
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
STYLES_MERGE = %i[margin_left padding_left].freeze
|
54
|
+
|
55
|
+
# Init the Attributes
|
56
|
+
def initialize(attributes = {})
|
57
|
+
super
|
58
|
+
@styles = {} # result styles
|
59
|
+
@initial = Set.new
|
60
|
+
end
|
61
|
+
|
62
|
+
# Processes the data attributes
|
63
|
+
#
|
64
|
+
# @return [Hash] hash of data attributes with 'data-' prefix removed and stripped values
|
65
|
+
def data
|
66
|
+
to_h.each_with_object({}) do |(key, value), res|
|
67
|
+
data_key = key.match /\Adata-(.+)/
|
68
|
+
res[data_key[1]] = value.strip if data_key
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Merge text styles
|
73
|
+
#
|
74
|
+
# @param text_styles [String] styles to parse and process
|
75
|
+
# @param options [Hash] options (container width/height/etc.)
|
76
|
+
def merge_text_styles!(text_styles, options: {})
|
77
|
+
hash_styles = Attributes.parse_styles(text_styles)
|
78
|
+
process_styles(hash_styles, options: options) unless hash_styles.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Remove an attribute value from the context styles
|
82
|
+
#
|
83
|
+
# @param context_styles [Hash] hash of the context styles that will be updated
|
84
|
+
# @param rule [Hash] rule from the STYLES_LIST to lookup in the context style for value removal
|
85
|
+
def remove_value(context_styles, rule)
|
86
|
+
if rule[:set] == :append_styles
|
87
|
+
context_styles[rule[:key]] -= rule[:values] if context_styles[:styles]
|
88
|
+
else
|
89
|
+
default = Context::DEFAULT_STYLES[rule[:key]]
|
90
|
+
default ? (context_styles[rule[:key]] = default) : context_styles.delete(rule[:key])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Update context styles applying the initial rules (if set)
|
95
|
+
#
|
96
|
+
# @param context_styles [Hash] hash of the context styles that will be updated
|
97
|
+
#
|
98
|
+
# @return [Hash] the update context styles
|
99
|
+
def update_styles(context_styles)
|
100
|
+
initial.each do |rule|
|
101
|
+
next unless rule
|
102
|
+
|
103
|
+
remove_value(context_styles, rule)
|
104
|
+
end
|
105
|
+
context_styles
|
106
|
+
end
|
107
|
+
|
108
|
+
class << self
|
109
|
+
# Merges attributes
|
110
|
+
#
|
111
|
+
# @param attributes [Hash] target attributes hash
|
112
|
+
# @param key [Symbol] key
|
113
|
+
# @param value
|
114
|
+
#
|
115
|
+
# @return [Hash] the updated hash of attributes
|
116
|
+
def merge_attr!(attributes, key, value)
|
117
|
+
return unless key
|
118
|
+
return (attributes[key] = value) unless Attributes::STYLES_MERGE.include?(key)
|
119
|
+
|
120
|
+
attributes[key] ||= 0
|
121
|
+
attributes[key] += value
|
122
|
+
end
|
123
|
+
|
124
|
+
# Parses a string of styles
|
125
|
+
#
|
126
|
+
# @param styles [String] styles to parse
|
127
|
+
#
|
128
|
+
# @return [Hash] hash of styles
|
129
|
+
def parse_styles(styles)
|
130
|
+
(styles || '').scan(/\s*([^:;]+)\s*:\s*([^;]+)\s*/).to_h
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def process_styles(hash_styles, options:)
|
137
|
+
hash_styles.each do |key, value|
|
138
|
+
rule = evaluate_rule(key, value)
|
139
|
+
next unless rule
|
140
|
+
|
141
|
+
apply_rule!(merged_styles: @styles, rule: rule, value: value, options: options)
|
142
|
+
end
|
143
|
+
@styles
|
144
|
+
end
|
145
|
+
|
146
|
+
def evaluate_rule(rule_key, attr_value)
|
147
|
+
key = nil
|
148
|
+
key = 'text-decoration-line-through' if rule_key == 'text-decoration' && attr_value == 'line-through'
|
149
|
+
key ||= rule_key
|
150
|
+
STYLES_LIST[key]
|
151
|
+
end
|
152
|
+
|
153
|
+
def apply_rule!(merged_styles:, rule:, value:, options:)
|
154
|
+
return (@initial << rule) if value == 'initial'
|
155
|
+
|
156
|
+
if rule[:set] == :append_styles
|
157
|
+
val = Utils.normalize_style(value, rule[:values])
|
158
|
+
(merged_styles[rule[:key]] ||= []) << val if val
|
159
|
+
else
|
160
|
+
opts = rule[:options] ? options[rule[:options]] : nil
|
161
|
+
val = Utils.send(rule[:set], value, options: opts)
|
162
|
+
merged_styles[rule[:key]] = val if val
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrawnHtml
|
4
|
+
module Callbacks
|
5
|
+
class Background
|
6
|
+
DEF_HIGHLIGHT = 'ffff00'
|
7
|
+
|
8
|
+
def initialize(pdf, color = nil)
|
9
|
+
@pdf = pdf
|
10
|
+
@color = color || DEF_HIGHLIGHT
|
11
|
+
end
|
12
|
+
|
13
|
+
def render_behind(fragment)
|
14
|
+
top, left = fragment.top_left
|
15
|
+
@pdf.draw_rectangle(x: left, y: top, width: fragment.width, height: fragment.height, color: @color)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PrawnHtml
|
4
|
+
module Callbacks
|
5
|
+
class StrikeThrough
|
6
|
+
def initialize(pdf, _item)
|
7
|
+
@pdf = pdf
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_in_front(fragment)
|
11
|
+
x1 = fragment.left
|
12
|
+
x2 = fragment.right
|
13
|
+
y = (fragment.top + fragment.bottom) / 2
|
14
|
+
@pdf.underline(x1: x1, x2: x2, y: y)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|