prawn-html 0.1.4 → 0.2.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: 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