prawn-html 0.1.4 → 0.2.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: 319cd3772829ddae0d52b3228b8544cb3906c32c58725082ce95da675bdb841f
4
+ data.tar.gz: e4cc3ec1cdbbe8ad655ef236d44a2c179178cf913d83f7bb307b2fe529927ae1
5
5
  SHA512:
6
- metadata.gz: ceec4ef90b155ea23f659771ab4e6275f12c9c5d4dd29a5da11b4d7f23512320ec71246623ff3c0eb82e0bed9a5e837292c40ac5a8d82585eaa4cb64cdc0653b
7
- data.tar.gz: efea6fe48069d63b56c8c2e7a0142ed959af8cb200e8bc6ff03f8b252614379130feed7bd54942d59a97689c43f3b8a407e739db2b3c9fd1087f10c75fe0ca35
6
+ metadata.gz: 58b388050c3f8c94075bab09353ca3974956b9eebb3fa1a7fdfff488ced919dc8182d744c620d2c2268b4fd26b76bbeaacfcb58717fdeee7c16862665176e66c
7
+ data.tar.gz: 386d529584f5cc8b9b6c326500270761c3a53f859243714980cb188d9496f820158ed83e1d399dc361e368b0a63b2eeaa585843719d83ca2fa75729b6ebffef4
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,7 +8,8 @@ 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
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
15
 
@@ -77,6 +78,13 @@ CSS attributes (dimensional units are ignored and considered in pixel):
77
78
  - **text-decoration**: `underline`, ex. `style="text-decoration: underline"`
78
79
  - **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
79
80
 
81
+ ## Data attributes
82
+
83
+ Some custom data attributes are used to pass options:
84
+
85
+ - **dash**: for *hr* tag, accepts an integer or a list of integers), ex. `data-data="2, 4, 3"`
86
+ - **mode**: allow to specify the text mode (stroke|fill||fill_stroke), ex. `data-mode="stroke"`
87
+
80
88
  ## Document styles
81
89
 
82
90
  [Experimental feature] You can define document CSS rules inside an _head_ tag, but with a limited support for now.
data/lib/prawn-html.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'oga'
3
4
  require 'prawn'
4
5
 
5
- require 'prawn_html/tags/base'
6
+ require 'prawn_html/tag'
6
7
  Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
7
8
 
8
9
  Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
@@ -3,57 +3,78 @@
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 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]
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 },
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_symbol },
24
+ 'font-weight' => { key: :styles, set: :append_symbol },
25
+ 'href' => { key: :link, set: :copy },
26
+ 'letter-spacing' => { key: :character_spacing, set: :convert_float },
27
+ 'text-decoration' => { key: :styles, set: :append_symbol },
28
+ # tag opening styles
29
+ 'margin-top' => { key: :margin_top, set: :convert_size },
30
+ 'padding-top' => { key: :padding_top, set: :convert_size },
31
+ # tag closing styles
32
+ 'margin-bottom' => { key: :margin_bottom, set: :convert_size },
33
+ 'padding-bottom' => { key: :padding_bottom, set: :convert_size },
34
+ # block styles
35
+ 'line-height' => { key: :leading, set: :convert_size },
36
+ 'margin-left' => { key: :margin_left, set: :convert_size },
37
+ 'padding-left' => { key: :padding_left, set: :convert_size },
38
+ 'text-align' => { key: :align, set: :convert_symbol }
30
39
  }.freeze
31
40
 
32
41
  STYLES_MERGE = %i[margin_left padding_left].freeze
33
42
 
34
43
  # 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 = {}
44
+ def initialize(attributes = {})
45
+ super
42
46
  @styles = {} # result styles
43
- parsed_styles = Attributes.parse_styles(hash.style)
44
- process_styles(parsed_styles)
47
+ return unless style
48
+
49
+ styles_hash = Attributes.parse_styles(style)
50
+ process_styles(styles_hash)
45
51
  end
46
52
 
47
- # Processes the styles attributes
53
+ # Processes the data attributes
48
54
  #
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
55
+ # @return [Hash] hash of data attributes with 'data-' prefix removed and stripped values
56
+ def data
57
+ to_h.each_with_object({}) do |(key, value), res|
58
+ data_key = key.match /\Adata-(.+)/
59
+ res[data_key[1]] = value.strip if data_key
60
+ end
61
+ end
62
+
63
+ # Merge already parsed styles
64
+ #
65
+ # @param parsed_styles [Hash] hash of parsed styles
66
+ def merge_styles!(parsed_styles)
67
+ @styles.merge!(parsed_styles)
68
+ end
54
69
 
55
- apply_rule(rule, value)
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)
56
76
  end
77
+ @styles
57
78
  end
58
79
 
59
80
  class << self
@@ -95,7 +116,6 @@ module PrawnHtml
95
116
  else
96
117
  val.to_f * PX
97
118
  end
98
- # pdf.bounds.height
99
119
  val.round(4)
100
120
  end
101
121
 
@@ -108,19 +128,28 @@ module PrawnHtml
108
128
  value.to_sym if value && !value.match?(/\A\s*\Z/)
109
129
  end
110
130
 
131
+ # Copy a value without conversion
132
+ #
133
+ # @param value
134
+ #
135
+ # @return value
136
+ def copy(value)
137
+ value
138
+ end
139
+
111
140
  # Merges attributes
112
141
  #
113
- # @param hash [Hash] target attributes hash
142
+ # @param attributes [Hash] target attributes hash
114
143
  # @param key [Symbol] key
115
144
  # @param value
116
145
  #
117
146
  # @return [Hash] the updated hash of attributes
118
- def merge_attr!(hash, key, value)
147
+ def merge_attr!(attributes, key, value)
119
148
  return unless key
120
- return (hash[key] = value) unless Attributes::STYLES_MERGE.include?(key)
149
+ return (attributes[key] = value) unless Attributes::STYLES_MERGE.include?(key)
121
150
 
122
- hash[key] ||= 0
123
- hash[key] += value
151
+ attributes[key] ||= 0
152
+ attributes[key] += value
124
153
  end
125
154
 
126
155
  # Parses a string of styles
@@ -146,11 +175,13 @@ module PrawnHtml
146
175
 
147
176
  private
148
177
 
149
- def apply_rule(rule, value)
178
+ def apply_rule!(result, rule, value)
179
+ return unless rule
180
+
150
181
  if rule[:set] == :append_symbol
151
- (send(rule[:dest])[rule[:key]] ||= []) << Attributes.convert_symbol(value)
182
+ (result[rule[:key]] ||= []) << Attributes.convert_symbol(value)
152
183
  else
153
- send(rule[:dest])[rule[:key]] = Attributes.send(rule[:set], value)
184
+ result[rule[:key]] = Attributes.send(rule[:set], value)
154
185
  end
155
186
  end
156
187
  end
@@ -10,34 +10,37 @@ module PrawnHtml
10
10
  def initialize(*_args)
11
11
  super
12
12
  @last_margin = 0
13
+ @last_text_node = false
13
14
  end
14
15
 
16
+ # Evaluate before content
17
+ #
18
+ # @return [String] before content string
15
19
  def before_content
16
- return '' if empty?
20
+ return '' if empty? || !last.respond_to?(:tag_styles)
17
21
 
18
- last.options[:before_content].to_s
22
+ last.tag_styles[:before_content].to_s
19
23
  end
20
24
 
21
- # Merges the context options
25
+ # Merges the context block styles
22
26
  #
23
- # @return [Hash] the hash of merged options
24
- def merge_options
27
+ # @return [Hash] the hash of merged styles
28
+ def block_styles
25
29
  each_with_object({}) do |element, res|
26
- element.options.each do |key, value|
30
+ element.block_styles.each do |key, value|
27
31
  Attributes.merge_attr!(res, key, value)
28
32
  end
29
33
  end
30
34
  end
31
35
 
32
- # Merge the context styles
36
+ # Merge the context styles for text nodes
33
37
  #
34
38
  # @return [Hash] the hash of merged styles
35
- def merge_styles
36
- context_styles = each_with_object({}) do |element, res|
39
+ def text_node_styles
40
+ each_with_object(base_styles) do |element, res|
37
41
  evaluate_element_styles(element, res)
38
42
  element.update_styles(res) if element.respond_to?(:update_styles)
39
43
  end
40
- base_styles.merge(context_styles)
41
44
  end
42
45
 
43
46
  private
@@ -49,7 +52,8 @@ module PrawnHtml
49
52
  end
50
53
 
51
54
  def evaluate_element_styles(element, res)
52
- element.styles.each do |key, val|
55
+ styles = element.styles.slice(*Attributes::STYLES_APPLY[:text_node])
56
+ styles.each do |key, val|
53
57
  if res.include?(key) && res[key].is_a?(Array)
54
58
  res[key] += val
55
59
  else
@@ -4,7 +4,6 @@ module PrawnHtml
4
4
  class DocumentRenderer
5
5
  NEW_LINE = { text: "\n" }.freeze
6
6
  SPACE = { text: ' ' }.freeze
7
- TAG_CLASSES = [Tags::A, Tags::B, Tags::Body, Tags::Br, Tags::Del, Tags::Div, Tags::H, Tags::Hr, Tags::I, Tags::Img, Tags::Li, Tags::Mark, Tags::P, Tags::Small, Tags::Span, Tags::U, Tags::Ul].freeze
8
7
 
9
8
  # Init the DocumentRenderer
10
9
  #
@@ -12,40 +11,40 @@ module PrawnHtml
12
11
  def initialize(pdf)
13
12
  @buffer = []
14
13
  @context = Context.new
15
- @doc_styles = {}
14
+ @document_styles = {}
16
15
  @pdf = pdf
17
16
  end
18
17
 
19
- # Assigns the document styles
18
+ # Evaluate the document styles and store the internally
20
19
  #
21
20
  # @param styles [Hash] styles hash with CSS selectors as keys and rules as values
22
21
  def assign_document_styles(styles)
23
- @doc_styles = styles.transform_values do |style_rules|
22
+ @document_styles = styles.transform_values do |style_rules|
24
23
  Attributes.new(style: style_rules).styles
25
24
  end
26
25
  end
27
26
 
28
27
  # On tag close callback
29
28
  #
30
- # @param element [Tags::Base] closing element wrapper
29
+ # @param element [Tag] closing element wrapper
31
30
  def on_tag_close(element)
32
31
  render_if_needed(element)
33
- apply_post_styles(element&.post_styles)
32
+ apply_tag_close_styles(element)
34
33
  context.last_text_node = false
35
34
  context.pop
36
35
  end
37
36
 
38
37
  # On tag open callback
39
38
  #
40
- # @param tag [String] the tag name of the opening element
39
+ # @param tag_name [String] the tag name of the opening element
41
40
  # @param attributes [Hash] an hash of the element attributes
42
41
  #
43
- # @return [Tags::Base] the opening element wrapper
44
- def on_tag_open(tag, attributes)
45
- tag_class = tag_classes[tag]
42
+ # @return [Tag] the opening element wrapper
43
+ def on_tag_open(tag_name, attributes)
44
+ tag_class = Tag.class_for(tag_name)
46
45
  return unless tag_class
47
46
 
48
- tag_class.new(tag, attributes).tap do |element|
47
+ tag_class.new(tag_name, attributes, document_styles).tap do |element|
49
48
  setup_element(element)
50
49
  end
51
50
  end
@@ -58,8 +57,9 @@ module PrawnHtml
58
57
  def on_text_node(content)
59
58
  return if content.match?(/\A\s*\Z/)
60
59
 
61
- text = content.gsub(/\A\s*\n\s*|\s*\n\s*\Z/, '').delete("\n").squeeze(' ')
62
- buffer << context.merge_styles.merge(text: ::Oga::HTML::Entities.decode(context.before_content) + text)
60
+ text = ::Oga::HTML::Entities.decode(context.before_content)
61
+ text += content.gsub(/\A\s*\n\s*|\s*\n\s*\Z/, '').delete("\n").squeeze(' ')
62
+ buffer << context.text_node_styles.merge(text: text)
63
63
  context.last_text_node = true
64
64
  nil
65
65
  end
@@ -68,7 +68,7 @@ module PrawnHtml
68
68
  def render
69
69
  return if buffer.empty?
70
70
 
71
- options = context.merge_options.slice(:align, :leading, :margin_left, :padding_left)
71
+ options = context.block_styles.slice(:align, :leading, :margin_left, :mode, :padding_left)
72
72
  output_content(buffer.dup, options)
73
73
  buffer.clear
74
74
  context.last_margin = 0
@@ -78,18 +78,11 @@ module PrawnHtml
78
78
 
79
79
  private
80
80
 
81
- attr_reader :buffer, :context, :doc_styles, :pdf
82
-
83
- def tag_classes
84
- @tag_classes ||= TAG_CLASSES.each_with_object({}) do |klass, res|
85
- res.merge!(klass.elements)
86
- end
87
- end
81
+ attr_reader :buffer, :context, :document_styles, :pdf
88
82
 
89
83
  def setup_element(element)
90
84
  add_space_if_needed unless render_if_needed(element)
91
- apply_pre_styles(element)
92
- element.apply_doc_styles(doc_styles)
85
+ apply_tag_open_styles(element)
93
86
  context.push(element)
94
87
  element.custom_render(pdf, context) if element.respond_to?(:custom_render)
95
88
  end
@@ -106,29 +99,24 @@ module PrawnHtml
106
99
  true
107
100
  end
108
101
 
109
- def apply_post_styles(styles)
110
- context.last_margin = styles[:margin_bottom].to_f
111
- return if !styles || styles.empty?
112
-
113
- pdf.move_down(context.last_margin.round(4)) if context.last_margin > 0
114
- pdf.move_down(styles[:padding_bottom].round(4)) if styles[:padding_bottom].to_f > 0
102
+ def apply_tag_close_styles(element)
103
+ tag_styles = element.tag_close_styles
104
+ context.last_margin = tag_styles[:margin_bottom].to_f
105
+ move_down = context.last_margin + tag_styles[:padding_bottom].to_f
106
+ pdf.move_down(move_down) if move_down > 0
115
107
  end
116
108
 
117
- def apply_pre_styles(element)
118
- pdf.move_down(element.options[:padding_top].round(4)) if element.options.include?(:padding_top)
119
- return if !element.pre_styles || element.pre_styles.empty?
120
-
121
- margin = (element.pre_styles[:margin_top] - context.last_margin).round(4)
122
- pdf.move_down(margin) if margin > 0
109
+ def apply_tag_open_styles(element)
110
+ tag_styles = element.tag_open_styles
111
+ move_down = (tag_styles[:margin_top].to_f - context.last_margin) + tag_styles[:padding_top].to_f
112
+ pdf.move_down(move_down) if move_down > 0
123
113
  end
124
114
 
125
115
  def output_content(buffer, options)
126
116
  buffer.each { |item| item[:callback] = item[:callback].new(pdf, item) if item[:callback] }
127
- if (left = options.delete(:margin_left).to_f + options.delete(:padding_left).to_f) > 0
128
- pdf.indent(left) { pdf.formatted_text(buffer, options) }
129
- else
130
- pdf.formatted_text(buffer, options)
131
- end
117
+ left_indent = options.delete(:margin_left).to_f + options.delete(:padding_left).to_f
118
+ options[:indent_paragraphs] = left_indent if left_indent > 0
119
+ pdf.formatted_text(buffer, options)
132
120
  end
133
121
  end
134
122
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'oga'
4
-
5
3
  module PrawnHtml
6
4
  class HtmlHandler
7
5
  # Init the HtmlHandler
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
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
6
+
7
+ attr_reader :attrs, :tag
8
+
9
+ # Init the Tag
10
+ #
11
+ # @param tag [Symbol] tag name
12
+ # @param attributes [Hash] hash of element attributes
13
+ # @param document_styles [Hash] hash of document styles
14
+ def initialize(tag, attributes = {}, document_styles = {})
15
+ @tag = tag
16
+ element_styles = attributes.delete(:style)
17
+ @attrs = Attributes.new(attributes)
18
+ process_styles(document_styles, element_styles)
19
+ end
20
+
21
+ # Is a block tag?
22
+ #
23
+ # @return [Boolean] true if the type of the tag is block, false otherwise
24
+ def block?
25
+ false
26
+ end
27
+
28
+ # Styles to apply to the block
29
+ #
30
+ # @return [Hash] hash of styles to apply
31
+ def block_styles
32
+ block_styles = styles.slice(*Attributes::STYLES_APPLY[:block])
33
+ block_styles[:mode] = attrs.data['mode'].to_sym if attrs.data.include?('mode')
34
+ block_styles
35
+ end
36
+
37
+ # Styles to apply on tag closing
38
+ #
39
+ # @return [Hash] hash of styles to apply
40
+ def tag_close_styles
41
+ styles.slice(*Attributes::STYLES_APPLY[:tag_close])
42
+ end
43
+
44
+ # Styles hash
45
+ #
46
+ # @return [Hash] hash of styles
47
+ def styles
48
+ attrs.styles
49
+ end
50
+
51
+ # Styles to apply on tag opening
52
+ #
53
+ # @return [Hash] hash of styles to apply
54
+ def tag_open_styles
55
+ styles.slice(*Attributes::STYLES_APPLY[:tag_open])
56
+ end
57
+
58
+ class << self
59
+ # Evaluate the Tag class from a tag name
60
+ #
61
+ # @params tag_name [Symbol] the tag name
62
+ #
63
+ # @return [Tag] the class for the tag if available or nil
64
+ def class_for(tag_name)
65
+ @tag_classes ||= TAG_CLASSES.each_with_object({}) do |tag_class, res|
66
+ klass = const_get("PrawnHtml::Tags::#{tag_class}")
67
+ k = [klass] * klass::ELEMENTS.size
68
+ res.merge!(klass::ELEMENTS.zip(k).to_h)
69
+ end
70
+ @tag_classes[tag_name]
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def evaluate_document_styles(document_styles)
77
+ selectors = [
78
+ tag.to_s,
79
+ attrs['class'] ? ".#{attrs['class']}" : nil,
80
+ attrs['id'] ? "##{attrs['id']}" : nil
81
+ ].compact!
82
+ document_styles.each_with_object({}) do |(sel, attributes), res|
83
+ res.merge!(attributes) if selectors.include?(sel)
84
+ end
85
+ end
86
+
87
+ def process_styles(document_styles, element_styles)
88
+ attrs.merge_styles!(attrs.process_styles(tag_styles)) if respond_to?(:tag_styles)
89
+ doc_styles = evaluate_document_styles(document_styles)
90
+ attrs.merge_styles!(doc_styles)
91
+ el_styles = Attributes.parse_styles(element_styles)
92
+ attrs.merge_styles!(attrs.process_styles(el_styles)) if el_styles
93
+ end
94
+ end
95
+ end
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class A < Base
5
+ class A < Tag
8
6
  ELEMENTS = [:a].freeze
9
7
 
10
- def styles
11
- attrs.hash.href ? super.merge(link: attrs.hash.href) : super
8
+ def tag_styles
9
+ attrs.href ? { 'href' => attrs.href } : {}
12
10
  end
13
11
  end
14
12
  end
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class B < Base
5
+ class B < Tag
8
6
  ELEMENTS = [:b, :strong].freeze
9
7
 
10
- def extra_attrs
8
+ def tag_styles
11
9
  {
12
10
  'font-weight' => 'bold'
13
11
  }
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Body < Base
5
+ class Body < Tag
8
6
  ELEMENTS = [:body].freeze
9
7
  end
10
8
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Br < Base
5
+ class Br < Tag
8
6
  ELEMENTS = [:br].freeze
9
7
 
10
8
  BR_SPACING = 12
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Del < Base
5
+ class Del < Tag
8
6
  ELEMENTS = [:del, :s].freeze
9
7
 
10
- def styles
11
- super.merge(
12
- callback: Callbacks::StrikeThrough
13
- )
8
+ def tag_styles
9
+ {
10
+ 'callback' => Callbacks::StrikeThrough
11
+ }
14
12
  end
15
13
  end
16
14
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Div < Base
5
+ class Div < Tag
8
6
  ELEMENTS = [:div].freeze
9
7
 
10
8
  def block?
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class H < Base
5
+ class H < Tag
8
6
  ELEMENTS = [:h1, :h2, :h3, :h4, :h5, :h6].freeze
9
7
 
10
8
  MARGINS_TOP = {
@@ -38,8 +36,8 @@ module PrawnHtml
38
36
  true
39
37
  end
40
38
 
41
- def extra_attrs
42
- @extra_attrs ||= {
39
+ def tag_styles
40
+ @tag_styles ||= {
43
41
  'font-size' => SIZES[tag].to_s,
44
42
  'font-weight' => 'bold',
45
43
  'margin-bottom' => MARGINS_BOTTOM[tag].to_s,
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Hr < Base
5
+ class Hr < Tag
8
6
  ELEMENTS = [:hr].freeze
9
7
 
10
8
  MARGIN_BOTTOM = 12
@@ -15,15 +13,29 @@ module PrawnHtml
15
13
  end
16
14
 
17
15
  def custom_render(pdf, _context)
16
+ dash = parse_dash_value(attrs.data['dash']) if attrs.data.include?('dash')
17
+ pdf.dash(dash) if dash
18
18
  pdf.stroke_horizontal_rule
19
+ pdf.undash if dash
19
20
  end
20
21
 
21
- def extra_attrs
22
- @extra_attrs ||= {
22
+ def tag_styles
23
+ @tag_styles ||= {
23
24
  'margin-bottom' => MARGIN_BOTTOM.to_s,
24
25
  'margin-top' => MARGIN_TOP.to_s,
25
26
  }
26
27
  end
28
+
29
+ private
30
+
31
+ def parse_dash_value(dash_string)
32
+ if dash_string.match? /\A\d+\Z/
33
+ dash_string.to_i
34
+ else
35
+ dash_array = dash_string.split(',')
36
+ dash_array.map(&:to_i) if dash_array.any?
37
+ end
38
+ end
27
39
  end
28
40
  end
29
41
  end
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class I < Base
5
+ class I < Tag
8
6
  ELEMENTS = [:i, :em].freeze
9
7
 
10
- def extra_attrs
8
+ def tag_styles
11
9
  {
12
10
  'font-style' => 'italic'
13
11
  }
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Img < Base
5
+ class Img < Tag
8
6
  ELEMENTS = [:img].freeze
9
7
 
10
8
  def block?
@@ -12,20 +10,20 @@ module PrawnHtml
12
10
  end
13
11
 
14
12
  def custom_render(pdf, context)
15
- styles = Attributes.parse_styles(attrs.hash.style)
16
- context_options = context.merge_options
17
- options = evaluate_styles(pdf, context_options.merge(styles))
18
- pdf.image(@attrs.hash.src, options)
13
+ styles = Attributes.parse_styles(attrs.style)
14
+ block_styles = context.block_styles
15
+ evaluated_styles = evaluate_styles(pdf, block_styles.merge(styles))
16
+ pdf.image(@attrs.src, evaluated_styles) if @attrs.src
19
17
  end
20
18
 
21
19
  private
22
20
 
23
21
  def evaluate_styles(pdf, styles)
24
- options = {}
25
- options[:width] = Attributes.convert_size(styles['width'], pdf.bounds.width) if styles.include?('width')
26
- options[:height] = Attributes.convert_size(styles['height'], pdf.bounds.height) if styles.include?('height')
27
- options[:position] = styles[:align] if %i[left center right].include?(styles[:align])
28
- options
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])
26
+ end
29
27
  end
30
28
  end
31
29
  end
@@ -1,20 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Li < Base
5
+ class Li < Tag
8
6
  ELEMENTS = [:li].freeze
9
7
 
10
8
  def block?
11
9
  true
12
10
  end
13
11
 
14
- def options
15
- super.merge(
12
+ def tag_styles
13
+ {
16
14
  before_content: '&bullet; '
17
- )
15
+ }
18
16
  end
19
17
  end
20
18
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Mark < Base
5
+ class Mark < Tag
8
6
  ELEMENTS = [:mark].freeze
9
7
 
10
- def styles
11
- super.merge(
12
- callback: Callbacks::Highlight
13
- )
8
+ def tag_styles
9
+ {
10
+ 'callback' => Callbacks::Highlight
11
+ }
14
12
  end
15
13
  end
16
14
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class P < Base
5
+ class P < Tag
8
6
  ELEMENTS = [:p].freeze
9
7
 
10
8
  MARGIN_BOTTOM = 6
@@ -14,8 +12,8 @@ module PrawnHtml
14
12
  true
15
13
  end
16
14
 
17
- def extra_attrs
18
- @extra_attrs ||= {
15
+ def tag_styles
16
+ @tag_styles ||= {
19
17
  'margin-bottom' => MARGIN_BOTTOM.to_s,
20
18
  'margin-top' => MARGIN_TOP.to_s
21
19
  }
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Small < Base
5
+ class Small < Tag
8
6
  ELEMENTS = [:small].freeze
9
7
 
10
8
  def update_styles(styles)
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Span < Base
5
+ class Span < Tag
8
6
  ELEMENTS = [:span].freeze
9
7
  end
10
8
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module PrawnHtml
4
4
  module Tags
5
- class U < Base
5
+ class U < Tag
6
6
  ELEMENTS = [:ins, :u].freeze
7
7
 
8
- def extra_attrs
8
+ def tag_styles
9
9
  {
10
10
  'text-decoration' => 'underline'
11
11
  }
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module PrawnHtml
6
4
  module Tags
7
- class Ul < Base
5
+ class Ul < Tag
8
6
  ELEMENTS = [:ul].freeze
9
7
 
10
8
  MARGIN_LEFT = 25
@@ -13,8 +11,8 @@ module PrawnHtml
13
11
  true
14
12
  end
15
13
 
16
- def extra_attrs
17
- @extra_attrs ||= {
14
+ def tag_styles
15
+ @tag_styles ||= {
18
16
  'margin-left' => MARGIN_LEFT.to_s,
19
17
  }
20
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrawnHtml # :nodoc:
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.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.1.4
4
+ version: 0.2.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-13 00:00:00.000000000 Z
11
+ date: 2021-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oga
@@ -53,9 +53,9 @@ files:
53
53
  - lib/prawn_html/context.rb
54
54
  - lib/prawn_html/document_renderer.rb
55
55
  - lib/prawn_html/html_handler.rb
56
+ - lib/prawn_html/tag.rb
56
57
  - lib/prawn_html/tags/a.rb
57
58
  - lib/prawn_html/tags/b.rb
58
- - lib/prawn_html/tags/base.rb
59
59
  - lib/prawn_html/tags/body.rb
60
60
  - lib/prawn_html/tags/br.rb
61
61
  - lib/prawn_html/tags/del.rb
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PrawnHtml
4
- module Tags
5
- class Base
6
- attr_reader :attrs, :styles, :tag
7
-
8
- def initialize(tag, attributes = {})
9
- @attrs = Attributes.new(attributes)
10
- @styles = attrs.styles
11
- @tag = tag
12
- attrs.process_styles(extra_attrs) unless extra_attrs.empty?
13
- end
14
-
15
- def apply_doc_styles(document_styles)
16
- selectors = [
17
- tag.to_s,
18
- attrs.hash['class'] ? ".#{attrs.hash['class']}" : nil,
19
- attrs.hash['id'] ? "##{attrs.hash['id']}" : nil
20
- ].compact!
21
- merged_styles = document_styles.each_with_object({}) do |(sel, attributes), res|
22
- res.merge!(attributes) if selectors.include?(sel)
23
- end
24
- @styles = merged_styles.merge(styles)
25
- end
26
-
27
- def block?
28
- false
29
- end
30
-
31
- def extra_attrs
32
- {}
33
- end
34
-
35
- def options
36
- attrs.options
37
- end
38
-
39
- def post_styles
40
- attrs.post_styles
41
- end
42
-
43
- def pre_styles
44
- attrs.pre_styles
45
- end
46
-
47
- class << self
48
- def elements
49
- self::ELEMENTS.each_with_object({}) { |el, list| list[el] = self }
50
- end
51
- end
52
- end
53
- end
54
- end