prawn-svg 0.36.2 → 0.38.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: 6e2b791daa33f85948c08bdafec3f1a3ab8dffa8b07dd50ce7bb7a55320d663b
4
- data.tar.gz: 78e45c7790de4b1cf2d328ad6ec8bbb77bab707cdeb8e75fcf52055410f36fc4
3
+ metadata.gz: d9d63836ebf08ec1ebd6320b61a8f4b93c5df29e91e6e1397b53c65d548e8afe
4
+ data.tar.gz: 25ad6658329c42589d086e592589908a1c556a631503578219c308ab095fa513
5
5
  SHA512:
6
- metadata.gz: da875e08d850fd7d3b74d9e5a629d4f3125bc933614e0d241ddd4c5122ad75495d6b530c01914ad0bc44dbcbdec41ee5e35196e008c484652bbb99ecb0e66e53
7
- data.tar.gz: 79f54a51c1acdea4a5d5de5bb72f130b5ca692aed48c72ceb36aced4f16df223be1a81f1152164dbdf3452571b79a676df96a4c38ba825afeedd32748e4f3919
6
+ metadata.gz: d73e5adc3fa832d2ed9159cf02cea3b47cf3923ff0e145af4de426effeb6157d4d133d600bf708ccd963a736d517fccb8a7c8d410ee479025288193659d34859
7
+ data.tar.gz: 20d5d9f28438c3ca4c23454541f763b1766f3df9719ced30a3451abc08e21913cf4276d2c148d9af695bab9e4670b01dcc1985fe5d9a9ebd94cfc08cee755818
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prawn-svg (0.36.2)
4
+ prawn-svg (0.38.0)
5
5
  css_parser (~> 1.6)
6
6
  matrix (~> 0.4.2)
7
7
  prawn (>= 0.11.1, < 3)
data/README.md CHANGED
@@ -114,9 +114,9 @@ prawn-svg supports CSS, both in `<style>` blocks and `style` attributes.
114
114
  In CSS selectors you can use element names, IDs, classes, attributes (existence, `=`, `^=`, `$=`, `*=`, `~=`, `|=`)
115
115
  and all combinators (` `, `>`, `+`, `~`).
116
116
  The pseudo-classes `:first-child`, `:last-child` and `:nth-child(n)` (where n is a number) also work.
117
+ `!important` is supported.
117
118
 
118
- Pseudo-elements and the other pseudo-classes are not supported. Specificity ordering is
119
- implemented, but `!important` is not.
119
+ Pseudo-elements and the other pseudo-classes are not supported.
120
120
 
121
121
  ## Not supported
122
122
 
@@ -1,5 +1,7 @@
1
1
  module Prawn::SVG::Attributes::Transform
2
2
  def parse_transform_attribute_and_call
3
+ # Some elements do not support transforms
4
+ return unless transformable?
3
5
  return unless (transform = attributes['transform'])
4
6
 
5
7
  matrix = matrix_for_pdf(parse_transform_attribute(transform))
@@ -1,7 +1,5 @@
1
1
  module Prawn::SVG::CSS
2
2
  class Stylesheets
3
- USER_AGENT_STYLESHEET = 'svg, symbol, image, marker, pattern, foreignObject { overflow: hidden }'.freeze
4
-
5
3
  attr_reader :css_parser, :root, :media
6
4
 
7
5
  def initialize(css_parser, root, media = :all)
@@ -11,7 +9,6 @@ module Prawn::SVG::CSS
11
9
  end
12
10
 
13
11
  def load
14
- load_user_agent_stylesheet
15
12
  load_style_elements
16
13
  xpath_styles = gather_xpath_styles
17
14
  associate_xpath_styles_with_elements(xpath_styles)
@@ -19,10 +16,6 @@ module Prawn::SVG::CSS
19
16
 
20
17
  private
21
18
 
22
- def load_user_agent_stylesheet
23
- css_parser.add_block!(USER_AGENT_STYLESHEET)
24
- end
25
-
26
19
  def load_style_elements
27
20
  REXML::XPath.match(root, '//style').each do |source|
28
21
  data = source.texts.map(&:value).join
@@ -36,7 +36,7 @@ class Prawn::SVG::Elements::Base
36
36
  @attributes = {}
37
37
  @properties = Prawn::SVG::Properties.new
38
38
 
39
- if source && !state.inside_use
39
+ if source && add_to_elements_by_id? && !state.inside_use
40
40
  id = source.attributes['id']
41
41
  id = id.strip if id
42
42
 
@@ -114,6 +114,10 @@ class Prawn::SVG::Elements::Base
114
114
  @calls.concat duplicate_calls(other.base_calls)
115
115
  end
116
116
 
117
+ def add_yield_call(&block)
118
+ add_call('svg:yield', block)
119
+ end
120
+
117
121
  def new_call_context_from_base
118
122
  old_calls = @calls
119
123
  @calls = @base_calls
@@ -142,7 +146,7 @@ class Prawn::SVG::Elements::Base
142
146
  source.elements.select do |elem|
143
147
  # To be strict, we shouldn't treat namespace-less elements as SVG, but for
144
148
  # backwards compatibility, and because it doesn't hurt, we will.
145
- elem.namespace == SVG_NAMESPACE || elem.namespace == ''
149
+ [SVG_NAMESPACE, ''].include?(elem.namespace)
146
150
  end
147
151
  end
148
152
 
@@ -203,20 +207,27 @@ class Prawn::SVG::Elements::Base
203
207
  end
204
208
 
205
209
  def extract_attributes_and_properties
206
- if (styles = document.element_styles[source])
207
- # TODO : implement !important, at the moment it's just ignored
208
- styles.each do |name, value, _important|
209
- @properties.set(name, value)
210
- end
210
+ # Apply user agent stylesheet
211
+ if %w[svg symbol image marker pattern foreignObject].include?(source.name)
212
+ @properties.set('overflow', 'hidden')
211
213
  end
212
214
 
213
- @properties.load_hash(parse_css_declarations(source.attributes['style'] || ''))
214
-
215
+ # Apply presentation attributes, and set attributes that aren't presentation attributes
215
216
  source.attributes.each do |name, value|
216
217
  # Properties#set returns nil if it's not a recognised property name
217
218
  @properties.set(name, value) or @attributes[name] = value
218
219
  end
219
220
 
221
+ # Apply stylesheet styles
222
+ if (styles = document.element_styles[source])
223
+ styles.each do |name, value, important|
224
+ @properties.set(name, value, important: important)
225
+ end
226
+ end
227
+
228
+ # Apply inline styles
229
+ @properties.load_hash(parse_css_declarations(source.attributes['style'] || ''))
230
+
220
231
  state.computed_properties.compute_properties(@properties)
221
232
  end
222
233
 
@@ -262,6 +273,14 @@ class Prawn::SVG::Elements::Base
262
273
  ['hidden', 'scroll'].include?(computed_properties.overflow)
263
274
  end
264
275
 
276
+ def transformable?
277
+ true
278
+ end
279
+
280
+ def add_to_elements_by_id?
281
+ true
282
+ end
283
+
265
284
  def stroke_width
266
285
  if computed_properties.stroke.none?
267
286
  0
@@ -0,0 +1,27 @@
1
+ #
2
+ # This element base class is used for elements that render directly to the Prawn canvas.
3
+ #
4
+ # Initially when I wrote prawn-svg, I was expecting to have multiple renderers and thought separating the codebase
5
+ # from the Prawn renderer would be a good idea. However, it turns out that the Prawn renderer was the only one
6
+ # that was targeted, and it ended up being tightly coupled with the Prawn library.
7
+ #
8
+ # This class is probably how I should have written it. Direct render is required to do text rendering properly
9
+ # because we need to know the width of all the things we print. As of the time of this comment it's the only
10
+ # system that uses DirectRenderBase in prawn-svg.
11
+ #
12
+ class Prawn::SVG::Elements::DirectRenderBase < Prawn::SVG::Elements::Base
13
+ # Called by Renderer when it finds the svg:render call added below.
14
+ def render(prawn, renderer); end
15
+
16
+ protected
17
+
18
+ def parse_and_apply
19
+ parse_standard_attributes
20
+ parse
21
+ apply_calls_from_standard_attributes
22
+ @parent_calls << ['svg:render', [self], [], []] unless computed_properties.display == 'none'
23
+ rescue SkipElementQuietly
24
+ rescue SkipElementError => e
25
+ @document.warnings << e.message
26
+ end
27
+ end
@@ -16,9 +16,9 @@ class Prawn::SVG::Elements::Polygon < Prawn::SVG::Elements::Base
16
16
  def commands
17
17
  @commands ||= [
18
18
  Prawn::SVG::Pathable::Move.new(@points[0])
19
- ] + @points[1..].map { |point|
19
+ ] + @points[1..].map do |point|
20
20
  Prawn::SVG::Pathable::Line.new(point)
21
- } + [
21
+ end + [
22
22
  Prawn::SVG::Pathable::Close.new(@points[0])
23
23
  ]
24
24
  end
@@ -1,70 +1,72 @@
1
- class Prawn::SVG::Elements::Text < Prawn::SVG::Elements::DepthFirstBase
2
- private
1
+ module Prawn::SVG
2
+ class Elements::Text < Elements::DirectRenderBase
3
+ Cursor = Struct.new(:x, :y)
3
4
 
4
- # This class doesn't represent an element in the SVG document; it's here
5
- # to hold together text information. Because of this, we overload the
6
- # parse_step and apply_step methods, and bypass all the normal processing
7
- # of the element, delegating it to our root text component.
5
+ def parse
6
+ @root_component = Elements::TextComponent.new(document, source, [], state.dup)
7
+ @root_component.process
8
8
 
9
- def parse_step
10
- state.text = Prawn::SVG::Elements::TextComponent::TextState.new
9
+ reintroduce_trailing_and_leading_whitespace
10
+ end
11
11
 
12
- @text_root = Prawn::SVG::Elements::TextComponent.new(document, source, nil, state.dup)
13
- @text_root.parse_step
12
+ def render(prawn, renderer)
13
+ @root_component.lay_out(prawn)
14
14
 
15
- reintroduce_trailing_and_leading_whitespace
16
- end
15
+ translate_x =
16
+ case @root_component.computed_properties.text_anchor
17
+ when 'middle'
18
+ -@root_component.calculated_width / 2.0
19
+ when 'end'
20
+ -@root_component.calculated_width
21
+ end
17
22
 
18
- def apply_step(calls)
19
- @base_calls = @calls = calls
20
- add_call_and_enter 'text_group'
21
- @text_root.apply_step(@calls)
22
- end
23
+ cursor = Cursor.new(0, document.sizing.output_height)
24
+ @root_component.render_component(prawn, renderer, cursor, translate_x)
25
+ end
23
26
 
24
- def drawable?
25
- false
26
- end
27
+ private
27
28
 
28
- def reintroduce_trailing_and_leading_whitespace
29
- printables = []
30
- built_printable_queue(printables, @text_root)
29
+ def reintroduce_trailing_and_leading_whitespace
30
+ text_nodes = []
31
+ build_text_node_queue(text_nodes, @root_component)
31
32
 
32
- remove_whitespace_only_printables_and_start_and_end(printables)
33
- remove_printables_that_are_completely_empty(printables)
34
- apportion_leading_and_trailing_spaces(printables)
35
- end
33
+ remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
34
+ remove_text_nodes_that_are_completely_empty(text_nodes)
35
+ apportion_leading_and_trailing_spaces(text_nodes)
36
+ end
36
37
 
37
- def built_printable_queue(queue, component)
38
- component.commands.each do |command|
39
- case command
40
- when Prawn::SVG::Elements::TextComponent::Printable
41
- queue << command
42
- else
43
- built_printable_queue(queue, command)
38
+ def build_text_node_queue(queue, component)
39
+ component.children.each do |element|
40
+ case element
41
+ when Elements::TextNode
42
+ queue << element
43
+ else
44
+ build_text_node_queue(queue, element)
45
+ end
44
46
  end
45
47
  end
46
- end
47
48
 
48
- def remove_whitespace_only_printables_and_start_and_end(printables)
49
- printables.pop while printables.last && printables.last.text.empty?
50
- printables.shift while printables.first && printables.first.text.empty?
51
- end
49
+ def remove_whitespace_only_text_nodes_and_start_and_end(text_nodes)
50
+ text_nodes.pop while text_nodes.last && text_nodes.last.text.empty?
51
+ text_nodes.shift while text_nodes.first && text_nodes.first.text.empty?
52
+ end
52
53
 
53
- def remove_printables_that_are_completely_empty(printables)
54
- printables.reject! do |printable|
55
- printable.text.empty? && !printable.trailing_space? && !printable.leading_space?
54
+ def remove_text_nodes_that_are_completely_empty(text_nodes)
55
+ text_nodes.reject! do |text_node|
56
+ text_node.text.empty? && !text_node.trailing_space? && !text_node.leading_space?
57
+ end
56
58
  end
57
- end
58
59
 
59
- def apportion_leading_and_trailing_spaces(printables)
60
- printables.each_cons(2) do |a, b|
61
- if a.text.empty?
62
- # Empty strings can only get a leading space from the previous non-empty text,
63
- # and never get a trailing space
64
- elsif a.trailing_space?
65
- a.text += ' '
66
- elsif b.leading_space?
67
- b.text = " #{b.text}"
60
+ def apportion_leading_and_trailing_spaces(text_nodes)
61
+ text_nodes.each_cons(2) do |a, b|
62
+ if a.text.empty?
63
+ # Empty strings can only get a leading space from the previous non-empty text,
64
+ # and never get a trailing space
65
+ elsif a.trailing_space?
66
+ a.text += ' '
67
+ elsif b.leading_space?
68
+ b.text = " #{b.text}"
69
+ end
68
70
  end
69
71
  end
70
72
  end