prawn-html 0.2.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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