prawn-html 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 998ac3f3429812cc60dfe2bdf0184a86c116b1b80c7522376f99e044f185207c
4
- data.tar.gz: 1a4ce4f9055fa8dd8ed121665102698b6077d69986974c366098d69759555d80
3
+ metadata.gz: 4dec0bd9a3746705bdaca34d49c8a8eff5700fd8a5feebc3b3452001cd239b0b
4
+ data.tar.gz: 74b5e31d3056560435ea939f50f713f11c44ad4c1f584873203b910754e1f782
5
5
  SHA512:
6
- metadata.gz: d443116a4be710f698f02da8092b19b0d83f9b319c0e7b431ae9cfe67d11c208ddea48ff5c995057bd3b28056dafcb2ff8461c1ad3be8134f9dcaa926b96ef73
7
- data.tar.gz: 530a30ff700f75312a5f72b38ef433f9f03a3eab9ac9d83297a3eacc20d680df4ab1f8a4bc3c5134146b3da71be6ffa23d151bc71824dc21022fe7c5aa592758
6
+ metadata.gz: 814c481a5fce1da43dcb04254ed97f76f234fbdcaa6826070b00594f80cd3b4396c190a2972c0f6f72e00d35e5fd1b534cf22c9ef932b8ee308562de7c6bc854
7
+ data.tar.gz: f99ce1543d1b28689b19ceddd8e86a73fae8e204bc98270e6a422dd93e44d89ff50366969b67e0fb2a9a4e91d03367981ab5c0f9481b91378df57e2304358f40
data/README.md CHANGED
@@ -39,6 +39,7 @@ HTML tags:
39
39
 
40
40
  - **a**: link
41
41
  - **b**: bold
42
+ - **blockquote**: block quotation element
42
43
  - **br**: new line
43
44
  - **del**: strike-through
44
45
  - **div**: block element
@@ -100,8 +101,7 @@ Some custom data attributes are used to pass options:
100
101
 
101
102
  ## Document styles
102
103
 
103
- [Experimental feature] You can define document CSS rules inside an _head_ tag, but with a limited support for now.
104
- Only single CSS selectors and basic ones are supported. Example:
104
+ You can define document CSS rules inside an _head_ tag. Example:
105
105
 
106
106
  ```html
107
107
  <!DOCTYPE html>
data/lib/prawn-html.rb CHANGED
@@ -168,9 +168,10 @@ require 'prawn'
168
168
 
169
169
  require 'prawn_html/utils'
170
170
 
171
+ Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
172
+
171
173
  require 'prawn_html/tag'
172
174
  Dir["#{__dir__}/prawn_html/tags/*.rb"].sort.each { |f| require f }
173
- Dir["#{__dir__}/prawn_html/callbacks/*.rb"].sort.each { |f| require f }
174
175
 
175
176
  require 'prawn_html/attributes'
176
177
  require 'prawn_html/context'
@@ -20,12 +20,12 @@ module PrawnHtml
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
- 'font-style' => { key: :styles, set: :append_symbol },
24
- 'font-weight' => { key: :styles, set: :append_symbol },
23
+ 'font-style' => { key: :styles, set: :append_styles },
24
+ 'font-weight' => { key: :styles, set: :append_styles },
25
25
  'href' => { key: :link, set: :copy_value },
26
26
  'letter-spacing' => { key: :character_spacing, set: :convert_float },
27
27
  'list-style-type' => { key: :list_style_type, set: :unquote },
28
- 'text-decoration' => { key: :styles, set: :append_symbol },
28
+ 'text-decoration' => { key: :styles, set: :append_styles },
29
29
  # tag opening styles
30
30
  'break-before' => { key: :break_before, set: :convert_symbol },
31
31
  'margin-top' => { key: :margin_top, set: :convert_size },
@@ -50,10 +50,6 @@ module PrawnHtml
50
50
  def initialize(attributes = {})
51
51
  super
52
52
  @styles = {} # result styles
53
- return unless style
54
-
55
- styles_hash = Attributes.parse_styles(style)
56
- process_styles(styles_hash)
57
53
  end
58
54
 
59
55
  # Processes the data attributes
@@ -66,21 +62,12 @@ module PrawnHtml
66
62
  end
67
63
  end
68
64
 
69
- # Merge already parsed styles
70
- #
71
- # @param parsed_styles [Hash] hash of parsed styles
72
- def merge_styles!(parsed_styles)
73
- @styles.merge!(parsed_styles)
74
- end
75
-
76
- # Processes the styles attributes
65
+ # Merge text styles
77
66
  #
78
- # @param styles_hash [Hash] hash of styles attributes
79
- def process_styles(styles_hash)
80
- styles_hash.each do |key, value|
81
- apply_rule!(@styles, STYLES_LIST[key], value)
82
- end
83
- @styles
67
+ # @param text_styles [String] styles to parse and process
68
+ def merge_text_styles!(text_styles)
69
+ hash_styles = Attributes.parse_styles(text_styles)
70
+ process_styles(hash_styles) unless hash_styles.empty?
84
71
  end
85
72
 
86
73
  class << self
@@ -114,11 +101,18 @@ module PrawnHtml
114
101
  def apply_rule!(result, rule, value)
115
102
  return unless rule
116
103
 
117
- if rule[:set] == :append_symbol
118
- (result[rule[:key]] ||= []) << Utils.convert_symbol(value)
104
+ if rule[:set] == :append_styles
105
+ (result[rule[:key]] ||= []) << Utils.normalize_style(value)
119
106
  else
120
107
  result[rule[:key]] = Utils.send(rule[:set], value)
121
108
  end
122
109
  end
110
+
111
+ def process_styles(hash_styles)
112
+ hash_styles.each do |key, value|
113
+ apply_rule!(@styles, STYLES_LIST[key], value)
114
+ end
115
+ @styles
116
+ end
123
117
  end
124
118
  end
@@ -32,9 +32,7 @@ module PrawnHtml
32
32
  #
33
33
  # @return [String] before content string
34
34
  def before_content
35
- return '' if empty? || !last.respond_to?(:tag_styles)
36
-
37
- last.tag_styles[:before_content].to_s
35
+ (last.respond_to?(:before_content) && last.before_content) || ''
38
36
  end
39
37
 
40
38
  # Merges the context block styles
@@ -11,21 +11,9 @@ module PrawnHtml
11
11
  def initialize(pdf)
12
12
  @buffer = []
13
13
  @context = Context.new
14
- @document_styles = {}
15
14
  @pdf = pdf
16
15
  end
17
16
 
18
- # Evaluate the document styles and store the internally
19
- #
20
- # @param styles [Hash] styles hash with CSS selectors as keys and rules as values
21
- def assign_document_styles(styles)
22
- @document_styles.merge!(
23
- styles.transform_values do |style_rules|
24
- Attributes.new(style: style_rules).styles
25
- end
26
- )
27
- end
28
-
29
17
  # On tag close callback
30
18
  #
31
19
  # @param element [Tag] closing element wrapper
@@ -40,13 +28,14 @@ module PrawnHtml
40
28
  #
41
29
  # @param tag_name [String] the tag name of the opening element
42
30
  # @param attributes [Hash] an hash of the element attributes
31
+ # @param element_styles [String] document styles to apply to the element
43
32
  #
44
33
  # @return [Tag] the opening element wrapper
45
- def on_tag_open(tag_name, attributes)
34
+ def on_tag_open(tag_name, attributes:, element_styles: '')
46
35
  tag_class = Tag.class_for(tag_name)
47
36
  return unless tag_class
48
37
 
49
- tag_class.new(tag_name, attributes, document_styles).tap do |element|
38
+ tag_class.new(tag_name, attributes: attributes, element_styles: element_styles).tap do |element|
50
39
  setup_element(element)
51
40
  end
52
41
  end
@@ -79,7 +68,7 @@ module PrawnHtml
79
68
 
80
69
  private
81
70
 
82
- attr_reader :buffer, :context, :document_styles, :pdf
71
+ attr_reader :buffer, :context, :pdf
83
72
 
84
73
  def setup_element(element)
85
74
  add_space_if_needed unless render_if_needed(element)
@@ -115,13 +104,20 @@ module PrawnHtml
115
104
  end
116
105
 
117
106
  def output_content(buffer, block_styles)
118
- buffer.each { |item| item[:callback] = item[:callback].new(pdf, item) if item[:callback] }
107
+ apply_callbacks(buffer)
119
108
  left_indent = block_styles[:margin_left].to_f + block_styles[:padding_left].to_f
120
109
  options = block_styles.slice(:align, :leading, :mode, :padding_left)
121
110
  options[:indent_paragraphs] = left_indent if left_indent > 0
122
111
  pdf.puts(buffer, options, bounding_box: bounds(block_styles))
123
112
  end
124
113
 
114
+ def apply_callbacks(buffer)
115
+ buffer.select { |item| item[:callback] }.each do |item|
116
+ callback = Tag::CALLBACKS[item[:callback]]
117
+ item[:callback] = callback.new(pdf, item)
118
+ end
119
+ end
120
+
125
121
  def bounds(block_styles)
126
122
  return unless block_styles[:position] == :absolute
127
123
 
@@ -4,12 +4,18 @@ require 'oga'
4
4
 
5
5
  module PrawnHtml
6
6
  class HtmlParser
7
+ REGEXP_STYLES = /\s*([^{\s]+)\s*{\s*([^}]*?)\s*}/m.freeze
8
+
7
9
  # Init the HtmlParser
8
10
  #
9
11
  # @param renderer [DocumentRenderer] document renderer
10
- def initialize(renderer)
12
+ # @param ignore_content_tags [Array] array of tags (symbols) to skip their contents while preparing the PDF document
13
+ def initialize(renderer, ignore_content_tags: %i[script style])
11
14
  @processing = false
15
+ @ignore = false
16
+ @ignore_content_tags = ignore_content_tags
12
17
  @renderer = renderer
18
+ @styles = {}
13
19
  end
14
20
 
15
21
  # Processes HTML and renders it
@@ -17,17 +23,19 @@ module PrawnHtml
17
23
  # @param html [String] The HTML content to process
18
24
  def process(html)
19
25
  @processing = !html.include?('<body')
20
- doc = Oga.parse_html(html)
21
- traverse_nodes(doc.children)
26
+ @document = Oga.parse_html(html)
27
+ traverse_nodes(document.children)
22
28
  renderer.flush
23
29
  end
24
30
 
25
31
  private
26
32
 
27
- attr_reader :processing, :renderer
33
+ attr_reader :document, :ignore, :processing, :renderer, :styles
28
34
 
29
35
  def traverse_nodes(nodes)
30
36
  nodes.each do |node|
37
+ next if node.is_a?(Oga::XML::Comment)
38
+
31
39
  element = node_open(node)
32
40
  traverse_nodes(node.children) if node.children.any?
33
41
  node_close(element) if element
@@ -37,21 +45,27 @@ module PrawnHtml
37
45
  def node_open(node)
38
46
  tag = node.is_a?(Oga::XML::Element) && init_element(node)
39
47
  return unless processing
48
+ return IgnoredTag.new(tag) if ignore
40
49
  return renderer.on_text_node(node.text) unless tag
41
50
 
42
- attributes = prepare_attributes(node)
43
- renderer.on_tag_open(tag, attributes)
51
+ renderer.on_tag_open(tag, attributes: prepare_attributes(node), element_styles: styles[node])
44
52
  end
45
53
 
46
54
  def init_element(node)
47
55
  node.name.downcase.to_sym.tap do |tag_name|
48
56
  @processing = true if tag_name == :body
49
- renderer.assign_document_styles(extract_styles(node.text)) if tag_name == :style
57
+ @ignore = true if @processing && @ignore_content_tags.include?(tag_name)
58
+ process_styles(node.text) if tag_name == :style
50
59
  end
51
60
  end
52
61
 
53
- def extract_styles(text)
54
- text.scan(/\s*([^{\s]+)\s*{\s*([^}]*?)\s*}/m).to_h
62
+ def process_styles(text_styles)
63
+ hash_styles = text_styles.scan(REGEXP_STYLES).to_h
64
+ hash_styles.each do |selector, rule|
65
+ document.css(selector).each do |node|
66
+ styles[node] = rule
67
+ end
68
+ end
55
69
  end
56
70
 
57
71
  def prepare_attributes(node)
@@ -61,10 +75,21 @@ module PrawnHtml
61
75
  end
62
76
 
63
77
  def node_close(element)
64
- renderer.on_tag_close(element) if @processing
78
+ if processing
79
+ renderer.on_tag_close(element) unless ignore
80
+ @ignore = false if ignore && @ignore_content_tags.include?(element.tag)
81
+ end
65
82
  @processing = false if element.tag == :body
66
83
  end
67
84
  end
68
85
 
86
+ class IgnoredTag
87
+ attr_accessor :tag
88
+
89
+ def initialize(tag_name)
90
+ @tag = tag_name
91
+ end
92
+ end
93
+
69
94
  HtmlHandler = HtmlParser
70
95
  end
@@ -2,7 +2,11 @@
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 Ol P Small Span U Ul].freeze
5
+ CALLBACKS = {
6
+ 'Highlight' => Callbacks::Highlight,
7
+ 'StrikeThrough' => Callbacks::StrikeThrough
8
+ }.freeze
9
+ TAG_CLASSES = %w[A B Blockquote Body Br Del Div H Hr I Img Li Mark Ol P Small Span U Ul].freeze
6
10
 
7
11
  attr_accessor :parent
8
12
  attr_reader :attrs, :tag
@@ -11,12 +15,11 @@ module PrawnHtml
11
15
  #
12
16
  # @param tag [Symbol] tag name
13
17
  # @param attributes [Hash] hash of element attributes
14
- # @param document_styles [Hash] hash of document styles
15
- def initialize(tag, attributes = {}, document_styles = {})
18
+ # @param element_styles [String] document styles tp apply to the element
19
+ def initialize(tag, attributes: {}, element_styles: '')
16
20
  @tag = tag
17
- element_styles = attributes.delete(:style)
18
21
  @attrs = Attributes.new(attributes)
19
- process_styles(document_styles, element_styles)
22
+ process_styles(element_styles, attributes['style'])
20
23
  end
21
24
 
22
25
  # Is a block tag?
@@ -74,23 +77,10 @@ module PrawnHtml
74
77
 
75
78
  private
76
79
 
77
- def evaluate_document_styles(document_styles)
78
- selectors = [
79
- tag.to_s,
80
- attrs['class'] ? ".#{attrs['class']}" : nil,
81
- attrs['id'] ? "##{attrs['id']}" : nil
82
- ].compact!
83
- document_styles.each_with_object({}) do |(sel, attributes), res|
84
- res.merge!(attributes) if selectors.include?(sel)
85
- end
86
- end
87
-
88
- def process_styles(document_styles, element_styles)
89
- attrs.merge_styles!(attrs.process_styles(tag_styles)) if respond_to?(:tag_styles)
90
- doc_styles = evaluate_document_styles(document_styles)
91
- attrs.merge_styles!(doc_styles)
92
- el_styles = Attributes.parse_styles(element_styles)
93
- attrs.merge_styles!(attrs.process_styles(el_styles)) if el_styles
80
+ def process_styles(element_styles, inline_styles)
81
+ attrs.merge_text_styles!(tag_styles) if respond_to?(:tag_styles)
82
+ attrs.merge_text_styles!(element_styles)
83
+ attrs.merge_text_styles!(inline_styles)
94
84
  end
95
85
  end
96
86
  end
@@ -6,7 +6,7 @@ module PrawnHtml
6
6
  ELEMENTS = [:a].freeze
7
7
 
8
8
  def tag_styles
9
- attrs.href ? { 'href' => attrs.href } : {}
9
+ "href: #{attrs.href}" if attrs.href
10
10
  end
11
11
  end
12
12
  end
@@ -6,9 +6,7 @@ module PrawnHtml
6
6
  ELEMENTS = [:b, :strong].freeze
7
7
 
8
8
  def tag_styles
9
- {
10
- 'font-weight' => 'bold'
11
- }
9
+ 'font-weight: bold'
12
10
  end
13
11
  end
14
12
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnHtml
4
+ module Tags
5
+ class Blockquote < Tag
6
+ ELEMENTS = [:blockquote].freeze
7
+
8
+ MARGIN_BOTTOM = 10
9
+ MARGIN_LEFT = 25
10
+ MARGIN_TOP = 10
11
+
12
+ def block?
13
+ true
14
+ end
15
+
16
+ def tag_styles
17
+ <<~STYLES
18
+ margin-bottom: #{MARGIN_BOTTOM}px;
19
+ margin-left: #{MARGIN_LEFT}px;
20
+ margin-top: #{MARGIN_TOP}px;
21
+ STYLES
22
+ end
23
+ end
24
+ end
25
+ end
@@ -6,9 +6,7 @@ module PrawnHtml
6
6
  ELEMENTS = [:del, :s].freeze
7
7
 
8
8
  def tag_styles
9
- {
10
- 'callback' => Callbacks::StrikeThrough
11
- }
9
+ 'callback: StrikeThrough'
12
10
  end
13
11
  end
14
12
  end
@@ -37,12 +37,12 @@ module PrawnHtml
37
37
  end
38
38
 
39
39
  def tag_styles
40
- @tag_styles ||= {
41
- 'font-size' => SIZES[tag].to_s,
42
- 'font-weight' => 'bold',
43
- 'margin-bottom' => MARGINS_BOTTOM[tag].to_s,
44
- 'margin-top' => MARGINS_TOP[tag].to_s
45
- }
40
+ <<~STYLES
41
+ font-size: #{SIZES[tag]}px;
42
+ font-weight: bold;
43
+ margin-bottom: #{MARGINS_BOTTOM[tag]}px;
44
+ margin-top: #{MARGINS_TOP[tag]}px;
45
+ STYLES
46
46
  end
47
47
  end
48
48
  end
@@ -18,10 +18,10 @@ module PrawnHtml
18
18
  end
19
19
 
20
20
  def tag_styles
21
- @tag_styles ||= {
22
- 'margin-bottom' => MARGIN_BOTTOM.to_s,
23
- 'margin-top' => MARGIN_TOP.to_s,
24
- }
21
+ <<~STYLES
22
+ margin-bottom: #{MARGIN_BOTTOM}px;
23
+ margin-top: #{MARGIN_TOP}px;
24
+ STYLES
25
25
  end
26
26
 
27
27
  private
@@ -6,9 +6,7 @@ module PrawnHtml
6
6
  ELEMENTS = [:i, :em].freeze
7
7
 
8
8
  def tag_styles
9
- {
10
- 'font-style' => 'italic'
11
- }
9
+ 'font-style: italic'
12
10
  end
13
11
  end
14
12
  end
@@ -9,16 +9,14 @@ module PrawnHtml
9
9
  true
10
10
  end
11
11
 
12
+ def before_content
13
+ @counter ? "#{@counter}. " : "#{@symbol} "
14
+ end
15
+
12
16
  def on_context_add(_context)
13
17
  @counter = (parent.counter += 1) if parent.is_a? Ol
14
18
  @symbol = parent.styles[:list_style_type] || '&bullet;' if parent.is_a? Ul
15
19
  end
16
-
17
- def tag_styles
18
- {
19
- before_content: @counter ? "#{@counter}. " : "#{@symbol} "
20
- }
21
- end
22
20
  end
23
21
  end
24
22
  end
@@ -6,9 +6,7 @@ module PrawnHtml
6
6
  ELEMENTS = [:mark].freeze
7
7
 
8
8
  def tag_styles
9
- {
10
- 'callback' => Callbacks::Highlight
11
- }
9
+ 'callback: Highlight'
12
10
  end
13
11
  end
14
12
  end
@@ -9,7 +9,7 @@ module PrawnHtml
9
9
 
10
10
  attr_accessor :counter
11
11
 
12
- def initialize(*_args)
12
+ def initialize(tag, attributes: {}, element_styles: '')
13
13
  super
14
14
  @counter = 0
15
15
  end
@@ -19,9 +19,7 @@ module PrawnHtml
19
19
  end
20
20
 
21
21
  def tag_styles
22
- @tag_styles ||= {
23
- 'margin-left' => MARGIN_LEFT.to_s,
24
- }
22
+ "margin-left: #{MARGIN_LEFT}px"
25
23
  end
26
24
  end
27
25
  end
@@ -13,10 +13,10 @@ module PrawnHtml
13
13
  end
14
14
 
15
15
  def tag_styles
16
- @tag_styles ||= {
17
- 'margin-bottom' => MARGIN_BOTTOM.to_s,
18
- 'margin-top' => MARGIN_TOP.to_s
19
- }
16
+ <<~STYLES
17
+ margin-bottom: #{MARGIN_BOTTOM}px;
18
+ margin-top: #{MARGIN_TOP}px;
19
+ STYLES
20
20
  end
21
21
  end
22
22
  end
@@ -6,9 +6,7 @@ module PrawnHtml
6
6
  ELEMENTS = [:ins, :u].freeze
7
7
 
8
8
  def tag_styles
9
- {
10
- 'text-decoration' => 'underline'
11
- }
9
+ 'text-decoration: underline'
12
10
  end
13
11
  end
14
12
  end
@@ -12,9 +12,7 @@ module PrawnHtml
12
12
  end
13
13
 
14
14
  def tag_styles
15
- @tag_styles ||= {
16
- 'margin-left' => MARGIN_LEFT.to_s,
17
- }
15
+ "margin-left: #{MARGIN_LEFT}px"
18
16
  end
19
17
  end
20
18
  end
@@ -2,6 +2,12 @@
2
2
 
3
3
  module PrawnHtml
4
4
  module Utils
5
+ NORMALIZE_STYLES = {
6
+ 'bold' => :bold,
7
+ 'italic' => :italic,
8
+ 'underline' => :underline
9
+ }.freeze
10
+
5
11
  # Converts a color string
6
12
  #
7
13
  # Supported formats:
@@ -74,6 +80,16 @@ module PrawnHtml
74
80
  value
75
81
  end
76
82
 
83
+ # Normalize a style value
84
+ #
85
+ # @param value [String] string value
86
+ #
87
+ # @return [Symbol] style value or nil
88
+ def normalize_style(value)
89
+ val = value&.strip&.downcase
90
+ NORMALIZE_STYLES[val]
91
+ end
92
+
77
93
  # Unquotes a string
78
94
  #
79
95
  # @param value [String] string
@@ -85,6 +101,7 @@ module PrawnHtml
85
101
  end
86
102
  end
87
103
 
88
- module_function :convert_color, :convert_float, :convert_size, :convert_symbol, :copy_value, :unquote
104
+ module_function :convert_color, :convert_float, :convert_size, :convert_symbol, :copy_value, :normalize_style,
105
+ :unquote
89
106
  end
90
107
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrawnHtml # :nodoc:
4
- VERSION = '0.3.2'
4
+ VERSION = '0.4.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.3.2
4
+ version: 0.4.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-27 00:00:00.000000000 Z
11
+ date: 2021-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oga
@@ -57,6 +57,7 @@ files:
57
57
  - lib/prawn_html/tag.rb
58
58
  - lib/prawn_html/tags/a.rb
59
59
  - lib/prawn_html/tags/b.rb
60
+ - lib/prawn_html/tags/blockquote.rb
60
61
  - lib/prawn_html/tags/body.rb
61
62
  - lib/prawn_html/tags/br.rb
62
63
  - lib/prawn_html/tags/del.rb