prawn-svg 0.28.0 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +18 -0
- data/LICENSE +1 -1
- data/README.md +14 -9
- data/lib/prawn-svg.rb +4 -0
- data/lib/prawn/svg/attributes/opacity.rb +4 -4
- data/lib/prawn/svg/attributes/transform.rb +2 -44
- data/lib/prawn/svg/calculators/document_sizing.rb +2 -2
- data/lib/prawn/svg/css/stylesheets.rb +40 -19
- data/lib/prawn/svg/elements.rb +2 -0
- data/lib/prawn/svg/elements/base.rb +11 -5
- data/lib/prawn/svg/elements/call_duplicator.rb +1 -1
- data/lib/prawn/svg/elements/gradient.rb +83 -25
- data/lib/prawn/svg/elements/image.rb +2 -2
- data/lib/prawn/svg/elements/path.rb +42 -29
- data/lib/prawn/svg/elements/root.rb +4 -1
- data/lib/prawn/svg/elements/text_component.rb +21 -5
- data/lib/prawn/svg/elements/use.rb +23 -7
- data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +23 -0
- data/lib/prawn/svg/interface.rb +38 -8
- data/lib/prawn/svg/loaders/data.rb +1 -1
- data/lib/prawn/svg/loaders/file.rb +4 -2
- data/lib/prawn/svg/properties.rb +1 -0
- data/lib/prawn/svg/transform_parser.rb +72 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/prawn-svg.gemspec +3 -3
- data/spec/integration_spec.rb +24 -24
- data/spec/prawn/svg/attributes/opacity_spec.rb +85 -0
- data/spec/prawn/svg/attributes/transform_spec.rb +30 -35
- data/spec/prawn/svg/calculators/document_sizing_spec.rb +4 -4
- data/spec/prawn/svg/css/stylesheets_spec.rb +17 -6
- data/spec/prawn/svg/elements/base_spec.rb +16 -16
- data/spec/prawn/svg/elements/gradient_spec.rb +79 -4
- data/spec/prawn/svg/elements/line_spec.rb +12 -12
- data/spec/prawn/svg/elements/marker_spec.rb +27 -27
- data/spec/prawn/svg/elements/path_spec.rb +29 -17
- data/spec/prawn/svg/elements/polygon_spec.rb +9 -9
- data/spec/prawn/svg/elements/polyline_spec.rb +7 -7
- data/spec/prawn/svg/elements/text_spec.rb +65 -50
- data/spec/prawn/svg/loaders/data_spec.rb +8 -0
- data/spec/prawn/svg/pathable_spec.rb +4 -4
- data/spec/prawn/svg/transform_parser_spec.rb +94 -0
- data/spec/sample_svg/double_opacity.svg +6 -0
- data/spec/sample_svg/gradient_transform.svg +19 -0
- data/spec/sample_svg/links.svg +18 -0
- data/spec/sample_svg/radgrad01-bounding.svg +26 -0
- data/spec/sample_svg/radgrad01.svg +26 -0
- data/spec/sample_svg/svg_fill.svg +5 -0
- data/spec/sample_svg/text-decoration.svg +4 -0
- data/spec/sample_svg/transform.svg +20 -0
- data/spec/sample_svg/use_disordered.svg +17 -0
- data/spec/spec_helper.rb +2 -2
- metadata +48 -11
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d054ded3422dc4cd1cb870dc09549c0a82c1e2813e314565c7a3aec70d95d188
|
4
|
+
data.tar.gz: 123a00b97f78f284a8e11fad37fed42cbec06d678675ec15ff7dafbcfe6505d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ece284ced16e7b6f4d7562c17a97780375f4e255eb4199d489cfb1fa473dc1f65e6b9fa48894079f0756b42a749cc58f34c00507e19ccdfddab55723c82311d
|
7
|
+
data.tar.gz: 8826f6f457372cc7df38869e6448974f5731fdee1040bca44b8446b71c57e0713340481a3400705c8fd11ec5d13959dcd99f24a0fe8820c89a37ca725545656c
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: test
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
rake:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
fail-fast: false
|
8
|
+
matrix:
|
9
|
+
ruby: [2.3, 2.4, 2.5, 2.6, 2.7, '3.0']
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v2
|
12
|
+
- name: Set up Ruby
|
13
|
+
uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
bundler-cache: true
|
16
|
+
ruby-version: ${{ matrix.ruby }}
|
17
|
+
- name: Run tests
|
18
|
+
run: bundle exec rake
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# prawn-svg
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/prawn-svg.svg)](https://badge.fury.io/rb/prawn-svg)
|
4
|
-
|
4
|
+
![Build Status](https://github.com/mogest/prawn-svg/actions/workflows/test.yml/badge.svg?branch=master)
|
5
5
|
|
6
6
|
An SVG renderer for the Prawn PDF library.
|
7
7
|
|
@@ -10,7 +10,7 @@ This will take an SVG document as input and render it into your PDF. Find out m
|
|
10
10
|
http://github.com/prawnpdf/prawn
|
11
11
|
|
12
12
|
prawn-svg is compatible with all versions of Prawn from 0.11.1 onwards, including the 1.x and 2.x series.
|
13
|
-
The minimum Ruby version required is 2.
|
13
|
+
The minimum Ruby version required is 2.3.0.
|
14
14
|
|
15
15
|
## Using prawn-svg
|
16
16
|
|
@@ -60,8 +60,8 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
|
|
60
60
|
- <tt><path></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
|
64
|
-
`text-anchor`, `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
|
63
|
+
- `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, 'textLength', 'lengthAdjust', and with extra properties
|
64
|
+
`text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`
|
65
65
|
|
66
66
|
- <tt><svg></tt>, <tt><g></tt> and <tt><symbol></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>`
|
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.
|
@@ -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><svg></tt>, <tt><image></tt> and `<marker>` elements
|
93
93
|
|
94
|
-
- transform methods:
|
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
|
|
@@ -107,12 +107,16 @@ In CSS selectors you can use element names, IDs, classes, attributes (existence,
|
|
107
107
|
and all combinators (` `, `>`, `+`, `~`).
|
108
108
|
The pseudo-classes `:first-child`, `:last-child` and `:nth-child(n)` (where n is a number) also work.
|
109
109
|
|
110
|
+
Warning: Ruby versions less than 2.6.0 have a bug in the REXML XPath implementation which means under some
|
111
|
+
conditions the `+` combinator will not pick up all matching elements. See `stylesheets_spec.rb` for an
|
112
|
+
explanation if you're stuck on an old version of Ruby.
|
113
|
+
|
110
114
|
Pseudo-elements and the other pseudo-classes are not supported. Specificity ordering is
|
111
115
|
implemented, but `!important` is not.
|
112
116
|
|
113
117
|
## Not supported
|
114
118
|
|
115
|
-
prawn-svg does not support
|
119
|
+
prawn-svg does not support hyperlinks, patterns or filters.
|
116
120
|
|
117
121
|
It does not support text in the clip area, but you can clip shapes and text by any shape.
|
118
122
|
|
@@ -131,5 +135,6 @@ Mac OS X and Debian Linux users. You can add to the font path:
|
|
131
135
|
|
132
136
|
In your Gemfile, put `gem 'prawn-svg'` before `gem 'prawn-rails'` so that prawn-rails can see the prawn-svg extension.
|
133
137
|
|
134
|
-
|
135
|
-
|
138
|
+
## Licence
|
139
|
+
|
140
|
+
MIT licence. Copyright Roger Nesbitt.
|
data/lib/prawn-svg.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
6
|
-
|
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
|
@@ -36,10 +36,10 @@ module Prawn::SVG::Calculators
|
|
36
36
|
@x_offset, @y_offset, @viewport_width, @viewport_height = values.map {|value| value.to_f}
|
37
37
|
|
38
38
|
if @viewport_width > 0 && @viewport_height > 0
|
39
|
-
# If neither the width nor height was specified, use the entire
|
39
|
+
# If neither the width nor height was specified, use the entire width and the viewbox ratio
|
40
|
+
# to determine the height.
|
40
41
|
if @output_width.nil? && @output_height.nil?
|
41
42
|
@output_width = container_width
|
42
|
-
@output_height = container_height
|
43
43
|
end
|
44
44
|
|
45
45
|
# If one of the output dimensions is missing, calculate it from the other one
|
@@ -32,13 +32,14 @@ module Prawn::SVG::CSS
|
|
32
32
|
rule_set.each_declaration { |*data| declarations << data }
|
33
33
|
|
34
34
|
rule_set.selectors.each do |selector_text|
|
35
|
-
selector = Prawn::SVG::CSS::SelectorParser.parse(selector_text)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
if selector = Prawn::SVG::CSS::SelectorParser.parse(selector_text)
|
36
|
+
xpath = css_selector_to_xpath(selector)
|
37
|
+
specificity = calculate_specificity(selector)
|
38
|
+
specificity << order
|
39
|
+
order += 1
|
40
40
|
|
41
|
-
|
41
|
+
xpath_styles << [xpath, declarations, specificity]
|
42
|
+
end
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
@@ -64,20 +65,48 @@ module Prawn::SVG::CSS
|
|
64
65
|
def css_selector_to_xpath(selector)
|
65
66
|
selector.map do |element|
|
66
67
|
pseudo_classes = Set.new(element[:pseudo_class])
|
68
|
+
require_function_name = false
|
67
69
|
|
68
70
|
result = case element[:combinator]
|
69
71
|
when :child
|
70
|
-
|
72
|
+
"/"
|
71
73
|
when :adjacent
|
72
74
|
pseudo_classes << 'first-child'
|
73
|
-
|
75
|
+
"/following-sibling::"
|
74
76
|
when :siblings
|
75
|
-
|
77
|
+
"/following-sibling::"
|
76
78
|
else
|
77
|
-
|
79
|
+
"//"
|
78
80
|
end
|
79
81
|
|
80
|
-
|
82
|
+
positions = []
|
83
|
+
pseudo_classes.each do |pc|
|
84
|
+
case pc
|
85
|
+
when "first-child" then positions << '1'
|
86
|
+
when "last-child" then positions << 'last()'
|
87
|
+
when /^nth-child\((\d+)\)$/ then positions << $1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if !positions.empty?
|
92
|
+
result << "*" unless require_function_name
|
93
|
+
require_function_name = true
|
94
|
+
|
95
|
+
logic = if positions.length == 1
|
96
|
+
positions.first
|
97
|
+
else
|
98
|
+
positions.map { |position| "position()=#{position}" }.join(" and ")
|
99
|
+
end
|
100
|
+
|
101
|
+
result << "[#{logic}]"
|
102
|
+
end
|
103
|
+
|
104
|
+
if require_function_name
|
105
|
+
result << "[name()=#{xpath_quote element[:name]}]" if element[:name]
|
106
|
+
else
|
107
|
+
result << (element[:name] || '*')
|
108
|
+
end
|
109
|
+
|
81
110
|
result << ((element[:class] || []).map { |name| "[contains(concat(' ',@class,' '), ' #{name} ')]" }.join)
|
82
111
|
result << ((element[:id] || []).map { |name| "[@id='#{name}']" }.join)
|
83
112
|
|
@@ -100,14 +129,6 @@ module Prawn::SVG::CSS
|
|
100
129
|
end
|
101
130
|
end
|
102
131
|
|
103
|
-
pseudo_classes.each do |pc|
|
104
|
-
case pc
|
105
|
-
when "first-child" then result << "[1]"
|
106
|
-
when "last-child" then result << "[last()]"
|
107
|
-
when /^nth-child\((\d+)\)$/ then result << "[#{$1}]"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
132
|
result
|
112
133
|
end.join
|
113
134
|
end
|
data/lib/prawn/svg/elements.rb
CHANGED
@@ -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,
|
@@ -27,6 +28,7 @@ module Prawn::SVG::Elements
|
|
27
28
|
use: Prawn::SVG::Elements::Use,
|
28
29
|
image: Prawn::SVG::Elements::Image,
|
29
30
|
linearGradient: Prawn::SVG::Elements::Gradient,
|
31
|
+
radialGradient: Prawn::SVG::Elements::Gradient,
|
30
32
|
marker: Prawn::SVG::Elements::Marker,
|
31
33
|
style: Prawn::SVG::Elements::Ignored, # because it is pre-parsed by Document
|
32
34
|
title: Prawn::SVG::Elements::Ignored,
|
@@ -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"
|
@@ -89,12 +91,12 @@ class Prawn::SVG::Elements::Base
|
|
89
91
|
parse_xml_space_attribute
|
90
92
|
end
|
91
93
|
|
92
|
-
def add_call(name, *arguments)
|
93
|
-
@calls << [name.to_s, arguments, []]
|
94
|
+
def add_call(name, *arguments, **kwarguments)
|
95
|
+
@calls << [name.to_s, arguments, kwarguments, []]
|
94
96
|
end
|
95
97
|
|
96
|
-
def add_call_and_enter(name, *arguments)
|
97
|
-
@calls << [name.to_s, arguments, []]
|
98
|
+
def add_call_and_enter(name, *arguments, **kwarguments)
|
99
|
+
@calls << [name.to_s, arguments, kwarguments, []]
|
98
100
|
@calls = @calls.last.last
|
99
101
|
end
|
100
102
|
|
@@ -166,7 +168,7 @@ class Prawn::SVG::Elements::Base
|
|
166
168
|
command = stroke ? 'fill_and_stroke' : 'fill'
|
167
169
|
|
168
170
|
if computed_properties.fill_rule == 'evenodd'
|
169
|
-
add_call_and_enter(command,
|
171
|
+
add_call_and_enter(command, fill_rule: :even_odd)
|
170
172
|
else
|
171
173
|
add_call_and_enter(command)
|
172
174
|
end
|
@@ -265,4 +267,8 @@ class Prawn::SVG::Elements::Base
|
|
265
267
|
element = document.elements_by_id[matches[1]] if matches
|
266
268
|
element if element && (expected_type.nil? || element.name == expected_type)
|
267
269
|
end
|
270
|
+
|
271
|
+
def href_attribute
|
272
|
+
attributes['xlink:href'] || attributes['href']
|
273
|
+
end
|
268
274
|
end
|
@@ -12,7 +12,7 @@ module Prawn::SVG::Elements::CallDuplicator
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def duplicate_call(call)
|
15
|
-
[call[0], duplicate_array(call[1]),
|
15
|
+
[call[0], duplicate_array(call[1]), duplicate_hash(call[2]), duplicate_calls(call[3])]
|
16
16
|
end
|
17
17
|
|
18
18
|
def duplicate_array(array)
|
@@ -1,10 +1,18 @@
|
|
1
1
|
class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
2
|
-
|
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,26 +24,56 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
16
24
|
end
|
17
25
|
|
18
26
|
def gradient_arguments(element)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
27
|
+
# Passing in a transformation matrix to the apply_transformations option is supported
|
28
|
+
# by a monkey patch installed by prawn-svg. Prawn only sees this as a truthy variable.
|
29
|
+
#
|
30
|
+
# See Prawn::SVG::Extensions::AdditionalGradientTransforms for details.
|
31
|
+
base_arguments = {stops: stops, apply_transformations: transform_matrix || true}
|
32
|
+
|
33
|
+
arguments = specific_gradient_arguments(element)
|
34
|
+
arguments.merge(base_arguments) if arguments
|
35
|
+
end
|
23
36
|
|
24
|
-
|
25
|
-
height = y1 - y2
|
37
|
+
private
|
26
38
|
|
27
|
-
|
28
|
-
|
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?
|
29
43
|
|
30
|
-
|
31
|
-
|
32
|
-
to = [@x2, @y2]
|
44
|
+
width = bounding_x2 - bounding_x1
|
45
|
+
height = bounding_y1 - bounding_y2
|
33
46
|
end
|
34
47
|
|
35
|
-
|
36
|
-
|
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]
|
37
52
|
|
38
|
-
|
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
|
39
77
|
|
40
78
|
def type
|
41
79
|
TAG_NAME_TO_TYPE.fetch(name)
|
@@ -51,10 +89,7 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
51
89
|
@units = attributes["gradientUnits"] == 'userSpaceOnUse' ? :user_space : :bounding_box
|
52
90
|
|
53
91
|
if transform = attributes["gradientTransform"]
|
54
|
-
|
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
|
92
|
+
@transform_matrix = parse_transform_attribute(transform)
|
58
93
|
end
|
59
94
|
|
60
95
|
if (spread_method = attributes['spreadMethod']) && spread_method != "pad"
|
@@ -63,18 +98,35 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
63
98
|
end
|
64
99
|
|
65
100
|
def load_coordinates
|
66
|
-
case
|
67
|
-
when :bounding_box
|
101
|
+
case [type, units]
|
102
|
+
when [:linear, :bounding_box]
|
68
103
|
@x1 = parse_zero_to_one(attributes["x1"], 0)
|
69
104
|
@y1 = parse_zero_to_one(attributes["y1"], 0)
|
70
105
|
@x2 = parse_zero_to_one(attributes["x2"], 1)
|
71
106
|
@y2 = parse_zero_to_one(attributes["y2"], 0)
|
72
107
|
|
73
|
-
when :user_space
|
108
|
+
when [:linear, :user_space]
|
74
109
|
@x1 = x(attributes["x1"])
|
75
110
|
@y1 = y(attributes["y1"])
|
76
111
|
@x2 = x(attributes["x2"])
|
77
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"
|
78
130
|
end
|
79
131
|
end
|
80
132
|
|
@@ -100,10 +152,16 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
100
152
|
end
|
101
153
|
end
|
102
154
|
|
103
|
-
|
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
|
104
159
|
|
105
|
-
|
106
|
-
|
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
|
107
165
|
end
|
108
166
|
|
109
167
|
def parse_zero_to_one(string, default = 0)
|