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.
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