savage 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/savage/direction_proxy.rb +18 -0
- data/lib/savage/directions/arc_to.rb +4 -5
- data/lib/savage/directions/close_path.rb +5 -1
- data/lib/savage/directions/coordinate_target.rb +0 -3
- data/lib/savage/directions/cubic_curve_to.rb +22 -11
- data/lib/savage/directions/horizontal_to.rb +3 -4
- data/lib/savage/directions/line_to.rb +3 -4
- data/lib/savage/directions/move_to.rb +3 -4
- data/lib/savage/directions/point_target.rb +0 -3
- data/lib/savage/directions/quadratic_curve_to.rb +22 -11
- data/lib/savage/directions/vertical_to.rb +3 -4
- data/lib/savage/parser.rb +139 -0
- data/lib/savage/path.rb +33 -4
- data/lib/savage/sub_path.rb +29 -29
- data/lib/savage/utils.rb +6 -0
- data/lib/savage.rb +2 -2
- data/savage.gemspec +5 -2
- data/spec/savage/directions/cubic_curve_to_spec.rb +81 -53
- data/spec/savage/directions/quadratic_curve_spec.rb +83 -41
- data/spec/savage/parser_spec.rb +174 -0
- data/spec/savage/path_spec.rb +88 -7
- data/spec/savage/sub_path_spec.rb +73 -30
- data/spec/shared/direction.rb +13 -5
- metadata +6 -3
- data/lib/savage/core_extensions/string.rb +0 -8
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Savage
|
2
|
+
module DirectionProxy
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def define_proxies(&block)
|
9
|
+
Directions.constants.each do |constant|
|
10
|
+
unless %w[PointTarget CoordinateTarget Point MoveTo].include? constant
|
11
|
+
sym = constant.gsub(/[A-Z]/) { |p| '_' + p.downcase }[1..-1].to_sym
|
12
|
+
block.call(sym,constant)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -14,11 +14,10 @@ module Savage
|
|
14
14
|
def to_command
|
15
15
|
command_code << "#{@radius.x} #{@radius.y} #{@rotation} #{bool_to_int(@large_arc)} #{bool_to_int(@sweep)} #{target.x} #{target.y}".gsub(/ -/,'-')
|
16
16
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
17
|
+
|
18
|
+
def command_code
|
19
|
+
(absolute?) ? 'A' : 'a'
|
20
|
+
end
|
22
21
|
end
|
23
22
|
end
|
24
23
|
end
|
@@ -3,23 +3,34 @@ module Savage
|
|
3
3
|
class CubicCurveTo < QuadraticCurveTo
|
4
4
|
attr_accessor :control_1
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(*args)
|
7
|
+
raise ArgumentError if args.length < 4
|
8
|
+
case args.length
|
9
|
+
when 4
|
10
|
+
super(args[0],args[1],args[2],args[3],true)
|
11
|
+
when 5
|
12
|
+
raise ArgumentError if args[4].kind_of?(Numeric)
|
13
|
+
super(args[0],args[1],args[2],args[3],args[4])
|
14
|
+
when 6
|
15
|
+
@control_1 = Point.new(args[0],args[1])
|
16
|
+
super(args[2],args[3],args[4],args[5],true)
|
17
|
+
when 7
|
18
|
+
@control_1 = Point.new(args[0],args[1])
|
19
|
+
super(args[2],args[3],args[4],args[5],args[6])
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
|
-
def to_command
|
12
|
-
command_code
|
23
|
+
def to_command
|
24
|
+
command_code << ((@control_1) ? "#{@control_1.x} #{@control_1.y} #{@control.x} #{@control.y} #{@target.x} #{@target.y}".gsub(/ -/,'-') : super().gsub(/[A-Za-z]/,''))
|
13
25
|
end
|
14
26
|
|
15
27
|
def control_2; @control; end
|
16
28
|
def control_2=(value); @control = value; end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
29
|
+
|
30
|
+
def command_code
|
31
|
+
return (absolute?) ? 'C' : 'c' if @control_1
|
32
|
+
(absolute?) ? 'S' : 's'
|
33
|
+
end
|
23
34
|
end
|
24
35
|
end
|
25
36
|
end
|
@@ -3,20 +3,31 @@ module Savage
|
|
3
3
|
class QuadraticCurveTo < PointTarget
|
4
4
|
attr_accessor :control
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(*args)
|
7
|
+
raise ArgumentError if args.length < 2
|
8
|
+
case args.length
|
9
|
+
when 2
|
10
|
+
super(args[0],args[1],true)
|
11
|
+
when 3
|
12
|
+
raise ArgumentError if args[2].kind_of?(Numeric)
|
13
|
+
super(args[0],args[1],args[2])
|
14
|
+
when 4
|
15
|
+
@control = Point.new(args[0],args[1])
|
16
|
+
super(args[2],args[3],true)
|
17
|
+
when 5
|
18
|
+
@control = Point.new(args[0],args[1])
|
19
|
+
super(args[2],args[3],args[4])
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
|
-
def to_command
|
12
|
-
command_code
|
23
|
+
def to_command
|
24
|
+
command_code << ((@control) ? "#{@control.x} #{@control.y} #{@target.x} #{@target.y}".gsub(/ -/,'-') : super().gsub(/[A-Za-z]/,''))
|
25
|
+
end
|
26
|
+
|
27
|
+
def command_code
|
28
|
+
return (absolute?) ? 'Q' : 'q' if @control
|
29
|
+
(absolute?) ? 'T' : 't'
|
13
30
|
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def command_code(continuous=false)
|
17
|
-
return (absolute?) ? 'Q' : 'q' unless continuous
|
18
|
-
(absolute?) ? 'T' : 't'
|
19
|
-
end
|
20
31
|
end
|
21
32
|
end
|
22
33
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Savage
|
2
|
+
class Parser
|
3
|
+
class << self
|
4
|
+
def parse(parsable)
|
5
|
+
raise TypeError if parsable.class != String
|
6
|
+
subpaths = extract_subpaths parsable
|
7
|
+
raise TypeError if (subpaths.empty?)
|
8
|
+
path = Path.new
|
9
|
+
path.subpaths = []
|
10
|
+
subpaths.each do |subpath|
|
11
|
+
path.subpaths << parse_subpath(subpath)
|
12
|
+
end
|
13
|
+
path
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def extract_subpaths(parsable)
|
18
|
+
subpaths = []
|
19
|
+
if move_index = parsable.index(/[Mm]/)
|
20
|
+
subpaths << parsable[0...move_index] if move_index > 0
|
21
|
+
parsable.scan /[Mm][0-9\-\. LlHhVvQqCcTtSsAaZz]*/ do |match_group|
|
22
|
+
subpaths << $&
|
23
|
+
end
|
24
|
+
else
|
25
|
+
subpaths << parsable
|
26
|
+
end
|
27
|
+
subpaths
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_subpath(parsable)
|
31
|
+
subpath = SubPath.new
|
32
|
+
subpath.directions = extract_directions parsable
|
33
|
+
subpath
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_directions(parsable)
|
37
|
+
directions = []
|
38
|
+
parsable.scan /[A-Za-z][0-9\-\. ]*/ do |match_group|
|
39
|
+
direction = build_direction $&
|
40
|
+
if direction.kind_of?(Array)
|
41
|
+
directions.concat direction
|
42
|
+
else
|
43
|
+
directions << direction
|
44
|
+
end
|
45
|
+
end
|
46
|
+
directions
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_direction(parsable)
|
50
|
+
direction = nil
|
51
|
+
implicit_directions = false
|
52
|
+
coordinates = extract_coordinates parsable
|
53
|
+
absolute = (parsable[0,1] == parsable[0,1].upcase) ? true : false
|
54
|
+
recurse_code = parsable[0,1]
|
55
|
+
case recurse_code
|
56
|
+
when /[Mm]/
|
57
|
+
x = coordinates.shift
|
58
|
+
y = coordinates.shift
|
59
|
+
raise TypeError if x.nil? || y.nil?
|
60
|
+
direction = Directions::MoveTo.new(x,y,absolute)
|
61
|
+
recurse_code = 'L'
|
62
|
+
when /[Ll]/
|
63
|
+
x = coordinates.shift
|
64
|
+
y = coordinates.shift
|
65
|
+
raise TypeError if x.nil? || y.nil?
|
66
|
+
direction = Directions::LineTo.new(x,y,absolute)
|
67
|
+
when /[Hh]/
|
68
|
+
target = coordinates.shift
|
69
|
+
raise TypeError if target.nil?
|
70
|
+
direction = Directions::HorizontalTo.new(target,absolute)
|
71
|
+
when /[Vv]/
|
72
|
+
target = coordinates.shift
|
73
|
+
raise TypeError if target.nil?
|
74
|
+
direction = Directions::VerticalTo.new(target,absolute)
|
75
|
+
when /[Cc]/
|
76
|
+
control_1_x = coordinates.shift
|
77
|
+
control_1_y = coordinates.shift
|
78
|
+
control_2_x = coordinates.shift
|
79
|
+
control_2_y = coordinates.shift
|
80
|
+
x = coordinates.shift
|
81
|
+
y = coordinates.shift
|
82
|
+
raise TypeError if x.nil? || y.nil? || control_1_x.nil? || control_1_y.nil? || control_2_x.nil? || control_2_y.nil?
|
83
|
+
direction = Directions::CubicCurveTo.new(control_1_x,control_1_y,control_2_x,control_2_y,x,y,absolute)
|
84
|
+
when /[Ss]/
|
85
|
+
control_2_x = coordinates.shift
|
86
|
+
control_2_y = coordinates.shift
|
87
|
+
x = coordinates.shift
|
88
|
+
y = coordinates.shift
|
89
|
+
raise TypeError if x.nil? || y.nil? || control_2_x.nil? || control_2_y.nil?
|
90
|
+
direction = Directions::CubicCurveTo.new(control_2_x,control_2_y,x,y,absolute)
|
91
|
+
when /[Qq]/
|
92
|
+
control_x = coordinates.shift
|
93
|
+
control_y = coordinates.shift
|
94
|
+
x = coordinates.shift
|
95
|
+
y = coordinates.shift
|
96
|
+
raise TypeError if x.nil? || y.nil? || control_x.nil? || control_y.nil?
|
97
|
+
direction = Directions::QuadraticCurveTo.new(control_x,control_y,x,y,absolute)
|
98
|
+
when /[Tt]/
|
99
|
+
x = coordinates.shift
|
100
|
+
y = coordinates.shift
|
101
|
+
raise TypeError if x.nil? || y.nil?
|
102
|
+
direction = Directions::QuadraticCurveTo.new(x,y,absolute)
|
103
|
+
when /[Aa]/
|
104
|
+
rx = coordinates.shift
|
105
|
+
ry = coordinates.shift
|
106
|
+
rotation = coordinates.shift
|
107
|
+
large_arc = (coordinates.shift > 0) ? true : false
|
108
|
+
sweep = (coordinates.shift > 0) ? true : false
|
109
|
+
x = coordinates.shift
|
110
|
+
y = coordinates.shift
|
111
|
+
raise TypeError if x.nil? || y.nil? || rx.nil? || ry.nil? || rotation.nil?
|
112
|
+
direction = Directions::ArcTo.new(rx,ry,rotation,large_arc,sweep,x,y,absolute)
|
113
|
+
when /[Zz]/
|
114
|
+
direction = Directions::ClosePath.new(absolute)
|
115
|
+
when /[^MmLlHhVvCcSsQqTtAaZz]/
|
116
|
+
coordinates = []
|
117
|
+
raise TypeError
|
118
|
+
end
|
119
|
+
unless coordinates.empty?
|
120
|
+
recursed_direction = build_direction(coordinates.join(' ').insert(0,recurse_code))
|
121
|
+
if recursed_direction.kind_of?(Array)
|
122
|
+
direction = [direction].concat recursed_direction
|
123
|
+
else
|
124
|
+
direction = [direction,recursed_direction]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
direction
|
128
|
+
end
|
129
|
+
|
130
|
+
def extract_coordinates(command_string)
|
131
|
+
coordinates = []
|
132
|
+
command_string.scan /-?\d+(\.\d+)?/ do |match_group|
|
133
|
+
coordinates << $&.to_f
|
134
|
+
end
|
135
|
+
coordinates
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/savage/path.rb
CHANGED
@@ -1,16 +1,45 @@
|
|
1
1
|
module Savage
|
2
2
|
class Path
|
3
|
+
require File.dirname(__FILE__) + "/direction_proxy"
|
3
4
|
require File.dirname(__FILE__) + "/sub_path"
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
include Utils
|
7
|
+
include DirectionProxy
|
8
|
+
|
9
|
+
attr_accessor :subpaths
|
10
|
+
|
11
|
+
define_proxies do |sym,const|
|
12
|
+
define_method(sym) do |*args|
|
13
|
+
@subpaths.last.send(sym,*args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
@subpaths = [SubPath.new]
|
19
|
+
@subpaths.last.move_to(*args) if (2..3).include?(*args.length)
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def directions
|
24
|
+
directions = []
|
25
|
+
@subpaths.each { |subpath| directions.concat(subpath.directions) }
|
26
|
+
directions
|
27
|
+
end
|
28
|
+
|
29
|
+
def move_to(*args)
|
30
|
+
unless (@subpaths.last.directions.empty?)
|
31
|
+
(@subpaths << SubPath.new(*args)).last
|
32
|
+
else
|
33
|
+
@subpaths.last.move_to(*args)
|
34
|
+
end
|
7
35
|
end
|
8
36
|
|
9
|
-
def
|
10
|
-
|
37
|
+
def closed?
|
38
|
+
@subpaths.last.closed?
|
11
39
|
end
|
12
40
|
|
13
41
|
def to_command
|
42
|
+
@subpaths.collect { |subpath| subpath.to_command }.join
|
14
43
|
end
|
15
44
|
end
|
16
45
|
end
|
data/lib/savage/sub_path.rb
CHANGED
@@ -1,47 +1,47 @@
|
|
1
1
|
module Savage
|
2
2
|
class SubPath
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
11
|
-
end
|
3
|
+
include Utils
|
4
|
+
include DirectionProxy
|
5
|
+
|
6
|
+
define_proxies do |sym,const|
|
7
|
+
define_method(sym) do |*args|
|
8
|
+
(@directions << constantize("Savage::Directions::" << const).new(*args)).last
|
9
|
+
end
|
12
10
|
end
|
13
11
|
|
14
|
-
attr_accessor :
|
12
|
+
attr_accessor :directions
|
15
13
|
|
16
14
|
def move_to(*args)
|
17
|
-
return nil unless @
|
18
|
-
|
19
|
-
@commands << new_move
|
20
|
-
new_move
|
15
|
+
return nil unless @directions.empty?
|
16
|
+
(@directions << Directions::MoveTo.new(*args)).last
|
21
17
|
end
|
22
18
|
|
23
|
-
def initialize
|
24
|
-
@
|
19
|
+
def initialize(*args)
|
20
|
+
@directions = []
|
21
|
+
move_to(*args) if (2..3).include?(args.length)
|
22
|
+
yield self if block_given?
|
25
23
|
end
|
26
24
|
|
27
|
-
# FIXME - refactor this monstrosity
|
28
25
|
def to_command
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
@directions.to_enum(:each_with_index).collect { |dir, i|
|
27
|
+
command_string = dir.to_command
|
28
|
+
if i > 0
|
29
|
+
prev_command_code = @directions[i-1].command_code
|
30
|
+
if dir.command_code == prev_command_code || (prev_command_code.match(/^[Mm]$/) && dir.command_code == 'L')
|
31
|
+
command_string.gsub!(/^[A-Za-z]/,'')
|
32
|
+
command_string.insert(0,' ') unless command_string.match(/^-/)
|
33
|
+
end
|
36
34
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
command_string
|
36
|
+
}.join
|
37
|
+
end
|
38
|
+
|
39
|
+
def commands
|
40
|
+
@directions
|
41
41
|
end
|
42
42
|
|
43
43
|
def closed?
|
44
|
-
@
|
44
|
+
@directions.last.kind_of? Directions::ClosePath
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/lib/savage/utils.rb
CHANGED
@@ -3,5 +3,11 @@ module Savage
|
|
3
3
|
def bool_to_int(value)
|
4
4
|
(value) ? 1 : 0
|
5
5
|
end
|
6
|
+
def constantize(string)
|
7
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ string
|
8
|
+
raise NameError, "#{string.inspect} is not a valid constant name!"
|
9
|
+
end
|
10
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
11
|
+
end
|
6
12
|
end
|
7
13
|
end
|
data/lib/savage.rb
CHANGED
data/savage.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{savage}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jeremy Holland"]
|
@@ -24,8 +24,8 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/savage.rb",
|
27
|
-
"lib/savage/core_extensions/string.rb",
|
28
27
|
"lib/savage/direction.rb",
|
28
|
+
"lib/savage/direction_proxy.rb",
|
29
29
|
"lib/savage/directions/arc_to.rb",
|
30
30
|
"lib/savage/directions/close_path.rb",
|
31
31
|
"lib/savage/directions/coordinate_target.rb",
|
@@ -36,6 +36,7 @@ Gem::Specification.new do |s|
|
|
36
36
|
"lib/savage/directions/point_target.rb",
|
37
37
|
"lib/savage/directions/quadratic_curve_to.rb",
|
38
38
|
"lib/savage/directions/vertical_to.rb",
|
39
|
+
"lib/savage/parser.rb",
|
39
40
|
"lib/savage/path.rb",
|
40
41
|
"lib/savage/sub_path.rb",
|
41
42
|
"lib/savage/utils.rb",
|
@@ -49,6 +50,7 @@ Gem::Specification.new do |s|
|
|
49
50
|
"spec/savage/directions/point_spec.rb",
|
50
51
|
"spec/savage/directions/quadratic_curve_spec.rb",
|
51
52
|
"spec/savage/directions/vertical_to_spec.rb",
|
53
|
+
"spec/savage/parser_spec.rb",
|
52
54
|
"spec/savage/path_spec.rb",
|
53
55
|
"spec/savage/sub_path_spec.rb",
|
54
56
|
"spec/savage_spec.rb",
|
@@ -74,6 +76,7 @@ Gem::Specification.new do |s|
|
|
74
76
|
"spec/savage/directions/point_spec.rb",
|
75
77
|
"spec/savage/directions/quadratic_curve_spec.rb",
|
76
78
|
"spec/savage/directions/vertical_to_spec.rb",
|
79
|
+
"spec/savage/parser_spec.rb",
|
77
80
|
"spec/savage/path_spec.rb",
|
78
81
|
"spec/savage/sub_path_spec.rb",
|
79
82
|
"spec/savage_spec.rb",
|