prawn-html 0.1.4 → 0.4.0

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: 2a2fb462a91991cadb770f8bbee7d07c99f670d46eeb62d2a3b467b1839e5364
4
- data.tar.gz: 1710135c8ca94e2de836e913ce8e177b9bc47b4ee37725a23729912b086aff25
3
+ metadata.gz: 4dec0bd9a3746705bdaca34d49c8a8eff5700fd8a5feebc3b3452001cd239b0b
4
+ data.tar.gz: 74b5e31d3056560435ea939f50f713f11c44ad4c1f584873203b910754e1f782
5
5
  SHA512:
6
- metadata.gz: ceec4ef90b155ea23f659771ab4e6275f12c9c5d4dd29a5da11b4d7f23512320ec71246623ff3c0eb82e0bed9a5e837292c40ac5a8d82585eaa4cb64cdc0653b
7
- data.tar.gz: efea6fe48069d63b56c8c2e7a0142ed959af8cb200e8bc6ff03f8b252614379130feed7bd54942d59a97689c43f3b8a407e739db2b3c9fd1087f10c75fe0ca35
6
+ metadata.gz: 814c481a5fce1da43dcb04254ed97f76f234fbdcaa6826070b00594f80cd3b4396c190a2972c0f6f72e00d35e5fd1b534cf22c9ef932b8ee308562de7c6bc854
7
+ data.tar.gz: f99ce1543d1b28689b19ceddd8e86a73fae8e204bc98270e6a422dd93e44d89ff50366969b67e0fb2a9a4e91d03367981ab5c0f9481b91378df57e2304358f40
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Prawn HTML
2
- [![gem version](https://badge.fury.io/rb/prawn-html.svg)](https://badge.fury.io/rb/prawn-html)
2
+ [![gem version](https://badge.fury.io/rb/prawn-html.svg)](https://rubygems.org/gems/prawn-html)
3
3
  [![linters](https://github.com/blocknotes/prawn-html/actions/workflows/linters.yml/badge.svg)](https://github.com/blocknotes/prawn-html/actions/workflows/linters.yml)
4
4
  [![specs](https://github.com/blocknotes/prawn-html/actions/workflows/specs.yml/badge.svg)](https://github.com/blocknotes/prawn-html/actions/workflows/specs.yml)
5
5
 
@@ -8,9 +8,10 @@ HTML to PDF renderer using [Prawn PDF](https://github.com/prawnpdf/prawn).
8
8
  Features:
9
9
  - support a [good set](#supported-tags--attributes) of HTML tags and CSS properties;
10
10
  - handle [document styles](#document-styles);
11
- - no extra settings: it just parses an input HTML and output to a Prawn PDF document.
11
+ - custom [data attributes](#data-attributes) for Prawn PDF features;
12
+ - no extra settings: it just parses an input HTML and outputs to a Prawn PDF document.
12
13
 
13
- **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*.
14
15
 
15
16
  > [prawn-styled-text](https://github.com/blocknotes/prawn-styled-text) rewritten from scratch, finally!
16
17
 
@@ -38,6 +39,7 @@ HTML tags:
38
39
 
39
40
  - **a**: link
40
41
  - **b**: bold
42
+ - **blockquote**: block quotation element
41
43
  - **br**: new line
42
44
  - **del**: strike-through
43
45
  - **div**: block element
@@ -49,38 +51,57 @@ HTML tags:
49
51
  - **img**: image
50
52
  - **li**: list item
51
53
  - **mark**: highlight
54
+ - **ol**: ordered list
52
55
  - **p**: block element
53
56
  - **s**: strike-through
54
57
  - **small**: smaller text
55
58
  - **span**: inline element
56
59
  - **strong**: bold
57
60
  - **u**: underline
58
- - **ul**: list
61
+ - **ul**: unordered list
59
62
 
60
63
  CSS attributes (dimensional units are ignored and considered in pixel):
61
64
 
62
- - **background**: for *mark* tag, only 3 or 6 hex digits format, ex. `style="background: #FECD08"`
63
- - **color**: only 3 or 6 hex digits format - ex. `style="color: #FB1"`
65
+ - **background**: for *mark* tag (3/6 hex digits or RGB or color name), ex. `style="background: #FECD08"`
66
+ - **break-after**: go to a new page after some elements, ex. `style="break-after: auto"`
67
+ - **break-before**: go to a new page before some elements, ex. `style="break-before: auto"`
68
+ - **color**: (3/6 hex digits or RGB or color name) ex. `style="color: #FB1"`
64
69
  - **font-family**: font must be registered, quotes are optional, ex. `style="font-family: Courier"`
65
70
  - **font-size**: ex. `style="font-size: 20px"`
66
71
  - **font-style**: values: *:italic*, ex. `style="font-style: italic"`
67
72
  - **font-weight**: values: *:bold*, ex. `style="font-weight: bold"`
68
73
  - **height**: for *img* tag, ex. `<img src="image.jpg" style="height: 200px"/>`
69
74
  - **href**: for *a* tag, ex. `<a href="http://www.google.com/">Google</a>`
75
+ - **left**: see *position (absolute)*
70
76
  - **letter-spacing**: ex. `style="letter-spacing: 1.5"`
71
77
  - **line-height**: ex. `style="line-height: 10px"`
78
+ - **list-style-type**: for *ul*, a string, ex. `style="list-style-type: '- '"`
72
79
  - **margin-bottom**: ex. `style="margin-bottom: 10px"`
73
80
  - **margin-left**: ex. `style="margin-left: 15px"`
74
81
  - **margin-top**: ex. `style="margin-top: 20px"`
82
+ - **position**: `absolute`, ex. `style="position: absolute; left: 20px; top: 100px"`
75
83
  - **src**: for *img* tag, ex. `<img src="image.jpg"/>`
76
84
  - **text-align**: `left` | `center` | `right` | `justify`, ex. `style="text-align: center"`
77
85
  - **text-decoration**: `underline`, ex. `style="text-decoration: underline"`
86
+ - **top**: see *position (absolute)*
78
87
  - **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
79
88
 
89
+ For colors, the supported formats are:
90
+ - 3 hex digits, ex. `color: #FB1`;
91
+ - 6 hex digits, ex. `color: #abcdef`;
92
+ - RGB, ex. `color: RGB(64, 0, 128)`;
93
+ - color name, ex. `color: yellow`.
94
+
95
+ ## Data attributes
96
+
97
+ Some custom data attributes are used to pass options:
98
+
99
+ - **dash**: for *hr* tag, accepts an integer or a list of integers), ex. `data-data="2, 4, 3"`
100
+ - **mode**: allow to specify the text mode (stroke|fill||fill_stroke), ex. `data-mode="stroke"`
101
+
80
102
  ## Document styles
81
103
 
82
- [Experimental feature] You can define document CSS rules inside an _head_ tag, but with a limited support for now.
83
- Only single CSS selectors and basic ones are supported. Example:
104
+ You can define document CSS rules inside an _head_ tag. Example:
84
105
 
85
106
  ```html
86
107
  <!DOCTYPE html>
@@ -105,6 +126,28 @@ Only single CSS selectors and basic ones are supported. Example:
105
126
  </html>
106
127
  ```
107
128
 
129
+ ## Additional notes
130
+
131
+ ### Rails: generate PDF on the fly
132
+
133
+ Sample controller's action to create a PDF from Rails:
134
+
135
+ ```rb
136
+ class SomeController < ApplicationController
137
+ def sample_action
138
+ respond_to do |format|
139
+ format.pdf do
140
+ pdf = Prawn::Document.new
141
+ PrawnHtml.append_html(pdf, '<h1 style="text-align: center">Just a test</h1>')
142
+ send_data(pdf.render, filename: 'sample.pdf', type: 'application/pdf')
143
+ end
144
+ end
145
+ end
146
+ end
147
+ ```
148
+
149
+ More details in this blogpost: [generate PDF from HTML](https://www.blocknot.es/2021-08-20-rails-generate-pdf-from-html/)
150
+
108
151
  ## Do you like it? Star it!
109
152
 
110
153
  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,24 +1,180 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'prawn'
4
-
5
- require 'prawn_html/tags/base'
6
- Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
7
-
8
- Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
9
-
10
- require 'prawn_html/attributes'
11
- require 'prawn_html/context'
12
- require 'prawn_html/document_renderer'
13
- require 'prawn_html/html_handler'
14
-
15
3
  module PrawnHtml
16
4
  PX = 0.66 # conversion constant for pixel sixes
17
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
+
18
157
  def append_html(pdf, html)
19
- handler = PrawnHtml::HtmlHandler.new(pdf)
20
- 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)
21
162
  end
22
163
 
23
164
  module_function :append_html
24
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'
@@ -3,124 +3,87 @@
3
3
  require 'ostruct'
4
4
 
5
5
  module PrawnHtml
6
- class Attributes
7
- attr_reader :hash, :options, :post_styles, :pre_styles, :styles
6
+ class Attributes < OpenStruct
7
+ attr_reader :styles
8
+
9
+ STYLES_APPLY = {
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]
14
+ }.freeze
8
15
 
9
16
  STYLES_LIST = {
10
- # styles
11
- 'background' => { key: :background, set: :convert_color, dest: :styles },
12
- 'color' => { key: :color, set: :convert_color, dest: :styles },
13
- 'font-family' => { key: :font, set: :unquote, dest: :styles },
14
- 'font-size' => { key: :size, set: :convert_size, dest: :styles },
15
- 'font-style' => { key: :styles, set: :append_symbol, dest: :styles },
16
- 'font-weight' => { key: :styles, set: :append_symbol, dest: :styles },
17
- 'letter-spacing' => { key: :character_spacing, set: :convert_float, dest: :styles },
18
- # pre styles
19
- 'margin-top' => { key: :margin_top, set: :convert_size, dest: :pre_styles },
20
- # post styles
21
- 'margin-bottom' => { key: :margin_bottom, set: :convert_size, dest: :post_styles },
22
- 'padding-bottom' => { key: :padding_bottom, set: :convert_size, dest: :post_styles },
23
- # options
24
- 'line-height' => { key: :leading, set: :convert_size, dest: :options },
25
- 'margin-left' => { key: :margin_left, set: :convert_size, dest: :options },
26
- 'padding-left' => { key: :padding_left, set: :convert_size, dest: :options },
27
- 'padding-top' => { key: :padding_top, set: :convert_size, dest: :options },
28
- 'text-align' => { key: :align, set: :convert_symbol, dest: :options },
29
- 'text-decoration' => { key: :styles, set: :append_symbol, dest: :styles }
17
+ # text node styles
18
+ 'background' => { key: :background, set: :convert_color },
19
+ 'callback' => { key: :callback, set: :copy_value },
20
+ 'color' => { key: :color, set: :convert_color },
21
+ 'font-family' => { key: :font, set: :unquote },
22
+ 'font-size' => { key: :size, set: :convert_size },
23
+ 'font-style' => { key: :styles, set: :append_styles },
24
+ 'font-weight' => { key: :styles, set: :append_styles },
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 },
29
+ # tag opening styles
30
+ 'break-before' => { key: :break_before, set: :convert_symbol },
31
+ 'margin-top' => { key: :margin_top, set: :convert_size },
32
+ 'padding-top' => { key: :padding_top, set: :convert_size },
33
+ # tag closing styles
34
+ 'break-after' => { key: :break_after, set: :convert_symbol },
35
+ 'margin-bottom' => { key: :margin_bottom, set: :convert_size },
36
+ 'padding-bottom' => { key: :padding_bottom, set: :convert_size },
37
+ # block styles
38
+ 'left' => { key: :left, set: :convert_size },
39
+ 'line-height' => { key: :leading, set: :convert_size },
40
+ 'margin-left' => { key: :margin_left, set: :convert_size },
41
+ 'padding-left' => { key: :padding_left, set: :convert_size },
42
+ 'position' => { key: :position, set: :convert_symbol },
43
+ 'text-align' => { key: :align, set: :convert_symbol },
44
+ 'top' => { key: :top, set: :convert_size }
30
45
  }.freeze
31
46
 
32
47
  STYLES_MERGE = %i[margin_left padding_left].freeze
33
48
 
34
49
  # Init the Attributes
35
- #
36
- # @param attributes [Hash] hash of attributes to parse
37
- def initialize(attributes)
38
- @hash = ::OpenStruct.new(attributes)
39
- @options = {}
40
- @post_styles = {}
41
- @pre_styles = {}
50
+ def initialize(attributes = {})
51
+ super
42
52
  @styles = {} # result styles
43
- parsed_styles = Attributes.parse_styles(hash.style)
44
- process_styles(parsed_styles)
45
53
  end
46
54
 
47
- # Processes the styles attributes
55
+ # Processes the data attributes
48
56
  #
49
- # @param attributes [Hash] hash of styles attributes
50
- def process_styles(styles)
51
- styles.each do |key, value|
52
- rule = STYLES_LIST[key]
53
- next unless rule
54
-
55
- apply_rule(rule, value)
57
+ # @return [Hash] hash of data attributes with 'data-' prefix removed and stripped values
58
+ def data
59
+ to_h.each_with_object({}) do |(key, value), res|
60
+ data_key = key.match /\Adata-(.+)/
61
+ res[data_key[1]] = value.strip if data_key
56
62
  end
57
63
  end
58
64
 
59
- class << self
60
- # Converts a color string
61
- #
62
- # @param value [String] HTML string color
63
- #
64
- # @return [String] adjusted string color
65
- def convert_color(value)
66
- val = value&.downcase || +''
67
- val.gsub!(/[^a-f0-9]/, '')
68
- return val unless val.size == 3
69
-
70
- a, b, c = val.chars
71
- a * 2 + b * 2 + c * 2
72
- end
73
-
74
- # Converts a decimal number string
75
- #
76
- # @param value [String] string decimal
77
- #
78
- # @return [Float] converted and rounded float number
79
- def convert_float(value)
80
- val = value&.gsub(/[^0-9.]/, '') || ''
81
- val.to_f.round(4)
82
- end
83
-
84
- # Converts a size string
85
- #
86
- # @param value [String] size string
87
- # @param container_size [Numeric] container size
88
- #
89
- # @return [Float] converted and rounded size
90
- def convert_size(value, container_size = nil)
91
- val = value&.gsub(/[^0-9.]/, '') || ''
92
- val =
93
- if container_size && value.include?('%')
94
- val.to_f * container_size * 0.01
95
- else
96
- val.to_f * PX
97
- end
98
- # pdf.bounds.height
99
- val.round(4)
100
- end
101
-
102
- # Converts a string to symbol
103
- #
104
- # @param value [String] string
105
- #
106
- # @return [Symbol] symbol
107
- def convert_symbol(value)
108
- value.to_sym if value && !value.match?(/\A\s*\Z/)
109
- end
65
+ # Merge text styles
66
+ #
67
+ # @param text_styles [String] styles to parse and process
68
+ def merge_text_styles!(text_styles)
69
+ hash_styles = Attributes.parse_styles(text_styles)
70
+ process_styles(hash_styles) unless hash_styles.empty?
71
+ end
110
72
 
73
+ class << self
111
74
  # Merges attributes
112
75
  #
113
- # @param hash [Hash] target attributes hash
76
+ # @param attributes [Hash] target attributes hash
114
77
  # @param key [Symbol] key
115
78
  # @param value
116
79
  #
117
80
  # @return [Hash] the updated hash of attributes
118
- def merge_attr!(hash, key, value)
81
+ def merge_attr!(attributes, key, value)
119
82
  return unless key
120
- return (hash[key] = value) unless Attributes::STYLES_MERGE.include?(key)
83
+ return (attributes[key] = value) unless Attributes::STYLES_MERGE.include?(key)
121
84
 
122
- hash[key] ||= 0
123
- hash[key] += value
85
+ attributes[key] ||= 0
86
+ attributes[key] += value
124
87
  end
125
88
 
126
89
  # Parses a string of styles
@@ -131,27 +94,25 @@ module PrawnHtml
131
94
  def parse_styles(styles)
132
95
  (styles || '').scan(/\s*([^:;]+)\s*:\s*([^;]+)\s*/).to_h
133
96
  end
134
-
135
- # Unquotes a string
136
- #
137
- # @param value [String] string
138
- #
139
- # @return [String] string without quotes at the beginning/ending
140
- def unquote(value)
141
- (value&.strip || +'').tap do |val|
142
- val.gsub!(/\A['"]|["']\Z/, '')
143
- end
144
- end
145
97
  end
146
98
 
147
99
  private
148
100
 
149
- def apply_rule(rule, value)
150
- if rule[:set] == :append_symbol
151
- (send(rule[:dest])[rule[:key]] ||= []) << Attributes.convert_symbol(value)
101
+ def apply_rule!(result, rule, value)
102
+ return unless rule
103
+
104
+ if rule[:set] == :append_styles
105
+ (result[rule[:key]] ||= []) << Utils.normalize_style(value)
152
106
  else
153
- send(rule[:dest])[rule[:key]] = Attributes.send(rule[:set], value)
107
+ result[rule[:key]] = Utils.send(rule[:set], value)
108
+ end
109
+ end
110
+
111
+ def process_styles(hash_styles)
112
+ hash_styles.each do |key, value|
113
+ apply_rule!(@styles, STYLES_LIST[key], value)
154
114
  end
115
+ @styles
155
116
  end
156
117
  end
157
118
  end