savage 0.1.0 → 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.
- 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",
|