prawn-svg 0.9.1.6 → 0.9.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/prawn-svg.rb +5 -3
- data/lib/prawn/svg/extension.rb +24 -0
- data/lib/prawn/svg/interface.rb +87 -0
- data/lib/prawn/svg/parser.rb +368 -362
- data/lib/prawn/svg/parser/path.rb +184 -0
- metadata +6 -6
- data/lib/prawn/svg/path.rb +0 -180
- data/lib/prawn/svg/svg.rb +0 -83
- data/lib/prawn/svg_document.rb +0 -22
@@ -0,0 +1,184 @@
|
|
1
|
+
module Prawn
|
2
|
+
module Svg
|
3
|
+
class Parser::Path
|
4
|
+
# Raised if the SVG path cannot be parsed.
|
5
|
+
InvalidError = Class.new(StandardError)
|
6
|
+
|
7
|
+
#
|
8
|
+
# Parses an SVG path and returns a Prawn-compatible call tree.
|
9
|
+
#
|
10
|
+
def parse(data)
|
11
|
+
cmd = values = nil
|
12
|
+
value = ""
|
13
|
+
@subpath_initial_point = @last_point = nil
|
14
|
+
@previous_control_point = @previous_quadratic_control_point = nil
|
15
|
+
@calls = []
|
16
|
+
|
17
|
+
data.each_char do |c|
|
18
|
+
if c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'
|
19
|
+
values << value.to_f if value != ""
|
20
|
+
run_path_command(cmd, values) if cmd
|
21
|
+
cmd = c
|
22
|
+
values = []
|
23
|
+
value = ""
|
24
|
+
elsif c >= '0' && c <= '9' || c == '.' || c == "-"
|
25
|
+
unless cmd
|
26
|
+
raise InvalidError, "Numerical value specified before character command in SVG path data"
|
27
|
+
end
|
28
|
+
value << c
|
29
|
+
elsif c == ' ' || c == "\t" || c == "\r" || c == "\n" || c == ","
|
30
|
+
if value != ""
|
31
|
+
values << value.to_f
|
32
|
+
value = ""
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise InvalidError, "Invalid character '#{c}' in SVG path data"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
values << value.to_f if value != ""
|
40
|
+
run_path_command(cmd, values) if cmd
|
41
|
+
|
42
|
+
@calls
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
private
|
47
|
+
def run_path_command(command, values)
|
48
|
+
upcase_command = command.upcase
|
49
|
+
relative = command != upcase_command
|
50
|
+
|
51
|
+
case upcase_command
|
52
|
+
when 'M' # moveto
|
53
|
+
x = values.shift
|
54
|
+
y = values.shift
|
55
|
+
|
56
|
+
if relative && @last_point
|
57
|
+
x += @last_point.first
|
58
|
+
y += @last_point.last
|
59
|
+
end
|
60
|
+
|
61
|
+
@last_point = @subpath_initial_point = [x, y]
|
62
|
+
@calls << ["move_to", @last_point]
|
63
|
+
|
64
|
+
return run_path_command('L', values) if values.any?
|
65
|
+
|
66
|
+
when 'Z' # closepath
|
67
|
+
if @subpath_initial_point
|
68
|
+
@calls << ["line_to", @subpath_initial_point]
|
69
|
+
@last_point = @subpath_initial_point
|
70
|
+
end
|
71
|
+
|
72
|
+
when 'L' # lineto
|
73
|
+
while values.any?
|
74
|
+
x = values.shift
|
75
|
+
y = values.shift
|
76
|
+
if relative && @last_point
|
77
|
+
x += @last_point.first
|
78
|
+
y += @last_point.last
|
79
|
+
end
|
80
|
+
@last_point = [x, y]
|
81
|
+
@calls << ["line_to", @last_point]
|
82
|
+
end
|
83
|
+
|
84
|
+
when 'H' # horizontal lineto
|
85
|
+
while values.any?
|
86
|
+
x = values.shift
|
87
|
+
x += @last_point.first if relative && @last_point
|
88
|
+
@last_point = [x, @last_point.last]
|
89
|
+
@calls << ["line_to", @last_point]
|
90
|
+
end
|
91
|
+
|
92
|
+
when 'V' # vertical lineto
|
93
|
+
while values.any?
|
94
|
+
y = values.shift
|
95
|
+
y += @last_point.last if relative && @last_point
|
96
|
+
@last_point = [@last_point.first, y]
|
97
|
+
@calls << ["line_to", @last_point]
|
98
|
+
end
|
99
|
+
|
100
|
+
when 'C' # curveto
|
101
|
+
while values.any?
|
102
|
+
x1, y1, x2, y2, x, y = (1..6).collect {values.shift}
|
103
|
+
if relative && @last_point
|
104
|
+
x += @last_point.first
|
105
|
+
x1 += @last_point.first
|
106
|
+
x2 += @last_point.first
|
107
|
+
y += @last_point.last
|
108
|
+
y1 += @last_point.last
|
109
|
+
y2 += @last_point.last
|
110
|
+
end
|
111
|
+
|
112
|
+
@last_point = [x, y]
|
113
|
+
@previous_control_point = [x2, y2]
|
114
|
+
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
115
|
+
end
|
116
|
+
|
117
|
+
when 'S' # shorthand/smooth curveto
|
118
|
+
while values.any?
|
119
|
+
x2, y2, x, y = (1..4).collect {values.shift}
|
120
|
+
if relative && @last_point
|
121
|
+
x += @last_point.first
|
122
|
+
x2 += @last_point.first
|
123
|
+
y += @last_point.last
|
124
|
+
y2 += @last_point.last
|
125
|
+
end
|
126
|
+
|
127
|
+
if @previous_control_point
|
128
|
+
x1 = 2 * @last_point.first - @previous_control_point.first
|
129
|
+
y1 = 2 * @last_point.last - @previous_control_point.last
|
130
|
+
else
|
131
|
+
x1, y1 = @last_point
|
132
|
+
end
|
133
|
+
|
134
|
+
@last_point = [x, y]
|
135
|
+
@previous_control_point = [x2, y2]
|
136
|
+
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
137
|
+
end
|
138
|
+
|
139
|
+
when 'Q', 'T' # quadratic curveto
|
140
|
+
while values.any?
|
141
|
+
if shorthand = upcase_command == 'T'
|
142
|
+
x, y = (1..2).collect {values.shift}
|
143
|
+
else
|
144
|
+
x1, y1, x, y = (1..4).collect {values.shift}
|
145
|
+
end
|
146
|
+
|
147
|
+
if relative && @last_point
|
148
|
+
x += @last_point.first
|
149
|
+
x1 += @last_point.first if x1
|
150
|
+
y += @last_point.last
|
151
|
+
y1 += @last_point.last if y1
|
152
|
+
end
|
153
|
+
|
154
|
+
if shorthand
|
155
|
+
if @previous_quadratic_control_point
|
156
|
+
x1 = 2 * @last_point.first - @previous_quadratic_control_point.first
|
157
|
+
y1 = 2 * @last_point.last - @previous_quadratic_control_point.last
|
158
|
+
else
|
159
|
+
x1, y1 = @last_point
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# convert from quadratic to cubic
|
164
|
+
cx1 = @last_point.first + (x1 - @last_point.first) * 2 / 3.0
|
165
|
+
cy1 = @last_point.last + (y1 - @last_point.last) * 2 / 3.0
|
166
|
+
cx2 = cx1 + (x - @last_point.first) / 3.0
|
167
|
+
cy2 = cy1 + (y - @last_point.last) / 3.0
|
168
|
+
|
169
|
+
@last_point = [x, y]
|
170
|
+
@previous_quadratic_control_point = [x1, y1]
|
171
|
+
|
172
|
+
@calls << ["curve_to", [x, y, cx1, cy1, cx2, cy2]]
|
173
|
+
end
|
174
|
+
|
175
|
+
when 'A'
|
176
|
+
# unsupported
|
177
|
+
end
|
178
|
+
|
179
|
+
@previous_control_point = nil unless %w(C S).include?(upcase_command)
|
180
|
+
@previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
metadata
CHANGED
@@ -6,8 +6,8 @@ version: !ruby/object:Gem::Version
|
|
6
6
|
- 0
|
7
7
|
- 9
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.9.1.
|
9
|
+
- 7
|
10
|
+
version: 0.9.1.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Roger Nesbitt
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-05-08 00:00:00 +12:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -57,10 +57,10 @@ extra_rdoc_files: []
|
|
57
57
|
files:
|
58
58
|
- README
|
59
59
|
- LICENSE
|
60
|
+
- lib/prawn/svg/extension.rb
|
61
|
+
- lib/prawn/svg/interface.rb
|
62
|
+
- lib/prawn/svg/parser/path.rb
|
60
63
|
- lib/prawn/svg/parser.rb
|
61
|
-
- lib/prawn/svg/path.rb
|
62
|
-
- lib/prawn/svg/svg.rb
|
63
|
-
- lib/prawn/svg_document.rb
|
64
64
|
- lib/prawn-svg.rb
|
65
65
|
has_rdoc: true
|
66
66
|
homepage: http://github.com/mogest/prawn-svg
|
data/lib/prawn/svg/path.rb
DELETED
@@ -1,180 +0,0 @@
|
|
1
|
-
class Prawn::Svg::Parser::Path
|
2
|
-
# Raised if the SVG path cannot be parsed.
|
3
|
-
InvalidError = Class.new(StandardError)
|
4
|
-
|
5
|
-
#
|
6
|
-
# Parses an SVG path and returns a Prawn-compatible call tree.
|
7
|
-
#
|
8
|
-
def parse(data)
|
9
|
-
cmd = values = nil
|
10
|
-
value = ""
|
11
|
-
@subpath_initial_point = @last_point = nil
|
12
|
-
@previous_control_point = @previous_quadratic_control_point = nil
|
13
|
-
@calls = []
|
14
|
-
|
15
|
-
data.each_char do |c|
|
16
|
-
if c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'
|
17
|
-
values << value.to_f if value != ""
|
18
|
-
run_path_command(cmd, values) if cmd
|
19
|
-
cmd = c
|
20
|
-
values = []
|
21
|
-
value = ""
|
22
|
-
elsif c >= '0' && c <= '9' || c == '.' || c == "-"
|
23
|
-
unless cmd
|
24
|
-
raise InvalidError, "Numerical value specified before character command in SVG path data"
|
25
|
-
end
|
26
|
-
value << c
|
27
|
-
elsif c == ' ' || c == "\t" || c == "\r" || c == "\n" || c == ","
|
28
|
-
if value != ""
|
29
|
-
values << value.to_f
|
30
|
-
value = ""
|
31
|
-
end
|
32
|
-
else
|
33
|
-
raise InvalidError, "Invalid character '#{c}' in SVG path data"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
values << value.to_f if value != ""
|
38
|
-
run_path_command(cmd, values) if cmd
|
39
|
-
|
40
|
-
@calls
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
private
|
45
|
-
def run_path_command(command, values)
|
46
|
-
upcase_command = command.upcase
|
47
|
-
relative = command != upcase_command
|
48
|
-
|
49
|
-
case upcase_command
|
50
|
-
when 'M' # moveto
|
51
|
-
x = values.shift
|
52
|
-
y = values.shift
|
53
|
-
|
54
|
-
if relative && @last_point
|
55
|
-
x += @last_point.first
|
56
|
-
y += @last_point.last
|
57
|
-
end
|
58
|
-
|
59
|
-
@last_point = @subpath_initial_point = [x, y]
|
60
|
-
@calls << ["move_to", @last_point]
|
61
|
-
|
62
|
-
return run_path_command('L', values) if values.any?
|
63
|
-
|
64
|
-
when 'Z' # closepath
|
65
|
-
if @subpath_initial_point
|
66
|
-
@calls << ["line_to", @subpath_initial_point]
|
67
|
-
@last_point = @subpath_initial_point
|
68
|
-
end
|
69
|
-
|
70
|
-
when 'L' # lineto
|
71
|
-
while values.any?
|
72
|
-
x = values.shift
|
73
|
-
y = values.shift
|
74
|
-
if relative && @last_point
|
75
|
-
x += @last_point.first
|
76
|
-
y += @last_point.last
|
77
|
-
end
|
78
|
-
@last_point = [x, y]
|
79
|
-
@calls << ["line_to", @last_point]
|
80
|
-
end
|
81
|
-
|
82
|
-
when 'H' # horizontal lineto
|
83
|
-
while values.any?
|
84
|
-
x = values.shift
|
85
|
-
x += @last_point.first if relative && @last_point
|
86
|
-
@last_point = [x, @last_point.last]
|
87
|
-
@calls << ["line_to", @last_point]
|
88
|
-
end
|
89
|
-
|
90
|
-
when 'V' # vertical lineto
|
91
|
-
while values.any?
|
92
|
-
y = values.shift
|
93
|
-
y += @last_point.last if relative && @last_point
|
94
|
-
@last_point = [@last_point.first, y]
|
95
|
-
@calls << ["line_to", @last_point]
|
96
|
-
end
|
97
|
-
|
98
|
-
when 'C' # curveto
|
99
|
-
while values.any?
|
100
|
-
x1, y1, x2, y2, x, y = (1..6).collect {values.shift}
|
101
|
-
if relative && @last_point
|
102
|
-
x += @last_point.first
|
103
|
-
x1 += @last_point.first
|
104
|
-
x2 += @last_point.first
|
105
|
-
y += @last_point.last
|
106
|
-
y1 += @last_point.last
|
107
|
-
y2 += @last_point.last
|
108
|
-
end
|
109
|
-
|
110
|
-
@last_point = [x, y]
|
111
|
-
@previous_control_point = [x2, y2]
|
112
|
-
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
113
|
-
end
|
114
|
-
|
115
|
-
when 'S' # shorthand/smooth curveto
|
116
|
-
while values.any?
|
117
|
-
x2, y2, x, y = (1..4).collect {values.shift}
|
118
|
-
if relative && @last_point
|
119
|
-
x += @last_point.first
|
120
|
-
x2 += @last_point.first
|
121
|
-
y += @last_point.last
|
122
|
-
y2 += @last_point.last
|
123
|
-
end
|
124
|
-
|
125
|
-
if @previous_control_point
|
126
|
-
x1 = 2 * @last_point.first - @previous_control_point.first
|
127
|
-
y1 = 2 * @last_point.last - @previous_control_point.last
|
128
|
-
else
|
129
|
-
x1, y1 = @last_point
|
130
|
-
end
|
131
|
-
|
132
|
-
@last_point = [x, y]
|
133
|
-
@previous_control_point = [x2, y2]
|
134
|
-
@calls << ["curve_to", [x, y, x1, y1, x2, y2]]
|
135
|
-
end
|
136
|
-
|
137
|
-
when 'Q', 'T' # quadratic curveto
|
138
|
-
while values.any?
|
139
|
-
if shorthand = upcase_command == 'T'
|
140
|
-
x, y = (1..2).collect {values.shift}
|
141
|
-
else
|
142
|
-
x1, y1, x, y = (1..4).collect {values.shift}
|
143
|
-
end
|
144
|
-
|
145
|
-
if relative && @last_point
|
146
|
-
x += @last_point.first
|
147
|
-
x1 += @last_point.first if x1
|
148
|
-
y += @last_point.last
|
149
|
-
y1 += @last_point.last if y1
|
150
|
-
end
|
151
|
-
|
152
|
-
if shorthand
|
153
|
-
if @previous_quadratic_control_point
|
154
|
-
x1 = 2 * @last_point.first - @previous_quadratic_control_point.first
|
155
|
-
y1 = 2 * @last_point.last - @previous_quadratic_control_point.last
|
156
|
-
else
|
157
|
-
x1, y1 = @last_point
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# convert from quadratic to cubic
|
162
|
-
cx1 = @last_point.first + (x1 - @last_point.first) * 2 / 3.0
|
163
|
-
cy1 = @last_point.last + (y1 - @last_point.last) * 2 / 3.0
|
164
|
-
cx2 = cx1 + (x - @last_point.first) / 3.0
|
165
|
-
cy2 = cy1 + (y - @last_point.last) / 3.0
|
166
|
-
|
167
|
-
@last_point = [x, y]
|
168
|
-
@previous_quadratic_control_point = [x1, y1]
|
169
|
-
|
170
|
-
@calls << ["curve_to", [x, y, cx1, cy1, cx2, cy2]]
|
171
|
-
end
|
172
|
-
|
173
|
-
when 'A'
|
174
|
-
# unsupported
|
175
|
-
end
|
176
|
-
|
177
|
-
@previous_control_point = nil unless %w(C S).include?(upcase_command)
|
178
|
-
@previous_quadratic_control_point = nil unless %w(Q T).include?(upcase_command)
|
179
|
-
end
|
180
|
-
end
|
data/lib/prawn/svg/svg.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Prawn::Svg makes a Prawn::Svg::Parser instance, uses that object to parse the supplied
|
3
|
-
# SVG into Prawn-compatible method calls, and then calls the Prawn methods.
|
4
|
-
#
|
5
|
-
class Prawn::Svg
|
6
|
-
DEFAULT_FONT_PATHS = ["/Library/Fonts", "/usr/share/fonts/truetype/**"]
|
7
|
-
|
8
|
-
@font_path = []
|
9
|
-
DEFAULT_FONT_PATHS.each {|path| @font_path << path if File.exists?(path)}
|
10
|
-
|
11
|
-
class << self; attr_accessor :font_path; end
|
12
|
-
|
13
|
-
attr_reader :data, :prawn, :parser, :options
|
14
|
-
|
15
|
-
# An +Array+ of warnings that occurred while parsing the SVG data. If this array is non-empty,
|
16
|
-
# it's likely that the SVG failed to render correctly.
|
17
|
-
attr_reader :parser_warnings
|
18
|
-
|
19
|
-
#
|
20
|
-
# Creates a Prawn::Svg object.
|
21
|
-
#
|
22
|
-
# +data+ is the SVG data to convert. +prawn+ is your Prawn::Document object.
|
23
|
-
#
|
24
|
-
# +options+ must contain the key :at, which takes a tuple of x and y co-ordinates.
|
25
|
-
#
|
26
|
-
# +options+ can optionally contain the key :width or :height. If both are
|
27
|
-
# specified, only :width will be used.
|
28
|
-
#
|
29
|
-
def initialize(data, prawn, options)
|
30
|
-
@data = data
|
31
|
-
@prawn = prawn
|
32
|
-
@options = options
|
33
|
-
|
34
|
-
@options[:at] or raise "options[:at] must be specified"
|
35
|
-
|
36
|
-
@parser = Parser.new(data, [prawn.bounds.width, prawn.bounds.height], options)
|
37
|
-
@parser_warnings = @parser.warnings
|
38
|
-
end
|
39
|
-
|
40
|
-
#
|
41
|
-
# Draws the SVG to the Prawn::Document object.
|
42
|
-
#
|
43
|
-
def draw
|
44
|
-
prawn.bounding_box(@options[:at], :width => @parser.width, :height => @parser.height) do
|
45
|
-
prawn.save_graphics_state do
|
46
|
-
proc_creator(prawn, @parser.parse).call
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
private
|
53
|
-
def proc_creator(prawn, calls)
|
54
|
-
Proc.new {issue_prawn_command(prawn, calls)}
|
55
|
-
end
|
56
|
-
|
57
|
-
def issue_prawn_command(prawn, calls)
|
58
|
-
calls.each do |call, arguments, children|
|
59
|
-
if rewrite_call_arguments(prawn, call, arguments) == false
|
60
|
-
issue_prawn_command(prawn, children) if children.any?
|
61
|
-
else
|
62
|
-
if children.empty?
|
63
|
-
prawn.send(call, *arguments)
|
64
|
-
else
|
65
|
-
prawn.send(call, *arguments, &proc_creator(prawn, children))
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def rewrite_call_arguments(prawn, call, arguments)
|
72
|
-
case call
|
73
|
-
when 'text_box'
|
74
|
-
if (anchor = arguments.last.delete(:text_anchor)) && %w(middle end).include?(anchor)
|
75
|
-
width = prawn.width_of(*arguments)
|
76
|
-
width /= 2 if anchor == 'middle'
|
77
|
-
arguments.last[:at][0] -= width
|
78
|
-
end
|
79
|
-
|
80
|
-
arguments.last[:at][1] += prawn.height_of(*arguments) / 3 * 2
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|