prawn-svg 0.29.1 → 0.30.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: 2dbf86f272347e8867b1e7262a49163c316d0160fc35802ceb1f7b0f8ed90e25
4
- data.tar.gz: 4244134c0616eaf4aa9ca8ef19bbde2137e589d4f3929dd30b55c4c3d5136c36
3
+ metadata.gz: 34f48795bbd6ee32d7c18228b833137dc52cb5b02f0446de0e3908d0827e7234
4
+ data.tar.gz: 4f654b0c484f81ba80660940d2823c836c0aaba0156cb9e803c722170fdec993
5
5
  SHA512:
6
- metadata.gz: 3d1874a6766112c3084355c21b517b7a1d413f0e9e88c806cb236d69edc9fb7e1c3e253f13746c18e5e9b57d1221341d5c309494429dbae4a377ef91b99c8a52
7
- data.tar.gz: bbd80743596ad9c2c25871c5c6542d2ddfb0c3026ac5d0efb61a894b501aabc6657e048f3d52b0b0e42e634e68b0d30f7e5096ce9bfdc81d4a7ffc7ac08d65e9
6
+ metadata.gz: 4bf2b53a65607c75da3907433b40e1f4aaee67a70268f562a3f6c554087ab7a66924874befc1da5a6954b86b71f0e32ab6bff5108f64f3582b677500dc0a2f8b
7
+ data.tar.gz: 25d862c9b5b6e25a3dc22ac581c2e04c025ff76b3f842033578f2565d8641f2a951d03e0a88c272e52f584c87fb27a2af90593ae6aea8d69c80ec4ec06d0b36a
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright 2010-2013 Roger Nesbitt
3
+ Copyright 2010-2019 Roger Nesbitt
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -60,7 +60,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
60
60
  - <tt>&lt;path&gt;</tt> supports all commands defined in SVG 1.1, although the
61
61
  implementation of elliptical arc is a bit rough at the moment.
62
62
 
63
- - `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, and with extra properties
63
+ - `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, 'textLength', 'lengthAdjust', and with extra properties
64
64
  `text-anchor`, `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>
@@ -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 with Prawn 2.2.0+ (gradientTransform, spreadMethod and stop-opacity are unimplemented.)
79
+ - `<linearGradient>` is 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.
@@ -91,7 +91,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
91
91
 
92
92
  - the <tt>preserveAspectRatio</tt> attribute on <tt>&lt;svg&gt;</tt>, <tt>&lt;image&gt;</tt> and `<marker>` elements
93
93
 
94
- - transform methods: <tt>translate()</tt>, <tt>rotate()</tt>, <tt>scale()</tt>, <tt>matrix()</tt>
94
+ - transform methods: `translate`, `translateX`, `translateY`, `rotate`, `scale`, `skewX`, `skewY`, `matrix`
95
95
 
96
96
  - colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>
97
97
 
@@ -135,5 +135,6 @@ Mac OS X and Debian Linux users. You can add to the font path:
135
135
 
136
136
  In your Gemfile, put `gem 'prawn-svg'` before `gem 'prawn-rails'` so that prawn-rails can see the prawn-svg extension.
137
137
 
138
- --
139
- Copyright Roger Nesbitt <roger@seriousorange.com>. MIT licence.
138
+ ## Licence
139
+
140
+ MIT licence. Copyright Roger Nesbitt.
@@ -10,6 +10,7 @@ require 'prawn/svg/calculators/arc_to_bezier_curve'
10
10
  require 'prawn/svg/calculators/aspect_ratio'
11
11
  require 'prawn/svg/calculators/document_sizing'
12
12
  require 'prawn/svg/calculators/pixels'
13
+ require 'prawn/svg/transform_parser'
13
14
  require 'prawn/svg/url_loader'
14
15
  require 'prawn/svg/loaders/data'
15
16
  require 'prawn/svg/loaders/file'
@@ -29,6 +30,9 @@ require 'prawn/svg/font'
29
30
  require 'prawn/svg/document'
30
31
  require 'prawn/svg/state'
31
32
 
33
+ require 'prawn/svg/extensions/additional_gradient_transforms'
34
+ Prawn::Document.prepend Prawn::SVG::Extensions::AdditionalGradientTransforms
35
+
32
36
  module Prawn
33
37
  Svg = SVG # backwards compatibility
34
38
  end
@@ -1,13 +1,13 @@
1
1
  module Prawn::SVG::Attributes::Opacity
2
2
  def parse_opacity_attributes_and_call
3
3
  # We can't do nested opacities quite like the SVG requires, but this is close enough.
4
- fill_opacity = stroke_opacity = clamp(properties.opacity.to_f, 0, 1) if properties.opacity
4
+ opacity = clamp(properties.opacity.to_f, 0, 1) if properties.opacity
5
5
  fill_opacity = clamp(properties.fill_opacity.to_f, 0, 1) if properties.fill_opacity
6
6
  stroke_opacity = clamp(properties.stroke_opacity.to_f, 0, 1) if properties.stroke_opacity
7
7
 
8
- if fill_opacity || stroke_opacity
9
- state.fill_opacity *= fill_opacity || 1
10
- state.stroke_opacity *= stroke_opacity || 1
8
+ if opacity || fill_opacity || stroke_opacity
9
+ state.fill_opacity *= [opacity || 1, fill_opacity || 1].min
10
+ state.stroke_opacity *= [opacity || 1, stroke_opacity || 1].min
11
11
 
12
12
  add_call_and_enter 'transparent', state.fill_opacity, state.stroke_opacity
13
13
  end
@@ -2,49 +2,7 @@ module Prawn::SVG::Attributes::Transform
2
2
  def parse_transform_attribute_and_call
3
3
  return unless transform = attributes['transform']
4
4
 
5
- parse_css_method_calls(transform).each do |name, arguments|
6
- case name
7
- when 'translate'
8
- x, y = arguments
9
- add_call_and_enter name, x_pixels(x.to_f), -y_pixels(y.to_f)
10
-
11
- when 'rotate'
12
- r, x, y = arguments.collect {|a| a.to_f}
13
- case arguments.length
14
- when 1
15
- add_call_and_enter name, -r, :origin => [0, y('0')]
16
- when 3
17
- add_call_and_enter name, -r, :origin => [x(x), y(y)]
18
- else
19
- warnings << "transform 'rotate' must have either one or three arguments"
20
- end
21
-
22
- when 'scale'
23
- x_scale = arguments[0].to_f
24
- y_scale = (arguments[1] || x_scale).to_f
25
- add_call_and_enter "transformation_matrix", x_scale, 0, 0, y_scale, 0, 0
26
-
27
- when 'matrix'
28
- if arguments.length != 6
29
- warnings << "transform 'matrix' must have six arguments"
30
- else
31
- a, b, c, d, e, f = arguments.collect {|argument| argument.to_f}
32
- add_call_and_enter "transformation_matrix", a, -b, -c, d, x_pixels(e), -y_pixels(f)
33
- end
34
-
35
- else
36
- warnings << "Unknown transformation '#{name}'; ignoring"
37
- end
38
- end
39
- end
40
-
41
- private
42
-
43
- def parse_css_method_calls(string)
44
- string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
45
- name, argument_string = call
46
- arguments = argument_string.strip.split(/\s*[,\s]\s*/)
47
- [name, arguments]
48
- end
5
+ matrix = parse_transform_attribute(transform)
6
+ add_call_and_enter "transformation_matrix", *matrix unless matrix == [1, 0, 0, 1, 0, 0]
49
7
  end
50
8
  end
@@ -13,6 +13,7 @@ module Prawn::SVG::Elements
13
13
  g: Prawn::SVG::Elements::Container,
14
14
  symbol: Prawn::SVG::Elements::Container,
15
15
  defs: Prawn::SVG::Elements::Container,
16
+ a: Prawn::SVG::Elements::Container,
16
17
  clipPath: Prawn::SVG::Elements::ClipPath,
17
18
  switch: Prawn::SVG::Elements::Container,
18
19
  svg: Prawn::SVG::Elements::Viewport,
@@ -11,6 +11,8 @@ class Prawn::SVG::Elements::Base
11
11
  include Prawn::SVG::Attributes::Stroke
12
12
  include Prawn::SVG::Attributes::Space
13
13
 
14
+ include Prawn::SVG::TransformParser
15
+
14
16
  PAINT_TYPES = %w(fill stroke)
15
17
  COMMA_WSP_REGEXP = Prawn::SVG::Elements::COMMA_WSP_REGEXP
16
18
  SVG_NAMESPACE = "http://www.w3.org/2000/svg"
@@ -32,7 +32,11 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
32
32
  to = [@x2, @y2]
33
33
  end
34
34
 
35
- {from: from, to: to, stops: @stops}
35
+ # Passing in a transformation matrix to the apply_transformations option is supported
36
+ # by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
37
+ #
38
+ # See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
39
+ {from: from, to: to, stops: @stops, apply_transformations: @transform_matrix || true}
36
40
  end
37
41
 
38
42
  private
@@ -51,10 +55,7 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
51
55
  @units = attributes["gradientUnits"] == 'userSpaceOnUse' ? :user_space : :bounding_box
52
56
 
53
57
  if transform = attributes["gradientTransform"]
54
- matrix = transform.split(COMMA_WSP_REGEXP).map(&:to_f)
55
- if matrix != [1, 0, 0, 1, 0, 0]
56
- raise SkipElementError, "prawn-svg does not yet support gradients with a non-identity gradientTransform attribute"
57
- end
58
+ @transform_matrix = parse_transform_attribute(transform)
58
59
  end
59
60
 
60
61
  if (spread_method = attributes['spreadMethod']) && spread_method != "pad"
@@ -16,6 +16,7 @@ class Prawn::SVG::Elements::Path < Prawn::SVG::Elements::Base
16
16
  require_attributes 'd'
17
17
 
18
18
  @commands = []
19
+ @last_point = nil
19
20
 
20
21
  data = attributes["d"].gsub(/#{OUTSIDE_SPACE_REGEXP}$/, '')
21
22
 
@@ -2,7 +2,7 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
2
2
  attr_reader :commands
3
3
 
4
4
  Printable = Struct.new(:element, :text, :leading_space?, :trailing_space?)
5
- TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode)
5
+ TextState = Struct.new(:parent, :x, :y, :dx, :dy, :rotation, :spacing, :mode, :text_length, :length_adjust)
6
6
 
7
7
  def parse
8
8
  if state.inside_clip_path
@@ -14,6 +14,8 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
14
14
  state.text.dx = (attributes['dx'] || "").split(COMMA_WSP_REGEXP).collect { |n| x_pixels(n) }
15
15
  state.text.dy = (attributes['dy'] || "").split(COMMA_WSP_REGEXP).collect { |n| y_pixels(n) }
16
16
  state.text.rotation = (attributes['rotate'] || "").split(COMMA_WSP_REGEXP).collect(&:to_f)
17
+ state.text.text_length = normalize_length(attributes['textLength'])
18
+ state.text.length_adjust = attributes['lengthAdjust']
17
19
  state.text.spacing = calculate_character_spacing
18
20
  state.text.mode = calculate_text_rendering_mode
19
21
 
@@ -133,6 +135,14 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
133
135
  opts.delete(:rotate)
134
136
  end
135
137
 
138
+ if state.text.text_length
139
+ if state.text.length_adjust == 'spacingAndGlyphs'
140
+ opts[:stretch_to_width] = state.text.text_length
141
+ else
142
+ opts[:pad_to_width] = state.text.text_length
143
+ end
144
+ end
145
+
136
146
  if remaining
137
147
  add_call 'draw_text', text[0..0], opts.dup
138
148
  text = text[1..-1]
@@ -222,4 +232,8 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
222
232
  # overridden, we don't want to call fill/stroke as draw_text does this for us
223
233
  def apply_drawing_call
224
234
  end
235
+
236
+ def normalize_length(length)
237
+ x_pixels(length) if length && length.match(/\d/)
238
+ end
225
239
  end
@@ -0,0 +1,23 @@
1
+ module Prawn::SVG::Extensions
2
+ module AdditionalGradientTransforms
3
+ def gradient_coordinates(gradient)
4
+ # As of Prawn 2.2.0, apply_transformations is used as purely a boolean.
5
+ #
6
+ # Here we're using it to optionally pass in a 6-tuple transformation matrix that gets applied to the
7
+ # gradient. This should be added to Prawn properly, and then this monkey patch will not be necessary.
8
+
9
+ if gradient.apply_transformations.is_a?(Array)
10
+ x1, y1, x2, y2, transformation = super
11
+ a, b, c, d, e, f = transformation
12
+ na, nb, nc, nd, ne, nf = gradient.apply_transformations
13
+
14
+ matrix = Matrix[[a, c, e], [b, d, f], [0, 0, 1]] * Matrix[[na, nc, ne], [nb, nd, nf], [0, 0, 1]]
15
+ new_transformation = matrix.to_a[0..1].transpose.flatten
16
+
17
+ [x1, y1, x2, y2, new_transformation]
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+ end
@@ -148,6 +148,19 @@ module Prawn
148
148
 
149
149
  width = prawn.width_of(text, options.merge(kerning: true))
150
150
 
151
+ if stretch_to_width = options.delete(:stretch_to_width)
152
+ factor = stretch_to_width.to_f * 100 / width.to_f
153
+ prawn.add_content "#{factor} Tz"
154
+ width = stretch_to_width.to_f
155
+ end
156
+
157
+ if pad_to_width = options.delete(:pad_to_width)
158
+ padding_required = pad_to_width.to_f - width.to_f
159
+ padding_per_character = padding_required / text.length.to_f
160
+ prawn.add_content "#{padding_per_character} Tc"
161
+ width = pad_to_width.to_f
162
+ end
163
+
151
164
  case options.delete(:text_anchor)
152
165
  when 'middle'
153
166
  at[0] -= width / 2
@@ -2,7 +2,7 @@ require 'base64'
2
2
 
3
3
  module Prawn::SVG::Loaders
4
4
  class Data
5
- REGEXP = %r[\A(?i:data):image/(png|jpeg);base64(;[a-z0-9]+)*,]
5
+ REGEXP = %r[\Adata:image/(png|jpeg);base64(;[a-z0-9]+)*,]i
6
6
 
7
7
  def from_url(url)
8
8
  return if url[0..4].downcase != "data:"
@@ -1,3 +1,5 @@
1
+ require 'addressable/uri'
2
+
1
3
  #
2
4
  # Load a file from disk.
3
5
  #
@@ -56,7 +58,7 @@ module Prawn::SVG::Loaders
56
58
  private
57
59
 
58
60
  def load_file(path)
59
- path = URI.decode(path)
61
+ path = Addressable::URI.unencode(path)
60
62
  path = build_absolute_and_expand_path(path)
61
63
  assert_valid_path!(path)
62
64
  assert_file_exists!(path)
@@ -0,0 +1,72 @@
1
+ module Prawn::SVG::TransformParser
2
+ def parse_transform_attribute(transform)
3
+ matrix = Matrix.identity(3)
4
+
5
+ parse_css_method_calls(transform).each do |name, arguments|
6
+ case name
7
+ when 'translate'
8
+ x, y = arguments
9
+ matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
10
+
11
+ when 'translateX'
12
+ x = arguments.first
13
+ matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, 0], [0, 0, 1]]
14
+
15
+ when 'translateY'
16
+ y = arguments.first
17
+ matrix *= Matrix[[1, 0, 0], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
18
+
19
+ when 'rotate'
20
+ angle, x, y = arguments.collect { |a| a.to_f }
21
+ angle = angle * Math::PI / 180.0
22
+
23
+ case arguments.length
24
+ when 1
25
+ matrix *= Matrix[[Math.cos(angle), Math.sin(angle), 0], [-Math.sin(angle), Math.cos(angle), 0], [0, 0, 1]]
26
+ when 3
27
+ matrix *= Matrix[[1, 0, x_pixels(x.to_f)], [0, 1, -y_pixels(y.to_f)], [0, 0, 1]]
28
+ matrix *= Matrix[[Math.cos(angle), Math.sin(angle), 0], [-Math.sin(angle), Math.cos(angle), 0], [0, 0, 1]]
29
+ matrix *= Matrix[[1, 0, -x_pixels(x.to_f)], [0, 1, y_pixels(y.to_f)], [0, 0, 1]]
30
+ else
31
+ warnings << "transform 'rotate' must have either one or three arguments"
32
+ end
33
+
34
+ when 'scale'
35
+ x_scale = arguments[0].to_f
36
+ y_scale = (arguments[1] || x_scale).to_f
37
+ matrix *= Matrix[[x_scale, 0, 0], [0, y_scale, 0], [0, 0, 1]]
38
+
39
+ when 'skewX'
40
+ angle = arguments[0].to_f * Math::PI / 180.0
41
+ matrix *= Matrix[[1, -Math.tan(angle), 0], [0, 1, 0], [0, 0, 1]]
42
+
43
+ when 'skewY'
44
+ angle = arguments[0].to_f * Math::PI / 180.0
45
+ matrix *= Matrix[[1, 0, 0], [-Math.tan(angle), 1, 0], [0, 0, 1]]
46
+
47
+ when 'matrix'
48
+ if arguments.length != 6
49
+ warnings << "transform 'matrix' must have six arguments"
50
+ else
51
+ a, b, c, d, e, f = arguments.collect { |argument| argument.to_f }
52
+ matrix *= Matrix[[a, -c, e], [-b, d, -f], [0, 0, 1]]
53
+ end
54
+
55
+ else
56
+ warnings << "Unknown/unsupported transformation '#{name}'; ignoring"
57
+ end
58
+ end
59
+
60
+ matrix.to_a[0..1].transpose.flatten
61
+ end
62
+
63
+ private
64
+
65
+ def parse_css_method_calls(string)
66
+ string.scan(/\s*(\w+)\(([^)]+)\)\s*/).collect do |call|
67
+ name, argument_string = call
68
+ arguments = argument_string.strip.split(/\s*[,\s]\s*/)
69
+ [name, arguments]
70
+ end
71
+ end
72
+ end
@@ -1,5 +1,5 @@
1
1
  module Prawn
2
2
  module SVG
3
- VERSION = '0.29.1'
3
+ VERSION = '0.30.0'
4
4
  end
5
5
  end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe Prawn::SVG::Attributes::Opacity do
4
+ class OpacityTestElement
5
+ include Prawn::SVG::Attributes::Opacity
6
+
7
+ attr_accessor :properties, :state
8
+
9
+ def initialize
10
+ @properties = ::Prawn::SVG::Properties.new
11
+ @state = ::Prawn::SVG::State.new
12
+ end
13
+
14
+ def clamp(value, min_value, max_value)
15
+ [[value, min_value].max, max_value].min
16
+ end
17
+ end
18
+
19
+ let(:element) { OpacityTestElement.new }
20
+
21
+ describe "#parse_opacity_attributes_and_call" do
22
+ subject { element.parse_opacity_attributes_and_call }
23
+
24
+ context "with no opacity specified" do
25
+ it "does nothing" do
26
+ expect(element).not_to receive(:add_call_and_enter)
27
+ subject
28
+ end
29
+ end
30
+
31
+ context "with opacity" do
32
+ it "sets fill and stroke opacity" do
33
+ element.properties.opacity = '0.4'
34
+
35
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.4)
36
+ subject
37
+
38
+ expect(element.state.fill_opacity).to eq 0.4
39
+ expect(element.state.stroke_opacity).to eq 0.4
40
+ end
41
+ end
42
+
43
+ context "with just fill opacity" do
44
+ it "sets fill opacity and sets stroke opacity to 1" do
45
+ element.properties.fill_opacity = '0.4'
46
+
47
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 1)
48
+ subject
49
+
50
+ expect(element.state.fill_opacity).to eq 0.4
51
+ expect(element.state.stroke_opacity).to eq 1
52
+ end
53
+ end
54
+
55
+ context "with an existing fill/stroke opacity" do
56
+ it "multiplies the new opacity by the old" do
57
+ element.state.fill_opacity = 0.5
58
+ element.state.stroke_opacity = 0.8
59
+
60
+ element.properties.fill_opacity = '0.4'
61
+ element.properties.stroke_opacity = '0.5'
62
+
63
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.4)
64
+ subject
65
+
66
+ expect(element.state.fill_opacity).to eq 0.2
67
+ expect(element.state.stroke_opacity).to eq 0.4
68
+ end
69
+ end
70
+
71
+ context "with stroke, fill, and opacity all specified" do
72
+ it "choses the lower of them" do
73
+ element.properties.fill_opacity = '0.4'
74
+ element.properties.stroke_opacity = '0.6'
75
+ element.properties.opacity = '0.5'
76
+
77
+ expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.5)
78
+ subject
79
+
80
+ expect(element.state.fill_opacity).to eq 0.4
81
+ expect(element.state.stroke_opacity).to eq 0.5
82
+ end
83
+ end
84
+ end
85
+ end
@@ -14,43 +14,38 @@ describe Prawn::SVG::Attributes::Transform do
14
14
 
15
15
  let(:element) { TransformTestElement.new }
16
16
 
17
- describe "#parse_transform_attribute_and_call" do
18
- subject { element.send :parse_transform_attribute_and_call }
19
-
20
- describe "translate" do
21
- it "handles a missing y argument" do
22
- expect(element).to receive(:add_call_and_enter).with('translate', -5.5, 0)
23
- expect(element).to receive(:x_pixels).with(-5.5).and_return(-5.5)
24
- expect(element).to receive(:y_pixels).with(0.0).and_return(0.0)
25
-
26
- element.attributes['transform'] = 'translate(-5.5)'
27
- subject
28
- end
17
+ subject { element.send :parse_transform_attribute_and_call }
18
+
19
+ context "when a non-identity matrix is requested" do
20
+ let(:transform) { 'translate(-5.5)' }
21
+
22
+ it "passes the transform and executes the returned matrix" do
23
+ expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 2, 3, 4, 5, 6])
24
+ expect(element).to receive(:add_call_and_enter).with('transformation_matrix', 1, 2, 3, 4, 5, 6)
25
+
26
+ element.attributes['transform'] = transform
27
+ subject
28
+ end
29
+ end
30
+
31
+ context "when an identity matrix is requested" do
32
+ let(:transform) { 'translate(0)' }
33
+
34
+ it "does not execute any commands" do
35
+ expect(element).to receive(:parse_transform_attribute).with(transform).and_return([1, 0, 0, 1, 0, 0])
36
+ expect(element).not_to receive(:add_call_and_enter)
37
+
38
+ element.attributes['transform'] = transform
39
+ subject
29
40
  end
41
+ end
42
+
43
+ context "when transform is blank" do
44
+ it "does nothing" do
45
+ expect(element).not_to receive(:parse_transform_attribute)
46
+ expect(element).not_to receive(:add_call_and_enter)
30
47
 
31
- describe "rotate" do
32
- it "handles a single angle argument" do
33
- expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [0, 0])
34
- expect(element).to receive(:y).with('0').and_return(0)
35
-
36
- element.attributes['transform'] = 'rotate(5.5)'
37
- subject
38
- end
39
-
40
- it "handles three arguments" do
41
- expect(element).to receive(:add_call_and_enter).with('rotate', -5.5, :origin => [1.0, 2.0])
42
- expect(element).to receive(:x).with(1.0).and_return(1.0)
43
- expect(element).to receive(:y).with(2.0).and_return(2.0)
44
-
45
- element.attributes['transform'] = 'rotate(5.5 1 2)'
46
- subject
47
- end
48
-
49
- it "does nothing and warns if two arguments" do
50
- expect(element).to receive(:warnings).and_return([])
51
- element.attributes['transform'] = 'rotate(5.5 1)'
52
- subject
53
- end
48
+ subject
54
49
  end
55
50
  end
56
51
  end
@@ -25,14 +25,14 @@ describe Prawn::SVG::Elements::Base do
25
25
  describe "applying calls from the standard attributes" do
26
26
  let(:svg) do
27
27
  <<-SVG
28
- <something transform="rotate(90)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
28
+ <something transform="scale(2)" fill-opacity="0.5" fill="red" stroke="blue" stroke-width="5"/>
29
29
  SVG
30
30
  end
31
31
 
32
32
  it "appends the relevant calls" do
33
33
  element.process
34
34
  expect(element.base_calls).to eq [
35
- ["rotate", [-90.0, {origin: [0, 600.0]}], [
35
+ ["transformation_matrix", [2, 0, 0, 2, 0, 0], [
36
36
  ["transparent", [0.5, 1], [
37
37
  ["fill_color", ["ff0000"], []],
38
38
  ["stroke_color", ["0000ff"], []],
@@ -29,7 +29,8 @@ describe Prawn::SVG::Elements::Gradient do
29
29
  expect(arguments).to eq(
30
30
  from: [100.0, 100.0],
31
31
  to: [120.0, 0.0],
32
- stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]]
32
+ stops: [[0, "ff0000"], [0.25, "ff0000"], [0.5, "ffffff"], [0.75, "0000ff"], [1, "0000ff"]],
33
+ apply_transformations: true,
33
34
  )
34
35
  end
35
36
 
@@ -54,7 +55,30 @@ describe Prawn::SVG::Elements::Gradient do
54
55
  expect(arguments).to eq(
55
56
  from: [100.0, 100.0],
56
57
  to: [200.0, 0.0],
57
- stops: [[0, "ff0000"], [1, "0000ff"]]
58
+ stops: [[0, "ff0000"], [1, "0000ff"]],
59
+ apply_transformations: true,
60
+ )
61
+ end
62
+ end
63
+
64
+ context "when gradientTransform is specified" do
65
+ let(:svg) do
66
+ <<-SVG
67
+ <linearGradient id="flag" gradientTransform="translateX(10) scale(2)" x1="0" y1="0" x2="10" y2="10">
68
+ <stop offset="0" stop-color="red"/>
69
+ <stop offset="1" stop-color="blue"/>
70
+ </linearGradient>
71
+ SVG
72
+ end
73
+
74
+ it "passes in the transform via the apply_transformations option" do
75
+ arguments = element.gradient_arguments(double(bounding_box: [0, 0, 10, 10]))
76
+
77
+ expect(arguments).to eq(
78
+ from: [0, 0],
79
+ to: [10, 10],
80
+ stops: [[0, "ff0000"], [1, "0000ff"]],
81
+ apply_transformations: [2, 0, 0, 2, 10, 0],
58
82
  )
59
83
  end
60
84
  end
@@ -29,6 +29,14 @@ RSpec.describe Prawn::SVG::Loaders::Data do
29
29
  end
30
30
  end
31
31
 
32
+ context "with a data URL that's uppercase" do
33
+ let(:url) { "DATA:IMAGE/PNG;BASE64;METADATA;HERE,aGVsbG8=" }
34
+
35
+ it "loads the data" do
36
+ expect(subject).to eq "hello"
37
+ end
38
+ end
39
+
32
40
  context "with a URL that's not a data scheme" do
33
41
  let(:url) { "http://some.host" }
34
42
 
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Prawn::SVG::TransformParser do
4
+ class Test
5
+ include Prawn::SVG::Calculators::Pixels
6
+ include Prawn::SVG::TransformParser
7
+
8
+ State = Struct.new(:viewport_sizing)
9
+ Properties = Struct.new(:numerical_font_size)
10
+ Document = Struct.new(:sizing)
11
+
12
+ def document
13
+ Document.new(_sizing)
14
+ end
15
+
16
+ def state
17
+ State.new(_sizing)
18
+ end
19
+
20
+ def computed_properties
21
+ Properties.new(14)
22
+ end
23
+
24
+ def _sizing
25
+ Prawn::SVG::Calculators::DocumentSizing.new([1000, 800])
26
+ end
27
+ end
28
+
29
+ subject { Test.new.parse_transform_attribute(transform) }
30
+
31
+ context "with no transform" do
32
+ let(:transform) { '' }
33
+ it { is_expected.to eq [1, 0, 0, 1, 0, 0] }
34
+ end
35
+
36
+ context "with translate" do
37
+ let(:transform) { 'translate(10 20)' }
38
+ it { is_expected.to eq [1, 0, 0, 1, 10, -20] }
39
+ end
40
+
41
+ context "with single argument translate" do
42
+ let(:transform) { 'translate(10)' }
43
+ it { is_expected.to eq [1, 0, 0, 1, 10, 0] }
44
+ end
45
+
46
+ context "with translateX" do
47
+ let(:transform) { 'translateX(10)' }
48
+ it { is_expected.to eq [1, 0, 0, 1, 10, 0] }
49
+ end
50
+
51
+ context "with translateY" do
52
+ let(:transform) { 'translateY(10)' }
53
+ it { is_expected.to eq [1, 0, 0, 1, 0, -10] }
54
+ end
55
+
56
+ let(:sin30) { Math.sin(30 * Math::PI / 180.0) }
57
+ let(:cos30) { Math.cos(30 * Math::PI / 180.0) }
58
+ let(:tan30) { Math.tan(30 * Math::PI / 180.0) }
59
+
60
+ context "with single argument rotate" do
61
+ let(:transform) { 'rotate(30)' }
62
+ it { is_expected.to eq [cos30, -sin30, sin30, cos30, 0, 0] }
63
+ end
64
+
65
+ context "with triple argument rotate" do
66
+ let(:transform) { 'rotate(30 100 200)' }
67
+ it { is_expected.to eq [cos30, -sin30, sin30, cos30, 113.39745962155611, 23.205080756887753] }
68
+ end
69
+
70
+ context "with scale" do
71
+ let(:transform) { 'scale(1.5)' }
72
+ it { is_expected.to eq [1.5, 0, 0, 1.5, 0, 0] }
73
+ end
74
+
75
+ context "with skewX" do
76
+ let(:transform) { 'skewX(30)' }
77
+ it { is_expected.to eq [1, 0, -tan30, 1, 0, 0] }
78
+ end
79
+
80
+ context "with skewY" do
81
+ let(:transform) { 'skewY(30)' }
82
+ it { is_expected.to eq [1, -tan30, 0, 1, 0, 0] }
83
+ end
84
+
85
+ context "with matrix" do
86
+ let(:transform) { 'matrix(1 2 3 4 5 6)' }
87
+ it { is_expected.to eq [1, -2, -3, 4, 5, -6] }
88
+ end
89
+
90
+ context "with multiple" do
91
+ let(:transform) { 'scale(2) translate(7) scale(3)' }
92
+ it { is_expected.to eq [6, 0, 0, 6, 14, 0] }
93
+ end
94
+ end
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="206.404" height="167.984" viewBox="0 0 54.611 44.446">
2
+ <g transform="translate(-27.725 -80.097)" stroke="#000" stroke-width=".5">
3
+ <ellipse cx="48.451" cy="103.356" rx="20.476" ry="19.462" fill="#52e0ff"/>
4
+ <ellipse cx="66.77" cy="102.32" rx="15.316" ry="21.973" fill="#ff6c52" style="opacity:.75; fill-opacity:1"/>
5
+ </g>
6
+ </svg>
@@ -0,0 +1,19 @@
1
+ <svg viewBox="0 0 420 200" xmlns="http://www.w3.org/2000/svg">
2
+ <linearGradient id="gradient1" gradientUnits="userSpaceOnUse"
3
+ x1="0" y1="0" x2="200" y2="200">
4
+ <stop offset="0%" stop-color="darkblue" />
5
+ <stop offset="50%" stop-color="skyblue" />
6
+ <stop offset="100%" stop-color="darkblue" />
7
+ </linearGradient>
8
+ <linearGradient id="gradient2" gradientUnits="userSpaceOnUse"
9
+ x1="0" y1="0" x2="200" y2="200"
10
+ xgradientTransform="skewX(45) translate(-100, 0)"
11
+ gradientTransform="matrix(0.5, 0, 0, 1, 0, 100)">
12
+ <stop offset="0%" stop-color="darkblue" />
13
+ <stop offset="50%" stop-color="skyblue" />
14
+ <stop offset="100%" stop-color="darkblue" />
15
+ </linearGradient>
16
+
17
+ <rect x="0" y="0" width="200" height="200" fill="url(#gradient1)" />
18
+ <rect x="0" y="0" width="200" height="200" fill="url(#gradient2)" transform="translate(220, 0)" />
19
+ </svg>
@@ -0,0 +1,18 @@
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="1200" height="400" viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
4
+ <text fill="#000000" font-family="sans-serif" font-size="13" textLength="78" x="14" y="20">You can use</text>
5
+ <a target="_top" xlink:actuate="onRequest" xlink:href="http://plantuml.com/start" xlink:show="new" xlink:title="http://plantuml.com/start" xlink:type="simple">
6
+ <text fill="#0000FF" font-family="sans-serif" font-size="13" text-decoration="underline" textLength="87" x="96" y="20">links in notes</text>
7
+ <line style="stroke: #0000FF; stroke-width: 1.0;" x1="96" x2="183" y1="20" y2="20"/>
8
+ </a>
9
+
10
+ <g transform="translate(0 20)">
11
+ <text fill="#000000" font-family="sans-serif" font-size="13" textLength="78" lengthAdjust="spacingAndGlyphs" x="14" y="20">You can use</text>
12
+ <a target="_top" xlink:actuate="onRequest" xlink:href="http://plantuml.com/start" xlink:show="new" xlink:title="http://plantuml.com/start" xlink:type="simple">
13
+ <text fill="#0000FF" font-family="sans-serif" font-size="13" text-decoration="underline" textLength="87" lengthAdjust="spacingAndGlyphs" x="96" y="20">links in notes</text>
14
+ <line style="stroke: #0000FF; stroke-width: 1.0;" x1="96" x2="183" y1="20" y2="20"/>
15
+ </a>
16
+ </g>
17
+ </svg>
18
+
@@ -0,0 +1,20 @@
1
+ <svg height="975" version="1.1" width="1275" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="0" y="0" width="200" height="100" fill="blue"/>
3
+ <rect x="0" y="0" width="200" height="100" fill="lightblue" transform="rotate(30 200 100) translate(200)"/>
4
+ <rect x="0" y="0" width="200" height="100" fill="lightblue" transform="rotate(-5) translate(400)"/>
5
+
6
+ <g transform="translate(0 200)">
7
+ <rect x="0" y="0" width="200" height="100" fill="green"/>
8
+ <rect x="0" y="0" width="200" height="100" fill="lightgreen" transform="skewX(35) translate(200)"/>
9
+ </g>
10
+
11
+ <g transform="translate(0 320)">
12
+ <rect x="0" y="0" width="200" height="100" fill="red"/>
13
+ <rect x="0" y="0" width="200" height="100" fill="pink" transform="skewY(5) translate(200)"/>
14
+ </g>
15
+
16
+ <g transform="translate(0 470)">
17
+ <rect x="0" y="0" width="200" height="100" fill="black"/>
18
+ <rect x="0" y="0" width="200" height="100" fill="grey" transform="translate(200) scale(1.5)"/>
19
+ </g>
20
+ </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.29.1
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-06 00:00:00.000000000 Z
11
+ date: 2019-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prawn
@@ -126,6 +126,7 @@ files:
126
126
  - lib/prawn/svg/elements/use.rb
127
127
  - lib/prawn/svg/elements/viewport.rb
128
128
  - lib/prawn/svg/extension.rb
129
+ - lib/prawn/svg/extensions/additional_gradient_transforms.rb
129
130
  - lib/prawn/svg/font.rb
130
131
  - lib/prawn/svg/font_registry.rb
131
132
  - lib/prawn/svg/interface.rb
@@ -135,11 +136,13 @@ files:
135
136
  - lib/prawn/svg/pathable.rb
136
137
  - lib/prawn/svg/properties.rb
137
138
  - lib/prawn/svg/state.rb
139
+ - lib/prawn/svg/transform_parser.rb
138
140
  - lib/prawn/svg/ttf.rb
139
141
  - lib/prawn/svg/url_loader.rb
140
142
  - lib/prawn/svg/version.rb
141
143
  - prawn-svg.gemspec
142
144
  - spec/integration_spec.rb
145
+ - spec/prawn/svg/attributes/opacity_spec.rb
143
146
  - spec/prawn/svg/attributes/transform_spec.rb
144
147
  - spec/prawn/svg/calculators/aspect_ratio_spec.rb
145
148
  - spec/prawn/svg/calculators/document_sizing_spec.rb
@@ -165,6 +168,7 @@ files:
165
168
  - spec/prawn/svg/loaders/web_spec.rb
166
169
  - spec/prawn/svg/pathable_spec.rb
167
170
  - spec/prawn/svg/properties_spec.rb
171
+ - spec/prawn/svg/transform_parser_spec.rb
168
172
  - spec/prawn/svg/ttf_spec.rb
169
173
  - spec/prawn/svg/url_loader_spec.rb
170
174
  - spec/sample_images/mushroom-long.jpg
@@ -180,9 +184,11 @@ files:
180
184
  - spec/sample_svg/cubic01a.svg
181
185
  - spec/sample_svg/cubic02.svg
182
186
  - spec/sample_svg/display_none.svg
187
+ - spec/sample_svg/double_opacity.svg
183
188
  - spec/sample_svg/ellipse01.svg
184
189
  - spec/sample_svg/gistfile1.svg
185
190
  - spec/sample_svg/google_charts.svg
191
+ - spec/sample_svg/gradient_transform.svg
186
192
  - spec/sample_svg/gradients.svg
187
193
  - spec/sample_svg/hidden_paths.svg
188
194
  - spec/sample_svg/highcharts.svg
@@ -190,6 +196,7 @@ files:
190
196
  - spec/sample_svg/image02_base64.svg
191
197
  - spec/sample_svg/image03.svg
192
198
  - spec/sample_svg/line01.svg
199
+ - spec/sample_svg/links.svg
193
200
  - spec/sample_svg/marker.svg
194
201
  - spec/sample_svg/markers_degenerate_cp.svg
195
202
  - spec/sample_svg/maths.svg
@@ -216,6 +223,7 @@ files:
216
223
  - spec/sample_svg/subviewports2.svg
217
224
  - spec/sample_svg/text_entities.svg
218
225
  - spec/sample_svg/text_stroke.svg
226
+ - spec/sample_svg/transform.svg
219
227
  - spec/sample_svg/tref01.svg
220
228
  - spec/sample_svg/triangle01.svg
221
229
  - spec/sample_svg/tspan01.svg
@@ -256,6 +264,7 @@ specification_version: 4
256
264
  summary: SVG renderer for Prawn PDF library
257
265
  test_files:
258
266
  - spec/integration_spec.rb
267
+ - spec/prawn/svg/attributes/opacity_spec.rb
259
268
  - spec/prawn/svg/attributes/transform_spec.rb
260
269
  - spec/prawn/svg/calculators/aspect_ratio_spec.rb
261
270
  - spec/prawn/svg/calculators/document_sizing_spec.rb
@@ -281,6 +290,7 @@ test_files:
281
290
  - spec/prawn/svg/loaders/web_spec.rb
282
291
  - spec/prawn/svg/pathable_spec.rb
283
292
  - spec/prawn/svg/properties_spec.rb
293
+ - spec/prawn/svg/transform_parser_spec.rb
284
294
  - spec/prawn/svg/ttf_spec.rb
285
295
  - spec/prawn/svg/url_loader_spec.rb
286
296
  - spec/sample_images/mushroom-long.jpg
@@ -296,9 +306,11 @@ test_files:
296
306
  - spec/sample_svg/cubic01a.svg
297
307
  - spec/sample_svg/cubic02.svg
298
308
  - spec/sample_svg/display_none.svg
309
+ - spec/sample_svg/double_opacity.svg
299
310
  - spec/sample_svg/ellipse01.svg
300
311
  - spec/sample_svg/gistfile1.svg
301
312
  - spec/sample_svg/google_charts.svg
313
+ - spec/sample_svg/gradient_transform.svg
302
314
  - spec/sample_svg/gradients.svg
303
315
  - spec/sample_svg/hidden_paths.svg
304
316
  - spec/sample_svg/highcharts.svg
@@ -306,6 +318,7 @@ test_files:
306
318
  - spec/sample_svg/image02_base64.svg
307
319
  - spec/sample_svg/image03.svg
308
320
  - spec/sample_svg/line01.svg
321
+ - spec/sample_svg/links.svg
309
322
  - spec/sample_svg/marker.svg
310
323
  - spec/sample_svg/markers_degenerate_cp.svg
311
324
  - spec/sample_svg/maths.svg
@@ -332,6 +345,7 @@ test_files:
332
345
  - spec/sample_svg/subviewports2.svg
333
346
  - spec/sample_svg/text_entities.svg
334
347
  - spec/sample_svg/text_stroke.svg
348
+ - spec/sample_svg/transform.svg
335
349
  - spec/sample_svg/tref01.svg
336
350
  - spec/sample_svg/triangle01.svg
337
351
  - spec/sample_svg/tspan01.svg