prawn-html 0.3.2 → 0.4.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: 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