prawn-svg 0.35.1 → 0.36.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/Gemfile.lock +3 -5
- data/README.md +4 -7
- data/lib/prawn/svg/attributes/opacity.rb +23 -8
- data/lib/prawn/svg/attributes/stroke.rb +7 -9
- data/lib/prawn/svg/attributes/transform.rb +1 -1
- data/lib/prawn/svg/calculators/pixels.rb +9 -22
- data/lib/prawn/svg/color.rb +58 -59
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/document.rb +5 -1
- data/lib/prawn/svg/elements/base.rb +22 -30
- data/lib/prawn/svg/elements/gradient.rb +99 -74
- data/lib/prawn/svg/elements/line.rb +1 -1
- data/lib/prawn/svg/elements/marker.rb +2 -0
- data/lib/prawn/svg/elements/root.rb +1 -1
- data/lib/prawn/svg/elements/text_component.rb +3 -3
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +313 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +208 -104
- data/lib/prawn/svg/renderer.rb +5 -0
- data/lib/prawn/svg/state.rb +5 -3
- data/lib/prawn/svg/transform_parser.rb +19 -13
- data/lib/prawn/svg/transform_utils.rb +37 -0
- data/lib/prawn/svg/version.rb +1 -1
- data/lib/prawn-svg.rb +7 -3
- data/prawn-svg.gemspec +4 -4
- data/spec/prawn/svg/attributes/opacity_spec.rb +27 -20
- data/spec/prawn/svg/attributes/transform_spec.rb +5 -2
- data/spec/prawn/svg/calculators/pixels_spec.rb +1 -2
- data/spec/prawn/svg/color_spec.rb +22 -52
- data/spec/prawn/svg/elements/base_spec.rb +9 -10
- data/spec/prawn/svg/elements/gradient_spec.rb +92 -36
- data/spec/prawn/svg/elements/marker_spec.rb +13 -15
- data/spec/prawn/svg/funciri_spec.rb +59 -0
- data/spec/prawn/svg/length_spec.rb +89 -0
- data/spec/prawn/svg/paint_spec.rb +96 -0
- data/spec/prawn/svg/pathable_spec.rb +3 -3
- data/spec/prawn/svg/pdfmatrix_spec.rb +60 -0
- data/spec/prawn/svg/percentage_spec.rb +60 -0
- data/spec/prawn/svg/properties_spec.rb +124 -107
- data/spec/prawn/svg/transform_parser_spec.rb +13 -13
- data/spec/sample_svg/gradient_stress_test.svg +115 -0
- metadata +17 -5
- data/lib/prawn/svg/extensions/additional_gradient_transforms.rb +0 -23
@@ -0,0 +1,67 @@
|
|
1
|
+
module Prawn::SVG
|
2
|
+
Paint = Struct.new(:color, :url) do
|
3
|
+
class << self
|
4
|
+
def none
|
5
|
+
new(:none, nil)
|
6
|
+
end
|
7
|
+
|
8
|
+
def black
|
9
|
+
new(Color.black, nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(value)
|
13
|
+
case CSS::ValuesParser.parse(value)
|
14
|
+
in [['url', [url]]]
|
15
|
+
# If there is no fallback color, and the URL is unresolvable, the spec says that the document is in error.
|
16
|
+
# Chrome appears to treat this the same as an explicit 'none', so we do the same.
|
17
|
+
new(:none, url)
|
18
|
+
in [['url', [url]], keyword_or_color]
|
19
|
+
parse_keyword_or_color(keyword_or_color, url)
|
20
|
+
in [['url', [url]], keyword_or_color, ['icc-color', _args]] # rubocop:disable Lint/DuplicateBranch
|
21
|
+
parse_keyword_or_color(keyword_or_color, url)
|
22
|
+
in [keyword_or_color, ['icc-color', _args]]
|
23
|
+
parse_keyword_or_color(keyword_or_color, nil)
|
24
|
+
in [keyword_or_color] # rubocop:disable Lint/DuplicateBranch
|
25
|
+
parse_keyword_or_color(keyword_or_color, nil)
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_keyword_or_color(value, url)
|
34
|
+
if value.is_a?(String)
|
35
|
+
keyword = value.downcase
|
36
|
+
return new(keyword.to_sym, url) if ['none', 'currentcolor'].include?(keyword)
|
37
|
+
end
|
38
|
+
|
39
|
+
color = Color.parse(value)
|
40
|
+
new(color, url) if color
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def none?
|
45
|
+
color == :none && (url.nil? || !!@unresolved_url)
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolve(gradients, current_color, color_mode)
|
49
|
+
if url
|
50
|
+
if url[0] == '#' && gradients && (gradient = gradients[url[1..]])
|
51
|
+
return gradient
|
52
|
+
else
|
53
|
+
@unresolved_url = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
case color
|
58
|
+
when :currentcolor
|
59
|
+
current_color
|
60
|
+
when :none
|
61
|
+
nil
|
62
|
+
else
|
63
|
+
color_mode == :cmyk ? color.to_cmyk : color
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Prawn::SVG::Percentage = Struct.new(:value)
|
2
|
+
|
3
|
+
class Prawn::SVG::Percentage
|
4
|
+
REGEXP = /\A([+-]?\d*(?:\.\d+)?)%\z/i.freeze
|
5
|
+
|
6
|
+
def self.parse(value, positive_only: false)
|
7
|
+
if (matches = value.match(REGEXP))
|
8
|
+
number = Float(matches[1], exception: false)
|
9
|
+
new(number) if number && (!positive_only || number >= 0)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_f
|
14
|
+
value
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_factor
|
18
|
+
value / 100.0
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_pixels(axis_length, _font_size)
|
22
|
+
value * axis_length / 100.0
|
23
|
+
end
|
24
|
+
end
|
data/lib/prawn/svg/properties.rb
CHANGED
@@ -1,127 +1,231 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
1
|
+
module Prawn::SVG
|
2
|
+
class Properties
|
3
|
+
Config = Struct.new(:default, :inheritable?, :valid_values, :attr, :ivar)
|
4
|
+
|
5
|
+
EM = 16
|
6
|
+
FONT_SIZES = {
|
7
|
+
'xx-small' => EM / 4,
|
8
|
+
'x-small' => EM / 4 * 2,
|
9
|
+
'small' => EM / 4 * 3,
|
10
|
+
'medium' => EM / 4 * 4,
|
11
|
+
'large' => EM / 4 * 5,
|
12
|
+
'x-large' => EM / 4 * 6,
|
13
|
+
'xx-large' => EM / 4 * 7
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
PROPERTIES = {
|
17
|
+
'clip-path' => Config.new('none', false, ['none', :funciri]),
|
18
|
+
'color' => Config.new(Color.black, true, [:color]),
|
19
|
+
'display' => Config.new('inline', false, %w[inline none]),
|
20
|
+
'dominant-baseline' => Config.new('auto', true, %w[auto middle]),
|
21
|
+
'fill' => Config.new(Paint.black, true, [:paint]),
|
22
|
+
'fill-opacity' => Config.new(1.0, true, [:number]),
|
23
|
+
'fill-rule' => Config.new('nonzero', true, %w[nonzero evenodd]),
|
24
|
+
'font-family' => Config.new('sans-serif', true, [:any]),
|
25
|
+
# Only the computed (numeric) value of font-size is inherited, not the value itself
|
26
|
+
'font-size' => Config.new(nil, false,
|
27
|
+
[:positive_length, :positive_percentage, 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'larger', 'smaller']),
|
28
|
+
'font-style' => Config.new('normal', true, %w[normal italic oblique]),
|
29
|
+
'font-variant' => Config.new('normal', true, %w[normal small-caps]),
|
30
|
+
'font-weight' => Config.new('normal', true, %w[normal bold 100 200 300 400 500 600 700 800 900]), # bolder/lighter not supported
|
31
|
+
'letter-spacing' => Config.new('normal', true, [:length, 'normal']),
|
32
|
+
'marker-end' => Config.new('none', true, [:funciri, 'none']),
|
33
|
+
'marker-mid' => Config.new('none', true, [:funciri, 'none']),
|
34
|
+
'marker-start' => Config.new('none', true, [:funciri, 'none']),
|
35
|
+
'opacity' => Config.new(1.0, false, [:number]),
|
36
|
+
'overflow' => Config.new('visible', false, %w[visible hidden scroll auto]),
|
37
|
+
'stop-color' => Config.new(Color.black, false, [:color_with_icc, 'currentcolor']),
|
38
|
+
'stop-opacity' => Config.new(1.0, false, [:number]),
|
39
|
+
'stroke' => Config.new(Paint.none, true, [:paint]),
|
40
|
+
'stroke-dasharray' => Config.new('none', true, [:dasharray, 'none']),
|
41
|
+
'stroke-linecap' => Config.new('butt', true, %w[butt round square]),
|
42
|
+
'stroke-linejoin' => Config.new('miter', true, %w[miter round bevel]),
|
43
|
+
'stroke-opacity' => Config.new(1.0, true, [:number]),
|
44
|
+
'stroke-width' => Config.new(1.0, true, [:positive_length, :positive_percentage]),
|
45
|
+
'text-anchor' => Config.new('start', true, %w[start middle end]),
|
46
|
+
'text-decoration' => Config.new('none', true, %w[none underline]),
|
47
|
+
'visibility' => Config.new('visible', true, %w[visible hidden collapse])
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
PROPERTIES.each do |name, value|
|
51
|
+
value.attr = name.gsub('-', '_')
|
52
|
+
value.ivar = "@#{value.attr}"
|
53
|
+
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
PROPERTY_CONFIGS = PROPERTIES.values
|
56
|
+
NAMES = PROPERTIES.keys
|
57
|
+
ATTR_NAMES = PROPERTIES.keys.map { |name| name.gsub('-', '_') }
|
54
58
|
|
55
|
-
|
59
|
+
attr_accessor(*ATTR_NAMES)
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
instance_variable_set(config.ivar, config.default)
|
61
|
+
def initialize
|
62
|
+
@numeric_font_size = EM
|
60
63
|
end
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
def load_default_stylesheet
|
66
|
+
PROPERTY_CONFIGS.each do |config|
|
67
|
+
instance_variable_set(config.ivar, config.default)
|
68
|
+
end
|
64
69
|
|
65
|
-
|
66
|
-
|
67
|
-
value = value.strip
|
68
|
-
keyword = value.downcase
|
69
|
-
keywords = config.keywords || ['inherit']
|
70
|
+
self
|
71
|
+
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
value = config.
|
73
|
+
def set(name, value)
|
74
|
+
name = name.to_s.downcase
|
75
|
+
if (config = PROPERTIES[name])
|
76
|
+
if (value = parse_value(config, value.strip))
|
77
|
+
instance_variable_set(config.ivar, value)
|
78
|
+
end
|
79
|
+
elsif name == 'font'
|
80
|
+
apply_font_shorthand(value)
|
75
81
|
end
|
82
|
+
end
|
76
83
|
|
77
|
-
|
84
|
+
def numeric_font_size
|
85
|
+
@numeric_font_size or raise 'numeric_font_size not set; this is only present in computed properties'
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_h
|
89
|
+
PROPERTIES.each.with_object({}) do |(name, config), result|
|
90
|
+
result[name] = instance_variable_get(config.ivar)
|
91
|
+
end
|
78
92
|
end
|
79
|
-
end
|
80
93
|
|
81
|
-
|
82
|
-
|
83
|
-
result[name] = instance_variable_get(config.ivar)
|
94
|
+
def load_hash(hash)
|
95
|
+
hash.each { |name, value| set(name, value) if value }
|
84
96
|
end
|
85
|
-
end
|
86
97
|
|
87
|
-
|
88
|
-
|
89
|
-
|
98
|
+
def compute_properties(other)
|
99
|
+
PROPERTY_CONFIGS.each do |config|
|
100
|
+
value = other.send(config.attr)
|
101
|
+
|
102
|
+
if value && value != 'inherit'
|
103
|
+
instance_variable_set(config.ivar, value)
|
90
104
|
|
91
|
-
|
92
|
-
|
93
|
-
|
105
|
+
elsif value.nil? && !config.inheritable?
|
106
|
+
instance_variable_set(config.ivar, config.default)
|
107
|
+
end
|
108
|
+
end
|
94
109
|
|
95
|
-
|
96
|
-
|
97
|
-
|
110
|
+
@numeric_font_size = calculate_numeric_font_size
|
111
|
+
nil
|
112
|
+
end
|
98
113
|
|
99
|
-
|
100
|
-
|
114
|
+
private
|
115
|
+
|
116
|
+
def calculate_numeric_font_size
|
117
|
+
case font_size
|
118
|
+
when Length
|
119
|
+
font_size.to_pixels(nil, numeric_font_size)
|
120
|
+
when Percentage
|
121
|
+
font_size.to_factor * numeric_font_size
|
122
|
+
when Numeric
|
123
|
+
font_size
|
124
|
+
when 'larger'
|
125
|
+
numeric_font_size + 4
|
126
|
+
when 'smaller'
|
127
|
+
numeric_font_size - 4
|
128
|
+
when nil, 'inherit'
|
129
|
+
numeric_font_size
|
130
|
+
when String
|
131
|
+
FONT_SIZES[font_size] or raise "Unknown font size keyword: #{font_size}"
|
132
|
+
else
|
133
|
+
raise "Unknown font size property value: #{font_size.inspect}"
|
101
134
|
end
|
102
135
|
end
|
103
|
-
end
|
104
136
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
137
|
+
def parse_value(config, value)
|
138
|
+
keyword = value.downcase
|
139
|
+
|
140
|
+
return 'inherit' if keyword == 'inherit'
|
141
|
+
|
142
|
+
config.valid_values.detect do |type|
|
143
|
+
result = parse_value_with_type(type, value, keyword)
|
144
|
+
break result if result
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_value_with_type(type, value, keyword)
|
149
|
+
case type
|
150
|
+
when String
|
151
|
+
keyword if type == keyword
|
152
|
+
when :color
|
153
|
+
values = Prawn::SVG::CSS::ValuesParser.parse(value)
|
154
|
+
Prawn::SVG::Color.parse(values[0]) if values.length == 1
|
155
|
+
when :color_with_icc
|
156
|
+
case Prawn::SVG::CSS::ValuesParser.parse(value)
|
157
|
+
in [other, ['icc-color', _args]]
|
158
|
+
Prawn::SVG::Color.parse(other)
|
159
|
+
in [other] # rubocop:disable Lint/DuplicateBranch
|
160
|
+
Prawn::SVG::Color.parse(other)
|
161
|
+
else
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
when :paint
|
165
|
+
Paint.parse(value)
|
166
|
+
when :funciri
|
167
|
+
FuncIRI.parse(value)
|
168
|
+
when :dasharray
|
169
|
+
value.split(Prawn::SVG::Elements::COMMA_WSP_REGEXP).map do |value|
|
170
|
+
Length.parse(value) || Percentage.parse(value) || break
|
171
|
+
end
|
172
|
+
when :number
|
173
|
+
Float(value, exception: false)
|
174
|
+
when :length
|
175
|
+
Length.parse(value)
|
176
|
+
when :percentage
|
177
|
+
Percentage.parse(value)
|
178
|
+
when :positive_length
|
179
|
+
Length.parse(value, positive_only: true)
|
180
|
+
when :positive_percentage
|
181
|
+
Percentage.parse(value, positive_only: true)
|
182
|
+
when :any
|
183
|
+
value
|
184
|
+
else
|
185
|
+
raise "Unknown valid value type: #{type}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
FONT_KEYWORD_MAPPING = ['font-style', 'font-variant', 'font-weight'].each.with_object({}) do |property, result|
|
190
|
+
PROPERTIES[property].valid_values.each do |value|
|
191
|
+
result[value] = property
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def apply_font_shorthand(value)
|
196
|
+
if value.strip.match(/\s/).nil?
|
197
|
+
case value.strip.downcase
|
198
|
+
when 'inherit'
|
199
|
+
load_hash('font-style' => 'inherit', 'font-variant' => 'inherit', 'font-weight' => 'inherit', 'font-size' => 'inherit', 'font-family' => 'inherit')
|
200
|
+
when 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar'
|
201
|
+
load_hash('font-style' => 'normal', 'font-variant' => 'normal', 'font-weight' => 'normal', 'font-size' => 'medium', 'font-family' => 'sans-serif')
|
202
|
+
end
|
203
|
+
|
204
|
+
return
|
205
|
+
end
|
206
|
+
|
207
|
+
properties = ['font-style', 'font-variant', 'font-weight'].each.with_object({}) do |property, result|
|
208
|
+
result[property] = PROPERTIES[property].default
|
209
|
+
end
|
210
|
+
|
211
|
+
values = CSS::FontParser.parse(value)
|
212
|
+
return if values.length < 2
|
213
|
+
|
214
|
+
properties['font-family'] = values.pop
|
215
|
+
font_size = values.pop.sub(%r{/.*}, '')
|
216
|
+
|
217
|
+
values.each do |keyword|
|
218
|
+
keyword = keyword.downcase
|
219
|
+
if (property = FONT_KEYWORD_MAPPING[keyword])
|
220
|
+
properties[property] = keyword
|
221
|
+
else
|
222
|
+
break
|
223
|
+
end
|
224
|
+
end or return
|
109
225
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
if value[-1] == '%'
|
114
|
-
numerical_font_size * (value.to_f / 100.0)
|
115
|
-
elsif value == 'larger'
|
116
|
-
numerical_font_size + 4
|
117
|
-
elsif value == 'smaller'
|
118
|
-
numerical_font_size - 4
|
119
|
-
elsif value.match(/(\d|\.)em\z/i)
|
120
|
-
numerical_font_size * value.to_f
|
121
|
-
elsif value.match(/(\d|\.)rem\z/i)
|
122
|
-
value.to_f * EM
|
123
|
-
else
|
124
|
-
FONT_SIZES[value] || value.to_f
|
226
|
+
set('font-size', font_size) or return
|
227
|
+
load_hash(properties)
|
228
|
+
value
|
125
229
|
end
|
126
230
|
end
|
127
231
|
end
|
data/lib/prawn/svg/renderer.rb
CHANGED
@@ -216,6 +216,11 @@ module Prawn
|
|
216
216
|
Renderer.new(prawn, sub_document, sub_options).draw
|
217
217
|
document.warnings.concat(sub_document.warnings)
|
218
218
|
yield
|
219
|
+
|
220
|
+
when 'svg:render_gradient'
|
221
|
+
type = arguments.first
|
222
|
+
GradientRenderer.new(prawn, type, **kwarguments).draw
|
223
|
+
yield
|
219
224
|
end
|
220
225
|
end
|
221
226
|
|
data/lib/prawn/svg/state.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
class Prawn::SVG::State
|
2
2
|
attr_accessor :text, :preserve_space,
|
3
|
-
:
|
3
|
+
:opacity, :last_fill_opacity, :last_stroke_opacity,
|
4
|
+
:stroke_width,
|
4
5
|
:computed_properties,
|
5
6
|
:viewport_sizing,
|
6
7
|
:inside_use, :inside_clip_path
|
7
8
|
|
8
9
|
def initialize
|
9
10
|
@stroke_width = 1
|
10
|
-
@
|
11
|
-
@
|
11
|
+
@opacity = 1
|
12
|
+
@last_fill_opacity = 1
|
13
|
+
@last_stroke_opacity = 1
|
12
14
|
@computed_properties = Prawn::SVG::Properties.new.load_default_stylesheet
|
13
15
|
end
|
14
16
|
|
@@ -1,32 +1,38 @@
|
|
1
1
|
module Prawn::SVG::TransformParser
|
2
|
-
|
2
|
+
include Prawn::SVG::PDFMatrix
|
3
|
+
|
4
|
+
def parse_transform_attribute(transform, space: :pdf)
|
3
5
|
matrix = Matrix.identity(3)
|
4
6
|
|
7
|
+
flip = space == :svg ? 1.0 : -1.0
|
8
|
+
|
5
9
|
parse_css_method_calls(transform).each do |name, arguments|
|
6
10
|
case name
|
7
11
|
when 'translate'
|
8
12
|
x, y = arguments
|
9
|
-
matrix *=
|
13
|
+
matrix *= translation_matrix(x_pixels(x.to_f), flip * y_pixels(y.to_f))
|
10
14
|
|
11
15
|
when 'translateX'
|
12
16
|
x = arguments.first
|
13
|
-
matrix *=
|
17
|
+
matrix *= translation_matrix(x_pixels(x.to_f), 0.0)
|
14
18
|
|
15
19
|
when 'translateY'
|
16
20
|
y = arguments.first
|
17
|
-
matrix *=
|
21
|
+
matrix *= translation_matrix(0.0, flip * y_pixels(y.to_f))
|
18
22
|
|
19
23
|
when 'rotate'
|
20
24
|
angle, x, y = arguments.collect(&:to_f)
|
21
25
|
angle = angle * Math::PI / 180.0
|
22
26
|
|
27
|
+
rotation = rotation_matrix(angle, space: space)
|
28
|
+
|
23
29
|
case arguments.length
|
24
30
|
when 1
|
25
|
-
matrix *=
|
31
|
+
matrix *= rotation
|
26
32
|
when 3
|
27
|
-
matrix *=
|
28
|
-
matrix *=
|
29
|
-
matrix *=
|
33
|
+
matrix *= translation_matrix(x_pixels(x.to_f), flip * y_pixels(y.to_f))
|
34
|
+
matrix *= rotation
|
35
|
+
matrix *= translation_matrix(-x_pixels(x.to_f), -flip * y_pixels(y.to_f))
|
30
36
|
else
|
31
37
|
warnings << "transform 'rotate' must have either one or three arguments"
|
32
38
|
end
|
@@ -34,20 +40,20 @@ module Prawn::SVG::TransformParser
|
|
34
40
|
when 'scale'
|
35
41
|
x_scale = arguments[0].to_f
|
36
42
|
y_scale = (arguments[1] || x_scale).to_f
|
37
|
-
matrix *=
|
43
|
+
matrix *= scale_matrix(x_scale, y_scale)
|
38
44
|
|
39
45
|
when 'skewX'
|
40
46
|
angle = arguments[0].to_f * Math::PI / 180.0
|
41
|
-
matrix *= Matrix[[1,
|
47
|
+
matrix *= Matrix[[1, flip * Math.tan(angle), 0], [0, 1, 0], [0, 0, 1]]
|
42
48
|
|
43
49
|
when 'skewY'
|
44
50
|
angle = arguments[0].to_f * Math::PI / 180.0
|
45
|
-
matrix *= Matrix[[1, 0, 0], [
|
51
|
+
matrix *= Matrix[[1, 0, 0], [flip * Math.tan(angle), 1, 0], [0, 0, 1]]
|
46
52
|
|
47
53
|
when 'matrix'
|
48
54
|
if arguments.length == 6
|
49
55
|
a, b, c, d, e, f = arguments.collect(&:to_f)
|
50
|
-
matrix *= Matrix[[a,
|
56
|
+
matrix *= Matrix[[a, flip * c, e], [flip * b, d, flip * f], [0, 0, 1]]
|
51
57
|
else
|
52
58
|
warnings << "transform 'matrix' must have six arguments"
|
53
59
|
end
|
@@ -57,7 +63,7 @@ module Prawn::SVG::TransformParser
|
|
57
63
|
end
|
58
64
|
end
|
59
65
|
|
60
|
-
matrix
|
66
|
+
matrix
|
61
67
|
end
|
62
68
|
|
63
69
|
private
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Prawn::SVG::PDFMatrix
|
2
|
+
def load_matrix(matrix)
|
3
|
+
if matrix.is_a?(Matrix) && matrix.row_count == 3 && matrix.column_count == 3
|
4
|
+
matrix
|
5
|
+
elsif matrix.is_a?(Array) && matrix.length == 6
|
6
|
+
Matrix[
|
7
|
+
[matrix[0], matrix[2], matrix[4]],
|
8
|
+
[matrix[1], matrix[3], matrix[5]],
|
9
|
+
[0.0, 0.0, 1.0]
|
10
|
+
]
|
11
|
+
else
|
12
|
+
raise ArgumentError, 'unexpected matrix format'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def matrix_for_pdf(matrix)
|
17
|
+
matrix.to_a[0..1].transpose.flatten
|
18
|
+
end
|
19
|
+
|
20
|
+
def translation_matrix(x = 0, y = 0)
|
21
|
+
Matrix[[1.0, 0.0, x.to_f], [0.0, 1.0, y.to_f], [0.0, 0.0, 1.0]]
|
22
|
+
end
|
23
|
+
|
24
|
+
def scale_matrix(x = 1, y = x)
|
25
|
+
Matrix[[x.to_f, 0.0, 0.0], [0.0, y.to_f, 0.0], [0.0, 0.0, 1.0]]
|
26
|
+
end
|
27
|
+
|
28
|
+
def rotation_matrix(angle, space: :pdf)
|
29
|
+
dir = space == :svg ? 1.0 : -1.0
|
30
|
+
|
31
|
+
Matrix[
|
32
|
+
[Math.cos(angle), -dir * Math.sin(angle), 0],
|
33
|
+
[dir * Math.sin(angle), Math.cos(angle), 0],
|
34
|
+
[0, 0, 1]
|
35
|
+
]
|
36
|
+
end
|
37
|
+
end
|
data/lib/prawn/svg/version.rb
CHANGED
data/lib/prawn-svg.rb
CHANGED
@@ -10,12 +10,17 @@ 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_utils'
|
13
14
|
require 'prawn/svg/transform_parser'
|
14
15
|
require 'prawn/svg/url_loader'
|
15
16
|
require 'prawn/svg/loaders/data'
|
16
17
|
require 'prawn/svg/loaders/file'
|
17
18
|
require 'prawn/svg/loaders/web'
|
18
19
|
require 'prawn/svg/color'
|
20
|
+
require 'prawn/svg/funciri'
|
21
|
+
require 'prawn/svg/length'
|
22
|
+
require 'prawn/svg/paint'
|
23
|
+
require 'prawn/svg/percentage'
|
19
24
|
require 'prawn/svg/attributes'
|
20
25
|
require 'prawn/svg/properties'
|
21
26
|
require 'prawn/svg/pathable'
|
@@ -23,6 +28,7 @@ require 'prawn/svg/elements'
|
|
23
28
|
require 'prawn/svg/extension'
|
24
29
|
require 'prawn/svg/renderer'
|
25
30
|
require 'prawn/svg/interface'
|
31
|
+
require 'prawn/svg/css/font_parser'
|
26
32
|
require 'prawn/svg/css/font_family_parser'
|
27
33
|
require 'prawn/svg/css/selector_parser'
|
28
34
|
require 'prawn/svg/css/stylesheets'
|
@@ -30,12 +36,10 @@ require 'prawn/svg/css/values_parser'
|
|
30
36
|
require 'prawn/svg/ttf'
|
31
37
|
require 'prawn/svg/font'
|
32
38
|
require 'prawn/svg/gradients'
|
39
|
+
require 'prawn/svg/gradient_renderer'
|
33
40
|
require 'prawn/svg/document'
|
34
41
|
require 'prawn/svg/state'
|
35
42
|
|
36
|
-
require 'prawn/svg/extensions/additional_gradient_transforms'
|
37
|
-
Prawn::Document.prepend Prawn::SVG::Extensions::AdditionalGradientTransforms
|
38
|
-
|
39
43
|
module Prawn
|
40
44
|
Svg = SVG # backwards compatibility
|
41
45
|
end
|
data/prawn-svg.gemspec
CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |gem|
|
|
19
19
|
|
20
20
|
gem.required_ruby_version = '>= 2.7.0'
|
21
21
|
|
22
|
-
gem.
|
23
|
-
gem.
|
24
|
-
gem.
|
25
|
-
gem.
|
22
|
+
gem.add_dependency 'css_parser', '~> 1.6'
|
23
|
+
gem.add_dependency 'matrix', '~> 0.4.2'
|
24
|
+
gem.add_dependency 'prawn', '>= 0.11.1', '< 3'
|
25
|
+
gem.add_dependency 'rexml', '>= 3.3.9', '< 4'
|
26
26
|
end
|