prawn-table-html 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +236 -0
  4. data/lib/prawn-html.rb +180 -0
  5. data/lib/prawn_html/attributes.rb +166 -0
  6. data/lib/prawn_html/callbacks/background.rb +19 -0
  7. data/lib/prawn_html/callbacks/strike_through.rb +18 -0
  8. data/lib/prawn_html/context.rb +100 -0
  9. data/lib/prawn_html/document_renderer.rb +172 -0
  10. data/lib/prawn_html/html_parser.rb +104 -0
  11. data/lib/prawn_html/instance.rb +18 -0
  12. data/lib/prawn_html/pdf_wrapper.rb +145 -0
  13. data/lib/prawn_html/tag.rb +93 -0
  14. data/lib/prawn_html/tags/a.rb +20 -0
  15. data/lib/prawn_html/tags/b.rb +13 -0
  16. data/lib/prawn_html/tags/blockquote.rb +25 -0
  17. data/lib/prawn_html/tags/body.rb +13 -0
  18. data/lib/prawn_html/tags/br.rb +21 -0
  19. data/lib/prawn_html/tags/code.rb +13 -0
  20. data/lib/prawn_html/tags/col.rb +37 -0
  21. data/lib/prawn_html/tags/colgroup.rb +13 -0
  22. data/lib/prawn_html/tags/del.rb +13 -0
  23. data/lib/prawn_html/tags/div.rb +13 -0
  24. data/lib/prawn_html/tags/h.rb +49 -0
  25. data/lib/prawn_html/tags/hr.rb +39 -0
  26. data/lib/prawn_html/tags/i.rb +13 -0
  27. data/lib/prawn_html/tags/img.rb +31 -0
  28. data/lib/prawn_html/tags/li.rb +39 -0
  29. data/lib/prawn_html/tags/mark.rb +13 -0
  30. data/lib/prawn_html/tags/ol.rb +43 -0
  31. data/lib/prawn_html/tags/p.rb +23 -0
  32. data/lib/prawn_html/tags/pre.rb +25 -0
  33. data/lib/prawn_html/tags/small.rb +15 -0
  34. data/lib/prawn_html/tags/span.rb +9 -0
  35. data/lib/prawn_html/tags/sub.rb +13 -0
  36. data/lib/prawn_html/tags/sup.rb +13 -0
  37. data/lib/prawn_html/tags/table.rb +53 -0
  38. data/lib/prawn_html/tags/tbody.rb +13 -0
  39. data/lib/prawn_html/tags/td.rb +43 -0
  40. data/lib/prawn_html/tags/th.rb +43 -0
  41. data/lib/prawn_html/tags/tr.rb +37 -0
  42. data/lib/prawn_html/tags/u.rb +13 -0
  43. data/lib/prawn_html/tags/ul.rb +40 -0
  44. data/lib/prawn_html/utils.rb +139 -0
  45. data/lib/prawn_html/version.rb +5 -0
  46. 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