prawn-svg 0.9.1.6 → 0.9.1.7
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.
- 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
|