prawn-markup 0.1.0 → 0.3.1

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
- SHA1:
3
- metadata.gz: 17db8afa6a062ac1cac8322276b7f013e2d9ba7c
4
- data.tar.gz: 03a009820c6851a0d06ca2812487757e1a142056
2
+ SHA256:
3
+ metadata.gz: fcd47aaef564239231b4546e4122f9f15625c15de5aa3d91bf0e3933e62d46ce
4
+ data.tar.gz: 77d0fa0b0b8261d6e3c0a8b7df75f797b31914c761c32dc229ba93edf269a587
5
5
  SHA512:
6
- metadata.gz: b9d20fb86722b5ade3804c648116b84e3d423b39a3ffcdeadd2340b6b5d886e1ba80a98fa0a570bf1b448d2e0f1a88058781ccf5330cb6f9c218e7c8467da770
7
- data.tar.gz: 8fdce13e68ce84ccbace42d051ffc5d66f3888ad9094af6aa79abc62a0251e386088ccc81e627dc0a3be67ef746c5d6e82575af4455f14121cf50ec3d89a54f6
6
+ metadata.gz: 395187b2fe33613d41bb9f4ac89f9b370cff0ac2a01212c96d5c88eea57e88a255e93a44e519f723b0955b468eb4896da06b7094ba3aa046302ab6733f3b8e6a
7
+ data.tar.gz: fd2c752f5eb61d1029371a4d2cd2476168476696d464853ba288ca263b6a8c903cb367a8f29dbc104f8e2d809b1f7cca02c779e863e9bd10385849be283d51db
data/README.md CHANGED
@@ -33,6 +33,27 @@ doc = Prawn::Document.new
33
33
  doc.markup('<p>Hello World</p><hr/><p>KTHXBYE</p>')
34
34
  ```
35
35
 
36
+ ## Supported HTML
37
+
38
+ This gem parses the given HTML and layouts the following elements in a vertical order:
39
+
40
+ * Text blocks: `p`, `div`, `ol`, `ul`, `li`, `hr`, `br`
41
+ * Text semantics: `a`, `b`, `strong`, `i`, `em`, `u`, `s`, `del`, `sub`, `sup`
42
+ * Headings: `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
43
+ * Tables: `table`, `tr`, `td`, `th`
44
+ * Media: `img`, `iframe`
45
+ * Inputs: `type=checkbox`, `type=radio`
46
+
47
+ All other elements are ignored, their content is added to the parent element. With a few exceptions, no CSS is processed. One exception is the `width` property of `img`, `td` and `th`, which may contain values in `cm`, `mm`, `px`, `pt`, `%` or `auto`.
48
+
49
+ If no explicit loader is given (see above), images are loaded from `http(s)` addresses or may be contained in the `src` attribute as base64 encoded data URIs. Prawn only supports `PNG` and `JPG`.
50
+
51
+ ## Example
52
+
53
+ Have a look at [showcase.html](spec/fixtures/showcase.html), which is rendered by the corresponding [spec](spec/prawn/markup/showcase_spec.rb). Uncomment the `lookatit` call there to directly open the generated PDF when running the spec with `spec spec/prawn/markup/showcase_spec.rb`.
54
+
55
+ ## Formatting Options
56
+
36
57
  To customize element formatting, do:
37
58
 
38
59
  ```ruby
@@ -46,44 +67,48 @@ doc.markup_options = {
46
67
  doc.markup('<p>Hello World</p><hr/><p>KTHXBYE</p>', text: { align: :center })
47
68
  ```
48
69
 
49
- Options may be set for `text`, `table` (`cell` and `header`) and `list` (`content` and `bullet`).
70
+ Options may be set for `text`, `heading[1-6]`, `table` (subkeys `cell` and `header`) and `list` (subkeys `content` and `bullet`).
50
71
 
51
- Text options include all keys from Prawns [#text](http://prawnpdf.org/api-docs/2.0/Prawn/Text.html#text-instance_method) method: `font`, `size`, `color`, `style`, `align`, `valign`, `leading`,`direction`, `character_spacing`, `indent_paragraphs`, `kerning`, `mode`.
72
+ Text and heading options include all keys from Prawns [#text](http://prawnpdf.org/api-docs/2.0/Prawn/Text.html#text-instance_method) method: `font`, `size`, `color`, `style`, `align`, `valign`, `leading`,`direction`, `character_spacing`, `indent_paragraphs`, `kerning`, `mode`.
52
73
 
53
74
  Tables and lists are rendered with [prawn-table](https://github.com/prawnpdf/prawn-table) and have the following additional options: `padding`, `borders`, `border_width`, `border_color`, `background_color`, `border_lines`, `rotate`, `overflow`, `min_font_size`. Options from `text` may be overridden.
54
75
 
55
76
  Beside these options handled by Prawn / prawn-table, the following values may be customized:
56
77
 
57
- * `[:text][:preprocessor]`: A proc/callable that is called each time before a chunk of text is rendered.
58
- * `[:text][:margin_bottom]`: Margin after each `<p>`, `<ol>`, `<ul>` or `<table>`. Defaults to about half a line.
59
- * `[:heading1-6][:margin_top]`: Margin before a heading. Default is 0.
60
- * `[:heading1-6][:margin_bottom]`: Margin after a heading. Default is 0.
61
- * `[:table][:placeholder][:too_large]`: If the table content does not fit into the current bounding box, this text/callable is rendered instead. Defaults to '[table content too large]'.
62
- * `[:table][:placeholder][:subtable_too_large]`: If the content of a subtable cannot be fitted into the table, this text is rendered instead. Defaults to '[nested tables with automatic width are not supported]'.
63
- * `[:list][:vertical_margin]`: Margin at the top and the bottom of a list. Default is 5.
64
- * `[:list][:bullet][:char]`: The text used as bullet in unordered lists. Default is '•'.
65
- * `[:list][:bullet][:margin]`: Margin before the bullet. Default is 10.
66
- * `[:list][:content][:margin]`: Margin between the bullet and the content. Default is 10.
67
- * `[:list][:placeholder][:too_large]`: If the list content does not fit into the current bounding box, this text/callable is rendered instead. Defaults to '[list content too large]'.
68
- * `[:image][:loader]`: A callable that accepts the `src` attribute as an argument an returns a value understood by Prawn's `image` method. Loads `http(s)` URLs and base64 encoded data URIs by default.
69
- * `[:image][:placeholder]`: If an image is not supported, this text/callable is rendered instead. Defaults to '[unsupported image]'.
70
- * `[:iframe][:placeholder]`: If the HTML contains IFrames, this text/callable is rendered instead.
71
- A callable gets the URL of the IFrame as an argument. Defaults to ignore iframes.
72
-
73
- ## Supported HTML
74
-
75
- This gem parses the given HTML and layouts the following elements in a vertical order:
76
-
77
- * Text content: `p`, `div`, `ol`, `ul`, `li`, `hr`
78
- * Text semantics: `b`, `strong`, `i`, `em`, `u`, `a`, `br`
79
- * Headings: `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
80
- * Tables: `table`, `tr`, `td`, `th`
81
- * Media: `img`, `iframe`
82
-
83
- All other elements are ignored, their content is added to the parent element. With a few exceptions, no CSS is processed. One exception is the `width` property of `img`, `td` and `th`, which may contain values in `cm`, `mm`, `px`, `pt`, `%` or `auto`.
84
-
85
- If no explicit loader is given (see above), images are loaded from `http(s)` addresses are may be contained in the `src` attribute as base64 encoded data URIs. Prawn only supports `PNG` and `JPG`.
86
-
78
+ * `:text`
79
+ * `:preprocessor`: A proc/callable that is called each time before a chunk of text is rendered.
80
+ * `:margin_bottom`: Margin after each `<p>`, `<ol>`, `<ul>` or `<table>`. Defaults to about half a line.
81
+ * `:heading1-6`
82
+ * `:margin_top`: Margin before a heading. Default is 0.
83
+ * `:margin_bottom`: Margin after a heading. Default is 0.
84
+ * `:table`
85
+ * `:placeholder`
86
+ * `:too_large`: If the table content does not fit into the current bounding box, this text/callable is rendered instead. Defaults to '[table content too large]'.
87
+ * `:subtable_too_large`: If the content of a subtable cannot be fitted into the table, this text is rendered instead. Defaults to '[nested tables with automatic width are not supported]'.
88
+ * `:list`
89
+ * `:vertical_margin`: Margin at the top and the bottom of a list. Default is 5.
90
+ * `:bullet`
91
+ * `:char`: The text used as bullet in unordered lists. Default is '•'.
92
+ * `:margin`: Margin before the bullet. Default is 10.
93
+ * `:content`
94
+ * `:margin`: Margin between the bullet and the content. Default is 10.
95
+ * `:placeholder`
96
+ * `:too_large`: If the list content does not fit into the current bounding box, this text/callable is rendered instead. Defaults to '[list content too large]'.
97
+ * `:image`
98
+ * `:loader`: A callable that accepts the `src` attribute as an argument an returns a value understood by Prawn's `image` method. Loads `http(s)` URLs and base64 encoded data URIs by default.
99
+ * `:placeholder`: If an image is not supported, this text/callable is rendered instead. Defaults to '[unsupported image]'.
100
+ * `:iframe`
101
+ * `:placeholder`: If the HTML contains IFrames, this text/callable is rendered instead.
102
+ A callable gets the URL of the IFrame as an argument. Defaults to ignore iframes.
103
+ * `:input`
104
+ * `:symbol_font`: A special font to print checkboxes and radios. Prawn's standard fonts do not support special unicode characters. Do not forget to update the document's `font_families`.
105
+ * `:symbol_font_size`: The size of the special font to print checkboxes and radios.
106
+ * `:checkbox`
107
+ * `:checked`: The char to print for a checked checkbox. Default is '☑'.
108
+ * `:unchecked`: The char to print for an unchecked checkbox. Default is '☐'.
109
+ * `:radio`
110
+ * `:checked`: The char to print for a checked radio. Default is '◉'.
111
+ * `:unchecked`: The char to print for an unchecked radio. Default is '○'.
87
112
 
88
113
  ## Development
89
114
 
data/lib/prawn/markup.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'prawn'
2
4
  require 'prawn/measurement_extensions'
3
5
  require 'prawn/table'
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Builders
4
6
  class ListBuilder < NestableBuilder
5
- BULLET_CHAR = '•'.freeze
7
+ BULLET_CHAR = '•'
6
8
  BULLET_MARGIN = 10
7
9
  CONTENT_MARGIN = 10
8
10
  VERTICAL_MARGIN = 5
@@ -22,7 +24,10 @@ module Prawn
22
24
  end
23
25
 
24
26
  def draw
25
- make(true).draw
27
+ # fix https://github.com/prawnpdf/prawn-table/issues/120
28
+ pdf.font_size(column_cell_style(:content)[:size] || pdf.font_size) do
29
+ make(true).draw
30
+ end
26
31
  end
27
32
 
28
33
  private
@@ -65,7 +70,7 @@ module Prawn
65
70
  data = item.nodes.map { |n| [normalize_list_item_node(n)] }
66
71
  style = column_cell_style(:content)
67
72
  .merge(borders: [], padding: [0, 0, padding_bottom, 0])
68
- pdf.make_table(data, cell_style: style) do
73
+ pdf.make_table(data, cell_style: style, column_widths: [content_width]) do
69
74
  rows(-1).padding = [0, 0, 0, 0]
70
75
  end
71
76
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Builders
@@ -47,10 +49,12 @@ module Prawn
47
49
  def extract_text_cell_style(hash)
48
50
  TEXT_STYLE_OPTIONS
49
51
  .each_with_object({}) { |key, h| h[key] = hash[key] }
50
- .tap do |options|
51
- options[:font_style] ||= options.delete(:style)
52
- options[:text_color] ||= options.delete(:color)
53
- end
52
+ .tap { |options| convert_style_options(options) }
53
+ end
54
+
55
+ def convert_style_options(hash)
56
+ hash[:font_style] ||= hash.delete(:style)
57
+ hash[:text_color] ||= hash.delete(:color)
54
58
  end
55
59
 
56
60
  def type_key(object)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Builders
@@ -20,7 +22,10 @@ module Prawn
20
22
  end
21
23
 
22
24
  def draw
23
- make.draw
25
+ # fix https://github.com/prawnpdf/prawn-table/issues/120
26
+ pdf.font_size(table_options[:cell][:size] || pdf.font_size) do
27
+ make.draw
28
+ end
24
29
  rescue Prawn::Errors::CannotFit => e
25
30
  if failover_on_error
26
31
  draw
@@ -169,7 +174,8 @@ module Prawn
169
174
 
170
175
  def distribute_remaing_width(count)
171
176
  equal_width = (total_width - column_width_sum) / count.to_f
172
- return if equal_width < 0
177
+ return if equal_width.negative?
178
+
173
179
  column_widths.map! { |width| width || equal_width }
174
180
  end
175
181
 
@@ -230,11 +236,6 @@ module Prawn
230
236
  convert_style_options(opts[:header])
231
237
  end
232
238
 
233
- def convert_style_options(hash)
234
- hash[:font_style] ||= hash.delete(:style)
235
- hash[:text_color] ||= hash.delete(:color)
236
- end
237
-
238
239
  def default_table_options
239
240
  {
240
241
  cell: {
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Elements
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Elements
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Elements
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Interface
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  # Processes known HTML tags. Unknown tags are ignored.
@@ -21,34 +23,38 @@ module Prawn
21
23
  self.logger = defined?(Rails) ? Rails.logger : nil
22
24
 
23
25
  require 'prawn/markup/processor/text'
26
+ require 'prawn/markup/processor/blocks'
24
27
  require 'prawn/markup/processor/headings'
25
28
  require 'prawn/markup/processor/images'
29
+ require 'prawn/markup/processor/inputs'
26
30
  require 'prawn/markup/processor/tables'
27
31
  require 'prawn/markup/processor/lists'
28
32
 
29
33
  prepend Prawn::Markup::Processor::Text
34
+ prepend Prawn::Markup::Processor::Blocks
30
35
  prepend Prawn::Markup::Processor::Headings
31
36
  prepend Prawn::Markup::Processor::Images
37
+ prepend Prawn::Markup::Processor::Inputs
32
38
  prepend Prawn::Markup::Processor::Tables
33
39
  prepend Prawn::Markup::Processor::Lists
34
40
 
35
41
  def initialize(pdf, options = {})
42
+ super()
36
43
  @pdf = pdf
37
44
  @options = options
38
45
  end
39
46
 
40
47
  def parse(html)
41
48
  return if html.to_s.strip.empty?
49
+
42
50
  reset
43
51
  html = Prawn::Markup::Normalizer.new(html).normalize
44
52
  Nokogiri::HTML::SAX::Parser.new(self).parse(html) { |ctx| ctx.recovery = true }
45
53
  end
46
54
 
47
55
  def start_element(name, attrs = [])
48
- stack.push(name: name, attrs: Hash[attrs])
49
- if self.class.known_elements.include?(name)
50
- send("start_#{name}") if respond_to?("start_#{name}", true)
51
- end
56
+ stack.push(name: name, attrs: attrs.to_h)
57
+ send("start_#{name}") if known_element?(name) && respond_to?("start_#{name}", true)
52
58
  end
53
59
 
54
60
  def end_element(name)
@@ -62,11 +68,11 @@ module Prawn
62
68
  end
63
69
 
64
70
  def error(string)
65
- logger.debug('SAX parsing error: ' + string) if logger
71
+ logger.info("SAX parsing error: #{string.strip}") if logger
66
72
  end
67
73
 
68
74
  def warning(string)
69
- logger.warn(string) if logger
75
+ logger.info("SAX parsing warning: #{string.strip}") if logger
70
76
  end
71
77
 
72
78
  private
@@ -75,7 +81,11 @@ module Prawn
75
81
 
76
82
  def reset
77
83
  @stack = []
78
- @text_buffer = ''
84
+ @text_buffer = +''
85
+ end
86
+
87
+ def known_element?(name)
88
+ self.class.known_elements.include?(name)
79
89
  end
80
90
 
81
91
  def append_text(string)
@@ -115,15 +125,14 @@ module Prawn
115
125
  def style_properties
116
126
  style = current_attrs['style']
117
127
  if style
118
- tokens = style.split(';').map { |p| p.split(':', 2).map(&:strip) }
119
- Hash[tokens]
128
+ style.split(';').map { |p| p.split(':', 2).map(&:strip) }.to_h
120
129
  else
121
130
  {}
122
131
  end
123
132
  end
124
133
 
125
134
  def placeholder_value(keys, *args)
126
- placeholder = dig_options(keys)
135
+ placeholder = dig_options(*keys)
127
136
  return if placeholder.nil?
128
137
 
129
138
  if placeholder.respond_to?(:call)
@@ -133,7 +142,7 @@ module Prawn
133
142
  end
134
143
  end
135
144
 
136
- def dig_options(keys)
145
+ def dig_options(*keys)
137
146
  keys.inject(options) { |opts, key| opts ? opts[key] : nil }
138
147
  end
139
148
 
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module Markup
5
+ module Processor::Blocks
6
+ def self.prepended(base)
7
+ base.known_elements.push('p', 'br', 'div', 'hr')
8
+ end
9
+
10
+ def start_br
11
+ append_text("\n")
12
+ end
13
+
14
+ def start_p
15
+ handle_text_element
16
+ end
17
+
18
+ def end_p
19
+ if inside_container?
20
+ append_new_line
21
+ append_text("\n")
22
+ else
23
+ add_paragraph
24
+ end
25
+ end
26
+
27
+ def start_div
28
+ handle_text_element
29
+ end
30
+
31
+ def end_div
32
+ handle_text_element
33
+ end
34
+
35
+ def start_hr
36
+ return if inside_container?
37
+
38
+ put_bottom_margin(nil)
39
+ add_current_text
40
+ pdf.move_down(hr_vertical_margin_top)
41
+ pdf.stroke_horizontal_rule
42
+ pdf.move_down(hr_vertical_margin_bottom)
43
+ end
44
+
45
+ def end_document
46
+ add_current_text
47
+ end
48
+
49
+ private
50
+
51
+ def handle_text_element
52
+ if inside_container?
53
+ append_new_line
54
+ else
55
+ add_current_text
56
+ end
57
+ end
58
+
59
+ def append_new_line
60
+ append_text("\n") if buffered_text? && text_buffer[-1] != "\n"
61
+ end
62
+
63
+ def add_paragraph
64
+ text = dump_text
65
+ text.gsub!(/[^\n]/, '') if text.strip.empty?
66
+ unless text.empty?
67
+ add_bottom_margin
68
+ add_formatted_text(text, text_options)
69
+ put_bottom_margin(text_margin_bottom)
70
+ end
71
+ end
72
+
73
+ def add_current_text(options = text_options)
74
+ add_bottom_margin
75
+ return unless buffered_text?
76
+
77
+ string = dump_text
78
+ string.strip!
79
+ add_formatted_text(string, options)
80
+ end
81
+
82
+ def add_bottom_margin
83
+ if @bottom_margin
84
+ pdf.move_down(@bottom_margin)
85
+ @bottom_margin = nil
86
+ end
87
+ end
88
+
89
+ def add_formatted_text(string, options = text_options)
90
+ with_font(options) do
91
+ pdf.text(string, options)
92
+ end
93
+ end
94
+
95
+ def with_font(options)
96
+ pdf.font(options[:font] || pdf.font.family,
97
+ size: options[:size],
98
+ style: options[:style]) do
99
+ return yield
100
+ end
101
+ end
102
+
103
+ def hr_vertical_margin_top
104
+ @hr_vertical_margin_top ||=
105
+ (text_options[:size] || pdf.font_size) / 2.0
106
+ end
107
+
108
+ def hr_vertical_margin_bottom
109
+ @hr_vertical_margin_bottom ||= with_font(text_options) do
110
+ hr_vertical_margin_top +
111
+ pdf.font.descender +
112
+ text_leading -
113
+ pdf.line_width
114
+ end
115
+ end
116
+
117
+ def reset
118
+ super
119
+ text_margin_bottom # pre-calculate
120
+ end
121
+
122
+ def text_margin_bottom
123
+ options[:text] ||= {}
124
+ options[:text][:margin_bottom] ||= default_text_margin_bottom
125
+ end
126
+
127
+ def default_text_margin_bottom
128
+ text_line_gap +
129
+ text_descender +
130
+ text_leading
131
+ end
132
+
133
+ def text_line_gap
134
+ @text_line_gap ||= with_font(text_options) { pdf.font.line_gap }
135
+ end
136
+
137
+ def text_descender
138
+ @text_descender ||= with_font(text_options) { pdf.font.descender }
139
+ end
140
+
141
+ def text_leading
142
+ text_options[:leading] || pdf.default_leading
143
+ end
144
+
145
+ def text_options
146
+ @text_options ||= HashMerger.deep(default_text_options, options[:text] || {})
147
+ end
148
+
149
+ def default_text_options
150
+ {
151
+ inline_format: true
152
+ }
153
+ end
154
+ end
155
+ end
156
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Processor::Headings
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'open-uri'
2
4
 
3
5
  module Prawn
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ module Markup
5
+ module Processor::Inputs
6
+
7
+ DEFAULT_CHECKABLE_CHARS = {
8
+ checkbox: {
9
+ checked: '☑',
10
+ unchecked: '☐'
11
+ },
12
+ radio: {
13
+ checked: '◉',
14
+ unchecked: '○'
15
+ }
16
+ }.freeze
17
+
18
+ def self.prepended(base)
19
+ base.known_elements.push('input')
20
+ end
21
+
22
+ def start_input
23
+ type = current_attrs['type'].to_sym
24
+ if DEFAULT_CHECKABLE_CHARS.keys.include?(type)
25
+ append_checked_symbol(type)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def append_checked_symbol(type)
32
+ char = checkable_symbol(type)
33
+ append_text(build_font_tag(char))
34
+ end
35
+
36
+ def checkable_symbol(type)
37
+ state = current_attrs.key?('checked') ? :checked : :unchecked
38
+ dig_options(:input, type, state) || DEFAULT_CHECKABLE_CHARS[type][state]
39
+ end
40
+
41
+ def symbol_font_options
42
+ @symbol_font_options ||= {
43
+ name: dig_options(:input, :symbol_font),
44
+ size: dig_options(:input, :symbol_font_size)
45
+ }.compact
46
+ end
47
+
48
+ def build_font_tag(content)
49
+ return content if symbol_font_options.empty?
50
+
51
+ out = +'<font'
52
+ symbol_font_options.each do |key, value|
53
+ out << " #{key}=\"#{value}\""
54
+ end
55
+ out << '>'
56
+ out << content
57
+ out << '</font>'
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Processor::Lists
@@ -32,10 +34,14 @@ module Prawn
32
34
  alias end_ul end_list
33
35
 
34
36
  def start_li
37
+ return unless inside_container?
38
+
35
39
  current_list.items << Elements::Item.new
36
40
  end
37
41
 
38
42
  def end_li
43
+ return unless inside_container?
44
+
39
45
  add_cell_text_node(current_list_item)
40
46
  end
41
47
 
@@ -61,7 +67,9 @@ module Prawn
61
67
  end
62
68
 
63
69
  def current_list_item
64
- current_list.items.last
70
+ items = current_list.items
71
+ items << Elements::Item.new if items.empty?
72
+ items.last
65
73
  end
66
74
 
67
75
  def inside_container?
@@ -81,12 +89,17 @@ module Prawn
81
89
  end
82
90
 
83
91
  def add_list(list)
84
- Builders::ListBuilder.new(pdf, list, pdf.bounds.width, options).draw
85
- put_bottom_margin(text_margin_bottom)
92
+ pdf.move_up(additional_cell_padding_top)
93
+ draw_list(list)
94
+ put_bottom_margin(text_margin_bottom + additional_cell_padding_top)
86
95
  rescue Prawn::Errors::CannotFit => e
87
96
  append_text(list_too_large_placeholder(e))
88
97
  end
89
98
 
99
+ def draw_list(list)
100
+ Builders::ListBuilder.new(pdf, list, pdf.bounds.width, options).draw
101
+ end
102
+
90
103
  def list_too_large_placeholder(error)
91
104
  placeholder_value(%i[list placeholder too_large], error) || '[list content too large]'
92
105
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Processor::Tables
@@ -26,19 +28,29 @@ module Prawn
26
28
  end
27
29
 
28
30
  def start_tr
31
+ return unless current_table
32
+
29
33
  current_table << []
30
34
  end
31
35
 
32
36
  def start_td
37
+ return unless current_table
38
+
33
39
  current_table.last << Elements::Cell.new(width: style_properties['width'])
34
40
  end
35
41
 
36
42
  def start_th
43
+ return unless current_table
44
+
37
45
  current_table.last << Elements::Cell.new(width: style_properties['width'], header: true)
38
46
  end
39
47
 
40
48
  def end_td
41
- add_cell_text_node(current_cell)
49
+ if current_table
50
+ add_cell_text_node(current_cell)
51
+ else
52
+ add_current_text
53
+ end
42
54
  end
43
55
  alias end_th end_td
44
56
 
@@ -73,6 +85,7 @@ module Prawn
73
85
 
74
86
  def add_cell_text_node(cell, options = {})
75
87
  return unless buffered_text?
88
+
76
89
  cell.nodes << options.merge(content: dump_text.strip)
77
90
  end
78
91
 
@@ -83,15 +96,24 @@ module Prawn
83
96
  end
84
97
 
85
98
  def add_table(cells)
86
- Builders::TableBuilder.new(pdf, cells, pdf.bounds.width, options).draw
87
- put_bottom_margin(text_margin_bottom)
99
+ draw_table(cells)
100
+ put_bottom_margin(text_margin_bottom + additional_cell_padding_top + text_leading)
88
101
  rescue Prawn::Errors::CannotFit => e
89
102
  append_text(table_too_large_placeholder(e))
90
103
  end
91
104
 
105
+ def draw_table(cells)
106
+ Builders::TableBuilder.new(pdf, cells, pdf.bounds.width, options).draw
107
+ end
108
+
92
109
  def table_too_large_placeholder(error)
93
110
  placeholder_value(%i[table placeholder too_large], error) || '[table content too large]'
94
111
  end
112
+
113
+ def additional_cell_padding_top
114
+ # as used in Prawn::Table::Cell::Text#draw_content move_down
115
+ (text_line_gap + text_descender) / 2
116
+ end
95
117
  end
96
118
  end
97
119
  end
@@ -1,42 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module Processor::Text
4
6
  def self.prepended(base)
5
- base.known_elements.push('p', 'br', 'div', 'b', 'strong', 'i', 'em', 'u', 'a', 'hr')
6
- end
7
-
8
- def start_br
9
- append_text("\n")
10
- end
11
-
12
- def start_p
13
- handle_text_element
14
- end
15
-
16
- def end_p
17
- if inside_container?
18
- append_new_line
19
- append_text("\n")
20
- else
21
- add_paragraph
22
- end
23
- end
24
-
25
- def start_div
26
- handle_text_element
27
- end
28
-
29
- def end_div
30
- handle_text_element
7
+ base.known_elements.push(
8
+ 'a', 'b', 'strong', 'i', 'em', 'u', 'strikethrough', 'strike', 's', 'del',
9
+ 'sub', 'sup'
10
+ )
31
11
  end
32
12
 
33
13
  def start_a
34
14
  append_text("<link href=\"#{current_attrs['href']}\">")
35
15
  end
16
+ alias start_link start_a
36
17
 
37
18
  def end_a
38
19
  append_text('</link>')
39
20
  end
21
+ alias end_link end_a
40
22
 
41
23
  def start_b
42
24
  append_text('<b>')
@@ -58,119 +40,44 @@ module Prawn
58
40
  end
59
41
  alias end_em end_i
60
42
 
61
- def start_hr
62
- return if inside_container?
63
-
64
- put_bottom_margin(nil)
65
- add_current_text
66
- pdf.move_down(hr_vertical_margin_top)
67
- pdf.stroke_horizontal_rule
68
- pdf.move_down(hr_vertical_margin_bottom)
43
+ def start_u
44
+ append_text('<u>')
69
45
  end
70
46
 
71
- def end_document
72
- add_current_text
47
+ def end_u
48
+ append_text('</u>')
73
49
  end
74
50
 
75
- private
76
-
77
- def handle_text_element
78
- if inside_container?
79
- append_new_line
80
- else
81
- add_current_text
82
- end
83
- end
84
-
85
- def append_new_line
86
- append_text("\n") if buffered_text? && text_buffer[-1] != "\n"
51
+ def start_strikethrough
52
+ append_text('<strikethrough>')
87
53
  end
54
+ alias start_s start_strikethrough
55
+ alias start_strike start_strikethrough
56
+ alias start_del start_strikethrough
88
57
 
89
- def add_paragraph
90
- text = dump_text
91
- text.gsub!(/[^\n]/, '') if text.strip.empty?
92
- unless text.empty?
93
- add_bottom_margin
94
- add_formatted_text(text, text_options)
95
- put_bottom_margin(text_margin_bottom)
96
- end
58
+ def end_strikethrough
59
+ append_text('</strikethrough>')
97
60
  end
61
+ alias end_s end_strikethrough
62
+ alias end_strike end_strikethrough
63
+ alias end_del end_strikethrough
98
64
 
99
- def add_current_text(options = text_options)
100
- add_bottom_margin
101
- return unless buffered_text?
102
-
103
- string = dump_text
104
- string.strip!
105
- add_formatted_text(string, options)
65
+ def start_sub
66
+ append_text('<sub>')
106
67
  end
107
68
 
108
- def add_bottom_margin
109
- if @bottom_margin
110
- pdf.move_down(@bottom_margin)
111
- @bottom_margin = nil
112
- end
69
+ def end_sub
70
+ append_text('</sub>')
113
71
  end
114
72
 
115
- def add_formatted_text(string, options = text_options)
116
- with_font(options) do
117
- pdf.text(string, options)
118
- end
73
+ def start_sup
74
+ append_text('<sup>')
119
75
  end
120
76
 
121
- def with_font(options)
122
- pdf.font(options[:font] || pdf.font.family,
123
- size: options[:size],
124
- style: options[:style]) do
125
- return yield
126
- end
77
+ def end_sup
78
+ append_text('</sup>')
127
79
  end
128
80
 
129
- def hr_vertical_margin_top
130
- @hr_vertical_margin_top ||=
131
- (text_options[:size] || pdf.font_size) / 2.0
132
- end
133
-
134
- def hr_vertical_margin_bottom
135
- @hr_vertical_margin_bottom ||= with_font(text_options) do
136
- hr_vertical_margin_top +
137
- pdf.font.descender +
138
- text_leading -
139
- pdf.line_width
140
- end
141
- end
142
-
143
- def reset
144
- super
145
- text_margin_bottom # pre-calculate
146
- end
147
-
148
- def text_margin_bottom
149
- options[:text] ||= {}
150
- options[:text][:margin_bottom] ||= default_text_margin_bottom
151
- end
152
-
153
- def default_text_margin_bottom
154
- with_font(text_options) do
155
- pdf.font.line_gap +
156
- pdf.font.descender +
157
- text_leading
158
- end
159
- end
160
-
161
- def text_leading
162
- text_options[:leading] || pdf.default_leading
163
- end
164
-
165
- def text_options
166
- @text_options ||= HashMerger.deep(default_text_options, options[:text] || {})
167
- end
168
-
169
- def default_text_options
170
- {
171
- inline_format: true
172
- }
173
- end
174
81
  end
175
82
  end
176
83
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  module HashMerger
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  # Normalizes HTML markup:
@@ -22,7 +24,7 @@ module Prawn
22
24
  close_self_closing_elements
23
25
  normalize_spaces
24
26
  replace_html_entities
25
- "<root>#{html}</root>"
27
+ "<body>#{html}</body>"
26
28
  end
27
29
 
28
30
  private
@@ -43,6 +45,7 @@ module Prawn
43
45
  html.gsub!(/&#{entity};/, string)
44
46
  end
45
47
  end
48
+
46
49
  end
47
50
  end
48
51
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
5
  class SizeConverter
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Prawn
2
4
  module Markup
3
- VERSION = '0.1.0'.freeze
5
+ VERSION = '0.3.1'
4
6
  end
5
7
  end
data/prawn-markup.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.license = 'MIT'
18
18
 
19
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
- f.match(%r{^(test|spec|features)/})
20
+ f.match(%r{^(((spec|bin)/)|\.|Gemfile|Rakefile)})
21
21
  end
22
22
  spec.bindir = 'exe'
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-markup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pascal Zumkehr
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-10 00:00:00.000000000 Z
11
+ date: 2021-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -159,18 +159,9 @@ executables: []
159
159
  extensions: []
160
160
  extra_rdoc_files: []
161
161
  files:
162
- - ".gitignore"
163
- - ".rspec"
164
- - ".rubocop.yml"
165
- - ".travis.yml"
166
162
  - CODE_OF_CONDUCT.md
167
- - Gemfile
168
- - Gemfile.lock
169
163
  - LICENSE.txt
170
164
  - README.md
171
- - Rakefile
172
- - bin/console
173
- - bin/setup
174
165
  - lib/prawn/markup.rb
175
166
  - lib/prawn/markup/builders/list_builder.rb
176
167
  - lib/prawn/markup/builders/nestable_builder.rb
@@ -180,8 +171,10 @@ files:
180
171
  - lib/prawn/markup/elements/list.rb
181
172
  - lib/prawn/markup/interface.rb
182
173
  - lib/prawn/markup/processor.rb
174
+ - lib/prawn/markup/processor/blocks.rb
183
175
  - lib/prawn/markup/processor/headings.rb
184
176
  - lib/prawn/markup/processor/images.rb
177
+ - lib/prawn/markup/processor/inputs.rb
185
178
  - lib/prawn/markup/processor/lists.rb
186
179
  - lib/prawn/markup/processor/tables.rb
187
180
  - lib/prawn/markup/processor/text.rb
@@ -194,7 +187,7 @@ homepage: https://github.com/puzzle/prawn-markup
194
187
  licenses:
195
188
  - MIT
196
189
  metadata: {}
197
- post_install_message:
190
+ post_install_message:
198
191
  rdoc_options: []
199
192
  require_paths:
200
193
  - lib
@@ -209,9 +202,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
209
202
  - !ruby/object:Gem::Version
210
203
  version: '0'
211
204
  requirements: []
212
- rubyforge_project:
213
- rubygems_version: 2.4.8
214
- signing_key:
205
+ rubygems_version: 3.0.6
206
+ signing_key:
215
207
  specification_version: 4
216
208
  summary: Parse simple HTML markup to include in Prawn PDFs
217
209
  test_files: []
data/.gitignore DELETED
@@ -1,12 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /spec/coverage/
9
- /tmp/
10
-
11
- # rspec failure tracking
12
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,64 +0,0 @@
1
- AllCops:
2
- DisplayCopNames: true
3
- Exclude:
4
- - '*.gemspec'
5
- - spec/**/*
6
- - vendor/**/*
7
-
8
- Metrics/AbcSize:
9
- Severity: error
10
-
11
- Metrics/ClassLength:
12
- Max: 250
13
- Severity: error
14
-
15
- Metrics/CyclomaticComplexity:
16
- Severity: error
17
-
18
- Metrics/LineLength:
19
- Max: 100
20
- Severity: error
21
-
22
- Metrics/MethodLength:
23
- Max: 10
24
- Severity: error
25
-
26
- Metrics/ModuleLength:
27
- Max: 150
28
- Severity: error
29
-
30
- Metrics/ParameterLists:
31
- Max: 4
32
- Severity: warning
33
-
34
- # Keep for now, easier with superclass definitions
35
- ClassAndModuleChildren:
36
- Enabled: false
37
-
38
- # The ones we use must exist for the entire class hierarchy.
39
- ClassVars:
40
- Enabled: false
41
-
42
- # Well, well, well
43
- Documentation:
44
- Enabled: false
45
-
46
- # Keep single line bodys for if and unless
47
- IfUnlessModifier:
48
- Enabled: false
49
-
50
- # We thinks that's fine for specs
51
- Layout/EmptyLinesAroundBlockBody:
52
- Enabled: false
53
-
54
- # We thinks that's fine
55
- Layout/EmptyLinesAroundClassBody:
56
- Enabled: false
57
-
58
- # We thinks that's fine
59
- Layout/EmptyLinesAroundModuleBody:
60
- Enabled: false
61
-
62
- # We think that's the developers choice
63
- Style/GuardClause:
64
- Enabled: false
data/.travis.yml DELETED
@@ -1,25 +0,0 @@
1
- sudo: false
2
-
3
- language: ruby
4
-
5
- rvm:
6
- - 2.5.1
7
-
8
- env:
9
- global:
10
- - CC_TEST_REPORTER_ID=d70fe39ffb8f2f7c5f837f0133d10a3a1c6784d0dd189153111577b60f74c603
11
-
12
- before_install: gem install bundler
13
-
14
- before_script:
15
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
16
- - chmod +x ./cc-test-reporter
17
- - ./cc-test-reporter before-build
18
-
19
- script:
20
- - bundle exec rake
21
- - bundle exec rubocop
22
-
23
- after_script:
24
- - ./cc-test-reporter format-coverage -t simplecov -o spec/coverage/codeclimate.json spec/coverage/.resultset.json
25
- - if [[ "$TRAVIS_TEST_RESULT" == 0 ]]; then ./cc-test-reporter upload-coverage -i spec/coverage/codeclimate.json; fi
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in prawn-markup.gemspec
4
- gemspec
data/Gemfile.lock DELETED
@@ -1,89 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- prawn-markup (0.1.0)
5
- nokogiri
6
- prawn
7
- prawn-table
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- Ascii85 (1.0.3)
13
- afm (0.2.2)
14
- ast (2.4.0)
15
- byebug (10.0.2)
16
- diff-lcs (1.3)
17
- docile (1.3.1)
18
- hashery (2.1.2)
19
- jaro_winkler (1.5.1)
20
- json (2.1.0)
21
- mini_portile2 (2.3.0)
22
- nokogiri (1.8.4)
23
- mini_portile2 (~> 2.3.0)
24
- parallel (1.12.1)
25
- parser (2.5.1.2)
26
- ast (~> 2.4.0)
27
- pdf-core (0.7.0)
28
- pdf-inspector (1.3.0)
29
- pdf-reader (>= 1.0, < 3.0.a)
30
- pdf-reader (2.1.0)
31
- Ascii85 (~> 1.0.0)
32
- afm (~> 0.2.1)
33
- hashery (~> 2.0)
34
- ruby-rc4
35
- ttfunk
36
- powerpack (0.1.2)
37
- prawn (2.2.2)
38
- pdf-core (~> 0.7.0)
39
- ttfunk (~> 1.5)
40
- prawn-table (0.2.2)
41
- prawn (>= 1.3.0, < 3.0.0)
42
- rainbow (3.0.0)
43
- rake (12.3.1)
44
- rspec (3.8.0)
45
- rspec-core (~> 3.8.0)
46
- rspec-expectations (~> 3.8.0)
47
- rspec-mocks (~> 3.8.0)
48
- rspec-core (3.8.0)
49
- rspec-support (~> 3.8.0)
50
- rspec-expectations (3.8.1)
51
- diff-lcs (>= 1.2.0, < 2.0)
52
- rspec-support (~> 3.8.0)
53
- rspec-mocks (3.8.0)
54
- diff-lcs (>= 1.2.0, < 2.0)
55
- rspec-support (~> 3.8.0)
56
- rspec-support (3.8.0)
57
- rubocop (0.58.2)
58
- jaro_winkler (~> 1.5.1)
59
- parallel (~> 1.10)
60
- parser (>= 2.5, != 2.5.1.1)
61
- powerpack (~> 0.1)
62
- rainbow (>= 2.2.2, < 4.0)
63
- ruby-progressbar (~> 1.7)
64
- unicode-display_width (~> 1.0, >= 1.0.1)
65
- ruby-progressbar (1.10.0)
66
- ruby-rc4 (0.1.5)
67
- simplecov (0.16.1)
68
- docile (~> 1.1)
69
- json (>= 1.8, < 3)
70
- simplecov-html (~> 0.10.0)
71
- simplecov-html (0.10.2)
72
- ttfunk (1.5.1)
73
- unicode-display_width (1.4.0)
74
-
75
- PLATFORMS
76
- ruby
77
-
78
- DEPENDENCIES
79
- bundler
80
- byebug
81
- pdf-inspector
82
- prawn-markup!
83
- rake
84
- rspec
85
- rubocop
86
- simplecov
87
-
88
- BUNDLED WITH
89
- 1.16.2
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task default: :spec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'bundler/setup'
4
- require 'prawn/markup'
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require 'irb'
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here