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 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",