prawn-html 0.2.0 → 0.3.0

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: c519c9608664ef77b5b4489752f49d29ae4dfda7ee42e43685ee5951524bd1e3
4
+ data.tar.gz: c768fa73bc5601bec246a963153a3d20f0d6712fa72dea6441a27495dc374cee
5
5
  SHA512:
6
- metadata.gz: 58b388050c3f8c94075bab09353ca3974956b9eebb3fa1a7fdfff488ced919dc8182d744c620d2c2268b4fd26b76bbeaacfcb58717fdeee7c16862665176e66c
7
- data.tar.gz: 386d529584f5cc8b9b6c326500270761c3a53f859243714980cb188d9496f820158ed83e1d399dc361e368b0a63b2eeaa585843719d83ca2fa75729b6ebffef4
6
+ metadata.gz: b62ed1e07608cdb62829fe50ae708c16d55b1510c49ff8e02eb47442dca105cfeb2b18e751dd5f84e421687ba970fecb3078db904d3fc867af93ecbe67361e07
7
+ data.tar.gz: 503c65d6d22d496c634634dca3c57eda3e864ce91448ad2c119e0cb89857e7731cdef7fd209a1f48d8bda82ad0dc8eff724e407a957bd4d8323f410385ebc906
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
 
@@ -50,34 +50,45 @@ HTML tags:
50
50
  - **img**: image
51
51
  - **li**: list item
52
52
  - **mark**: highlight
53
+ - **ol**: ordered list
53
54
  - **p**: block element
54
55
  - **s**: strike-through
55
56
  - **small**: smaller text
56
57
  - **span**: inline element
57
58
  - **strong**: bold
58
59
  - **u**: underline
59
- - **ul**: list
60
+ - **ul**: unordered list
60
61
 
61
62
  CSS attributes (dimensional units are ignored and considered in pixel):
62
63
 
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"`
64
+ - **background**: for *mark* tag (3/6 hex digits or RGB or color name), ex. `style="background: #FECD08"`
65
+ - **color**: (3/6 hex digits or RGB or color name) ex. `style="color: #FB1"`
65
66
  - **font-family**: font must be registered, quotes are optional, ex. `style="font-family: Courier"`
66
67
  - **font-size**: ex. `style="font-size: 20px"`
67
68
  - **font-style**: values: *:italic*, ex. `style="font-style: italic"`
68
69
  - **font-weight**: values: *:bold*, ex. `style="font-weight: bold"`
69
70
  - **height**: for *img* tag, ex. `<img src="image.jpg" style="height: 200px"/>`
70
71
  - **href**: for *a* tag, ex. `<a href="http://www.google.com/">Google</a>`
72
+ - **left**: see *position (absolute)*
71
73
  - **letter-spacing**: ex. `style="letter-spacing: 1.5"`
72
74
  - **line-height**: ex. `style="line-height: 10px"`
75
+ - **list-style-type**: for *ul*, a string, ex. `style="list-style-type: '- '"`
73
76
  - **margin-bottom**: ex. `style="margin-bottom: 10px"`
74
77
  - **margin-left**: ex. `style="margin-left: 15px"`
75
78
  - **margin-top**: ex. `style="margin-top: 20px"`
79
+ - **position**: `absolute`, ex. `style="position: absolute; left: 20px; top: 100px"`
76
80
  - **src**: for *img* tag, ex. `<img src="image.jpg"/>`
77
81
  - **text-align**: `left` | `center` | `right` | `justify`, ex. `style="text-align: center"`
78
82
  - **text-decoration**: `underline`, ex. `style="text-decoration: underline"`
83
+ - **top**: see *position (absolute)*
79
84
  - **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
80
85
 
86
+ For colors, the supported formats are:
87
+ - 3 hex digits, ex. `color: #FB1`;
88
+ - 6 hex digits, ex. `color: #abcdef`;
89
+ - RGB, ex. `color: RGB(64, 0, 128)`;
90
+ - color name, ex. `color: yellow`.
91
+
81
92
  ## Data attributes
82
93
 
83
94
  Some custom data attributes are used to pass options:
@@ -113,6 +124,28 @@ Only single CSS selectors and basic ones are supported. Example:
113
124
  </html>
114
125
  ```
115
126
 
127
+ ## Additional notes
128
+
129
+ ### Rails: generate PDF on the fly
130
+
131
+ Sample controller's action to create a PDF from Rails:
132
+
133
+ ```rb
134
+ class SomeController < ApplicationController
135
+ def sample_action
136
+ respond_to do |format|
137
+ format.pdf do
138
+ pdf = Prawn::Document.new
139
+ PrawnHtml.append_html(pdf, '<h1 style="text-align: center">Just a test</h1>')
140
+ send_data(pdf.render, filename: 'sample.pdf', type: 'application/pdf')
141
+ end
142
+ end
143
+ end
144
+ end
145
+ ```
146
+
147
+ More details in this blogpost: [generate PDF from HTML](https://www.blocknot.es/2021-08-20-rails-generate-pdf-from-html/)
148
+
116
149
  ## Do you like it? Star it!
117
150
 
118
151
  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,176 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'oga'
4
3
  require 'prawn'
5
4
 
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
5
  module PrawnHtml
17
6
  PX = 0.66 # conversion constant for pixel sixes
18
7
 
8
+ COLORS = {
9
+ 'aliceblue' => 'f0f8ff',
10
+ 'antiquewhite' => 'faebd7',
11
+ 'aqua' => '00ffff',
12
+ 'aquamarine' => '7fffd4',
13
+ 'azure' => 'f0ffff',
14
+ 'beige' => 'f5f5dc',
15
+ 'bisque' => 'ffe4c4',
16
+ 'black' => '000000',
17
+ 'blanchedalmond' => 'ffebcd',
18
+ 'blue' => '0000ff',
19
+ 'blueviolet' => '8a2be2',
20
+ 'brown' => 'a52a2a',
21
+ 'burlywood' => 'deb887',
22
+ 'cadetblue' => '5f9ea0',
23
+ 'chartreuse' => '7fff00',
24
+ 'chocolate' => 'd2691e',
25
+ 'coral' => 'ff7f50',
26
+ 'cornflowerblue' => '6495ed',
27
+ 'cornsilk' => 'fff8dc',
28
+ 'crimson' => 'dc143c',
29
+ 'cyan' => '00ffff',
30
+ 'darkblue' => '00008b',
31
+ 'darkcyan' => '008b8b',
32
+ 'darkgoldenrod' => 'b8860b',
33
+ 'darkgray' => 'a9a9a9',
34
+ 'darkgreen' => '006400',
35
+ 'darkgrey' => 'a9a9a9',
36
+ 'darkkhaki' => 'bdb76b',
37
+ 'darkmagenta' => '8b008b',
38
+ 'darkolivegreen' => '556b2f',
39
+ 'darkorange' => 'ff8c00',
40
+ 'darkorchid' => '9932cc',
41
+ 'darkred' => '8b0000',
42
+ 'darksalmon' => 'e9967a',
43
+ 'darkseagreen' => '8fbc8f',
44
+ 'darkslateblue' => '483d8b',
45
+ 'darkslategray' => '2f4f4f',
46
+ 'darkslategrey' => '2f4f4f',
47
+ 'darkturquoise' => '00ced1',
48
+ 'darkviolet' => '9400d3',
49
+ 'deeppink' => 'ff1493',
50
+ 'deepskyblue' => '00bfff',
51
+ 'dimgray' => '696969',
52
+ 'dimgrey' => '696969',
53
+ 'dodgerblue' => '1e90ff',
54
+ 'firebrick' => 'b22222',
55
+ 'floralwhite' => 'fffaf0',
56
+ 'forestgreen' => '228b22',
57
+ 'fuchsia' => 'ff00ff',
58
+ 'gainsboro' => 'dcdcdc',
59
+ 'ghostwhite' => 'f8f8ff',
60
+ 'gold' => 'ffd700',
61
+ 'goldenrod' => 'daa520',
62
+ 'gray' => '808080',
63
+ 'green' => '008000',
64
+ 'greenyellow' => 'adff2f',
65
+ 'grey' => '808080',
66
+ 'honeydew' => 'f0fff0',
67
+ 'hotpink' => 'ff69b4',
68
+ 'indianred' => 'cd5c5c',
69
+ 'indigo' => '4b0082',
70
+ 'ivory' => 'fffff0',
71
+ 'khaki' => 'f0e68c',
72
+ 'lavender' => 'e6e6fa',
73
+ 'lavenderblush' => 'fff0f5',
74
+ 'lawngreen' => '7cfc00',
75
+ 'lemonchiffon' => 'fffacd',
76
+ 'lightblue' => 'add8e6',
77
+ 'lightcoral' => 'f08080',
78
+ 'lightcyan' => 'e0ffff',
79
+ 'lightgoldenrodyellow' => 'fafad2',
80
+ 'lightgray' => 'd3d3d3',
81
+ 'lightgreen' => '90ee90',
82
+ 'lightgrey' => 'd3d3d3',
83
+ 'lightpink' => 'ffb6c1',
84
+ 'lightsalmon' => 'ffa07a',
85
+ 'lightseagreen' => '20b2aa',
86
+ 'lightskyblue' => '87cefa',
87
+ 'lightslategray' => '778899',
88
+ 'lightslategrey' => '778899',
89
+ 'lightsteelblue' => 'b0c4de',
90
+ 'lightyellow' => 'ffffe0',
91
+ 'lime' => '00ff00',
92
+ 'limegreen' => '32cd32',
93
+ 'linen' => 'faf0e6',
94
+ 'magenta' => 'ff00ff',
95
+ 'maroon' => '800000',
96
+ 'mediumaquamarine' => '66cdaa',
97
+ 'mediumblue' => '0000cd',
98
+ 'mediumorchid' => 'ba55d3',
99
+ 'mediumpurple' => '9370db',
100
+ 'mediumseagreen' => '3cb371',
101
+ 'mediumslateblue' => '7b68ee',
102
+ 'mediumspringgreen' => '00fa9a',
103
+ 'mediumturquoise' => '48d1cc',
104
+ 'mediumvioletred' => 'c71585',
105
+ 'midnightblue' => '191970',
106
+ 'mintcream' => 'f5fffa',
107
+ 'mistyrose' => 'ffe4e1',
108
+ 'moccasin' => 'ffe4b5',
109
+ 'navajowhite' => 'ffdead',
110
+ 'navy' => '000080',
111
+ 'oldlace' => 'fdf5e6',
112
+ 'olive' => '808000',
113
+ 'olivedrab' => '6b8e23',
114
+ 'orange' => 'ffa500',
115
+ 'orangered' => 'ff4500',
116
+ 'orchid' => 'da70d6',
117
+ 'palegoldenrod' => 'eee8aa',
118
+ 'palegreen' => '98fb98',
119
+ 'paleturquoise' => 'afeeee',
120
+ 'palevioletred' => 'db7093',
121
+ 'papayawhip' => 'ffefd5',
122
+ 'peachpuff' => 'ffdab9',
123
+ 'peru' => 'cd853f',
124
+ 'pink' => 'ffc0cb',
125
+ 'plum' => 'dda0dd',
126
+ 'powderblue' => 'b0e0e6',
127
+ 'purple' => '800080',
128
+ 'rebeccapurple' => '663399',
129
+ 'red' => 'ff0000',
130
+ 'rosybrown' => 'bc8f8f',
131
+ 'royalblue' => '4169e1',
132
+ 'saddlebrown' => '8b4513',
133
+ 'salmon' => 'fa8072',
134
+ 'sandybrown' => 'f4a460',
135
+ 'seagreen' => '2e8b57',
136
+ 'seashell' => 'fff5ee',
137
+ 'sienna' => 'a0522d',
138
+ 'silver' => 'c0c0c0',
139
+ 'skyblue' => '87ceeb',
140
+ 'slateblue' => '6a5acd',
141
+ 'slategray' => '708090',
142
+ 'slategrey' => '708090',
143
+ 'snow' => 'fffafa',
144
+ 'springgreen' => '00ff7f',
145
+ 'steelblue' => '4682b4',
146
+ 'tan' => 'd2b48c',
147
+ 'teal' => '008080',
148
+ 'thistle' => 'd8bfd8',
149
+ 'tomato' => 'ff6347',
150
+ 'turquoise' => '40e0d0',
151
+ 'violet' => 'ee82ee',
152
+ 'wheat' => 'f5deb3',
153
+ 'white' => 'ffffff',
154
+ 'whitesmoke' => 'f5f5f5',
155
+ 'yellow' => 'ffff00',
156
+ 'yellowgreen' => '9acd32'
157
+ }.freeze
158
+
19
159
  def append_html(pdf, html)
20
- handler = PrawnHtml::HtmlHandler.new(pdf)
21
- handler.process(html)
160
+ html_parser = PrawnHtml::HtmlParser.new(pdf)
161
+ html_parser.process(html)
22
162
  end
23
163
 
24
164
  module_function :append_html
25
165
  end
166
+
167
+ require 'prawn_html/utils'
168
+
169
+ require 'prawn_html/tag'
170
+ Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
171
+ Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
172
+
173
+ require 'prawn_html/attributes'
174
+ require 'prawn_html/context'
175
+ require 'prawn_html/document_renderer'
176
+ require 'prawn_html/html_parser'
@@ -7,23 +7,24 @@ module PrawnHtml
7
7
  attr_reader :styles
8
8
 
9
9
  STYLES_APPLY = {
10
- block: %i[align leading margin_left padding_left],
10
+ block: %i[align leading left margin_left padding_left position top],
11
11
  tag_close: %i[margin_bottom padding_bottom],
12
12
  tag_open: %i[margin_top padding_top],
13
- text_node: %i[background callback character_spacing color font link size styles]
13
+ text_node: %i[background callback character_spacing color font link list_style_type size styles]
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
23
  'font-style' => { key: :styles, set: :append_symbol },
24
24
  'font-weight' => { key: :styles, set: :append_symbol },
25
- 'href' => { key: :link, set: :copy },
25
+ 'href' => { key: :link, set: :copy_value },
26
26
  'letter-spacing' => { key: :character_spacing, set: :convert_float },
27
+ 'list-style-type' => { key: :list_style_type, set: :unquote },
27
28
  'text-decoration' => { key: :styles, set: :append_symbol },
28
29
  # tag opening styles
29
30
  'margin-top' => { key: :margin_top, set: :convert_size },
@@ -32,10 +33,13 @@ module PrawnHtml
32
33
  'margin-bottom' => { key: :margin_bottom, set: :convert_size },
33
34
  'padding-bottom' => { key: :padding_bottom, set: :convert_size },
34
35
  # block styles
36
+ 'left' => { key: :left, set: :convert_size },
35
37
  'line-height' => { key: :leading, set: :convert_size },
36
38
  'margin-left' => { key: :margin_left, set: :convert_size },
37
39
  'padding-left' => { key: :padding_left, set: :convert_size },
38
- 'text-align' => { key: :align, set: :convert_symbol }
40
+ 'position' => { key: :position, set: :convert_symbol },
41
+ 'text-align' => { key: :align, set: :convert_symbol },
42
+ 'top' => { key: :top, set: :convert_size }
39
43
  }.freeze
40
44
 
41
45
  STYLES_MERGE = %i[margin_left padding_left].freeze
@@ -78,65 +82,6 @@ module PrawnHtml
78
82
  end
79
83
 
80
84
  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
85
  # Merges attributes
141
86
  #
142
87
  # @param attributes [Hash] target attributes hash
@@ -160,17 +105,6 @@ module PrawnHtml
160
105
  def parse_styles(styles)
161
106
  (styles || '').scan(/\s*([^:;]+)\s*:\s*([^;]+)\s*/).to_h
162
107
  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
108
  end
175
109
 
176
110
  private
@@ -179,9 +113,9 @@ module PrawnHtml
179
113
  return unless rule
180
114
 
181
115
  if rule[:set] == :append_symbol
182
- (result[rule[:key]] ||= []) << Attributes.convert_symbol(value)
116
+ (result[rule[:key]] ||= []) << Utils.convert_symbol(value)
183
117
  else
184
- result[rule[:key]] = Attributes.send(rule[:set], value)
118
+ result[rule[:key]] = Utils.send(rule[:set], value)
185
119
  end
186
120
  end
187
121
  end
@@ -13,6 +13,21 @@ module PrawnHtml
13
13
  @last_text_node = false
14
14
  end
15
15
 
16
+ # Add an element to the context
17
+ #
18
+ # Set the parent for the previous element in the chain.
19
+ # Run `on_context_add` callback method on the added element.
20
+ #
21
+ # @param element [Tag] the element to add
22
+ #
23
+ # @return [Context] the context updated
24
+ def add(element)
25
+ element.parent = last
26
+ push(element)
27
+ element.on_context_add(self) if element.respond_to?(:on_context_add)
28
+ self
29
+ end
30
+
16
31
  # Evaluate before content
17
32
  #
18
33
  # @return [String] before content string
@@ -19,9 +19,11 @@ module PrawnHtml
19
19
  #
20
20
  # @param styles [Hash] styles hash with CSS selectors as keys and rules as values
21
21
  def assign_document_styles(styles)
22
- @document_styles = styles.transform_values do |style_rules|
23
- Attributes.new(style: style_rules).styles
24
- end
22
+ @document_styles.merge!(
23
+ styles.transform_values do |style_rules|
24
+ Attributes.new(style: style_rules).styles
25
+ end
26
+ )
25
27
  end
26
28
 
27
29
  # On tag close callback
@@ -68,8 +70,7 @@ module PrawnHtml
68
70
  def render
69
71
  return if buffer.empty?
70
72
 
71
- options = context.block_styles.slice(:align, :leading, :margin_left, :mode, :padding_left)
72
- output_content(buffer.dup, options)
73
+ output_content(buffer.dup, context.block_styles)
73
74
  buffer.clear
74
75
  context.last_margin = 0
75
76
  end
@@ -83,7 +84,7 @@ module PrawnHtml
83
84
  def setup_element(element)
84
85
  add_space_if_needed unless render_if_needed(element)
85
86
  apply_tag_open_styles(element)
86
- context.push(element)
87
+ context.add(element)
87
88
  element.custom_render(pdf, context) if element.respond_to?(:custom_render)
88
89
  end
89
90
 
@@ -112,11 +113,30 @@ module PrawnHtml
112
113
  pdf.move_down(move_down) if move_down > 0
113
114
  end
114
115
 
115
- def output_content(buffer, options)
116
+ def output_content(buffer, block_styles)
116
117
  buffer.each { |item| item[:callback] = item[:callback].new(pdf, item) if item[:callback] }
117
- left_indent = options.delete(:margin_left).to_f + options.delete(:padding_left).to_f
118
+ left_indent = block_styles[:margin_left].to_f + block_styles[:padding_left].to_f
119
+ options = block_styles.slice(:align, :leading, :mode, :padding_left)
118
120
  options[:indent_paragraphs] = left_indent if left_indent > 0
119
- pdf.formatted_text(buffer, options)
121
+ formatted_text(buffer, options, bounding_box: bounds(block_styles))
122
+ end
123
+
124
+ def formatted_text(buffer, options, bounding_box: nil)
125
+ return pdf.formatted_text(buffer, options) unless bounding_box
126
+
127
+ current_y = pdf.cursor
128
+ pdf.bounding_box(*bounding_box) do
129
+ pdf.formatted_text(buffer, options)
130
+ end
131
+ pdf.move_cursor_to(current_y)
132
+ end
133
+
134
+ def bounds(block_styles)
135
+ return unless block_styles[:position] == :absolute
136
+
137
+ y = pdf.bounds.height - (block_styles[:top] || 0)
138
+ w = pdf.bounds.width - (block_styles[:left] || 0)
139
+ [[block_styles[:left] || 0, y], { width: w }]
120
140
  end
121
141
  end
122
142
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'oga'
4
+
3
5
  module PrawnHtml
4
- class HtmlHandler
5
- # Init the HtmlHandler
6
+ class HtmlParser
7
+ # Init the HtmlParser
6
8
  #
7
9
  # @param pdf [Prawn::Document] Target Prawn PDF document
8
10
  def initialize(pdf)
@@ -44,7 +46,7 @@ module PrawnHtml
44
46
  def init_element(node)
45
47
  node.name.downcase.to_sym.tap do |tag_name|
46
48
  @processing = true if tag_name == :body
47
- renderer.assign_document_styles(extract_styles(node.text)) if tag_name == :style && !@processing
49
+ renderer.assign_document_styles(extract_styles(node.text)) if tag_name == :style
48
50
  end
49
51
  end
50
52
 
@@ -63,4 +65,6 @@ module PrawnHtml
63
65
  @processing = false if element.tag == :body
64
66
  end
65
67
  end
68
+
69
+ HtmlHandler = HtmlParser
66
70
  end
@@ -2,8 +2,9 @@
2
2
 
3
3
  module PrawnHtml
4
4
  class Tag
5
- TAG_CLASSES = %w[A B Body Br Del Div H Hr I Img Li Mark P Small Span U Ul].freeze
5
+ TAG_CLASSES = %w[A B Body Br Del Div H Hr I Img Li Mark Ol P Small Span U Ul].freeze
6
6
 
7
+ attr_accessor :parent
7
8
  attr_reader :attrs, :tag
8
9
 
9
10
  # Init the Tag
@@ -14,7 +14,7 @@ module PrawnHtml
14
14
  def custom_render(pdf, context)
15
15
  return if context.last_text_node
16
16
 
17
- @spacing ||= Attributes.convert_size(BR_SPACING.to_s)
17
+ @spacing ||= Utils.convert_size(BR_SPACING.to_s)
18
18
  pdf.move_down(@spacing)
19
19
  end
20
20
  end
@@ -14,9 +14,9 @@ module PrawnHtml
14
14
 
15
15
  def custom_render(pdf, _context)
16
16
  dash = parse_dash_value(attrs.data['dash']) if attrs.data.include?('dash')
17
- pdf.dash(dash) if dash
18
- pdf.stroke_horizontal_rule
19
- pdf.undash if dash
17
+ around_stroke(pdf, old_color: pdf.stroke_color, new_color: attrs.styles[:color], dash: dash) do
18
+ pdf.stroke_horizontal_rule
19
+ end
20
20
  end
21
21
 
22
22
  def tag_styles
@@ -28,6 +28,14 @@ module PrawnHtml
28
28
 
29
29
  private
30
30
 
31
+ def around_stroke(pdf, old_color:, new_color:, dash:)
32
+ pdf.dash(dash) if dash
33
+ pdf.stroke_color = new_color if new_color
34
+ yield
35
+ pdf.stroke_color = old_color if new_color
36
+ pdf.undash if dash
37
+ end
38
+
31
39
  def parse_dash_value(dash_string)
32
40
  if dash_string.match? /\A\d+\Z/
33
41
  dash_string.to_i
@@ -10,19 +10,19 @@ module PrawnHtml
10
10
  end
11
11
 
12
12
  def custom_render(pdf, context)
13
- styles = Attributes.parse_styles(attrs.style)
13
+ parsed_styles = Attributes.parse_styles(attrs.style)
14
14
  block_styles = context.block_styles
15
- evaluated_styles = evaluate_styles(pdf, block_styles.merge(styles))
15
+ evaluated_styles = evaluate_styles(pdf, block_styles.merge(parsed_styles))
16
16
  pdf.image(@attrs.src, evaluated_styles) if @attrs.src
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- def evaluate_styles(pdf, styles)
21
+ def evaluate_styles(pdf, img_styles)
22
22
  {}.tap do |result|
23
- result[:width] = Attributes.convert_size(styles['width'], pdf.bounds.width) if styles.include?('width')
24
- result[:height] = Attributes.convert_size(styles['height'], pdf.bounds.height) if styles.include?('height')
25
- result[:position] = styles[:align] if %i[left center right].include?(styles[:align])
23
+ result[:width] = Utils.convert_size(img_styles['width'], pdf.bounds.width) if img_styles.include?('width')
24
+ result[:height] = Utils.convert_size(img_styles['height'], pdf.bounds.height) if img_styles.include?('height')
25
+ result[:position] = img_styles[:align] if %i[left center right].include?(img_styles[:align])
26
26
  end
27
27
  end
28
28
  end
@@ -9,9 +9,14 @@ module PrawnHtml
9
9
  true
10
10
  end
11
11
 
12
+ def on_context_add(_context)
13
+ @counter = (parent.counter += 1) if parent.is_a? Ol
14
+ @symbol = parent.styles[:list_style_type] || '&bullet;' if parent.is_a? Ul
15
+ end
16
+
12
17
  def tag_styles
13
18
  {
14
- before_content: '&bullet; '
19
+ before_content: @counter ? "#{@counter}. " : "#{@symbol} "
15
20
  }
16
21
  end
17
22
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Ol < Tag
6
+ ELEMENTS = [:ol].freeze
7
+
8
+ MARGIN_LEFT = 25
9
+
10
+ attr_accessor :counter
11
+
12
+ def initialize(*_args)
13
+ super
14
+ @counter = 0
15
+ end
16
+
17
+ def block?
18
+ true
19
+ end
20
+
21
+ def tag_styles
22
+ @tag_styles ||= {
23
+ 'margin-left' => MARGIN_LEFT.to_s,
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Utils
5
+ # Converts a color string
6
+ #
7
+ # Supported formats:
8
+ # - 3 hex digits, ex. `color: #FB1`;
9
+ # - 6 hex digits, ex. `color: #abcdef`;
10
+ # - RGB, ex. `color: RGB(64, 0, 128)`;
11
+ # - color name, ex. `color: red`.
12
+ #
13
+ # @param value [String] HTML string color
14
+ #
15
+ # @return [String] adjusted string color or nil if value is invalid
16
+ def convert_color(value)
17
+ val = value.to_s.strip.downcase
18
+ return Regexp.last_match[1] if val.match /\A#([a-f0-9]{6})\Z/ # rubocop:disable Performance/RedundantMatch
19
+
20
+ if val.match /\A#([a-f0-9]{3})\Z/ # rubocop:disable Performance/RedundantMatch
21
+ r, g, b = Regexp.last_match[1].chars
22
+ return r * 2 + g * 2 + b * 2
23
+ end
24
+ if val.match /\Argb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\Z/ # rubocop:disable Performance/RedundantMatch
25
+ r, g, b = Regexp.last_match[1..3].map { |v| v.to_i.to_s(16) }
26
+ return "#{r.rjust(2, '0')}#{g.rjust(2, '0')}#{b.rjust(2, '0')}"
27
+ end
28
+
29
+ COLORS[val]
30
+ end
31
+
32
+ # Converts a decimal number string
33
+ #
34
+ # @param value [String] string decimal
35
+ #
36
+ # @return [Float] converted and rounded float number
37
+ def convert_float(value)
38
+ val = value&.gsub(/[^0-9.]/, '') || ''
39
+ val.to_f.round(4)
40
+ end
41
+
42
+ # Converts a size string
43
+ #
44
+ # @param value [String] size string
45
+ # @param container_size [Numeric] container size
46
+ #
47
+ # @return [Float] converted and rounded size
48
+ def convert_size(value, container_size = nil)
49
+ val = value&.gsub(/[^0-9.]/, '') || ''
50
+ val =
51
+ if container_size && value.include?('%')
52
+ val.to_f * container_size * 0.01
53
+ else
54
+ val.to_f * PX
55
+ end
56
+ val.round(4)
57
+ end
58
+
59
+ # Converts a string to symbol
60
+ #
61
+ # @param value [String] string
62
+ #
63
+ # @return [Symbol] symbol
64
+ def convert_symbol(value)
65
+ value.to_sym if value && !value.match?(/\A\s*\Z/)
66
+ end
67
+
68
+ # Copy a value without conversion
69
+ #
70
+ # @param value
71
+ #
72
+ # @return value
73
+ def copy_value(value)
74
+ value
75
+ end
76
+
77
+ # Unquotes a string
78
+ #
79
+ # @param value [String] string
80
+ #
81
+ # @return [String] string without quotes at the beginning/ending
82
+ def unquote(value)
83
+ (value&.strip || +'').tap do |val|
84
+ val.gsub!(/\A['"]|["']\Z/, '')
85
+ end
86
+ end
87
+
88
+ module_function :convert_color, :convert_float, :convert_size, :convert_symbol, :copy_value, :unquote
89
+ end
90
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrawnHtml # :nodoc:
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-html
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mattia Roccoberton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-20 00:00:00.000000000 Z
11
+ date: 2021-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oga
@@ -52,7 +52,7 @@ files:
52
52
  - lib/prawn_html/callbacks/strike_through.rb
53
53
  - lib/prawn_html/context.rb
54
54
  - lib/prawn_html/document_renderer.rb
55
- - lib/prawn_html/html_handler.rb
55
+ - lib/prawn_html/html_parser.rb
56
56
  - lib/prawn_html/tag.rb
57
57
  - lib/prawn_html/tags/a.rb
58
58
  - lib/prawn_html/tags/b.rb
@@ -66,11 +66,13 @@ files:
66
66
  - lib/prawn_html/tags/img.rb
67
67
  - lib/prawn_html/tags/li.rb
68
68
  - lib/prawn_html/tags/mark.rb
69
+ - lib/prawn_html/tags/ol.rb
69
70
  - lib/prawn_html/tags/p.rb
70
71
  - lib/prawn_html/tags/small.rb
71
72
  - lib/prawn_html/tags/span.rb
72
73
  - lib/prawn_html/tags/u.rb
73
74
  - lib/prawn_html/tags/ul.rb
75
+ - lib/prawn_html/utils.rb
74
76
  - lib/prawn_html/version.rb
75
77
  homepage: https://github.com/blocknotes/prawn-html
76
78
  licenses: