prawn-svg 0.29.1 → 0.30.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: 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