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 CHANGED
@@ -1 +1 @@
1
- 0.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
- private
19
- def command_code
20
- (absolute?) ? 'A' : 'a'
21
- end
17
+
18
+ def command_code
19
+ (absolute?) ? 'A' : 'a'
20
+ end
22
21
  end
23
22
  end
24
23
  end
@@ -7,7 +7,11 @@ module Savage
7
7
  end
8
8
 
9
9
  def to_command
10
- command_string = (absolute?) ? 'Z' : 'z'
10
+ command_code
11
+ end
12
+
13
+ def command_code
14
+ (absolute?) ? 'Z' : 'z'
11
15
  end
12
16
  end
13
17
  end
@@ -12,9 +12,6 @@ module Savage
12
12
  def to_command
13
13
  command_code << @target.to_s
14
14
  end
15
-
16
- private
17
- def command_code; ''; end;
18
15
  end
19
16
  end
20
17
  end
@@ -3,23 +3,34 @@ module Savage
3
3
  class CubicCurveTo < QuadraticCurveTo
4
4
  attr_accessor :control_1
5
5
 
6
- def initialize(control_1_x, control_1_y, control_2_x, control_2_y, target_x, target_y, absolute=true)
7
- @control_1 = Point.new(control_1_x, control_1_y)
8
- super(control_2_x, control_2_y, target_x, target_y, absolute)
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(continuous=false)
12
- command_code(continuous) << ((!continuous) ? "#{@control_1.x} #{@control_1.y} #{@control.x} #{@control.y} #{@target.x} #{@target.y}".gsub(/ -/,'-') : super().gsub(/[A-Za-z]/,''))
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
- private
19
- def command_code(continuous=false)
20
- return (absolute?) ? 'C' : 'c' unless continuous
21
- (absolute?) ? 'S' : 's'
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
@@ -1,10 +1,9 @@
1
1
  module Savage
2
2
  module Directions
3
3
  class HorizontalTo < CoordinateTarget
4
- private
5
- def command_code
6
- (absolute?) ? 'H' : 'h'
7
- end
4
+ def command_code
5
+ (absolute?) ? 'H' : 'h'
6
+ end
8
7
  end
9
8
  end
10
9
  end
@@ -1,10 +1,9 @@
1
1
  module Savage
2
2
  module Directions
3
3
  class LineTo < PointTarget
4
- private
5
- def command_code
6
- (absolute?) ? 'L' : 'l'
7
- end
4
+ def command_code
5
+ (absolute?) ? 'L' : 'l'
6
+ end
8
7
  end
9
8
  end
10
9
  end
@@ -1,10 +1,9 @@
1
1
  module Savage
2
2
  module Directions
3
3
  class MoveTo < PointTarget
4
- private
5
- def command_code
6
- (absolute?) ? 'M' : 'm'
7
- end
4
+ def command_code
5
+ (absolute?) ? 'M' : 'm'
6
+ end
8
7
  end
9
8
  end
10
9
  end
@@ -12,9 +12,6 @@ module Savage
12
12
  def to_command
13
13
  command_code << "#{@target.x.to_s} #{@target.y.to_s}".gsub(/ -/,'-')
14
14
  end
15
-
16
- private
17
- def command_code; ''; end;
18
15
  end
19
16
  end
20
17
  end
@@ -3,20 +3,31 @@ module Savage
3
3
  class QuadraticCurveTo < PointTarget
4
4
  attr_accessor :control
5
5
 
6
- def initialize(control_x, control_y, target_x, target_y, absolute=true)
7
- @control = Point.new(control_x, control_y)
8
- super(target_x, target_y, absolute)
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(continuous=false)
12
- command_code(continuous) << ((!continuous) ? "#{@control.x} #{@control.y} #{@target.x} #{@target.y}".gsub(/ -/,'-') : super().gsub(/[A-Za-z]/,''))
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
@@ -1,10 +1,9 @@
1
1
  module Savage
2
2
  module Directions
3
3
  class VerticalTo < CoordinateTarget
4
- private
5
- def command_code
6
- (absolute?) ? 'V' : 'v'
7
- end
4
+ def command_code
5
+ (absolute?) ? 'V' : 'v'
6
+ end
8
7
  end
9
8
  end
10
9
  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
- def initialize(path_string=nil)
6
- raise ArgumentError unless path_string.nil? || path_string.kind_of?(String)
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 subpaths
10
- return []
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
@@ -1,47 +1,47 @@
1
1
  module Savage
2
2
  class SubPath
3
- Directions.constants.each do |constant|
4
- unless %w[PointTarget CoordinateTarget Point MoveTo].include? constant
5
- sym = constant.gsub(/[A-Z]/) { |p| '_' + p.downcase }[1..-1].to_sym
6
- define_method(sym) do |*args|
7
- new_command = ("Savage::Directions::" << constant).constantize.new(*args)
8
- @commands << new_command
9
- new_command
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 :commands
12
+ attr_accessor :directions
15
13
 
16
14
  def move_to(*args)
17
- return nil unless @commands.empty?
18
- new_move = Directions::MoveTo.new(*args)
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
- @commands = []
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
- prev_command = nil
30
- command = ''
31
- @commands.each do |dir|
32
- this_command = dir.to_command
33
- if dir.class == prev_command || (dir.class == Directions::LineTo && prev_command == Directions::MoveTo)
34
- this_command.gsub!(/^[A-Za-z]/,'')
35
- this_command = " " << this_command unless this_command.match(/^-/)
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
- prev_command = dir.class
38
- command << this_command
39
- end
40
- command
35
+ command_string
36
+ }.join
37
+ end
38
+
39
+ def commands
40
+ @directions
41
41
  end
42
42
 
43
43
  def closed?
44
- @commands.last.kind_of? Directions::ClosePath
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
@@ -1,9 +1,9 @@
1
1
  SAVAGE_PATH = File.dirname(__FILE__) + "/savage/"
2
2
  [
3
- 'core_extensions/string',
4
3
  'utils',
5
4
  'direction',
6
- 'path'
5
+ 'path',
6
+ 'parser'
7
7
  ].each do |library|
8
8
  require SAVAGE_PATH + library
9
9
  end
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.1.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",