prawn-svg 0.32.0 → 0.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +20 -3
- data/.gitignore +1 -0
- data/README.md +32 -24
- data/lib/prawn/svg/attributes/stroke.rb +5 -0
- data/lib/prawn/svg/color.rb +46 -22
- data/lib/prawn/svg/css/selector_parser.rb +11 -11
- data/lib/prawn/svg/css/stylesheets.rb +4 -4
- data/lib/prawn/svg/document.rb +20 -4
- data/lib/prawn/svg/elements/base.rb +10 -11
- data/lib/prawn/svg/elements/gradient.rb +2 -2
- data/lib/prawn/svg/elements/image.rb +54 -21
- data/lib/prawn/svg/elements/path.rb +0 -2
- data/lib/prawn/svg/elements/root.rb +1 -1
- data/lib/prawn/svg/elements/text_component.rb +12 -3
- data/lib/prawn/svg/elements/use.rb +20 -12
- data/lib/prawn/svg/elements.rb +1 -0
- data/lib/prawn/svg/gradients.rb +48 -0
- data/lib/prawn/svg/interface.rb +17 -187
- data/lib/prawn/svg/loaders/data.rb +4 -3
- data/lib/prawn/svg/properties.rb +2 -0
- data/lib/prawn/svg/renderer.rb +236 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +2 -0
- data/prawn-svg.gemspec +18 -16
- data/spec/prawn/svg/color_spec.rb +29 -16
- data/spec/prawn/svg/document_spec.rb +17 -0
- data/spec/prawn/svg/elements/marker_spec.rb +1 -1
- data/spec/prawn/svg/elements/root_spec.rb +28 -0
- data/spec/prawn/svg/elements/text_spec.rb +27 -0
- data/spec/prawn/svg/interface_spec.rb +10 -2
- data/spec/sample_images/image_svg_embed.svg +6 -0
- data/spec/sample_svg/cmyk.svg +5 -0
- data/spec/sample_svg/gradients-cmyk.svg +40 -0
- data/spec/sample_svg/image_svg.svg +9 -0
- data/spec/sample_svg/ordering.svg +12 -0
- data/spec/sample_svg/symbol.svg +12 -0
- data/spec/spec_helper.rb +29 -5
- metadata +67 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa5a995fd0ec6d8426dcc87603695e91ff743e1fddc559da6cc7fcc08ab5eb88
|
4
|
+
data.tar.gz: d76a03c53c2d7dab41db812e3309f59f32baefb730970015a8bb35d6fb791653
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a686e69f4759243fc9b672a4bad663f17bf3f4f1a7016e560d816859d3d49464dfbb6416d877cf35f5362cd316111661e96817b645f707cc19999bae7e98f17
|
7
|
+
data.tar.gz: 57fbf5408c56228a286ca10221759440c4fc1326c0855acc4c5a74a30652ca59af277d170cf5bcb7f40b24bfe511e6ff82fd24e81da301e15595dc7f9d283e7a
|
data/.github/workflows/test.yml
CHANGED
@@ -1,14 +1,31 @@
|
|
1
1
|
name: test
|
2
2
|
on: [push, pull_request]
|
3
3
|
jobs:
|
4
|
-
|
4
|
+
rake_new:
|
5
5
|
runs-on: ubuntu-latest
|
6
6
|
strategy:
|
7
7
|
fail-fast: false
|
8
8
|
matrix:
|
9
|
-
ruby: [2.
|
9
|
+
ruby: [2.6, 2.7, '3.0', 3.1, 3.2, 3.3]
|
10
10
|
steps:
|
11
|
-
- uses: actions/checkout@
|
11
|
+
- uses: actions/checkout@v4
|
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
|
19
|
+
env:
|
20
|
+
RUBYOPT: "--enable-frozen-string-literal"
|
21
|
+
rake_old:
|
22
|
+
runs-on: ubuntu-latest
|
23
|
+
strategy:
|
24
|
+
fail-fast: false
|
25
|
+
matrix:
|
26
|
+
ruby: [2.5]
|
27
|
+
steps:
|
28
|
+
- uses: actions/checkout@v4
|
12
29
|
- name: Set up Ruby
|
13
30
|
uses: ruby/setup-ruby@v1
|
14
31
|
with:
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -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.5.0.
|
14
14
|
|
15
15
|
## Using prawn-svg
|
16
16
|
|
@@ -33,7 +33,8 @@ Option | Data type | Description
|
|
33
33
|
:enable_web_requests | boolean | If true, prawn-svg will make http and https requests to fetch images. Defaults to true.
|
34
34
|
:enable_file_requests_with_root | string | If not nil, prawn-svg will serve `file:` URLs from your local disk if the file is located under the specified directory. It is very dangerous to specify the root path ("/") if you're not fully in control of your input SVG. Defaults to `nil` (off).
|
35
35
|
:cache_images | boolean | If true, prawn-svg will cache the result of all URL requests. Defaults to false.
|
36
|
-
:fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to
|
36
|
+
:fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to `nil`, prawn-svg will ignore a request for an unknown font and log a warning.
|
37
|
+
:color_mode | :rgb, :cmyk | Output color mode. Defaults to :rgb.
|
37
38
|
|
38
39
|
## Examples
|
39
40
|
|
@@ -53,51 +54,57 @@ Option | Data type | Description
|
|
53
54
|
|
54
55
|
prawn-svg supports most but not all of the full SVG 1.1 specification. It currently supports:
|
55
56
|
|
56
|
-
-
|
57
|
+
- `<line>`, `<polyline>`, `<polygon>`, `<circle>` and `<ellipse>`
|
57
58
|
|
58
|
-
-
|
59
|
+
- `<rect>`. Rounded rects are supported, but only one radius is applied to all corners.
|
59
60
|
|
60
|
-
-
|
61
|
+
- `<path>` supports all commands defined in SVG 1.1, although the
|
61
62
|
implementation of elliptical arc is a bit rough at the moment.
|
62
63
|
|
63
|
-
- `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`,
|
64
|
-
`text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`,
|
64
|
+
- `<text>`, `<tspan>` and `<tref>` with attributes `x`, `y`, `dx`, `dy`, `rotate`, `textLength`, `lengthAdjust`,
|
65
|
+
and with extra properties `text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`,
|
66
|
+
`font-weight`, `font-style`, `letter-spacing`, `dominant-baseline` (middle only)
|
65
67
|
|
66
|
-
-
|
68
|
+
- `<svg>`, `<g>` and `<symbol>`
|
67
69
|
|
68
|
-
-
|
70
|
+
- `<use>`
|
69
71
|
|
70
|
-
-
|
72
|
+
- `<style>` (see CSS section below)
|
71
73
|
|
72
|
-
-
|
73
|
-
(`file:` is disabled by default for
|
74
|
+
- `<image>` referencing a JPEG, PNG, or SVG image, with `http:`, `https:`, `data:image/jpeg;base64`,
|
75
|
+
`data:image/png;base64`, `data:image/svg+xml;base64` and `file:` schemes (`file:` is disabled by default for
|
76
|
+
security reasons, see Options section above)
|
74
77
|
|
75
|
-
-
|
78
|
+
- `<clipPath>`
|
76
79
|
|
77
80
|
- `<marker>`
|
78
81
|
|
79
|
-
- `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and
|
82
|
+
- `<linearGradient>` and `<radialGradient>` are implemented on Prawn 2.2.0+ with attributes `gradientUnits` and
|
83
|
+
`gradientTransform` (`spreadMethod` and `stop-opacity` are unimplemented.)
|
80
84
|
|
81
85
|
- `<switch>` and `<foreignObject>`, although prawn-svg cannot handle any data that is not SVG so `<foreignObject>`
|
82
86
|
tags are always ignored.
|
83
87
|
|
84
|
-
- properties: `clip-path`, `color`, `display`, `fill`, `fill-opacity`, `fill-rule`, `opacity`, `overflow`,
|
88
|
+
- properties: `clip-path`, `color`, `display`, `fill`, `fill-opacity`, `fill-rule`, `opacity`, `overflow`,
|
89
|
+
`stroke`, `stroke-dasharray`, `stroke-linecap`, `stroke-linejoin`, `stroke-opacity`, `stroke-width`
|
85
90
|
|
86
91
|
- properties on lines, polylines, polygons and paths: `marker-end`, `marker-mid`, `marker-start`
|
87
92
|
|
88
93
|
- attributes on all elements: `class`, `id`, `style`, `transform`, `xml:space`
|
89
94
|
|
90
|
-
- the
|
95
|
+
- the `viewBox` attribute on `<svg>` and `<marker>` elements
|
91
96
|
|
92
|
-
- the
|
97
|
+
- the `preserveAspectRatio` attribute on `<svg>`, `<image>` and `<marker>` elements
|
93
98
|
|
94
99
|
- transform methods: `translate`, `translateX`, `translateY`, `rotate`, `scale`, `skewX`, `skewY`, `matrix`
|
95
100
|
|
96
|
-
- colors: HTML standard names,
|
101
|
+
- colors: HTML standard names, `#xxx`, `#xxxxxx`, `rgb(1, 2, 3)`, `rgb(1%, 2%, 3%)`, and also the non-standard
|
102
|
+
`device-cmyk(1, 2, 3, 4)` for CMYK colors
|
97
103
|
|
98
|
-
- measurements specified in
|
104
|
+
- measurements specified in `pt`, `cm`, `dm`, `ft`, `in`, `m`, `mm`, `yd`, `pc`, `%`
|
99
105
|
|
100
|
-
- fonts: generic CSS fonts, built-in PDF fonts, and any TTF fonts in your fonts path, specified in any of the
|
106
|
+
- fonts: generic CSS fonts, built-in PDF fonts, and any TTF fonts in your fonts path, specified in any of the
|
107
|
+
measurements above plus `em` or `rem`
|
101
108
|
|
102
109
|
## CSS
|
103
110
|
|
@@ -116,7 +123,7 @@ implemented, but `!important` is not.
|
|
116
123
|
|
117
124
|
## Not supported
|
118
125
|
|
119
|
-
prawn-svg does not support hyperlinks, patterns or filters.
|
126
|
+
prawn-svg does not support hyperlinks, patterns, masks or filters.
|
120
127
|
|
121
128
|
It does not support text in the clip area, but you can clip shapes and text by any shape.
|
122
129
|
|
@@ -124,8 +131,9 @@ It does not support text in the clip area, but you can clip shapes and text by a
|
|
124
131
|
|
125
132
|
### Fonts
|
126
133
|
|
127
|
-
By default, prawn-svg has a fonts path of
|
128
|
-
|
134
|
+
By default, prawn-svg has a fonts path of `["/Library/Fonts", "/System/Library/Fonts",
|
135
|
+
"#{ENV["HOME"]}/Library/Fonts", "/usr/share/fonts/truetype"]` to catch MacOS and Debian Linux users. You can add
|
136
|
+
to the font path:
|
129
137
|
|
130
138
|
```ruby
|
131
139
|
Prawn::SVG::FontRegistry.font_path << "/my/font/directory"
|
@@ -137,4 +145,4 @@ In your Gemfile, put `gem 'prawn-svg'` before `gem 'prawn-rails'` so that prawn-
|
|
137
145
|
|
138
146
|
## Licence
|
139
147
|
|
140
|
-
MIT licence. Copyright
|
148
|
+
MIT licence. Copyright Mog Nesbitt.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Prawn::SVG::Attributes::Stroke
|
2
2
|
CAP_STYLE_TRANSLATIONS = {"butt" => :butt, "round" => :round, "square" => :projecting_square}
|
3
|
+
JOIN_STYLE_TRANSLATIONS = {"miter" => :miter, "round" => :round, "bevel" => :bevel}
|
3
4
|
|
4
5
|
def parse_stroke_attributes_and_call
|
5
6
|
if width_string = properties.stroke_width
|
@@ -11,6 +12,10 @@ module Prawn::SVG::Attributes::Stroke
|
|
11
12
|
if (linecap = properties.stroke_linecap) && linecap != 'inherit'
|
12
13
|
add_call('cap_style', CAP_STYLE_TRANSLATIONS.fetch(linecap, :butt))
|
13
14
|
end
|
15
|
+
|
16
|
+
if (linejoin = properties.stroke_linejoin) && linejoin != 'inherit'
|
17
|
+
add_call('join_style', JOIN_STYLE_TRANSLATIONS.fetch(linejoin, :miter))
|
18
|
+
end
|
14
19
|
|
15
20
|
if dasharray = properties.stroke_dasharray
|
16
21
|
case dasharray
|
data/lib/prawn/svg/color.rb
CHANGED
@@ -1,17 +1,9 @@
|
|
1
1
|
class Prawn::SVG::Color
|
2
|
-
|
3
|
-
|
2
|
+
RGB = Struct.new(:value)
|
3
|
+
CMYK = Struct.new(:value)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
def ==(other)
|
10
|
-
value == other.value
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
DEFAULT_COLOR = Hex.new("000000")
|
5
|
+
RGB_DEFAULT_COLOR = RGB.new("000000")
|
6
|
+
CMYK_DEFAULT_COLOR = CMYK.new([0, 0, 0, 100])
|
15
7
|
|
16
8
|
HTML_COLORS = {
|
17
9
|
'aliceblue' => 'f0f8ff',
|
@@ -163,24 +155,25 @@ class Prawn::SVG::Color
|
|
163
155
|
'yellowgreen' => '9acd32'
|
164
156
|
}.freeze
|
165
157
|
|
166
|
-
|
167
|
-
RGB_REGEXP = /\Argb\(#{
|
158
|
+
VALUE_REGEXP = "\s*(-?[0-9.]+%?)\s*"
|
159
|
+
RGB_REGEXP = /\Argb\(#{VALUE_REGEXP},#{VALUE_REGEXP},#{VALUE_REGEXP}\)\z/i
|
160
|
+
CMYK_REGEXP = /\Adevice-cmyk\(#{VALUE_REGEXP},#{VALUE_REGEXP},#{VALUE_REGEXP},#{VALUE_REGEXP}\)\z/i
|
168
161
|
URL_REGEXP = /\Aurl\(([^)]*)\)\z/i
|
169
162
|
|
170
|
-
def self.parse(color_string, gradients = nil)
|
163
|
+
def self.parse(color_string, gradients = nil, color_mode = :rgb)
|
171
164
|
url_specified = false
|
172
165
|
|
173
166
|
components = color_string.to_s.strip.scan(/([^(\s]+(\([^)]*\))?)/)
|
174
167
|
|
175
168
|
result = components.map do |color, *_|
|
176
169
|
if m = color.match(/\A#([0-9a-f])([0-9a-f])([0-9a-f])\z/i)
|
177
|
-
|
170
|
+
RGB.new("#{m[1] * 2}#{m[2] * 2}#{m[3] * 2}")
|
178
171
|
|
179
172
|
elsif color.match(/\A#[0-9a-f]{6}\z/i)
|
180
|
-
|
173
|
+
RGB.new(color[1..6])
|
181
174
|
|
182
175
|
elsif hex = HTML_COLORS[color.downcase]
|
183
|
-
|
176
|
+
hex_color(hex, color_mode)
|
184
177
|
|
185
178
|
elsif m = color.match(RGB_REGEXP)
|
186
179
|
hex = (1..3).collect do |n|
|
@@ -189,7 +182,16 @@ class Prawn::SVG::Color
|
|
189
182
|
"%02x" % clamp(value.round, 0, 255)
|
190
183
|
end.join
|
191
184
|
|
192
|
-
|
185
|
+
RGB.new(hex)
|
186
|
+
|
187
|
+
elsif m = color.match(CMYK_REGEXP)
|
188
|
+
cmyk = (1..4).collect do |n|
|
189
|
+
value = m[n].to_f
|
190
|
+
value *= 100 unless m[n][-1..-1] == '%'
|
191
|
+
clamp(value, 0, 100)
|
192
|
+
end
|
193
|
+
|
194
|
+
CMYK.new(cmyk)
|
193
195
|
|
194
196
|
elsif matches = color.match(URL_REGEXP)
|
195
197
|
url_specified = true
|
@@ -203,18 +205,40 @@ class Prawn::SVG::Color
|
|
203
205
|
# Generally, we default to black if the colour was unparseable.
|
204
206
|
# http://www.w3.org/TR/SVG/painting.html section 11.2 says if a URL was
|
205
207
|
# supplied without a fallback, that's an error.
|
206
|
-
result <<
|
208
|
+
result << default_color(color_mode) unless url_specified
|
207
209
|
|
208
210
|
result.compact
|
209
211
|
end
|
210
212
|
|
211
|
-
def self.
|
212
|
-
result = parse(color).detect {|result| result.is_a?(
|
213
|
+
def self.css_color_to_prawn_color(color)
|
214
|
+
result = parse(color).detect {|result| result.is_a?(RGB) || result.is_a?(CMYK)}
|
213
215
|
result.value if result
|
214
216
|
end
|
215
217
|
|
216
218
|
protected
|
219
|
+
|
217
220
|
def self.clamp(value, min_value, max_value)
|
218
221
|
[[value, min_value].max, max_value].min
|
219
222
|
end
|
223
|
+
|
224
|
+
def self.default_color(color_mode)
|
225
|
+
color_mode == :cmyk ? CMYK_DEFAULT_COLOR : RGB_DEFAULT_COLOR
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.hex_color(hex, color_mode)
|
229
|
+
if color_mode == :cmyk
|
230
|
+
r, g, b = [hex[0..1], hex[2..3], hex[4..5]].map { |h| h.to_i(16) / 255.0 }
|
231
|
+
k = 1 - [r, g, b].max
|
232
|
+
if k == 1
|
233
|
+
CMYK.new([0, 0, 0, 100])
|
234
|
+
else
|
235
|
+
c = (1 - r - k) / (1 - k)
|
236
|
+
m = (1 - g - k) / (1 - k)
|
237
|
+
y = (1 - b - k) / (1 - k)
|
238
|
+
CMYK.new([c, m, y, k].map { |v| (v * 100).round })
|
239
|
+
end
|
240
|
+
else
|
241
|
+
RGB.new(hex)
|
242
|
+
end
|
243
|
+
end
|
220
244
|
end
|
@@ -10,7 +10,7 @@ module Prawn::SVG::CSS
|
|
10
10
|
case token
|
11
11
|
when Modifier
|
12
12
|
part = token.type
|
13
|
-
result.last[part] ||= part == :name ? "" : []
|
13
|
+
result.last[part] ||= part == :name ? +"" : []
|
14
14
|
when Identifier
|
15
15
|
return unless part
|
16
16
|
result.last[part] << token.name
|
@@ -48,7 +48,7 @@ module Prawn::SVG::CSS
|
|
48
48
|
case attribute
|
49
49
|
when :pre_key
|
50
50
|
if VALID_CSS_IDENTIFIER_CHAR.match(char)
|
51
|
-
result.last.key = char
|
51
|
+
result.last.key = String.new(char)
|
52
52
|
attribute = :key
|
53
53
|
elsif char != " " && char != "\t"
|
54
54
|
return
|
@@ -60,7 +60,7 @@ module Prawn::SVG::CSS
|
|
60
60
|
elsif char == "]"
|
61
61
|
attribute = nil
|
62
62
|
elsif "=*~^|$".include?(char)
|
63
|
-
result.last.operator = char
|
63
|
+
result.last.operator = String.new(char)
|
64
64
|
attribute = :operator
|
65
65
|
elsif char == " " || char == "\t"
|
66
66
|
attribute = :pre_operator
|
@@ -70,7 +70,7 @@ module Prawn::SVG::CSS
|
|
70
70
|
|
71
71
|
when :pre_operator
|
72
72
|
if "=*~^|$".include?(char)
|
73
|
-
result.last.operator = char
|
73
|
+
result.last.operator = String.new(char)
|
74
74
|
attribute = :operator
|
75
75
|
elsif char != " " && char != "\t"
|
76
76
|
return
|
@@ -82,25 +82,25 @@ module Prawn::SVG::CSS
|
|
82
82
|
elsif char == " " || char == "\t"
|
83
83
|
attribute = :pre_value
|
84
84
|
elsif char == '"' || char == "'"
|
85
|
-
result.last.value = ''
|
86
|
-
attribute = char
|
85
|
+
result.last.value = +''
|
86
|
+
attribute = String.new(char)
|
87
87
|
else
|
88
|
-
result.last.value = char
|
88
|
+
result.last.value = String.new(char)
|
89
89
|
attribute = :value
|
90
90
|
end
|
91
91
|
|
92
92
|
when :pre_value
|
93
93
|
if char == '"' || char == "'"
|
94
|
-
result.last.value = ''
|
95
|
-
attribute = char
|
94
|
+
result.last.value = +''
|
95
|
+
attribute = String.new(char)
|
96
96
|
elsif char != " " && char != "\t"
|
97
|
-
result.last.value = char
|
97
|
+
result.last.value = String.new(char)
|
98
98
|
attribute = :value
|
99
99
|
end
|
100
100
|
|
101
101
|
when :value
|
102
102
|
if char == "]"
|
103
|
-
result.last.value = result.last.value.rstrip
|
103
|
+
result.last.value = String.new(result.last.value.rstrip)
|
104
104
|
attribute = nil
|
105
105
|
else
|
106
106
|
result.last.value << char
|
@@ -69,14 +69,14 @@ module Prawn::SVG::CSS
|
|
69
69
|
|
70
70
|
result = case element[:combinator]
|
71
71
|
when :child
|
72
|
-
"/"
|
72
|
+
+"/"
|
73
73
|
when :adjacent
|
74
74
|
pseudo_classes << 'first-child'
|
75
|
-
"/following-sibling::"
|
75
|
+
+"/following-sibling::"
|
76
76
|
when :siblings
|
77
|
-
"/following-sibling::"
|
77
|
+
+"/following-sibling::"
|
78
78
|
else
|
79
|
-
"//"
|
79
|
+
+"//"
|
80
80
|
end
|
81
81
|
|
82
82
|
positions = []
|
data/lib/prawn/svg/document.rb
CHANGED
@@ -13,9 +13,10 @@ class Prawn::SVG::Document
|
|
13
13
|
:font_registry,
|
14
14
|
:url_loader,
|
15
15
|
:elements_by_id, :gradients,
|
16
|
-
:element_styles
|
16
|
+
:element_styles,
|
17
|
+
:color_mode
|
17
18
|
|
18
|
-
def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new)
|
19
|
+
def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new, attribute_overrides: {})
|
19
20
|
@root = REXML::Document.new(data).root
|
20
21
|
|
21
22
|
if @root.nil?
|
@@ -29,9 +30,10 @@ class Prawn::SVG::Document
|
|
29
30
|
@warnings = []
|
30
31
|
@options = options
|
31
32
|
@elements_by_id = {}
|
32
|
-
@gradients =
|
33
|
+
@gradients = Prawn::SVG::Gradients.new(self)
|
33
34
|
@fallback_font_name = options.fetch(:fallback_font_name, DEFAULT_FALLBACK_FONT_NAME)
|
34
35
|
@font_registry = font_registry
|
36
|
+
@color_mode = load_color_mode
|
35
37
|
|
36
38
|
@url_loader = Prawn::SVG::UrlLoader.new(
|
37
39
|
enable_cache: options[:cache_images],
|
@@ -39,7 +41,10 @@ class Prawn::SVG::Document
|
|
39
41
|
enable_file_with_root: options[:enable_file_requests_with_root]
|
40
42
|
)
|
41
43
|
|
42
|
-
|
44
|
+
attributes = @root.attributes.dup
|
45
|
+
attribute_overrides.each { |key, value| attributes.add(REXML::Attribute.new(key, value)) }
|
46
|
+
|
47
|
+
@sizing = Prawn::SVG::Calculators::DocumentSizing.new(bounds, attributes)
|
43
48
|
calculate_sizing(requested_width: options[:width], requested_height: options[:height])
|
44
49
|
|
45
50
|
@element_styles = Prawn::SVG::CSS::Stylesheets.new(css_parser, root).load
|
@@ -52,4 +57,15 @@ class Prawn::SVG::Document
|
|
52
57
|
sizing.requested_height = requested_height
|
53
58
|
sizing.calculate
|
54
59
|
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def load_color_mode
|
64
|
+
case @options[:color_mode]
|
65
|
+
when nil, :rgb then :rgb
|
66
|
+
when :cmyk then :cmyk
|
67
|
+
else
|
68
|
+
raise ArgumentError, ":color_mode must be set to :rgb (default) or :cmyk"
|
69
|
+
end
|
70
|
+
end
|
55
71
|
end
|
@@ -189,11 +189,11 @@ class Prawn::SVG::Elements::Base
|
|
189
189
|
color = computed_properties.color
|
190
190
|
end
|
191
191
|
|
192
|
-
results = Prawn::SVG::Color.parse(color, document.gradients)
|
192
|
+
results = Prawn::SVG::Color.parse(color, document.gradients, document.color_mode)
|
193
193
|
|
194
194
|
success = results.detect do |result|
|
195
195
|
case result
|
196
|
-
when Prawn::SVG::Color::
|
196
|
+
when Prawn::SVG::Color::RGB, Prawn::SVG::Color::CMYK
|
197
197
|
add_call "#{type}_color", result.value
|
198
198
|
true
|
199
199
|
when Prawn::SVG::Elements::Gradient
|
@@ -237,16 +237,15 @@ class Prawn::SVG::Elements::Base
|
|
237
237
|
|
238
238
|
def parse_css_declarations(declarations)
|
239
239
|
# copied from css_parser
|
240
|
-
declarations
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
240
|
+
declarations
|
241
|
+
.gsub(/(^[\s]*)|([\s]*$)/, '')
|
242
|
+
.split(/[\;$]+/m)
|
243
|
+
.each_with_object({}) do |decs, output|
|
244
|
+
if matches = decs.match(/\s*(.[^:]*)\s*\:\s*(.[^;]*)\s*(;|\Z)/i)
|
245
|
+
property, value, _ = matches.captures
|
246
|
+
output[property.downcase] = value
|
247
|
+
end
|
247
248
|
end
|
248
|
-
end
|
249
|
-
output
|
250
249
|
end
|
251
250
|
|
252
251
|
def require_attributes(*names)
|
@@ -147,8 +147,8 @@ class Prawn::SVG::Elements::Gradient < Prawn::SVG::Elements::Base
|
|
147
147
|
offset = result.last.first
|
148
148
|
end
|
149
149
|
|
150
|
-
if
|
151
|
-
result << [offset,
|
150
|
+
if color = Prawn::SVG::Color.css_color_to_prawn_color(child.properties.stop_color)
|
151
|
+
result << [offset, color]
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
@@ -3,29 +3,32 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
|
3
3
|
def initialize(data)
|
4
4
|
@data = data
|
5
5
|
end
|
6
|
+
|
6
7
|
def read
|
7
8
|
@data
|
8
9
|
end
|
9
|
-
|
10
|
-
end
|
10
|
+
|
11
|
+
def rewind; end
|
11
12
|
end
|
12
13
|
|
14
|
+
ImageData = Struct.new(:dimensions, :document)
|
15
|
+
|
13
16
|
def parse
|
14
17
|
require_attributes 'width', 'height'
|
15
18
|
|
16
|
-
raise SkipElementQuietly if state.computed_properties.display ==
|
19
|
+
raise SkipElementQuietly if state.computed_properties.display == 'none'
|
17
20
|
|
18
21
|
@url = href_attribute
|
19
|
-
if @url.nil?
|
20
|
-
raise SkipElementError, "image tag must have an href or xlink:href"
|
21
|
-
end
|
22
|
+
raise SkipElementError, 'image tag must have an href or xlink:href' if @url.nil?
|
22
23
|
|
23
24
|
x = x(attributes['x'] || 0)
|
24
25
|
y = y(attributes['y'] || 0)
|
25
26
|
width = x_pixels(attributes['width'])
|
26
27
|
height = y_pixels(attributes['height'])
|
28
|
+
preserveAspectRatio = attributes['preserveAspectRatio']
|
27
29
|
|
28
30
|
raise SkipElementQuietly if width.zero? || height.zero?
|
31
|
+
|
29
32
|
require_positive_value width, height
|
30
33
|
|
31
34
|
@image = begin
|
@@ -34,7 +37,9 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
|
34
37
|
raise SkipElementError, "Error retrieving URL #{@url}: #{e.message}"
|
35
38
|
end
|
36
39
|
|
37
|
-
@
|
40
|
+
@image_data = process_image(@image, width, height, preserveAspectRatio)
|
41
|
+
|
42
|
+
@aspect = Prawn::SVG::Calculators::AspectRatio.new(preserveAspectRatio, [width, height], @image_data.dimensions)
|
38
43
|
|
39
44
|
@clip_x = x
|
40
45
|
@clip_y = y
|
@@ -49,15 +54,21 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
|
49
54
|
|
50
55
|
def apply
|
51
56
|
if @aspect.slice?
|
52
|
-
add_call
|
53
|
-
add_call
|
54
|
-
add_call
|
57
|
+
add_call 'save'
|
58
|
+
add_call 'rectangle', [@clip_x, @clip_y], @clip_width, @clip_height
|
59
|
+
add_call 'clip'
|
55
60
|
end
|
56
61
|
|
57
|
-
|
62
|
+
if (document = @image_data.document)
|
63
|
+
add_call_and_enter 'translate', @x, @y
|
64
|
+
add_call 'svg:render_sub_document', document
|
65
|
+
else
|
66
|
+
options = { width: @width, height: @height, at: [@x, @y] }
|
67
|
+
|
68
|
+
add_call 'image', FakeIO.new(@image), options
|
69
|
+
end
|
58
70
|
|
59
|
-
add_call
|
60
|
-
add_call "restore" if @aspect.slice?
|
71
|
+
add_call 'restore' if @aspect.slice?
|
61
72
|
end
|
62
73
|
|
63
74
|
def bounding_box
|
@@ -66,16 +77,38 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
|
66
77
|
|
67
78
|
protected
|
68
79
|
|
69
|
-
def
|
70
|
-
handler =
|
71
|
-
|
72
|
-
|
73
|
-
|
80
|
+
def process_image(data, width, height, preserveAspectRatio)
|
81
|
+
if (handler = find_image_handler(data))
|
82
|
+
image = handler.new(data)
|
83
|
+
ImageData.new([image.width.to_f, image.height.to_f], nil)
|
84
|
+
|
85
|
+
elsif potentially_svg?(data)
|
86
|
+
document = Prawn::SVG::Document.new(
|
87
|
+
data, [width, height], { width: width, height: height },
|
88
|
+
attribute_overrides: { 'preserveAspectRatio' => preserveAspectRatio }
|
89
|
+
)
|
90
|
+
|
91
|
+
dimensions = [document.sizing.output_width, document.sizing.output_height]
|
92
|
+
ImageData.new(dimensions, document)
|
93
|
+
|
74
94
|
else
|
75
|
-
|
95
|
+
raise_invalid_image_type
|
76
96
|
end
|
97
|
+
rescue Prawn::SVG::Document::InvalidSVGData
|
98
|
+
raise_invalid_image_type
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_image_handler(data)
|
102
|
+
Prawn.image_handler.find(data)
|
103
|
+
rescue StandardError
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def potentially_svg?(data)
|
108
|
+
data.include?('<svg')
|
109
|
+
end
|
77
110
|
|
78
|
-
|
79
|
-
|
111
|
+
def raise_invalid_image_type
|
112
|
+
raise SkipElementError, 'Unsupported image type supplied to image tag'
|
80
113
|
end
|
81
114
|
end
|
@@ -9,7 +9,7 @@ class Prawn::SVG::Elements::Root < Prawn::SVG::Elements::Base
|
|
9
9
|
|
10
10
|
def apply
|
11
11
|
if [nil, 'inherit', 'none', 'currentColor'].include?(properties.fill)
|
12
|
-
add_call 'fill_color',
|
12
|
+
add_call 'fill_color', Prawn::SVG::Color.default_color(@document.color_mode).value
|
13
13
|
end
|
14
14
|
|
15
15
|
add_call 'transformation_matrix', @document.sizing.x_scale, 0, 0, @document.sizing.y_scale, 0, 0
|