prawn-table-html 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![gem version](https://badge.fury.io/rb/prawn-html.svg)](https://rubygems.org/gems/prawn-html)
|
3
|
+
[![gem downloads](https://badgen.net/rubygems/dt/prawn-html)](https://rubygems.org/gems/prawn-html)
|
4
|
+
[![maintainability](https://api.codeclimate.com/v1/badges/db674db00817d56ca1e9/maintainability)](https://codeclimate.com/github/blocknotes/prawn-html/maintainability)
|
5
|
+
[![linters](https://github.com/blocknotes/prawn-html/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/prawn-html/actions/workflows/linters.yml)
|
6
|
+
[![specs](https://github.com/blocknotes/prawn-html/actions/workflows/specs.yml/badge.svg)](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
|