kvg_character_recognition 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|