prawn-html 0.2.0 → 0.3.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: 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: