prawn-svg 0.30.0 → 0.31.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: 34f48795bbd6ee32d7c18228b833137dc52cb5b02f0446de0e3908d0827e7234
4
- data.tar.gz: 4f654b0c484f81ba80660940d2823c836c0aaba0156cb9e803c722170fdec993
3
+ metadata.gz: 9ed78a64dc94709fc5e53f1d6f521f93e3bf41196c29dde616c6139743c9c889
4
+ data.tar.gz: 7a0a5683370d3478e8b9717069e0f4012337aaf6250106065dfd5ad9b4458a91
5
5
  SHA512:
6
- metadata.gz: 4bf2b53a65607c75da3907433b40e1f4aaee67a70268f562a3f6c554087ab7a66924874befc1da5a6954b86b71f0e32ab6bff5108f64f3582b677500dc0a2f8b
7
- data.tar.gz: 25d862c9b5b6e25a3dc22ac581c2e04c025ff76b3f842033578f2565d8641f2a951d03e0a88c272e52f584c87fb27a2af90593ae6aea8d69c80ec4ec06d0b36a
6
+ metadata.gz: a58fd3f4508d486b3632f2e337f5e2fe97ef8c2baeeeda9b4f53f816ff18eb10b3e8879c98ea10c645becb29c8351a7addc6867c22abfeda77a7edf654607f44
7
+ data.tar.gz: 3bdbbc06f25405d3f51de30e81c39406f7ccf535e507bb36b43df3939d1a4931b763cb5308bde72674ab3f2ffcb55171b958f93a3b603bf5cf17a1bbd24d44b4
@@ -1,8 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.10
4
- - 2.2.7
5
- - 2.3.3
6
- - 2.4.0
7
- - 2.5.3
8
- - 2.6.1
3
+ - 2.2.10
4
+ - 2.3.8
5
+ - 2.4.10
6
+ - 2.5.8
7
+ - 2.6.6
8
+ - 2.7.1
data/README.md CHANGED
@@ -61,7 +61,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
61
61
  implementation of elliptical arc is a bit rough at the moment.
62
62
 
63
63
  - `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, 'textLength', 'lengthAdjust', and with extra properties
64
- `text-anchor`, `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
64
+ `text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
65
65
 
66
66
  - <tt>&lt;svg&gt;</tt>, <tt>&lt;g&gt;</tt> and <tt>&lt;symbol&gt;</tt>
67
67
 
@@ -76,7 +76,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
76
76
 
77
77
  - `<marker>`
78
78
 
79
- - `<linearGradient>` is implemented on Prawn 2.2.0+ with attributes `gradientUnits` and `gradientTransform` (spreadMethod and stop-opacity are unimplemented.)
79
+ - `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and `gradientTransform` (spreadMethod and stop-opacity are unimplemented.)
80
80
 
81
81
  - `<switch>` and `<foreignObject>`, although prawn-svg cannot handle any data that is not SVG so `<foreignObject>`
82
82
  tags are always ignored.
@@ -116,7 +116,7 @@ implemented, but `!important` is not.
116
116
 
117
117
  ## Not supported
118
118
 
119
- prawn-svg does not support radial gradients, patterns or filters.
119
+ prawn-svg does not support hyperlinks, patterns or filters.
120
120
 
121
121
  It does not support text in the clip area, but you can clip shapes and text by any shape.
122
122
 
@@ -28,6 +28,7 @@ module Prawn::SVG::Elements
28
28
  use: Prawn::SVG::Elements::Use,
29
29
  image: Prawn::SVG::Elements::Image,
30
30
  linearGradient: Prawn::SVG::Elements::Gradient,
31
+ radialGradient: Prawn::SVG::Elements::Gradient,
31
32
  marker: Prawn::SVG::Elements::Marker,
32
33
  style: Prawn::SVG::Elements::Ignored, # because it is pre-parsed by Document
33
34
  title: Prawn::SVG::Elements::Ignored,
@@ -267,4 +267,8 @@ class Prawn::SVG::Elements::Base
267
267
  element = document.elements_by_id[matches[1]] if matches
268
268
  element if element && (expected_type.nil? || element.name == expected_type)
269
269
  end
270
+
271
+ def href_attribute
272
+ attributes['xlink:href'] || attributes['href']
273
+ end
270
274
  end
@@ -1,10 +1,18 @@
1
1
  class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
2
- TAG_NAME_TO_TYPE = {"linearGradient" => :linear}
2
+ attr_reader :parent_gradient
3
+ attr_reader :x1, :y1, :x2, :y2, :cx, :cy, :fx, :fy, :radius, :units, :stops, :transform_matrix
4
+
5
+ TAG_NAME_TO_TYPE = {
6
+ "linearGradient" => :linear,
7
+ "radialGradient" => :radial
8
+ }
3
9
 
4
10
  def parse
5
11
  # A gradient tag without an ID is inaccessible and can never be used
6
12
  raise SkipElementQuietly if attributes['id'].nil?
7
13
 
14
+ @parent_gradient = document.gradients[href_attribute[1..-1]] if href_attribute && href_attribute[0] == '#'
15
+
8
16
  assert_compatible_prawn_version
9
17
  load_gradient_configuration
10
18
  load_coordinates
@@ -16,31 +24,57 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
16
24
  end
17
25
 
18
26
  def gradient_arguments(element)
19
- case @units
20
- when :bounding_box
21
- x1, y1, x2, y2 = element.bounding_box
22
- return if y2.nil?
23
-
24
- width = x2 - x1
25
- height = y1 - y2
26
-
27
- from = [x1 + width * @x1, y1 - height * @y1]
28
- to = [x1 + width * @x2, y1 - height * @y2]
29
-
30
- when :user_space
31
- from = [@x1, @y1]
32
- to = [@x2, @y2]
33
- end
34
-
35
27
  # Passing in a transformation matrix to the apply_transformations option is supported
36
28
  # by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
37
29
  #
38
30
  # See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
39
- {from: from, to: to, stops: @stops, apply_transformations: @transform_matrix || true}
31
+ base_arguments = {stops: stops, apply_transformations: transform_matrix || true}
32
+
33
+ arguments = specific_gradient_arguments(element)
34
+ arguments.merge(base_arguments) if arguments
40
35
  end
41
36
 
42
37
  private
43
38
 
39
+ def specific_gradient_arguments(element)
40
+ if units == :bounding_box
41
+ bounding_x1, bounding_y1, bounding_x2, bounding_y2 = element.bounding_box
42
+ return if bounding_y2.nil?
43
+
44
+ width = bounding_x2 - bounding_x1
45
+ height = bounding_y1 - bounding_y2
46
+ end
47
+
48
+ case [type, units]
49
+ when [:linear, :bounding_box]
50
+ from = [bounding_x1 + width * x1, bounding_y1 - height * y1]
51
+ to = [bounding_x1 + width * x2, bounding_y1 - height * y2]
52
+
53
+ {from: from, to: to}
54
+
55
+ when [:linear, :user_space]
56
+ {from: [x1, y1], to: [x2, y2]}
57
+
58
+ when [:radial, :bounding_box]
59
+ center = [bounding_x1 + width * cx, bounding_y1 - height * cy]
60
+ focus = [bounding_x1 + width * fx, bounding_y1 - height * fy]
61
+
62
+ # Note: Chrome, at least, implements radial bounding box radiuses as
63
+ # having separate X and Y components, so in bounding box mode their
64
+ # gradients come out as ovals instead of circles. PDF radial shading
65
+ # doesn't have the option to do this, and it's confusing why the
66
+ # Chrome user space gradients don't apply the same logic anyway.
67
+ hypot = Math.sqrt(width * width + height * height)
68
+ {from: focus, r1: 0, to: center, r2: radius * hypot}
69
+
70
+ when [:radial, :user_space]
71
+ {from: [fx, fy], r1: 0, to: [cx, cy], r2: radius}
72
+
73
+ else
74
+ raise "unexpected type/unit system"
75
+ end
76
+ end
77
+
44
78
  def type
45
79
  TAG_NAME_TO_TYPE.fetch(name)
46
80
  end
@@ -64,18 +98,35 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
64
98
  end
65
99
 
66
100
  def load_coordinates
67
- case @units
68
- when :bounding_box
101
+ case [type, units]
102
+ when [:linear, :bounding_box]
69
103
  @x1 = parse_zero_to_one(attributes["x1"], 0)
70
104
  @y1 = parse_zero_to_one(attributes["y1"], 0)
71
105
  @x2 = parse_zero_to_one(attributes["x2"], 1)
72
106
  @y2 = parse_zero_to_one(attributes["y2"], 0)
73
107
 
74
- when :user_space
108
+ when [:linear, :user_space]
75
109
  @x1 = x(attributes["x1"])
76
110
  @y1 = y(attributes["y1"])
77
111
  @x2 = x(attributes["x2"])
78
112
  @y2 = y(attributes["y2"])
113
+
114
+ when [:radial, :bounding_box]
115
+ @cx = parse_zero_to_one(attributes["cx"], 0.5)
116
+ @cy = parse_zero_to_one(attributes["cy"], 0.5)
117
+ @fx = parse_zero_to_one(attributes["fx"], cx)
118
+ @fy = parse_zero_to_one(attributes["fy"], cy)
119
+ @radius = parse_zero_to_one(attributes["r"], 0.5)
120
+
121
+ when [:radial, :user_space]
122
+ @cx = x(attributes["cx"] || '50%')
123
+ @cy = y(attributes["cy"] || '50%')
124
+ @fx = x(attributes["fx"] || attributes["cx"])
125
+ @fy = y(attributes["fy"] || attributes["cy"])
126
+ @radius = pixels(attributes["r"] || '50%')
127
+
128
+ else
129
+ raise "unexpected type/unit system"
79
130
  end
80
131
  end
81
132
 
@@ -101,10 +152,16 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
101
152
  end
102
153
  end
103
154
 
104
- raise SkipElementError, "gradient does not have any valid stops" if @stops.empty?
155
+ if stops.empty?
156
+ if parent_gradient.nil? || parent_gradient.stops.empty?
157
+ raise SkipElementError, "gradient does not have any valid stops"
158
+ end
105
159
 
106
- @stops.unshift([0, @stops.first.last]) if @stops.first.first > 0
107
- @stops.push([1, @stops.last.last]) if @stops.last.first < 1
160
+ @stops = parent_gradient.stops
161
+ else
162
+ stops.unshift([0, stops.first.last]) if stops.first.first > 0
163
+ stops.push([1, stops.last.last]) if stops.last.first < 1
164
+ end
108
165
  end
109
166
 
110
167
  def parse_zero_to_one(string, default = 0)
@@ -15,9 +15,9 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
15
15
 
16
16
  raise SkipElementQuietly if state.computed_properties.display == "none"
17
17
 
18
- @url = attributes['xlink:href'] || attributes['href']
18
+ @url = href_attribute
19
19
  if @url.nil?
20
- raise SkipElementError, "image tag must have an xlink:href"
20
+ raise SkipElementError, "image tag must have an href or xlink:href"
21
21
  end
22
22
 
23
23
  x = x(attributes['x'] || 0)
@@ -4,10 +4,31 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
4
4
 
5
5
  INSIDE_SPACE_REGEXP = /[ \t\r\n,]*/
6
6
  OUTSIDE_SPACE_REGEXP = /[ \t\r\n]*/
7
- INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?)/
8
- VALUES_REGEXP = /^#{INSIDE_REGEXP}/
7
+ INSIDE_REGEXP = /#{INSIDE_SPACE_REGEXP}(?>([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:(?<=[0-9])[eE][+-]?[0-9]+)?))/
8
+ FLAG_REGEXP = /#{INSIDE_SPACE_REGEXP}([01])/
9
9
  COMMAND_REGEXP = /^#{OUTSIDE_SPACE_REGEXP}([A-Za-z])((?:#{INSIDE_REGEXP})*)#{OUTSIDE_SPACE_REGEXP}/
10
10
 
11
+ A_PARAMETERS_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{FLAG_REGEXP}#{FLAG_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
12
+ ONE_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}/
13
+ TWO_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
14
+ FOUR_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
15
+ SIX_PARAMETER_REGEXP = /^#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}#{INSIDE_REGEXP}/
16
+
17
+ COMMAND_MATCH_MAP = {
18
+ 'A' => A_PARAMETERS_REGEXP,
19
+ 'C' => SIX_PARAMETER_REGEXP,
20
+ 'H' => ONE_PARAMETER_REGEXP,
21
+ 'L' => TWO_PARAMETER_REGEXP,
22
+ 'M' => TWO_PARAMETER_REGEXP,
23
+ 'Q' => FOUR_PARAMETER_REGEXP,
24
+ 'S' => FOUR_PARAMETER_REGEXP,
25
+ 'T' => TWO_PARAMETER_REGEXP,
26
+ 'V' => ONE_PARAMETER_REGEXP,
27
+ 'Z' => //,
28
+ }
29
+
30
+ PARAMETERLESS_COMMANDS = COMMAND_MATCH_MAP.select { |_, v| v == // }.map(&:first)
31
+
11
32
  FLOAT_ERROR_DELTA = 1e-10
12
33
 
13
34
  attr_reader :commands
@@ -23,14 +44,13 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
23
44
  matched_commands = match_all(data, COMMAND_REGEXP)
24
45
  raise SkipElementError, "Invalid/unsupported syntax for SVG path data" if matched_commands.nil?
25
46
 
26
- catch :invalid_command do
27
- matched_commands.each do |matched_command|
28
- command = matched_command[1]
29
- matched_values = match_all(matched_command[2], VALUES_REGEXP)
30
- raise "should be impossible to have invalid inside data, but we ended up here" if matched_values.nil?
31
- values = matched_values.collect {|value| value[1].to_f}
32
- parse_path_command(command, values)
33
- end
47
+ matched_commands.each do |(command, parameters)|
48
+ regexp = COMMAND_MATCH_MAP[command.upcase] or break
49
+ matched_values = match_all(parameters, regexp) or break
50
+ values = matched_values.map { |value| value.map(&:to_f) }
51
+ break if values.empty? && !PARAMETERLESS_COMMANDS.include?(command.upcase)
52
+
53
+ parse_path_command(command, values)
34
54
  end
35
55
  end
36
56
 
@@ -49,8 +69,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
49
69
 
50
70
  case upcase_command
51
71
  when 'M' # moveto
52
- x = values.shift or throw :invalid_command
53
- y = values.shift or throw :invalid_command
72
+ x, y = values.shift
54
73
 
55
74
  if relative && @last_point
56
75
  x += @last_point.first
@@ -69,8 +88,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
69
88
 
70
89
  when 'L' # lineto
71
90
  while values.any?
72
- x = values.shift
73
- y = values.shift or throw :invalid_command
91
+ x, y = values.shift
74
92
  if relative && @last_point
75
93
  x += @last_point.first
76
94
  y += @last_point.last
@@ -81,22 +99,21 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
81
99
 
82
100
  when 'H' # horizontal lineto
83
101
  while values.any?
84
- x = values.shift
102
+ x = values.shift.first
85
103
  x += @last_point.first if relative && @last_point
86
104
  push_command Prawn::SVG::Pathable::Line.new([x, @last_point.last])
87
105
  end
88
106
 
89
107
  when 'V' # vertical lineto
90
108
  while values.any?
91
- y = values.shift
109
+ y = values.shift.first
92
110
  y += @last_point.last if relative && @last_point
93
111
  push_command Prawn::SVG::Pathable::Line.new([@last_point.first, y])
94
112
  end
95
113
 
96
114
  when 'C' # curveto
97
115
  while values.any?
98
- x1, y1, x2, y2, x, y = values.shift(6)
99
- throw :invalid_command unless y
116
+ x1, y1, x2, y2, x, y = values.shift
100
117
 
101
118
  if relative && @last_point
102
119
  x += @last_point.first
@@ -113,8 +130,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
113
130
 
114
131
  when 'S' # shorthand/smooth curveto
115
132
  while values.any?
116
- x2, y2, x, y = values.shift(4)
117
- throw :invalid_command unless y
133
+ x2, y2, x, y = values.shift
118
134
 
119
135
  if relative && @last_point
120
136
  x += @last_point.first
@@ -137,13 +153,11 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
137
153
  when 'Q', 'T' # quadratic curveto
138
154
  while values.any?
139
155
  if shorthand = upcase_command == 'T'
140
- x, y = values.shift(2)
156
+ x, y = values.shift
141
157
  else
142
- x1, y1, x, y = values.shift(4)
158
+ x1, y1, x, y = values.shift
143
159
  end
144
160
 
145
- throw :invalid_command unless y
146
-
147
161
  if relative && @last_point
148
162
  x += @last_point.first
149
163
  x1 += @last_point.first if x1
@@ -175,8 +189,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
175
189
  return unless @last_point
176
190
 
177
191
  while values.any?
178
- rx, ry, phi, fa, fs, x2, y2 = values.shift(7)
179
- throw :invalid_command unless y2
192
+ rx, ry, phi, fa, fs, x2, y2 = values.shift
180
193
 
181
194
  x1, y1 = @last_point
182
195
 
@@ -277,9 +290,8 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
277
290
  def match_all(string, regexp) # regexp must start with ^
278
291
  result = []
279
292
  while string != ""
280
- matches = string.match(regexp)
281
- result << matches
282
- return if matches.nil?
293
+ matches = string.match(regexp) or return
294
+ result << matches.captures
283
295
  string = matches.post_match
284
296
  end
285
297
  result
@@ -8,7 +8,10 @@ class Prawn::SVG::Elements::Root < Prawn::SVG::Elements::Base
8
8
  end
9
9
 
10
10
  def apply
11
- add_call 'fill_color', '000000'
11
+ if [nil, 'inherit', 'none', 'currentColor'].include?(properties.fill)
12
+ add_call 'fill_color', '000000'
13
+ end
14
+
12
15
  add_call 'transformation_matrix', @document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0
13
16
  add_call 'transformation_matrix', 1, 0, 0, 1, -@document.sizing.x_offset, @document.sizing.y_offset
14
17
  end
@@ -46,9 +46,11 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
46
46
  opts = {
47
47
  size: computed_properties.numerical_font_size,
48
48
  style: font && font.subfamily,
49
- text_anchor: computed_properties.text_anchor
49
+ text_anchor: computed_properties.text_anchor,
50
50
  }
51
51
 
52
+ opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
53
+
52
54
  if state.text.parent
53
55
  add_call_and_enter 'character_spacing', state.text.spacing unless state.text.spacing == state.text.parent.spacing
54
56
  add_call_and_enter 'text_rendering_mode', state.text.mode unless state.text.mode == state.text.parent.mode
@@ -183,7 +185,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
183
185
  end
184
186
 
185
187
  def find_referenced_element
186
- href = attributes['xlink:href']
188
+ href = href_attribute
187
189
 
188
190
  if href && href[0..0] == '#'
189
191
  element = document.elements_by_id[href[1..-1]]
@@ -1,19 +1,35 @@
1
1
  class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
2
- attr_reader :referenced_element
2
+ attr_reader :referenced_element_class
3
+ attr_reader :referenced_element_source
3
4
 
4
5
  def parse
5
- require_attributes 'xlink:href'
6
-
7
- href = attributes['xlink:href']
6
+ href = href_attribute
7
+ if href.nil?
8
+ raise SkipElementError, "use tag must have an href or xlink:href"
9
+ end
8
10
 
9
11
  if href[0..0] != '#'
10
12
  raise SkipElementError, "use tag has an href that is not a reference to an id; this is not supported"
11
13
  end
12
14
 
13
15
  id = href[1..-1]
14
- @referenced_element = @document.elements_by_id[id]
16
+ referenced_element = @document.elements_by_id[id]
17
+
18
+ if referenced_element
19
+ @referenced_element_class = referenced_element.class
20
+ @referenced_element_source = referenced_element.source
21
+ else
22
+ # Perhaps the element is defined further down in the document. This is not recommended but still valid SVG,
23
+ # so we'll support it with an exception case that's not particularly performant.
24
+ raw_element = REXML::XPath.match(@document.root, %(//*[@id="#{id.gsub('"', '\"')}"])).first
25
+
26
+ if raw_element
27
+ @referenced_element_class = Prawn::SVG::Elements::TAG_CLASS_MAPPING[raw_element.name.to_sym]
28
+ @referenced_element_source = raw_element
29
+ end
30
+ end
15
31
 
16
- if referenced_element.nil?
32
+ if referenced_element_class.nil?
17
33
  raise SkipElementError, "no tag with ID '#{id}' was found, referenced by use tag"
18
34
  end
19
35
 
@@ -36,7 +52,7 @@ class Prawn::SVG::Elements::Use < Prawn::SVG::Elements::Base
36
52
  def process_child_elements
37
53
  add_call "save"
38
54
 
39
- child = referenced_element.class.new(referenced_element.document, referenced_element.source, calls, state.dup)
55
+ child = referenced_element_class.new(document, referenced_element_source, calls, state.dup)
40
56
  child.process
41
57
 
42
58
  add_call "restore"
@@ -119,7 +119,7 @@ module Prawn
119
119
 
120
120
  if skip
121
121
  # the call has been overridden
122
- elsif children.empty?
122
+ elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
123
123
  prawn.send(call, *arguments)
124
124
  else
125
125
  prawn.send(call, *arguments, &proc_creator(prawn, children))
@@ -172,6 +172,15 @@ module Prawn
172
172
  @cursor = [at[0] + width, at[1]]
173
173
  end
174
174
 
175
+ decoration = options.delete(:decoration)
176
+ if decoration == 'underline'
177
+ prawn.save_graphics_state do
178
+ prawn.line_width 1
179
+ prawn.line [at[0], at[1] - 1.25], [at[0] + width, at[1] - 1.25]
180
+ prawn.stroke
181
+ end
182
+ end
183
+
175
184
  when 'transformation_matrix'
176
185
  left = prawn.bounds.absolute_left
177
186
  top = prawn.bounds.absolute_top
@@ -37,6 +37,7 @@ class Prawn::SVG::Properties
37
37
  "stroke-opacity" => Config.new("1", true),
38
38
  "stroke-width" => Config.new("1", true),
39
39
  "text-anchor" => Config.new("start", true, %w(inherit start middle end), true),
40
+ 'text-decoration' => Config.new('none', true, %w(inherit none underline), true),
40
41
  }.freeze
41
42
 
42
43
  PROPERTIES.each do |name, value|
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module SVG
3
- VERSION = '0.30.0'
3
+ VERSION = '0.31.0'
4
4
  end
5
5
  end
@@ -22,5 +22,5 @@ Gem::Specification.new do |gem|
22
22
  gem.add_runtime_dependency "prawn", ">= 0.11.1", "< 3"
23
23
  gem.add_runtime_dependency "css_parser", "~> 1.6"
24
24
  gem.add_development_dependency "rspec", "~> 3.0"
25
- gem.add_development_dependency "rake", "~> 10.1"
25
+ gem.add_development_dependency "rake", "~> 13.0"
26
26
  end
@@ -9,7 +9,7 @@ describe Prawn::SVG::Elements::Gradient do
9
9
  element.process
10
10
  end
11
11
 
12
- describe "object bounding box" do
12
+ describe "object bounding box with linear gradient" do
13
13
  let(:svg) do
14
14
  <<-SVG
15
15
  <linearGradient id="flag" x1="0" x2="0.2" y1="0" y2="1">
@@ -40,7 +40,35 @@ describe Prawn::SVG::Elements::Gradient do
40
40
  end
41
41
  end
42
42
 
43
- describe "user space on use" do
43
+ describe "object bounding box with radial gradient" do
44
+ let(:svg) do
45
+ <<-SVG
46
+ <radialGradient id="flag" cx="0" cy="0.2" fx="0.5" r="0.8">
47
+ <stop offset="25%" stop-color="red"/>
48
+ <stop offset="50%" stop-color="white"/>
49
+ <stop offset="75%" stop-color="blue"/>
50
+ </radialGradient>
51
+ SVG
52
+ end
53
+
54
+ it "is stored in the document gradients table" do
55
+ expect(document.gradients["flag"]).to eq element
56
+ end
57
+
58
+ it "returns correct gradient arguments for an element" do
59
+ arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0]))
60
+ expect(arguments).to eq(
61
+ from: [150, 80],
62
+ to: [100, 80],
63
+ r1: 0,
64
+ r2: Math.sqrt((0.8 * 100) ** 2 + (0.8 * 100) ** 2),
65
+ stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]],
66
+ apply_transformations: true,
67
+ )
68
+ end
69
+ end
70
+
71
+ describe "user space on use with linear gradient" do
44
72
  let(:svg) do
45
73
  <<-SVG
46
74
  <linearGradient id="flag" gradientUnits="userSpaceOnUse" x1="100" y1="500" x2="200" y2="600">
@@ -61,6 +89,29 @@ describe Prawn::SVG::Elements::Gradient do
61
89
  end
62
90
  end
63
91
 
92
+ describe "user space on use with radial gradient" do
93
+ let(:svg) do
94
+ <<-SVG
95
+ <radialGradient id="flag" gradientUnits="userSpaceOnUse" fx="100" fy="500" cx="200" cy="600" r="150">
96
+ <stop offset="0" stop-color="red"/>
97
+ <stop offset="1" stop-color="blue"/>
98
+ </radialGradient>
99
+ SVG
100
+ end
101
+
102
+ it "returns correct gradient arguments for an element" do
103
+ arguments = element.gradient_arguments(double)
104
+ expect(arguments).to eq(
105
+ from: [100.0, 100.0],
106
+ to: [200.0, 0.0],
107
+ r1: 0,
108
+ r2: 150,
109
+ stops: [[0, "ff0000"], [1, "0000ff"]],
110
+ apply_transformations: true,
111
+ )
112
+ end
113
+ end
114
+
64
115
  context "when gradientTransform is specified" do
65
116
  let(:svg) do
66
117
  <<-SVG
@@ -11,22 +11,22 @@ describe Prawn::SVG::Elements::Path do
11
11
 
12
12
  describe "command parsing" do
13
13
  context "with a valid path" do
14
- let(:d) { "A12.34 -56.78 89B4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 c31,-2e-5C 6,7 T QX 0 Z" }
14
+ let(:d) { "m12.34 -56.78 1 2M4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 L31,-2e-5L 6,7 Z ZZa50 50 0 100 100" }
15
15
 
16
16
  it "correctly parses" do
17
17
  calls = []
18
- path.stub(:parse_path_command) {|*args| calls << args}
18
+ allow(path).to receive(:parse_path_command) {|*args| calls << args}
19
19
  path.parse
20
20
 
21
- calls.should == [
22
- ["A", [12.34, -56.78, 89]],
23
- ["B", [4, 5, 12, -34, -0.5, 0.7, 3, 2.3e3, 4e4, 4e4]],
24
- ["c", [31, -2e-5]],
25
- ["C", [6, 7]],
26
- ["T", []],
27
- ["Q", []],
28
- ["X", [0]],
29
- ["Z", []]
21
+ expect(calls).to eq [
22
+ ["m", [[12.34, -56.78], [1, 2]]],
23
+ ["M", [[4, 5], [12, -34], [-0.5, 0.7], [3, 2.3e3], [4e4, 4e4]]],
24
+ ["L", [[31, -2e-5]]],
25
+ ["L", [[6, 7]]],
26
+ ["Z", []],
27
+ ["Z", []],
28
+ ["Z", []],
29
+ ["a", [[50, 50, 0, 1, 0, 0, 100]]],
30
30
  ]
31
31
  end
32
32
  end
@@ -36,12 +36,12 @@ describe Prawn::SVG::Elements::Path do
36
36
 
37
37
  it "treats subsequent points to m/M command as relative/absolute depending on command" do
38
38
  [
39
- ["M", [1,2,3,4]],
40
- ["L", [3,4]],
41
- ["m", [5,6,7,8]],
42
- ["l", [7,8]]
39
+ ["M", [[1,2],[3,4]]],
40
+ ["L", [[3,4]]],
41
+ ["m", [[5,6],[7,8]]],
42
+ ["l", [[7,8]]]
43
43
  ].each do |args|
44
- path.should_receive(:parse_path_command).with(*args).and_call_original
44
+ expect(path).to receive(:parse_path_command).with(*args).and_call_original
45
45
  end
46
46
 
47
47
  path.parse
@@ -52,7 +52,7 @@ describe Prawn::SVG::Elements::Path do
52
52
  let(:d) { "" }
53
53
 
54
54
  it "correctly parses" do
55
- path.should_not_receive(:run_path_command)
55
+ expect(path).not_to receive(:run_path_command)
56
56
  path.parse
57
57
  end
58
58
  end
@@ -292,5 +292,17 @@ describe Prawn::SVG::Elements::Path do
292
292
  ]
293
293
  end
294
294
  end
295
+
296
+ context "with highly-compressed flags" do
297
+ let(:d) { "M100,100a50 50 0 100 100" }
298
+
299
+ it "correctly parses them" do
300
+ expect(subject).to eq [
301
+ Prawn::SVG::Elements::Path::Move.new([100.0, 100.0]),
302
+ Prawn::SVG::Elements::Path::Curve.new([50.0, 150.0], [72.57081148225681, 100.0], [50.0, 122.57081148225681]),
303
+ Prawn::SVG::Elements::Path::Curve.new([99.99999999999999, 200.0], [50.0, 177.42918851774317], [72.5708114822568, 200.0])
304
+ ]
305
+ end
306
+ end
295
307
  end
296
308
  end
@@ -88,6 +88,21 @@ Even more
88
88
  end
89
89
  end
90
90
 
91
+ describe "underline" do
92
+ let(:svg) { '<text text-decoration="underline">underlined</text>' }
93
+
94
+ it "marks the element to be underlined" do
95
+ element.process
96
+
97
+ expect(element.base_calls).to eq [
98
+ ["text_group", [], [
99
+ ["font", ["Helvetica", {:style=>:normal}], []],
100
+ ["draw_text", ["underlined", default_style.merge(decoration: 'underline')], []]
101
+ ]]
102
+ ]
103
+ end
104
+ end
105
+
91
106
  describe "fill/stroke modes" do
92
107
  context "with a stroke and no fill" do
93
108
  let(:svg) { '<text stroke="red" fill="none">stroked</text>' }
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
5
+ xmlns="http://www.w3.org/2000/svg">
6
+ <desc>Example radgrad01 - fill a rectangle by referencing a
7
+ radial gradient paint server</desc>
8
+ <g>
9
+ <defs>
10
+ <radialGradient id="MyGradient"
11
+ cx="0.5" cy="0.5" r="0.2" fx="0.5" fy="0.5">
12
+ <stop offset="0%" stop-color="red" />
13
+ <stop offset="50%" stop-color="blue" />
14
+ <stop offset="100%" stop-color="red" />
15
+ </radialGradient>
16
+ </defs>
17
+
18
+ <!-- Outline the drawing area in blue -->
19
+ <rect fill="none" stroke="blue"
20
+ x="1" y="1" width="798" height="398"/>
21
+
22
+ <!-- The rectangle is filled using a radial gradient paint server -->
23
+ <rect fill="url(#MyGradient)" stroke="black" stroke-width="5"
24
+ x="100" y="100" width="600" height="200"/>
25
+ </g>
26
+ </svg>
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+ <svg width="8cm" height="4cm" viewBox="0 0 800 400" version="1.1"
5
+ xmlns="http://www.w3.org/2000/svg">
6
+ <desc>Example radgrad01 - fill a rectangle by referencing a
7
+ radial gradient paint server</desc>
8
+ <g>
9
+ <defs>
10
+ <radialGradient id="MyGradient" gradientUnits="userSpaceOnUse"
11
+ cx="400" cy="200" r="50%" fx="200" fy="200">
12
+ <stop offset="0%" stop-color="red" />
13
+ <stop offset="50%" stop-color="blue" />
14
+ <stop offset="100%" stop-color="red" />
15
+ </radialGradient>
16
+ </defs>
17
+
18
+ <!-- Outline the drawing area in blue -->
19
+ <rect fill="none" stroke="blue"
20
+ x="1" y="1" width="798" height="398"/>
21
+
22
+ <!-- The rectangle is filled using a radial gradient paint server -->
23
+ <rect fill="url(#MyGradient)" stroke="black" stroke-width="5"
24
+ x="100" y="100" width="600" height="200"/>
25
+ </g>
26
+ </svg>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg width="12cm" height="5.25cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" version="1.1" fill="red">
4
+ <text style="font-size: 40pt;">This is red text.</text>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg viewBox="0 0 250 50" xmlns="http://www.w3.org/2000/svg">
2
+ <text y="20" text-decoration="underline">Underlined text</text>
3
+ <text x="0" y="40" text-decoration="line-through">Struck-through text</text>
4
+ </svg>
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg width="12cm" height="4cm" viewBox="0 0 1200 400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink='http://www.w3.org/1999/xlink' version="1.1">
4
+ <use xlink:href="#two-rects"/>
5
+ <use xlink:href="#two-rects" x="90" y="90"/>
6
+ <use xlink:href="#two-rects" x="180" y="180"/>
7
+
8
+ <use xlink:href="#circle" x="0" y="0" style="opacity: 0.5;" transform="scale(1.3)"/>
9
+ <use xlink:href="#circle" x="0" y="0" style="opacity: 0.2;" transform="scale(1.7)"/>
10
+
11
+ <circle id="circle" cx="600" cy="200" r="100" fill="red" stroke="blue" stroke-width="10" />
12
+
13
+ <symbol id="two-rects">
14
+ <rect x="1" y="1" width="1198" height="398" fill="none" stroke="blue" stroke-width="2"/>
15
+ <rect x="400" y="100" width="400" height="200" fill="yellow" stroke="navy" stroke-width="10" />
16
+ </symbol>
17
+ </svg>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prawn-svg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.0
4
+ version: 0.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Nesbitt
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-14 00:00:00.000000000 Z
11
+ date: 2020-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn
@@ -64,14 +64,14 @@ dependencies:
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '10.1'
67
+ version: '13.0'
68
68
  type: :development
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '10.1'
74
+ version: '13.0'
75
75
  description: This gem allows you to render SVG directly into a PDF using the 'prawn'
76
76
  gem. Since PDF is vector-based, you'll get nice scaled graphics if you use SVG
77
77
  instead of an image.
@@ -213,6 +213,8 @@ files:
213
213
  - spec/sample_svg/polyline01.svg
214
214
  - spec/sample_svg/preserve-space.svg
215
215
  - spec/sample_svg/quad01.svg
216
+ - spec/sample_svg/radgrad01-bounding.svg
217
+ - spec/sample_svg/radgrad01.svg
216
218
  - spec/sample_svg/rect01.svg
217
219
  - spec/sample_svg/rect02.svg
218
220
  - spec/sample_svg/rotate_scale.svg
@@ -221,6 +223,8 @@ files:
221
223
  - spec/sample_svg/subfamilies.svg
222
224
  - spec/sample_svg/subviewports.svg
223
225
  - spec/sample_svg/subviewports2.svg
226
+ - spec/sample_svg/svg_fill.svg
227
+ - spec/sample_svg/text-decoration.svg
224
228
  - spec/sample_svg/text_entities.svg
225
229
  - spec/sample_svg/text_stroke.svg
226
230
  - spec/sample_svg/transform.svg
@@ -234,6 +238,7 @@ files:
234
238
  - spec/sample_svg/tspan05.svg
235
239
  - spec/sample_svg/tspan91.svg
236
240
  - spec/sample_svg/use.svg
241
+ - spec/sample_svg/use_disordered.svg
237
242
  - spec/sample_svg/viewbox.svg
238
243
  - spec/sample_svg/viewport.svg
239
244
  - spec/sample_svg/warning-radioactive.svg
@@ -243,7 +248,7 @@ homepage: http://github.com/mogest/prawn-svg
243
248
  licenses:
244
249
  - MIT
245
250
  metadata: {}
246
- post_install_message:
251
+ post_install_message:
247
252
  rdoc_options: []
248
253
  require_paths:
249
254
  - lib
@@ -258,8 +263,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
263
  - !ruby/object:Gem::Version
259
264
  version: '0'
260
265
  requirements: []
261
- rubygems_version: 3.0.1
262
- signing_key:
266
+ rubygems_version: 3.0.3
267
+ signing_key:
263
268
  specification_version: 4
264
269
  summary: SVG renderer for Prawn PDF library
265
270
  test_files:
@@ -335,6 +340,8 @@ test_files:
335
340
  - spec/sample_svg/polyline01.svg
336
341
  - spec/sample_svg/preserve-space.svg
337
342
  - spec/sample_svg/quad01.svg
343
+ - spec/sample_svg/radgrad01-bounding.svg
344
+ - spec/sample_svg/radgrad01.svg
338
345
  - spec/sample_svg/rect01.svg
339
346
  - spec/sample_svg/rect02.svg
340
347
  - spec/sample_svg/rotate_scale.svg
@@ -343,6 +350,8 @@ test_files:
343
350
  - spec/sample_svg/subfamilies.svg
344
351
  - spec/sample_svg/subviewports.svg
345
352
  - spec/sample_svg/subviewports2.svg
353
+ - spec/sample_svg/svg_fill.svg
354
+ - spec/sample_svg/text-decoration.svg
346
355
  - spec/sample_svg/text_entities.svg
347
356
  - spec/sample_svg/text_stroke.svg
348
357
  - spec/sample_svg/transform.svg
@@ -356,6 +365,7 @@ test_files:
356
365
  - spec/sample_svg/tspan05.svg
357
366
  - spec/sample_svg/tspan91.svg
358
367
  - spec/sample_svg/use.svg
368
+ - spec/sample_svg/use_disordered.svg
359
369
  - spec/sample_svg/viewbox.svg
360
370
  - spec/sample_svg/viewport.svg
361
371
  - spec/sample_svg/warning-radioactive.svg