kvg_character_recognition 0.1.3 → 0.2.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/kvg_character_recognition.gemspec +1 -1
- data/lib/kvg_character_recognition.rb +16 -29
- data/lib/kvg_character_recognition/datastore.rb +0 -2
- data/lib/kvg_character_recognition/heatmap_feature.rb +50 -0
- data/lib/kvg_character_recognition/kvg_parser.rb +272 -0
- data/lib/kvg_character_recognition/non_structural_feature.rb +72 -0
- data/lib/kvg_character_recognition/normalization.rb +195 -0
- data/lib/kvg_character_recognition/preprocessor.rb +28 -212
- data/lib/kvg_character_recognition/recognizer.rb +16 -18
- data/lib/kvg_character_recognition/template.rb +43 -0
- data/lib/kvg_character_recognition/trainer.rb +50 -73
- data/lib/kvg_character_recognition/utils.rb +0 -311
- data/lib/kvg_character_recognition/version.rb +1 -1
- metadata +7 -2
@@ -1,37 +1,35 @@
|
|
1
|
-
require 'matrix'
|
2
1
|
module KvgCharacterRecognition
|
3
2
|
#This class contains methods calculating similarity scores between input pattern and template patterns
|
4
3
|
module Recognizer
|
4
|
+
extend KvgCharacterRecognition::Trainer
|
5
5
|
|
6
6
|
#This method selects all templates from the database which should be further examined
|
7
7
|
#It filtered out those characters with a too great difference in number of points and strokes to the input character
|
8
|
-
def self.select_templates
|
9
|
-
p_min =
|
10
|
-
p_max =
|
11
|
-
s_min =
|
12
|
-
s_max =
|
8
|
+
def self.select_templates datastore, number_of_points, number_of_strokes
|
9
|
+
p_min = number_of_points - 100
|
10
|
+
p_max = number_of_points + 100
|
11
|
+
s_min = number_of_strokes - 12
|
12
|
+
s_max = number_of_strokes + 12
|
13
13
|
datastore.characters_in_range(p_min..p_max, s_min..s_max)
|
14
14
|
end
|
15
15
|
|
16
16
|
#This method calculates similarity scores which is an average of the somehow weighted sum of the euclidean distance of
|
17
|
-
#1.
|
18
|
-
#2.
|
17
|
+
#1. 17x17 smoothed heatmap
|
18
|
+
#2. manhattan distance of directional feature densities in average
|
19
19
|
#Params:
|
20
20
|
#+strokes+:: strokes are not preprocessed
|
21
21
|
#+datastore+:: JSONDatastore or custom datastore type having method characters_in_stroke_range(min..max)
|
22
22
|
def self.scores strokes, datastore
|
23
|
-
|
24
|
-
|
23
|
+
strokes = preprocess(strokes)
|
24
|
+
heatmaps = heatmaps(strokes)
|
25
|
+
templates = select_templates datastore, @number_of_points, strokes.count
|
25
26
|
|
27
|
+
#scores = datastore.data.map do |cand|
|
26
28
|
scores = templates.map do |cand|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
heatmap_line_density_slant_score = Math.manhattan_distance(cand[:heatmap_smoothed_coarse_with_slant], character.heatmap_smoothed_coarse_with_slant)
|
32
|
-
|
33
|
-
|
34
|
-
[[heatmap_bi_moment_score, heatmap_line_density_score, heatmap_bi_moment_slant_score, heatmap_line_density_slant_score].min, cand]
|
29
|
+
score = Math.manhattan_distance(heatmaps[0], cand[:heatmaps][0]) +
|
30
|
+
Math.manhattan_distance(heatmaps[1], cand[:heatmaps][1]) +
|
31
|
+
Math.manhattan_distance(heatmaps[2], cand[:heatmaps][2])
|
32
|
+
[score, cand]
|
35
33
|
end
|
36
34
|
|
37
35
|
scores.sort{ |a, b| a[0] <=> b[0] }
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module KvgCharacterRecognition
|
2
|
+
class Template
|
3
|
+
extend KvgCharacterRecognition::Trainer
|
4
|
+
#This method populates the datastore with parsed template patterns from the kanjivg file in xml format
|
5
|
+
#Params:
|
6
|
+
#+xml+:: download the latest xml release from https://github.com/KanjiVG/kanjivg/releases
|
7
|
+
#+datastore+:: JSONDatastore or custom datastore type having methods store, persist!
|
8
|
+
def self.parse_from_xml xml, datastore, kanji_list=[]
|
9
|
+
file = File.open(xml) { |f| Nokogiri::XML(f) }
|
10
|
+
|
11
|
+
file.xpath("//kanji").each do |kanji|
|
12
|
+
#id has format: "kvg:kanji_CODEPOINT"
|
13
|
+
codepoint = kanji.attributes["id"].value.split("_")[1]
|
14
|
+
value = [codepoint.hex].pack("U")
|
15
|
+
if kanji_list.empty?
|
16
|
+
next unless codepoint.hex >= "04e00".hex && codepoint.hex <= "09faf".hex
|
17
|
+
else
|
18
|
+
next unless codepoint.hex >= "04e00".hex && codepoint.hex <= "09faf".hex && kanji_list.include?(value)
|
19
|
+
end
|
20
|
+
puts "#{codepoint} #{value}"
|
21
|
+
|
22
|
+
# parse strokes
|
23
|
+
strokes = kanji.xpath("g//path").map{|p| p.attributes["d"].value }.map{ |stroke| KvgCharacterRecognition::KvgParser::Stroke.new(stroke).to_a }
|
24
|
+
|
25
|
+
strokes = preprocess(strokes)
|
26
|
+
|
27
|
+
#Store to database
|
28
|
+
#--------------
|
29
|
+
character = {
|
30
|
+
value: value,
|
31
|
+
codepoint: codepoint.hex,
|
32
|
+
number_of_strokes: strokes.count,
|
33
|
+
number_of_points: @number_of_points,
|
34
|
+
heatmaps: heatmaps(strokes)
|
35
|
+
}
|
36
|
+
|
37
|
+
datastore.store character
|
38
|
+
end
|
39
|
+
|
40
|
+
datastore.persist!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,81 +1,58 @@
|
|
1
1
|
module KvgCharacterRecognition
|
2
2
|
module Trainer
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@number_of_points = @bi_moment_preprocessed_strokes.flatten(1).count
|
26
|
-
|
27
|
-
line_density_normalized_strokes = Preprocessor.line_density_normalize(@bi_moment_preprocessed_strokes)
|
28
|
-
@line_density_preprocessed_strokes = Preprocessor.preprocess(line_density_normalized_strokes, CONFIG[:interpolate_distance], CONFIG[:downsample_interval], true)
|
29
|
-
line_density_normalized_strokes_with_slant = Preprocessor.line_density_normalize(@bi_moment_preprocessed_strokes_with_slant)
|
30
|
-
@line_density_preprocessed_strokes_with_slant = Preprocessor.preprocess(line_density_normalized_strokes_with_slant, CONFIG[:interpolate_distance], CONFIG[:downsample_interval], true)
|
31
|
-
|
32
|
-
@heatmap_smoothed_coarse = Preprocessor.smooth_heatmap(Preprocessor.heatmap(@line_density_preprocessed_strokes.flatten(1), CONFIG[:heatmap_coarse_grid], CONFIG[:size])).to_a
|
33
|
-
@heatmap_smoothed_granular = Preprocessor.smooth_heatmap(Preprocessor.heatmap(@bi_moment_preprocessed_strokes.flatten(1), CONFIG[:heatmap_granular_grid], CONFIG[:size])).to_a
|
34
|
-
|
35
|
-
@heatmap_smoothed_coarse_with_slant = Preprocessor.smooth_heatmap(Preprocessor.heatmap(@line_density_preprocessed_strokes_with_slant.flatten(1), CONFIG[:heatmap_coarse_grid], CONFIG[:size])).to_a
|
36
|
-
@heatmap_smoothed_granular_with_slant = Preprocessor.smooth_heatmap(Preprocessor.heatmap(@bi_moment_preprocessed_strokes_with_slant.flatten(1), CONFIG[:heatmap_granular_grid], CONFIG[:size])).to_a
|
37
|
-
|
38
|
-
end
|
3
|
+
@@config = { downsample_rate: 4,
|
4
|
+
interpolate_distance: 0.8,
|
5
|
+
size: 109,
|
6
|
+
smooth: true,
|
7
|
+
smooth_weights: [1,2,3,2,1],
|
8
|
+
smooth_filter_weights: [1/9.0, 1/9.0, 1/9.0, 1/9.0, 1/9.0, 1/9.0, 1/9.0, 1/9.0, 1/9.0],
|
9
|
+
heatmap_number_of_grids: 17
|
10
|
+
}
|
11
|
+
@@preprocessor = Preprocessor.new(@@config[:interpolate_distance],
|
12
|
+
@@config[:size],
|
13
|
+
@@config[:smooth],
|
14
|
+
@@config[:smooth_weights])
|
15
|
+
|
16
|
+
# this variable will be set in the method preprocess
|
17
|
+
@number_of_points = 0
|
18
|
+
|
19
|
+
# preprocess strokes and set the number_of_points variable
|
20
|
+
# !the preprocessed strokes are not downsampled
|
21
|
+
def preprocess strokes
|
22
|
+
strokes = @@preprocessor.preprocess(strokes)
|
23
|
+
@number_of_points = strokes.flatten(1).count
|
24
|
+
strokes
|
39
25
|
end
|
40
26
|
|
41
|
-
#This method
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
#--------------
|
64
|
-
character = {
|
65
|
-
value: value,
|
66
|
-
codepoint: codepoint.hex,
|
67
|
-
number_of_strokes: strokes.count,
|
68
|
-
number_of_points: chr.number_of_points,
|
69
|
-
heatmap_smoothed_coarse: chr.heatmap_smoothed_coarse,
|
70
|
-
heatmap_smoothed_granular: chr.heatmap_smoothed_granular,
|
71
|
-
heatmap_smoothed_coarse_with_slant: chr.heatmap_smoothed_coarse_with_slant,
|
72
|
-
heatmap_smoothed_granular_with_slant: chr.heatmap_smoothed_granular_with_slant
|
73
|
-
}
|
74
|
-
|
75
|
-
datastore.store character
|
76
|
-
end
|
27
|
+
# This method returns the 3x 17x17 direction feature vector
|
28
|
+
# strokes are preprocessed
|
29
|
+
def heatmaps strokes
|
30
|
+
weights = @@config[:smooth_filter_weights]
|
31
|
+
number_of_grids = @@config[:heatmap_number_of_grids]
|
32
|
+
|
33
|
+
bi_normed = strokes
|
34
|
+
ld_normed = @@preprocessor.line_density_normalize(bi_normed).map{ |stroke| downsample(stroke, @@config[:downsample_rate]) }
|
35
|
+
pd_normed = @@preprocessor.point_density_normalize(bi_normed).map{ |stroke| downsample(stroke, @@config[:downsample_rate]) }
|
36
|
+
bi_normed = bi_normed.map{ |stroke| downsample(stroke, @@config[:downsample_rate]) }
|
37
|
+
|
38
|
+
# feature extraction
|
39
|
+
heatmaps_map = HeatmapFeature.new(bi_normed,
|
40
|
+
ld_normed,
|
41
|
+
pd_normed,
|
42
|
+
@@config[:size],
|
43
|
+
number_of_grids,
|
44
|
+
weights).heatmaps
|
45
|
+
|
46
|
+
# convert to feature vector
|
47
|
+
Matrix.columns(heatmaps_map.to_a).to_a
|
48
|
+
end
|
77
49
|
|
78
|
-
|
50
|
+
private
|
51
|
+
#This methods downsamples a stroke in given interval
|
52
|
+
#The number of points in the stroke will be reduced
|
53
|
+
def downsample stroke, interval
|
54
|
+
stroke.each_slice(interval).map(&:first)
|
79
55
|
end
|
56
|
+
|
80
57
|
end
|
81
58
|
end
|
@@ -19,314 +19,3 @@ module Math
|
|
19
19
|
sum
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
23
|
-
module KvgCharacterRecognition
|
24
|
-
|
25
|
-
#This class can be used for storing heatmap count and directional feature densities
|
26
|
-
#basically it is a nxm matrix with an initial value in each cell
|
27
|
-
class Map
|
28
|
-
#Make a new map with
|
29
|
-
#Params:
|
30
|
-
#+n+:: row length
|
31
|
-
#+m+:: column length
|
32
|
-
#+initial_value+:: for heatmap initial_value = 0 and for directional feature densities initial_value = [0, 0, 0, 0] <= [weight in e1, weight in e2, ...]
|
33
|
-
def initialize n, m, initial_value
|
34
|
-
@array = Array.new(n * m, initial_value)
|
35
|
-
@n = n
|
36
|
-
@m = m
|
37
|
-
end
|
38
|
-
|
39
|
-
#Access value in the cell of i-th row and j-th column
|
40
|
-
#e.g. map[i,j]
|
41
|
-
def [](i, j)
|
42
|
-
@array[j*@n + i]
|
43
|
-
end
|
44
|
-
|
45
|
-
#Store value in the cell of i-th row and j-th column
|
46
|
-
#e.g. map[i,j] = value
|
47
|
-
def []=(i, j, value)
|
48
|
-
@array[j*@n + i] = value
|
49
|
-
end
|
50
|
-
|
51
|
-
def to_a
|
52
|
-
@array
|
53
|
-
end
|
54
|
-
|
55
|
-
#Normaly n is the same as m
|
56
|
-
def size
|
57
|
-
@n
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
#This module contains classes which can be used to parse a svg command
|
63
|
-
#The code is copied from https://github.com/rogerbraun/KVG-Tools
|
64
|
-
#Methods for generating sexp or xml outputs are removed
|
65
|
-
module KvgParser
|
66
|
-
#A Point
|
67
|
-
class Point
|
68
|
-
attr_accessor :x, :y, :color
|
69
|
-
|
70
|
-
def initialize(x,y, color = :black)
|
71
|
-
@x,@y, @color = x, y, color
|
72
|
-
end
|
73
|
-
|
74
|
-
#Basic point arithmetics
|
75
|
-
def +(p2)
|
76
|
-
return Point.new(@x + p2.x, @y + p2.y)
|
77
|
-
end
|
78
|
-
|
79
|
-
def -(p2)
|
80
|
-
return Point.new(@x - p2.x, @y - p2.y)
|
81
|
-
end
|
82
|
-
|
83
|
-
def dist(p2)
|
84
|
-
return Math.sqrt((p2.x - @x)**2 + (p2.y - @y)**2)
|
85
|
-
end
|
86
|
-
|
87
|
-
def *(number)
|
88
|
-
return Point.new(@x * number, @y * number)
|
89
|
-
end
|
90
|
-
|
91
|
-
#to array
|
92
|
-
def to_a
|
93
|
-
[@x.round(2), @y.round(2)]
|
94
|
-
end
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
# SVG_M represents the moveto command.
|
99
|
-
# SVG Syntax is:
|
100
|
-
# m x y
|
101
|
-
# It sets the current cursor to the point (x,y).
|
102
|
-
# As always, capitalization denotes absolute values.
|
103
|
-
# Takes a Point as argument.
|
104
|
-
# If given 2 Points, the second argument is treated as the current cursor.
|
105
|
-
class SVG_M
|
106
|
-
|
107
|
-
def initialize(p1, p2 = Point.new(0,0))
|
108
|
-
@p = p1 + p2
|
109
|
-
end
|
110
|
-
|
111
|
-
def to_points
|
112
|
-
return []
|
113
|
-
end
|
114
|
-
|
115
|
-
def current_cursor
|
116
|
-
return @p
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
# SVG_C represents the cubic Bézier curveto command.
|
122
|
-
# Syntax is:
|
123
|
-
# c x1 y1 x2 y2 x y
|
124
|
-
# It sets the current cursor to the point (x,y).
|
125
|
-
# As always, capitalization denotes absolute values.
|
126
|
-
# Takes 4 Points as argument, the fourth being the current cursor
|
127
|
-
# If constructed using SVG_C.relative, the current cursor is added to every
|
128
|
-
# point.
|
129
|
-
class SVG_C
|
130
|
-
|
131
|
-
def initialize(c1,c2,p,current_cursor)
|
132
|
-
@c1,@c2,@p,@current_cursor = c1,c2,p,current_cursor
|
133
|
-
@@c_color = :green
|
134
|
-
end
|
135
|
-
|
136
|
-
def SVG_C.relative(c1,c2,p,current_cursor)
|
137
|
-
SVG_C.new(c1 + current_cursor, c2 + current_cursor, p + current_cursor, current_cursor)
|
138
|
-
end
|
139
|
-
|
140
|
-
def second_point
|
141
|
-
@c2
|
142
|
-
end
|
143
|
-
|
144
|
-
# This implements the algorithm found here:
|
145
|
-
# http://www.cubic.org/docs/bezier.htm
|
146
|
-
# Takes 2 Points and a factor between 0 and 1
|
147
|
-
def linear_interpolation(a,b,factor)
|
148
|
-
|
149
|
-
xr = a.x + ((b.x - a.x) * factor)
|
150
|
-
yr = a.y + ((b.y - a.y) * factor)
|
151
|
-
|
152
|
-
return Point.new(xr,yr);
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
def switch_color
|
157
|
-
if @@c_color == :green
|
158
|
-
@@c_color = :red
|
159
|
-
elsif @@c_color == :red
|
160
|
-
@@c_color = :purple
|
161
|
-
else
|
162
|
-
@@c_color = :green
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def make_curvepoint(factor)
|
167
|
-
ab = linear_interpolation(@current_cursor,@c1,factor)
|
168
|
-
bc = linear_interpolation(@c1,@c2,factor)
|
169
|
-
cd = linear_interpolation(@c2,@p,factor)
|
170
|
-
|
171
|
-
abbc = linear_interpolation(ab,bc,factor)
|
172
|
-
bccd = linear_interpolation(bc,cd,factor)
|
173
|
-
return linear_interpolation(abbc,bccd,factor)
|
174
|
-
end
|
175
|
-
|
176
|
-
def length(points)
|
177
|
-
old_point = @current_cursor;
|
178
|
-
length = 0.0
|
179
|
-
factor = points.to_f
|
180
|
-
|
181
|
-
(1..points).each {|point|
|
182
|
-
new_point = make_curvepoint(point/(factor.to_f))
|
183
|
-
length += old_point.dist(new_point)
|
184
|
-
old_point = new_point
|
185
|
-
}
|
186
|
-
return length
|
187
|
-
end
|
188
|
-
|
189
|
-
# This gives back an array of points on the curve. The argument given
|
190
|
-
# denotes how the distance between each point.
|
191
|
-
def make_curvepoint_array(distance)
|
192
|
-
result = Array.new
|
193
|
-
|
194
|
-
l = length(20)
|
195
|
-
points = l * distance
|
196
|
-
factor = points.to_f
|
197
|
-
|
198
|
-
(0..points).each {|point|
|
199
|
-
result.push(make_curvepoint(point/(factor.to_f)))
|
200
|
-
}
|
201
|
-
|
202
|
-
return result
|
203
|
-
end
|
204
|
-
|
205
|
-
|
206
|
-
def to_points
|
207
|
-
return make_curvepoint_array(0.3)
|
208
|
-
end
|
209
|
-
|
210
|
-
def current_cursor
|
211
|
-
@p
|
212
|
-
end
|
213
|
-
|
214
|
-
end
|
215
|
-
|
216
|
-
# SVG_S represents the smooth curveto command.
|
217
|
-
# Syntax is:
|
218
|
-
# s x2 y2 x y
|
219
|
-
# It sets the current cursor to the point (x,y).
|
220
|
-
# As always, capitalization denotes absolute values.
|
221
|
-
# Takes 3 Points as argument, the third being the current cursor
|
222
|
-
# If constructed using SVG_S.relative, the current cursor is added to every
|
223
|
-
# point.
|
224
|
-
class SVG_S < SVG_C
|
225
|
-
|
226
|
-
def initialize(c2, p, current_cursor,previous_point)
|
227
|
-
super(SVG_S.reflect(previous_point,current_cursor), c2, p, current_cursor)
|
228
|
-
end
|
229
|
-
|
230
|
-
# The reflection in this case is rather tricky. Using SVG_C.relative, the
|
231
|
-
# offset of current_cursor is added to all the positions (except current_cursor).
|
232
|
-
# The reflected point, however is already calculated in absolute values.
|
233
|
-
# Because of this, we have to subtract the current_cursor from the reflected
|
234
|
-
# point, as it is already added later. I think I got the classes somewhat wrong.
|
235
|
-
# Maybe points should get a field whether they are absolute oder relative?
|
236
|
-
# Don't know yet. It works now, though!
|
237
|
-
def SVG_S.relative(c2, p, current_cursor, previous_point)
|
238
|
-
SVG_C.relative(SVG_S.reflect(previous_point,current_cursor) - current_cursor, c2, p, current_cursor)
|
239
|
-
end
|
240
|
-
|
241
|
-
def SVG_S.reflect(p, mirror)
|
242
|
-
return mirror + (mirror - p)
|
243
|
-
end
|
244
|
-
|
245
|
-
end
|
246
|
-
|
247
|
-
|
248
|
-
# Stroke represent one stroke, which is a series of SVG commands.
|
249
|
-
class Stroke
|
250
|
-
COMMANDS = ["M", "C", "c", "s", "S"]
|
251
|
-
|
252
|
-
def initialize(stroke_as_code)
|
253
|
-
@command_list = parse(stroke_as_code)
|
254
|
-
end
|
255
|
-
|
256
|
-
def to_points
|
257
|
-
return @command_list.map{|element| element.to_points}.flatten
|
258
|
-
end
|
259
|
-
|
260
|
-
#to array
|
261
|
-
#TODO: better implementation using composite pattern
|
262
|
-
def to_a
|
263
|
-
to_points.map{|point| point.to_a}
|
264
|
-
end
|
265
|
-
|
266
|
-
def split_elements(line)
|
267
|
-
# This is magic.
|
268
|
-
return line.gsub("-",",-").gsub("s",",s,").gsub("S",",S,").gsub("c",",c,").gsub("C",",C,").gsub("m", "M").gsub("M","M,").gsub("[","").gsub(";",",;,").gsub(",,",",").gsub(" ,", ",").gsub(", ", ",").gsub(" ", ",").split(/,/);
|
269
|
-
end
|
270
|
-
|
271
|
-
def parse(stroke_as_code)
|
272
|
-
elements = split_elements(stroke_as_code).delete_if{ |e| e == "" }
|
273
|
-
command_list = Array.new
|
274
|
-
current_cursor = Point.new(0,0);
|
275
|
-
|
276
|
-
while elements != [] do
|
277
|
-
|
278
|
-
case elements.slice!(0)
|
279
|
-
when "M"
|
280
|
-
x,y = elements.slice!(0..1)
|
281
|
-
m = SVG_M.new(Point.new(x.to_f,y.to_f))
|
282
|
-
current_cursor = m.current_cursor
|
283
|
-
command_list.push(m)
|
284
|
-
|
285
|
-
when "C"
|
286
|
-
x1,y1,x2,y2,x,y = elements.slice!(0..5)
|
287
|
-
c = SVG_C.new(Point.new(x1.to_f,y1.to_f), Point.new(x2.to_f,y2.to_f), Point.new(x.to_f,y.to_f), current_cursor)
|
288
|
-
current_cursor = c.current_cursor
|
289
|
-
command_list.push(c)
|
290
|
-
|
291
|
-
#handle polybezier
|
292
|
-
unless elements.empty? || COMMANDS.include?(elements.first)
|
293
|
-
elements.unshift("C")
|
294
|
-
end
|
295
|
-
when "c"
|
296
|
-
x1,y1,x2,y2,x,y = elements.slice!(0..5)
|
297
|
-
c = SVG_C.relative(Point.new(x1.to_f,y1.to_f), Point.new(x2.to_f,y2.to_f), Point.new(x.to_f,y.to_f), current_cursor)
|
298
|
-
current_cursor = c.current_cursor
|
299
|
-
command_list.push(c)
|
300
|
-
|
301
|
-
#handle polybezier
|
302
|
-
unless elements.empty? || COMMANDS.include?(elements.first)
|
303
|
-
elements.unshift("c")
|
304
|
-
end
|
305
|
-
|
306
|
-
when "s"
|
307
|
-
x2,y2,x,y = elements.slice!(0..3)
|
308
|
-
reflected_point = command_list[-1].second_point
|
309
|
-
s = SVG_S.relative(Point.new(x2.to_f,y2.to_f), Point.new(x.to_f,y.to_f), current_cursor, reflected_point)
|
310
|
-
current_cursor = s.current_cursor
|
311
|
-
command_list.push(s)
|
312
|
-
|
313
|
-
when "S"
|
314
|
-
x2,y2,x,y = elements.slice!(0..3)
|
315
|
-
reflected_point = command_list[-1].second_point
|
316
|
-
s = SVG_S.new(Point.new(x2.to_f,y2.to_f), Point.new(x.to_f,y.to_f), current_cursor,reflected_point)
|
317
|
-
current_cursor = s.current_cursor
|
318
|
-
command_list.push(s)
|
319
|
-
|
320
|
-
else
|
321
|
-
#print "You should not be here\n"
|
322
|
-
|
323
|
-
end
|
324
|
-
|
325
|
-
end
|
326
|
-
|
327
|
-
return command_list
|
328
|
-
end
|
329
|
-
|
330
|
-
end
|
331
|
-
end
|
332
|
-
end
|