prawn-html 0.2.0 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 319cd3772829ddae0d52b3228b8544cb3906c32c58725082ce95da675bdb841f
4
- data.tar.gz: e4cc3ec1cdbbe8ad655ef236d44a2c179178cf913d83f7bb307b2fe529927ae1
3
+ metadata.gz: 3eec6595a60748790725f32e92e7b749d92086e5331d8a572d287a3a90f9d83a
4
+ data.tar.gz: 89bcbe740f84c4ecdea2877e96d0f677a25bf424325b41fa40caf3d1351fba96
5
5
  SHA512:
6
- metadata.gz: 58b388050c3f8c94075bab09353ca3974956b9eebb3fa1a7fdfff488ced919dc8182d744c620d2c2268b4fd26b76bbeaacfcb58717fdeee7c16862665176e66c
7
- data.tar.gz: 386d529584f5cc8b9b6c326500270761c3a53f859243714980cb188d9496f820158ed83e1d399dc361e368b0a63b2eeaa585843719d83ca2fa75729b6ebffef4
6
+ metadata.gz: 9b34f31b07aaac30910fd1bcb2118b9cbe92bbdaf4ac80850520887e7c3803a6bb1217764551baeeb1a88d695d7b26c4885a4d3065bbd1f20527d37a5723c7bf
7
+ data.tar.gz: 6ed594bd070e4e4cf20519c28094f7d57eeb779f60c4867f380aab22946d20401bac307e17db47c0560b2cf1cdc591d80e07c3ff54999db92bd3040e19cc18d7
data/README.md CHANGED
@@ -11,7 +11,7 @@ Features:
11
11
  - custom [data attributes](#data-attributes) for Prawn PDF features;
12
12
  - no extra settings: it just parses an input HTML and outputs to a Prawn PDF document.
13
13
 
14
- **Notice**: render HTML documents properly is not an easy task, this gem support only some HTML tags and a small set of CSS attributes. If you need more rendering accuracy take a look at other projects like WickedPDF.
14
+ **Notice**: render HTML documents properly is not an easy task, this gem support only some HTML tags and a small set of CSS attributes. If you need more rendering accuracy take a look at other projects like *WickedPDF* or *PDFKit*.
15
15
 
16
16
  > [prawn-styled-text](https://github.com/blocknotes/prawn-styled-text) rewritten from scratch, finally!
17
17
 
@@ -39,7 +39,9 @@ HTML tags:
39
39
 
40
40
  - **a**: link
41
41
  - **b**: bold
42
+ - **blockquote**: block quotation element
42
43
  - **br**: new line
44
+ - **code**: inline code element
43
45
  - **del**: strike-through
44
46
  - **div**: block element
45
47
  - **em**: italic
@@ -50,34 +52,50 @@ HTML tags:
50
52
  - **img**: image
51
53
  - **li**: list item
52
54
  - **mark**: highlight
55
+ - **ol**: ordered list
53
56
  - **p**: block element
57
+ - **pre**: preformatted text element
54
58
  - **s**: strike-through
55
59
  - **small**: smaller text
56
60
  - **span**: inline element
57
61
  - **strong**: bold
62
+ - **sub**: subscript element
63
+ - **sup**: superscript element
58
64
  - **u**: underline
59
- - **ul**: list
65
+ - **ul**: unordered list
60
66
 
61
67
  CSS attributes (dimensional units are ignored and considered in pixel):
62
68
 
63
- - **background**: for *mark* tag, only 3 or 6 hex digits format, ex. `style="background: #FECD08"`
64
- - **color**: only 3 or 6 hex digits format - ex. `style="color: #FB1"`
69
+ - **background**: for *mark* tag (3/6 hex digits or RGB or color name), ex. `style="background: #FECD08"`
70
+ - **break-after**: go to a new page after some elements, ex. `style="break-after: auto"`
71
+ - **break-before**: go to a new page before some elements, ex. `style="break-before: auto"`
72
+ - **color**: (3/6 hex digits or RGB or color name) ex. `style="color: #FB1"`
65
73
  - **font-family**: font must be registered, quotes are optional, ex. `style="font-family: Courier"`
66
74
  - **font-size**: ex. `style="font-size: 20px"`
67
75
  - **font-style**: values: *:italic*, ex. `style="font-style: italic"`
68
76
  - **font-weight**: values: *:bold*, ex. `style="font-weight: bold"`
69
77
  - **height**: for *img* tag, ex. `<img src="image.jpg" style="height: 200px"/>`
70
78
  - **href**: for *a* tag, ex. `<a href="http://www.google.com/">Google</a>`
79
+ - **left**: see *position (absolute)*
71
80
  - **letter-spacing**: ex. `style="letter-spacing: 1.5"`
72
81
  - **line-height**: ex. `style="line-height: 10px"`
82
+ - **list-style-type**: for *ul*, a string, ex. `style="list-style-type: '- '"`
73
83
  - **margin-bottom**: ex. `style="margin-bottom: 10px"`
74
84
  - **margin-left**: ex. `style="margin-left: 15px"`
75
85
  - **margin-top**: ex. `style="margin-top: 20px"`
86
+ - **position**: `absolute`, ex. `style="position: absolute; left: 20px; top: 100px"`
76
87
  - **src**: for *img* tag, ex. `<img src="image.jpg"/>`
77
88
  - **text-align**: `left` | `center` | `right` | `justify`, ex. `style="text-align: center"`
78
89
  - **text-decoration**: `underline`, ex. `style="text-decoration: underline"`
90
+ - **top**: see *position (absolute)*
79
91
  - **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
80
92
 
93
+ For colors, the supported formats are:
94
+ - 3 hex digits, ex. `color: #FB1`;
95
+ - 6 hex digits, ex. `color: #abcdef`;
96
+ - RGB, ex. `color: RGB(64, 0, 128)`;
97
+ - color name, ex. `color: yellow`.
98
+
81
99
  ## Data attributes
82
100
 
83
101
  Some custom data attributes are used to pass options:
@@ -87,8 +105,7 @@ Some custom data attributes are used to pass options:
87
105
 
88
106
  ## Document styles
89
107
 
90
- [Experimental feature] You can define document CSS rules inside an _head_ tag, but with a limited support for now.
91
- Only single CSS selectors and basic ones are supported. Example:
108
+ You can define document CSS rules inside an _head_ tag. Example:
92
109
 
93
110
  ```html
94
111
  <!DOCTYPE html>
@@ -113,6 +130,28 @@ Only single CSS selectors and basic ones are supported. Example:
113
130
  </html>
114
131
  ```
115
132
 
133
+ ## Additional notes
134
+
135
+ ### Rails: generate PDF on the fly
136
+
137
+ Sample controller's action to create a PDF from Rails:
138
+
139
+ ```rb
140
+ class SomeController < ApplicationController
141
+ def sample_action
142
+ respond_to do |format|
143
+ format.pdf do
144
+ pdf = Prawn::Document.new
145
+ PrawnHtml.append_html(pdf, '<h1 style="text-align: center">Just a test</h1>')
146
+ send_data(pdf.render, filename: 'sample.pdf', type: 'application/pdf')
147
+ end
148
+ end
149
+ end
150
+ end
151
+ ```
152
+
153
+ More details in this blogpost: [generate PDF from HTML](https://www.blocknot.es/2021-08-20-rails-generate-pdf-from-html/)
154
+
116
155
  ## Do you like it? Star it!
117
156
 
118
157
  If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
data/lib/prawn-html.rb CHANGED
@@ -1,25 +1,180 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'oga'
4
- require 'prawn'
5
-
6
- require 'prawn_html/tag'
7
- Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
8
-
9
- Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
10
-
11
- require 'prawn_html/attributes'
12
- require 'prawn_html/context'
13
- require 'prawn_html/document_renderer'
14
- require 'prawn_html/html_handler'
15
-
16
3
  module PrawnHtml
17
4
  PX = 0.66 # conversion constant for pixel sixes
18
5
 
6
+ COLORS = {
7
+ 'aliceblue' => 'f0f8ff',
8
+ 'antiquewhite' => 'faebd7',
9
+ 'aqua' => '00ffff',
10
+ 'aquamarine' => '7fffd4',
11
+ 'azure' => 'f0ffff',
12
+ 'beige' => 'f5f5dc',
13
+ 'bisque' => 'ffe4c4',
14
+ 'black' => '000000',
15
+ 'blanchedalmond' => 'ffebcd',
16
+ 'blue' => '0000ff',
17
+ 'blueviolet' => '8a2be2',
18
+ 'brown' => 'a52a2a',
19
+ 'burlywood' => 'deb887',
20
+ 'cadetblue' => '5f9ea0',
21
+ 'chartreuse' => '7fff00',
22
+ 'chocolate' => 'd2691e',
23
+ 'coral' => 'ff7f50',
24
+ 'cornflowerblue' => '6495ed',
25
+ 'cornsilk' => 'fff8dc',
26
+ 'crimson' => 'dc143c',
27
+ 'cyan' => '00ffff',
28
+ 'darkblue' => '00008b',
29
+ 'darkcyan' => '008b8b',
30
+ 'darkgoldenrod' => 'b8860b',
31
+ 'darkgray' => 'a9a9a9',
32
+ 'darkgreen' => '006400',
33
+ 'darkgrey' => 'a9a9a9',
34
+ 'darkkhaki' => 'bdb76b',
35
+ 'darkmagenta' => '8b008b',
36
+ 'darkolivegreen' => '556b2f',
37
+ 'darkorange' => 'ff8c00',
38
+ 'darkorchid' => '9932cc',
39
+ 'darkred' => '8b0000',
40
+ 'darksalmon' => 'e9967a',
41
+ 'darkseagreen' => '8fbc8f',
42
+ 'darkslateblue' => '483d8b',
43
+ 'darkslategray' => '2f4f4f',
44
+ 'darkslategrey' => '2f4f4f',
45
+ 'darkturquoise' => '00ced1',
46
+ 'darkviolet' => '9400d3',
47
+ 'deeppink' => 'ff1493',
48
+ 'deepskyblue' => '00bfff',
49
+ 'dimgray' => '696969',
50
+ 'dimgrey' => '696969',
51
+ 'dodgerblue' => '1e90ff',
52
+ 'firebrick' => 'b22222',
53
+ 'floralwhite' => 'fffaf0',
54
+ 'forestgreen' => '228b22',
55
+ 'fuchsia' => 'ff00ff',
56
+ 'gainsboro' => 'dcdcdc',
57
+ 'ghostwhite' => 'f8f8ff',
58
+ 'gold' => 'ffd700',
59
+ 'goldenrod' => 'daa520',
60
+ 'gray' => '808080',
61
+ 'green' => '008000',
62
+ 'greenyellow' => 'adff2f',
63
+ 'grey' => '808080',
64
+ 'honeydew' => 'f0fff0',
65
+ 'hotpink' => 'ff69b4',
66
+ 'indianred' => 'cd5c5c',
67
+ 'indigo' => '4b0082',
68
+ 'ivory' => 'fffff0',
69
+ 'khaki' => 'f0e68c',
70
+ 'lavender' => 'e6e6fa',
71
+ 'lavenderblush' => 'fff0f5',
72
+ 'lawngreen' => '7cfc00',
73
+ 'lemonchiffon' => 'fffacd',
74
+ 'lightblue' => 'add8e6',
75
+ 'lightcoral' => 'f08080',
76
+ 'lightcyan' => 'e0ffff',
77
+ 'lightgoldenrodyellow' => 'fafad2',
78
+ 'lightgray' => 'd3d3d3',
79
+ 'lightgreen' => '90ee90',
80
+ 'lightgrey' => 'd3d3d3',
81
+ 'lightpink' => 'ffb6c1',
82
+ 'lightsalmon' => 'ffa07a',
83
+ 'lightseagreen' => '20b2aa',
84
+ 'lightskyblue' => '87cefa',
85
+ 'lightslategray' => '778899',
86
+ 'lightslategrey' => '778899',
87
+ 'lightsteelblue' => 'b0c4de',
88
+ 'lightyellow' => 'ffffe0',
89
+ 'lime' => '00ff00',
90
+ 'limegreen' => '32cd32',
91
+ 'linen' => 'faf0e6',
92
+ 'magenta' => 'ff00ff',
93
+ 'maroon' => '800000',
94
+ 'mediumaquamarine' => '66cdaa',
95
+ 'mediumblue' => '0000cd',
96
+ 'mediumorchid' => 'ba55d3',
97
+ 'mediumpurple' => '9370db',
98
+ 'mediumseagreen' => '3cb371',
99
+ 'mediumslateblue' => '7b68ee',
100
+ 'mediumspringgreen' => '00fa9a',
101
+ 'mediumturquoise' => '48d1cc',
102
+ 'mediumvioletred' => 'c71585',
103
+ 'midnightblue' => '191970',
104
+ 'mintcream' => 'f5fffa',
105
+ 'mistyrose' => 'ffe4e1',
106
+ 'moccasin' => 'ffe4b5',
107
+ 'navajowhite' => 'ffdead',
108
+ 'navy' => '000080',
109
+ 'oldlace' => 'fdf5e6',
110
+ 'olive' => '808000',
111
+ 'olivedrab' => '6b8e23',
112
+ 'orange' => 'ffa500',
113
+ 'orangered' => 'ff4500',
114
+ 'orchid' => 'da70d6',
115
+ 'palegoldenrod' => 'eee8aa',
116
+ 'palegreen' => '98fb98',
117
+ 'paleturquoise' => 'afeeee',
118
+ 'palevioletred' => 'db7093',
119
+ 'papayawhip' => 'ffefd5',
120
+ 'peachpuff' => 'ffdab9',
121
+ 'peru' => 'cd853f',
122
+ 'pink' => 'ffc0cb',
123
+ 'plum' => 'dda0dd',
124
+ 'powderblue' => 'b0e0e6',
125
+ 'purple' => '800080',
126
+ 'rebeccapurple' => '663399',
127
+ 'red' => 'ff0000',
128
+ 'rosybrown' => 'bc8f8f',
129
+ 'royalblue' => '4169e1',
130
+ 'saddlebrown' => '8b4513',
131
+ 'salmon' => 'fa8072',
132
+ 'sandybrown' => 'f4a460',
133
+ 'seagreen' => '2e8b57',
134
+ 'seashell' => 'fff5ee',
135
+ 'sienna' => 'a0522d',
136
+ 'silver' => 'c0c0c0',
137
+ 'skyblue' => '87ceeb',
138
+ 'slateblue' => '6a5acd',
139
+ 'slategray' => '708090',
140
+ 'slategrey' => '708090',
141
+ 'snow' => 'fffafa',
142
+ 'springgreen' => '00ff7f',
143
+ 'steelblue' => '4682b4',
144
+ 'tan' => 'd2b48c',
145
+ 'teal' => '008080',
146
+ 'thistle' => 'd8bfd8',
147
+ 'tomato' => 'ff6347',
148
+ 'turquoise' => '40e0d0',
149
+ 'violet' => 'ee82ee',
150
+ 'wheat' => 'f5deb3',
151
+ 'white' => 'ffffff',
152
+ 'whitesmoke' => 'f5f5f5',
153
+ 'yellow' => 'ffff00',
154
+ 'yellowgreen' => '9acd32'
155
+ }.freeze
156
+
19
157
  def append_html(pdf, html)
20
- handler = PrawnHtml::HtmlHandler.new(pdf)
21
- handler.process(html)
158
+ pdf_wrapper = PdfWrapper.new(pdf)
159
+ renderer = DocumentRenderer.new(pdf_wrapper)
160
+ html_parser = PrawnHtml::HtmlParser.new(renderer)
161
+ html_parser.process(html)
22
162
  end
23
163
 
24
164
  module_function :append_html
25
165
  end
166
+
167
+ require 'prawn'
168
+
169
+ require 'prawn_html/utils'
170
+
171
+ Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
172
+
173
+ require 'prawn_html/tag'
174
+ Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
175
+
176
+ require 'prawn_html/attributes'
177
+ require 'prawn_html/context'
178
+ require 'prawn_html/pdf_wrapper'
179
+ require 'prawn_html/document_renderer'
180
+ require 'prawn_html/html_parser'
@@ -7,35 +7,43 @@ module PrawnHtml
7
7
  attr_reader :styles
8
8
 
9
9
  STYLES_APPLY = {
10
- block: %i[align leading margin_left padding_left],
11
- tag_close: %i[margin_bottom padding_bottom],
12
- tag_open: %i[margin_top padding_top],
13
- text_node: %i[background callback character_spacing color font link size styles]
10
+ block: %i[align leading left margin_left padding_left position top],
11
+ tag_close: %i[margin_bottom padding_bottom break_after],
12
+ tag_open: %i[margin_top padding_top break_before],
13
+ text_node: %i[background callback character_spacing color font link list_style_type size styles white_space]
14
14
  }.freeze
15
15
 
16
16
  STYLES_LIST = {
17
17
  # text node styles
18
18
  'background' => { key: :background, set: :convert_color },
19
- 'callback' => { key: :callback, set: :copy },
19
+ 'callback' => { key: :callback, set: :copy_value },
20
20
  'color' => { key: :color, set: :convert_color },
21
21
  'font-family' => { key: :font, set: :unquote },
22
22
  'font-size' => { key: :size, set: :convert_size },
23
- 'font-style' => { key: :styles, set: :append_symbol },
24
- 'font-weight' => { key: :styles, set: :append_symbol },
25
- 'href' => { key: :link, set: :copy },
23
+ 'font-style' => { key: :styles, set: :append_styles },
24
+ 'font-weight' => { key: :styles, set: :append_styles },
25
+ 'href' => { key: :link, set: :copy_value },
26
26
  'letter-spacing' => { key: :character_spacing, set: :convert_float },
27
- 'text-decoration' => { key: :styles, set: :append_symbol },
27
+ 'list-style-type' => { key: :list_style_type, set: :unquote },
28
+ 'text-decoration' => { key: :styles, set: :append_styles },
29
+ 'vertical-align' => { key: :styles, set: :append_styles },
30
+ 'white-space' => { key: :white_space, set: :convert_symbol },
28
31
  # tag opening styles
32
+ 'break-before' => { key: :break_before, set: :convert_symbol },
29
33
  'margin-top' => { key: :margin_top, set: :convert_size },
30
34
  'padding-top' => { key: :padding_top, set: :convert_size },
31
35
  # tag closing styles
36
+ 'break-after' => { key: :break_after, set: :convert_symbol },
32
37
  'margin-bottom' => { key: :margin_bottom, set: :convert_size },
33
38
  'padding-bottom' => { key: :padding_bottom, set: :convert_size },
34
39
  # block styles
40
+ 'left' => { key: :left, set: :convert_size },
35
41
  'line-height' => { key: :leading, set: :convert_size },
36
42
  'margin-left' => { key: :margin_left, set: :convert_size },
37
43
  'padding-left' => { key: :padding_left, set: :convert_size },
38
- 'text-align' => { key: :align, set: :convert_symbol }
44
+ 'position' => { key: :position, set: :convert_symbol },
45
+ 'text-align' => { key: :align, set: :convert_symbol },
46
+ 'top' => { key: :top, set: :convert_size }
39
47
  }.freeze
40
48
 
41
49
  STYLES_MERGE = %i[margin_left padding_left].freeze
@@ -44,10 +52,6 @@ module PrawnHtml
44
52
  def initialize(attributes = {})
45
53
  super
46
54
  @styles = {} # result styles
47
- return unless style
48
-
49
- styles_hash = Attributes.parse_styles(style)
50
- process_styles(styles_hash)
51
55
  end
52
56
 
53
57
  # Processes the data attributes
@@ -60,83 +64,15 @@ module PrawnHtml
60
64
  end
61
65
  end
62
66
 
63
- # Merge already parsed styles
67
+ # Merge text styles
64
68
  #
65
- # @param parsed_styles [Hash] hash of parsed styles
66
- def merge_styles!(parsed_styles)
67
- @styles.merge!(parsed_styles)
68
- end
69
-
70
- # Processes the styles attributes
71
- #
72
- # @param styles_hash [Hash] hash of styles attributes
73
- def process_styles(styles_hash)
74
- styles_hash.each do |key, value|
75
- apply_rule!(@styles, STYLES_LIST[key], value)
76
- end
77
- @styles
69
+ # @param text_styles [String] styles to parse and process
70
+ def merge_text_styles!(text_styles)
71
+ hash_styles = Attributes.parse_styles(text_styles)
72
+ process_styles(hash_styles) unless hash_styles.empty?
78
73
  end
79
74
 
80
75
  class << self
81
- # Converts a color string
82
- #
83
- # @param value [String] HTML string color
84
- #
85
- # @return [String] adjusted string color
86
- def convert_color(value)
87
- val = value&.downcase || +''
88
- val.gsub!(/[^a-f0-9]/, '')
89
- return val unless val.size == 3
90
-
91
- a, b, c = val.chars
92
- a * 2 + b * 2 + c * 2
93
- end
94
-
95
- # Converts a decimal number string
96
- #
97
- # @param value [String] string decimal
98
- #
99
- # @return [Float] converted and rounded float number
100
- def convert_float(value)
101
- val = value&.gsub(/[^0-9.]/, '') || ''
102
- val.to_f.round(4)
103
- end
104
-
105
- # Converts a size string
106
- #
107
- # @param value [String] size string
108
- # @param container_size [Numeric] container size
109
- #
110
- # @return [Float] converted and rounded size
111
- def convert_size(value, container_size = nil)
112
- val = value&.gsub(/[^0-9.]/, '') || ''
113
- val =
114
- if container_size && value.include?('%')
115
- val.to_f * container_size * 0.01
116
- else
117
- val.to_f * PX
118
- end
119
- val.round(4)
120
- end
121
-
122
- # Converts a string to symbol
123
- #
124
- # @param value [String] string
125
- #
126
- # @return [Symbol] symbol
127
- def convert_symbol(value)
128
- value.to_sym if value && !value.match?(/\A\s*\Z/)
129
- end
130
-
131
- # Copy a value without conversion
132
- #
133
- # @param value
134
- #
135
- # @return value
136
- def copy(value)
137
- value
138
- end
139
-
140
76
  # Merges attributes
141
77
  #
142
78
  # @param attributes [Hash] target attributes hash
@@ -160,17 +96,6 @@ module PrawnHtml
160
96
  def parse_styles(styles)
161
97
  (styles || '').scan(/\s*([^:;]+)\s*:\s*([^;]+)\s*/).to_h
162
98
  end
163
-
164
- # Unquotes a string
165
- #
166
- # @param value [String] string
167
- #
168
- # @return [String] string without quotes at the beginning/ending
169
- def unquote(value)
170
- (value&.strip || +'').tap do |val|
171
- val.gsub!(/\A['"]|["']\Z/, '')
172
- end
173
- end
174
99
  end
175
100
 
176
101
  private
@@ -178,11 +103,18 @@ module PrawnHtml
178
103
  def apply_rule!(result, rule, value)
179
104
  return unless rule
180
105
 
181
- if rule[:set] == :append_symbol
182
- (result[rule[:key]] ||= []) << Attributes.convert_symbol(value)
106
+ if rule[:set] == :append_styles
107
+ (result[rule[:key]] ||= []) << Utils.normalize_style(value)
183
108
  else
184
- result[rule[:key]] = Attributes.send(rule[:set], value)
109
+ result[rule[:key]] = Utils.send(rule[:set], value)
185
110
  end
186
111
  end
112
+
113
+ def process_styles(hash_styles)
114
+ hash_styles.each do |key, value|
115
+ apply_rule!(@styles, STYLES_LIST[key], value)
116
+ end
117
+ @styles
118
+ end
187
119
  end
188
120
  end