prawn-svg 0.32.0 → 0.34.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 +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
|