prawn-svg 0.31.0 → 0.33.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 +35 -0
- data/README.md +8 -6
- 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 +15 -2
- data/lib/prawn/svg/elements/base.rb +15 -16
- data/lib/prawn/svg/elements/call_duplicator.rb +1 -1
- data/lib/prawn/svg/elements/gradient.rb +2 -2
- data/lib/prawn/svg/elements/image.rb +6 -7
- 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 +10 -5
- data/lib/prawn/svg/gradients.rb +48 -0
- data/lib/prawn/svg/interface.rb +23 -8
- data/lib/prawn/svg/properties.rb +2 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +1 -0
- data/prawn-svg.gemspec +17 -15
- data/spec/integration_spec.rb +24 -24
- data/spec/prawn/svg/color_spec.rb +29 -16
- data/spec/prawn/svg/css/stylesheets_spec.rb +1 -19
- data/spec/prawn/svg/document_spec.rb +17 -0
- data/spec/prawn/svg/elements/base_spec.rb +15 -15
- 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/polygon_spec.rb +9 -9
- data/spec/prawn/svg/elements/polyline_spec.rb +7 -7
- data/spec/prawn/svg/elements/root_spec.rb +28 -0
- data/spec/prawn/svg/elements/text_spec.rb +80 -53
- data/spec/prawn/svg/interface_spec.rb +10 -2
- data/spec/prawn/svg/pathable_spec.rb +4 -4
- data/spec/sample_svg/cmyk.svg +5 -0
- data/spec/sample_svg/gradients-cmyk.svg +40 -0
- data/spec/sample_svg/ordering.svg +12 -0
- data/spec/spec_helper.rb +2 -2
- metadata +53 -16
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9f71b82858f8ee91e1ccaf2903c2e2a0d49108b1532a81d15c2275362a0d136
|
4
|
+
data.tar.gz: f3336c6a727df24e6a5877e4d26cf030eabbb1c9af9852f95c35c5bfe5128995
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94f1a140e837ab37b088cc659bc8ef63a3e89778539236441f43e7b4885b9028cce1cb83c9e9636b00ff2ac740770ec8d853c123aa42b0764fb1264405269e76
|
7
|
+
data.tar.gz: 4d02f2e14875214bb5460ace33925f9ab17302b39f46e0a37e0e5521e63b0fdd2f23f6018c8e048bf2bce7d148548aa86b5b9967dca827faddccfa23e8e09b13
|
@@ -0,0 +1,35 @@
|
|
1
|
+
name: test
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
rake_new:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
fail-fast: false
|
8
|
+
matrix:
|
9
|
+
ruby: [2.6, 2.7, '3.0', 3.1, 3.2]
|
10
|
+
steps:
|
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
|
29
|
+
- name: Set up Ruby
|
30
|
+
uses: ruby/setup-ruby@v1
|
31
|
+
with:
|
32
|
+
bundler-cache: true
|
33
|
+
ruby-version: ${{ matrix.ruby }}
|
34
|
+
- name: Run tests
|
35
|
+
run: bundle exec rake
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# prawn-svg
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/prawn-svg)
|
4
|
-
|
4
|
+

|
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
|
|
@@ -34,6 +34,7 @@ Option | Data type | Description
|
|
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
36
|
:fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to <tt>nil</tt>, 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
|
|
@@ -61,7 +62,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
|
|
61
62
|
implementation of elliptical arc is a bit rough at the moment.
|
62
63
|
|
63
64
|
- `<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
|
+
`text-anchor`, `text-decoration` (underline only), `font-size`, `font-family`, `font-weight`, `font-style`, `letter-spacing`, `dominant-baseline` (middle only)
|
65
66
|
|
66
67
|
- <tt><svg></tt>, <tt><g></tt> and <tt><symbol></tt>
|
67
68
|
|
@@ -69,7 +70,7 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
|
|
69
70
|
|
70
71
|
- <tt><style></tt> (see CSS section below)
|
71
72
|
|
72
|
-
-
|
73
|
+
- `<image>` referencing a JPEG or PNG image, with `http:`, `https:`, `data:image/jpeg;base64`, `data:image/png;base64` and `file:` schemes
|
73
74
|
(`file:` is disabled by default for security reasons, see Options section above)
|
74
75
|
|
75
76
|
- <tt><clipPath></tt>
|
@@ -93,7 +94,8 @@ prawn-svg supports most but not all of the full SVG 1.1 specification. It curre
|
|
93
94
|
|
94
95
|
- transform methods: `translate`, `translateX`, `translateY`, `rotate`, `scale`, `skewX`, `skewY`, `matrix`
|
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
|
+
- colors: HTML standard names, <tt>#xxx</tt>, <tt>#xxxxxx</tt>, <tt>rgb(1, 2, 3)</tt>, <tt>rgb(1%, 2%, 3%)</tt>,
|
98
|
+
and also the non-standard `device-cmyk(1, 2, 3, 4)` for CMYK colors
|
97
99
|
|
98
100
|
- measurements specified in <tt>pt</tt>, <tt>cm</tt>, <tt>dm</tt>, <tt>ft</tt>, <tt>in</tt>, <tt>m</tt>, <tt>mm</tt>, <tt>yd</tt>, <tt>pc</tt>, <tt>%</tt>
|
99
101
|
|
@@ -116,7 +118,7 @@ implemented, but `!important` is not.
|
|
116
118
|
|
117
119
|
## Not supported
|
118
120
|
|
119
|
-
prawn-svg does not support hyperlinks, patterns or filters.
|
121
|
+
prawn-svg does not support hyperlinks, patterns, masks or filters.
|
120
122
|
|
121
123
|
It does not support text in the clip area, but you can clip shapes and text by any shape.
|
122
124
|
|
@@ -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,7 +13,8 @@ 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
19
|
def initialize(data, bounds, options, font_registry: nil, css_parser: CssParser::Parser.new)
|
19
20
|
@root = REXML::Document.new(data).root
|
@@ -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],
|
@@ -52,4 +54,15 @@ class Prawn::SVG::Document
|
|
52
54
|
sizing.requested_height = requested_height
|
53
55
|
sizing.calculate
|
54
56
|
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def load_color_mode
|
61
|
+
case @options[:color_mode]
|
62
|
+
when nil, :rgb then :rgb
|
63
|
+
when :cmyk then :cmyk
|
64
|
+
else
|
65
|
+
raise ArgumentError, ":color_mode must be set to :rgb (default) or :cmyk"
|
66
|
+
end
|
67
|
+
end
|
55
68
|
end
|
@@ -91,12 +91,12 @@ class Prawn::SVG::Elements::Base
|
|
91
91
|
parse_xml_space_attribute
|
92
92
|
end
|
93
93
|
|
94
|
-
def add_call(name, *arguments)
|
95
|
-
@calls << [name.to_s, arguments, []]
|
94
|
+
def add_call(name, *arguments, **kwarguments)
|
95
|
+
@calls << [name.to_s, arguments, kwarguments, []]
|
96
96
|
end
|
97
97
|
|
98
|
-
def add_call_and_enter(name, *arguments)
|
99
|
-
@calls << [name.to_s, arguments, []]
|
98
|
+
def add_call_and_enter(name, *arguments, **kwarguments)
|
99
|
+
@calls << [name.to_s, arguments, kwarguments, []]
|
100
100
|
@calls = @calls.last.last
|
101
101
|
end
|
102
102
|
|
@@ -168,7 +168,7 @@ class Prawn::SVG::Elements::Base
|
|
168
168
|
command = stroke ? 'fill_and_stroke' : 'fill'
|
169
169
|
|
170
170
|
if computed_properties.fill_rule == 'evenodd'
|
171
|
-
add_call_and_enter(command,
|
171
|
+
add_call_and_enter(command, fill_rule: :even_odd)
|
172
172
|
else
|
173
173
|
add_call_and_enter(command)
|
174
174
|
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)
|
@@ -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)
|
@@ -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
|
|
@@ -67,15 +67,14 @@ class Prawn::SVG::Elements::Image < Prawn::SVG::Elements::Base
|
|
67
67
|
protected
|
68
68
|
|
69
69
|
def image_dimensions(data)
|
70
|
-
handler =
|
71
|
-
|
72
|
-
elsif data[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
|
73
|
-
Prawn::Images::PNG
|
74
|
-
else
|
75
|
-
raise SkipElementError, "Unsupported image type supplied to image tag; Prawn only supports JPG and PNG"
|
70
|
+
unless (handler = find_image_handler(data))
|
71
|
+
raise SkipElementError, 'Unsupported image type supplied to image tag'
|
76
72
|
end
|
77
|
-
|
78
73
|
image = handler.new(data)
|
79
74
|
[image.width.to_f, image.height.to_f]
|
80
75
|
end
|
76
|
+
|
77
|
+
def find_image_handler(data)
|
78
|
+
Prawn.image_handler.find(data) rescue nil
|
79
|
+
end
|
81
80
|
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
|
@@ -41,14 +41,15 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
41
41
|
font = select_font
|
42
42
|
apply_font(font) if font
|
43
43
|
|
44
|
-
# text_anchor
|
45
|
-
# and so we handle
|
44
|
+
# text_anchor and dominant_baseline aren't Prawn options; we have to do some math to support them
|
45
|
+
# and so we handle them in Prawn::SVG::Interface#rewrite_call_arguments
|
46
46
|
opts = {
|
47
47
|
size: computed_properties.numerical_font_size,
|
48
48
|
style: font && font.subfamily,
|
49
49
|
text_anchor: computed_properties.text_anchor,
|
50
50
|
}
|
51
51
|
|
52
|
+
opts[:dominant_baseline] = computed_properties.dominant_baseline unless computed_properties.dominant_baseline == 'auto'
|
52
53
|
opts[:decoration] = computed_properties.text_decoration unless computed_properties.text_decoration == 'none'
|
53
54
|
|
54
55
|
if state.text.parent
|
@@ -146,10 +147,10 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
146
147
|
end
|
147
148
|
|
148
149
|
if remaining
|
149
|
-
add_call 'draw_text', text[0..0], opts.dup
|
150
|
+
add_call 'draw_text', text[0..0], **opts.dup
|
150
151
|
text = text[1..-1]
|
151
152
|
else
|
152
|
-
add_call 'draw_text', text, opts.dup
|
153
|
+
add_call 'draw_text', text, **opts.dup
|
153
154
|
|
154
155
|
# we can get to this path with rotations still pending
|
155
156
|
# solve this by shifting them out by the number of
|
@@ -171,7 +172,11 @@ class Prawn::SVG::Elements::TextComponent < Prawn::SVG::Elements::DepthFirstBase
|
|
171
172
|
|
172
173
|
def svg_text_children
|
173
174
|
text_children.select do |child|
|
174
|
-
child.node_type == :text ||
|
175
|
+
child.node_type == :text || (
|
176
|
+
child.node_type == :element && (
|
177
|
+
child.namespace == SVG_NAMESPACE || child.namespace == ''
|
178
|
+
)
|
179
|
+
)
|
175
180
|
end
|
176
181
|
end
|
177
182
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Prawn::SVG
|
2
|
+
class Gradients
|
3
|
+
def initialize(document)
|
4
|
+
@document = document
|
5
|
+
@gradients_by_id = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](id)
|
9
|
+
id &&= id.strip
|
10
|
+
return unless id && id != ''
|
11
|
+
|
12
|
+
if element = @gradients_by_id[id]
|
13
|
+
element
|
14
|
+
elsif raw_element = find_raw_gradient_element_by_id(id)
|
15
|
+
create_gradient_element(raw_element)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(id, gradient)
|
20
|
+
@gradients_by_id[id] = gradient
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def find_raw_gradient_element_by_id(id)
|
26
|
+
raw_element = find_raw_element_by_id(id)
|
27
|
+
raw_element if gradient_element?(raw_element)
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_gradient_element(raw_element)
|
31
|
+
Elements::Gradient.new(@document, raw_element, [], new_state).tap(&:process)
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_raw_element_by_id(id)
|
35
|
+
REXML::XPath.match(@document.root, %(//*[@id="#{id.gsub('"', '\"')}"])).first
|
36
|
+
end
|
37
|
+
|
38
|
+
def gradient_element?(raw_element)
|
39
|
+
Elements::TAG_CLASS_MAPPING[raw_element.name.to_sym] == Elements::Gradient
|
40
|
+
end
|
41
|
+
|
42
|
+
def new_state
|
43
|
+
State.new.tap do |state|
|
44
|
+
state.viewport_sizing = @document.sizing
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/prawn/svg/interface.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
module Prawn
|
6
6
|
module SVG
|
7
7
|
class Interface
|
8
|
-
VALID_OPTIONS = [:at, :position, :vposition, :width, :height, :cache_images, :enable_web_requests, :enable_file_requests_with_root, :fallback_font_name]
|
8
|
+
VALID_OPTIONS = [:at, :position, :vposition, :width, :height, :cache_images, :enable_web_requests, :enable_file_requests_with_root, :fallback_font_name, :color_mode]
|
9
9
|
|
10
10
|
attr_reader :data, :prawn, :document, :options
|
11
11
|
|
@@ -109,10 +109,10 @@ module Prawn
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def issue_prawn_command(prawn, calls)
|
112
|
-
calls.each do |call, arguments, children|
|
112
|
+
calls.each do |call, arguments, kwarguments, children|
|
113
113
|
skip = false
|
114
114
|
|
115
|
-
rewrite_call_arguments(prawn, call, arguments) do
|
115
|
+
rewrite_call_arguments(prawn, call, arguments, kwarguments) do
|
116
116
|
issue_prawn_command(prawn, children) if children.any?
|
117
117
|
skip = true
|
118
118
|
end
|
@@ -120,27 +120,42 @@ module Prawn
|
|
120
120
|
if skip
|
121
121
|
# the call has been overridden
|
122
122
|
elsif children.empty? && call != 'transparent' # some prawn calls complain if they aren't supplied a block
|
123
|
-
|
123
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
124
|
+
prawn.send(call, *arguments, **kwarguments)
|
125
|
+
else
|
126
|
+
prawn.send(call, *arguments)
|
127
|
+
end
|
124
128
|
else
|
125
|
-
|
129
|
+
if RUBY_VERSION >= '2.7' || !kwarguments.empty?
|
130
|
+
prawn.send(call, *arguments, **kwarguments, &proc_creator(prawn, children))
|
131
|
+
else
|
132
|
+
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
133
|
+
end
|
126
134
|
end
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
130
|
-
def rewrite_call_arguments(prawn, call, arguments)
|
138
|
+
def rewrite_call_arguments(prawn, call, arguments, kwarguments)
|
131
139
|
case call
|
132
140
|
when 'text_group'
|
133
141
|
@cursor = [0, document.sizing.output_height]
|
134
142
|
yield
|
135
143
|
|
136
144
|
when 'draw_text'
|
137
|
-
text, options = arguments
|
145
|
+
text, options = arguments.first, kwarguments
|
138
146
|
|
139
147
|
at = options.fetch(:at)
|
140
148
|
|
141
149
|
at[0] = @cursor[0] if at[0] == :relative
|
142
150
|
at[1] = @cursor[1] if at[1] == :relative
|
143
151
|
|
152
|
+
case options.delete(:dominant_baseline)
|
153
|
+
when 'middle'
|
154
|
+
height = prawn.font.height
|
155
|
+
at[1] -= height / 2.0
|
156
|
+
@cursor = [at[0], at[1]]
|
157
|
+
end
|
158
|
+
|
144
159
|
if offset = options.delete(:offset)
|
145
160
|
at[0] += offset[0]
|
146
161
|
at[1] -= offset[1]
|
@@ -208,7 +223,7 @@ module Prawn
|
|
208
223
|
# prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke. 'b' is 'h' (closepath) + 'B', and we
|
209
224
|
# never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
|
210
225
|
# and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
|
211
|
-
even_odd =
|
226
|
+
even_odd = kwarguments[:fill_rule] == :even_odd
|
212
227
|
content = even_odd ? 'B*' : 'B'
|
213
228
|
prawn.add_content content
|
214
229
|
|