vector_salad 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.yardopts +1 -0
  4. data/README.md +6 -5
  5. data/examples/birthday.png +0 -0
  6. data/examples/birthday.svg +604 -604
  7. data/examples/bunny_card.png +0 -0
  8. data/examples/bunny_card.svg +110 -110
  9. data/examples/circles.png +0 -0
  10. data/examples/circles.svg +2 -2
  11. data/examples/rects.rb +4 -4
  12. data/examples/squares.png +0 -0
  13. data/examples/squares.rb +4 -4
  14. data/examples/squares.svg +6 -6
  15. data/lib/contracts_contracts.rb +4 -4
  16. data/lib/vector_salad/canvas.rb +17 -0
  17. data/lib/vector_salad/dsl.rb +29 -28
  18. data/lib/vector_salad/export_with_magic.rb +1 -0
  19. data/lib/vector_salad/exporters/base_exporter.rb +1 -0
  20. data/lib/vector_salad/exporters/svg_exporter.rb +30 -22
  21. data/lib/vector_salad/interpolate.rb +13 -12
  22. data/lib/vector_salad/magic.rb +4 -3
  23. data/lib/vector_salad/mixins/at.rb +1 -0
  24. data/lib/vector_salad/mixins/mixins.rb +5 -0
  25. data/lib/vector_salad/monkeypatches.rb +4 -0
  26. data/lib/vector_salad/shape_proxy.rb +1 -0
  27. data/lib/vector_salad/standard_shapes/basic_shape.rb +25 -17
  28. data/lib/vector_salad/standard_shapes/circle.rb +9 -3
  29. data/lib/vector_salad/standard_shapes/clip.rb +10 -9
  30. data/lib/vector_salad/standard_shapes/custom.rb +23 -4
  31. data/lib/vector_salad/standard_shapes/difference.rb +5 -6
  32. data/lib/vector_salad/standard_shapes/exclusion.rb +5 -6
  33. data/lib/vector_salad/standard_shapes/flip.rb +8 -2
  34. data/lib/vector_salad/standard_shapes/heart.rb +3 -1
  35. data/lib/vector_salad/standard_shapes/hexagon.rb +3 -2
  36. data/lib/vector_salad/standard_shapes/intersection.rb +5 -6
  37. data/lib/vector_salad/standard_shapes/iso_tri.rb +3 -2
  38. data/lib/vector_salad/standard_shapes/jitter.rb +10 -7
  39. data/lib/vector_salad/standard_shapes/move.rb +7 -1
  40. data/lib/vector_salad/standard_shapes/multi_path.rb +17 -6
  41. data/lib/vector_salad/standard_shapes/n.rb +6 -3
  42. data/lib/vector_salad/standard_shapes/oval.rb +3 -1
  43. data/lib/vector_salad/standard_shapes/path.rb +28 -14
  44. data/lib/vector_salad/standard_shapes/pentagon.rb +2 -1
  45. data/lib/vector_salad/standard_shapes/polygon.rb +3 -1
  46. data/lib/vector_salad/standard_shapes/rect.rb +3 -1
  47. data/lib/vector_salad/standard_shapes/rotate.rb +3 -5
  48. data/lib/vector_salad/standard_shapes/scale.rb +8 -7
  49. data/lib/vector_salad/standard_shapes/square.rb +6 -21
  50. data/lib/vector_salad/standard_shapes/standard_shapes.rb +6 -0
  51. data/lib/vector_salad/standard_shapes/transform.rb +6 -5
  52. data/lib/vector_salad/standard_shapes/triangle.rb +3 -2
  53. data/lib/vector_salad/standard_shapes/union.rb +5 -6
  54. data/lib/vector_salad/version.rb +1 -1
  55. data/lib/vector_salad.rb +1 -1
  56. metadata +4 -2
@@ -1,21 +1,20 @@
1
- require 'vector_salad/standard_shapes/clip'
2
- require 'vector_salad/standard_shapes/multi_path'
1
+ require "vector_salad/standard_shapes/clip"
2
+ require "vector_salad/standard_shapes/multi_path"
3
3
 
4
4
  module VectorSalad
5
5
  module StandardShapes
6
+ # Subtract the contained shapes.
6
7
  class Difference < Clip
7
- # Subtract paths.
8
8
  # The first path is used as the subject, subsequent paths are subtracted
9
9
  # from the first.
10
10
  #
11
- # Examples:
12
- #
11
+ # @example
13
12
  # Difference.new do
14
13
  # canvas << Path.new([0,0], [90,90], [0,90])
15
14
  # canvas << Path.new([50,0], [95,0], [50, 70])
16
15
  # end
17
16
  #
18
- # # Using DSL:
17
+ # @example Using DSL:
19
18
  # difference do
20
19
  # path([0,0], [90,90], [0,90])
21
20
  # path([50,0], [95,0], [50, 70])
@@ -1,21 +1,20 @@
1
- require 'vector_salad/standard_shapes/clip'
2
- require 'vector_salad/standard_shapes/multi_path'
1
+ require "vector_salad/standard_shapes/clip"
2
+ require "vector_salad/standard_shapes/multi_path"
3
3
 
4
4
  module VectorSalad
5
5
  module StandardShapes
6
+ # Exclude the contained shapes.
6
7
  class Exclusion < Clip
7
- # Exclude paths.
8
8
  # The first path is used as the subject, subsequent paths are excluded
9
9
  # from the first.
10
10
  #
11
- # Examples:
12
- #
11
+ # @example
13
12
  # Exclusion.new do
14
13
  # canvas << Path.new([0,0], [90,90], [0,90])
15
14
  # canvas << Path.new([50,0], [95,0], [50, 70])
16
15
  # end
17
16
  #
18
- # # Using DSL:
17
+ # @example Using DSL:
19
18
  # exclusion do
20
19
  # path([0,0], [90,90], [0,90])
21
20
  # path([50,0], [95,0], [50, 70])
@@ -1,12 +1,18 @@
1
- require 'vector_salad/standard_shapes/transform'
1
+ require "vector_salad/standard_shapes/transform"
2
2
 
3
3
  module VectorSalad
4
4
  module StandardShapes
5
+ # Flip the contained shapes.
5
6
  class Flip < Transform
6
7
  # Flip the contained shapes on the specified axis.
7
8
  #
8
- # Examples:
9
+ # @example
10
+ # Flip.new(:x) do
11
+ # canvas << Triangle.new(30, at: [50, -50])
12
+ # canvas << Pentagon.new(40, at: [50, -100])
13
+ # end
9
14
  #
15
+ # @example Using DSL:
10
16
  # flip(:x) do
11
17
  # triangle(30, at: [50, -50])
12
18
  # pentagon(40, at: [50, -100])
@@ -2,6 +2,7 @@ require "vector_salad/standard_shapes/polygon"
2
2
 
3
3
  module VectorSalad
4
4
  module StandardShapes
5
+ # Perfect heart shape.
5
6
  class Heart < BasicShape
6
7
  # Create a perfect heart
7
8
  Contract Pos, {} => Heart
@@ -16,13 +17,14 @@ module VectorSalad
16
17
  circle(r)[-r, 0]
17
18
  circle(r)[r, 0]
18
19
  difference do
19
- square(l)[-l/2, -l/2].rotate(45).move(0, r2 / 2)
20
+ square(l)[-l / 2, -l / 2].rotate(45).move(0, r2 / 2)
20
21
  square(l * 2)[-l, -l * 2]
21
22
  end
22
23
  end
23
24
  self
24
25
  end
25
26
 
27
+ # Convert the shape to a path
26
28
  def to_path
27
29
  @shape
28
30
  end
@@ -1,11 +1,12 @@
1
- require 'vector_salad/standard_shapes/polygon'
1
+ require "vector_salad/standard_shapes/polygon"
2
2
 
3
3
  module VectorSalad
4
4
  module StandardShapes
5
+ # Regular hexagon shape.
5
6
  class Hexagon < Polygon
6
7
  # Create a regular hexagon.
7
8
  #
8
- # Examples:
9
+ # @example
9
10
  # new(100)
10
11
  def initialize(radius, **options)
11
12
  super(6, radius, **options)
@@ -1,21 +1,20 @@
1
- require 'vector_salad/standard_shapes/clip'
2
- require 'vector_salad/standard_shapes/multi_path'
1
+ require "vector_salad/standard_shapes/clip"
2
+ require "vector_salad/standard_shapes/multi_path"
3
3
 
4
4
  module VectorSalad
5
5
  module StandardShapes
6
+ # Intersect the contained shapes.
6
7
  class Intersection < Clip
7
- # Intersect paths.
8
8
  # The first path is used as the subject, subsequent paths are intersected
9
9
  # with the first.
10
10
  #
11
- # Examples:
12
- #
11
+ # @example
13
12
  # Intersection.new do
14
13
  # canvas << Path.new([0,0], [90,90], [0,90])
15
14
  # canvas << Path.new([50,0], [95,0], [50, 70])
16
15
  # end
17
16
  #
18
- # # Using DSL:
17
+ # @example Using DSL:
19
18
  # intersection do
20
19
  # path([0,0], [90,90], [0,90])
21
20
  # path([50,0], [95,0], [50, 70])
@@ -4,6 +4,7 @@ require "vector_salad/mixins/at"
4
4
 
5
5
  module VectorSalad
6
6
  module StandardShapes
7
+ # Isosceles or right-angle triangle shape.
7
8
  class IsoTri < BasicShape
8
9
  include VectorSalad::Mixins::At
9
10
 
@@ -11,8 +12,7 @@ module VectorSalad
11
12
 
12
13
  # Create an isosceles or right-angle triangle.
13
14
  #
14
- # Examples:
15
- #
15
+ # @example
16
16
  # new(100)
17
17
  # new(100, 150)
18
18
  #
@@ -26,6 +26,7 @@ module VectorSalad
26
26
  self
27
27
  end
28
28
 
29
+ # Convert the shape to a path
29
30
  def to_path
30
31
  Path.new(
31
32
  N.n(@x, @y),
@@ -1,18 +1,21 @@
1
- require 'vector_salad/standard_shapes/transform'
1
+ require "vector_salad/standard_shapes/transform"
2
2
 
3
3
  module VectorSalad
4
4
  module StandardShapes
5
+ # Jitter the position of nodes in the contained shapes randomly.
5
6
  class Jitter < Transform
6
- # Moves the contained shapes by the specified x and y amounts relatively.
7
- #
8
- # Examples:
7
+ # @example
8
+ # Jitter.new(5) do
9
+ # canvas << Triangle.new(30, at: [50, -50])
10
+ # canvas << Pentagon.new(40, at: [50, -100])
11
+ # end
9
12
  #
10
- # move(50, -10) do
13
+ # @example Using DSL:
14
+ # jitter(5) do
11
15
  # triangle(30, at: [50, -50])
12
16
  # pentagon(40, at: [50, -100])
13
17
  # end
14
-
15
- # Jitter the position of nodes in a Path randomly.
18
+ #
16
19
  # @param max The maximum offset
17
20
  # @param min The minimum offset (default 0)
18
21
  # @param fn The quantization number of sides
@@ -2,11 +2,17 @@ require "vector_salad/standard_shapes/transform"
2
2
 
3
3
  module VectorSalad
4
4
  module StandardShapes
5
+ # Move the contained shapes.
5
6
  class Move < Transform
6
7
  # Moves the contained shapes by the specified x and y amounts relatively.
7
8
  #
8
- # Examples:
9
+ # @example
10
+ # Move.new do
11
+ # canvas << Triangle.new(30, at: [50, -50])
12
+ # canvas << Pentagon.new(40, at: [50, -100])
13
+ # end
9
14
  #
15
+ # @example Using DSL:
10
16
  # move(50, -10) do
11
17
  # triangle(30, at: [50, -50])
12
18
  # pentagon(40, at: [50, -100])
@@ -1,21 +1,22 @@
1
- require 'vector_salad/standard_shapes/basic_shape'
2
- require 'vector_salad/standard_shapes/n'
3
- require 'vector_salad/interpolate'
1
+ require "vector_salad/standard_shapes/basic_shape"
2
+ require "vector_salad/standard_shapes/n"
3
+ require "vector_salad/interpolate"
4
4
 
5
5
  module VectorSalad
6
6
  module StandardShapes
7
+ # MultiPath shape is a collection of Paths.
8
+ # It is mainly to store the result of {Clip} operations.
7
9
  class MultiPath < BasicShape
8
10
  attr_reader :paths, :closed
9
11
 
10
- # A MultiPath is a collection of Paths.
11
- # It is mainly the result of {Clip} operations.
12
12
  # See {Path} for details on constructing paths.
13
13
  #
14
- # Examples:
14
+ # @example
15
15
  # new(
16
16
  # Path.n([0,0], [0,300], [300,300], [300,0], [0,0]),
17
17
  # Path.n([100,100], [200,100], [200,200], [100,200], [100,100])
18
18
  # )
19
+ # @example
19
20
  # new(
20
21
  # [[0,0], [0,300], [300,300], [300,0], [0,0]],
21
22
  # [[100,100], [200,100], [200,200], [100,200], [100,100]]
@@ -56,22 +57,32 @@ module VectorSalad
56
57
  each_send(:scale, multiplier)
57
58
  end
58
59
 
60
+ # Convert the shape to a path
59
61
  def to_path
60
62
  self
61
63
  end
62
64
 
65
+ # Convert the complex paths in the MultiPath to bezier paths.
66
+ def to_bezier_path
67
+ self.class.new(*@paths.map(&:to_bezier_path), **@options)
68
+ end
69
+
70
+ # Convert the complex paths in the MultiPath to cubic bezier paths only.
63
71
  def to_cubic_path
64
72
  self.class.new(*@paths.map(&:to_cubic_path), **@options)
65
73
  end
66
74
 
75
+ # Convert the complex paths in the MultiPath to simple paths.
67
76
  def to_simple_path
68
77
  self.class.new(*@paths.map(&:to_simple_path), **@options)
69
78
  end
70
79
 
80
+ # Returns self
71
81
  def to_multi_path
72
82
  self
73
83
  end
74
84
 
85
+ # Return an array of paths that are and array of node coordinates.
75
86
  def to_a
76
87
  @paths.map(&:to_a)
77
88
  end
@@ -2,6 +2,9 @@ require "vector_salad/mixins/at"
2
2
 
3
3
  module VectorSalad
4
4
  module StandardShapes
5
+ # N node. A node is the simplest primitive but useless on its own.
6
+ # Use nodes to build up a path (see Path).
7
+ # A node is a point in space with x and y coordinates.
5
8
  class N
6
9
  include VectorSalad::Mixins::At
7
10
  include Contracts
@@ -14,9 +17,9 @@ module VectorSalad
14
17
  self
15
18
  end
16
19
 
17
- # A node is the simplest primitive but useless on its own. Use nodes to
18
- # build up a path (see Path). A node is a point in space with x and y
19
- # coordinates. The coordinates must be nil if the node type is :mirror,
20
+ # Create an N node.
21
+ #
22
+ # The coordinates must be nil if the node type is :mirror,
20
23
  # else they must be numeric.
21
24
  #
22
25
  # A node also has a type, the simplest is :node for corners.
@@ -4,13 +4,14 @@ require "vector_salad/mixins/at"
4
4
 
5
5
  module VectorSalad
6
6
  module StandardShapes
7
+ # Oval shape.
7
8
  class Oval < BasicShape
8
9
  include VectorSalad::Mixins::At
9
10
  attr_reader :width, :height
10
11
 
11
12
  # Create an oval.
12
13
  #
13
- # Examples:
14
+ # @example
14
15
  # new(100, 200)
15
16
  Contract Pos, Pos, {} => Oval
16
17
  def initialize(width, height, **options)
@@ -20,6 +21,7 @@ module VectorSalad
20
21
  self
21
22
  end
22
23
 
24
+ # Convert the shape to a path
23
25
  def to_path
24
26
  # http://stackoverflow.com/a/13338311
25
27
  # c = 4 * (Math.sqrt(2) - 1) / 3
@@ -1,19 +1,19 @@
1
- require 'spiro'
1
+ require "spiro"
2
2
 
3
- require 'vector_salad/standard_shapes/basic_shape'
4
- require 'vector_salad/standard_shapes/n'
5
- require 'vector_salad/interpolate'
3
+ require "vector_salad/standard_shapes/basic_shape"
4
+ require "vector_salad/standard_shapes/n"
5
+ require "vector_salad/interpolate"
6
6
 
7
7
  module VectorSalad
8
8
  module StandardShapes
9
+ # The simplest shape primitive, all shapes can be represented as a Path.
9
10
  class Path < BasicShape
10
11
  attr_reader :nodes, :closed
11
12
 
12
- # The simplest shape primitive, all shapes can be represented as a Path.
13
13
  # A path is made up of N nodes and these nodes can have different types
14
14
  # (see N).
15
15
  #
16
- # Examples:
16
+ # @example
17
17
  # new([0,0], [0,1], [1,1])
18
18
  #
19
19
  # @param nodes x,y coordinate arrays or N node instances
@@ -24,17 +24,17 @@ module VectorSalad
24
24
  nodes.each_index do |i|
25
25
  node = nodes[i].class == Array ? N.new(*nodes[i]) : nodes[i]
26
26
  if i == 0 && ![:node, :g2, :g4, :left, :right].include?(node.type)
27
- fail 'First node in a path must be :node or :spiro type.'
27
+ fail "First node in a path must be :node or :spiro type."
28
28
  end
29
29
  case node.type
30
30
  when :cubic
31
31
  unless nodes[i - 1].type == :node ||
32
32
  (nodes[i - 2].type == :node && nodes[i - 1].type == :cubic)
33
- fail ':cubic nodes must follow a :node and at most 1 other :cubic.'
33
+ fail ":cubic node must follow a :node and at most 1 other :cubic."
34
34
  end
35
35
  when :quadratic
36
36
  unless nodes[i - 1].type == :node
37
- fail ':quadratic nodes must follow a :node.'
37
+ fail ":quadratic nodes must follow a :node."
38
38
  end
39
39
  when :mirror
40
40
  if nodes[i - 1].type == :node &&
@@ -48,8 +48,8 @@ module VectorSalad
48
48
 
49
49
  node.type = source.type
50
50
  else
51
- fail ':reflect nodes must be preceeded by a :node with a
52
- :quadratic or :cubic before that.'
51
+ fail ":reflect nodes must be preceeded by a :node with a
52
+ :quadratic or :cubic before that."
53
53
  end
54
54
  when :node
55
55
  end
@@ -96,8 +96,9 @@ module VectorSalad
96
96
 
97
97
  # Flips the path on the specified axis.
98
98
  #
99
- # Examples:
99
+ # @example
100
100
  # flip(:x)
101
+ # @example
101
102
  # flip(:y)
102
103
  Contract Or[:x, :y] => Path
103
104
  def flip(axis)
@@ -111,8 +112,10 @@ module VectorSalad
111
112
  end
112
113
 
113
114
  # Rotates the Path by the specified angle about the origin.
114
- # Examples:
115
+ #
116
+ # @example
115
117
  # rotate(90)
118
+ # @example
116
119
  # rotate(-45)
117
120
  Contract Num => Path
118
121
  def rotate(angle)
@@ -135,6 +138,7 @@ module VectorSalad
135
138
  # Scale a Path by multiplier about the origin.
136
139
  # Supply just 1 multiplier to scale evenly, or x and y multipliers
137
140
  # to stretch or squash the axies.
141
+ #
138
142
  # @param x_multiplier 1 is no change, 2 is double size, 0.5 is half, etc.
139
143
  Contract Num, Maybe[Num] => Path
140
144
  def scale(x_multiplier, y_multiplier = x_multiplier)
@@ -150,6 +154,7 @@ module VectorSalad
150
154
  end
151
155
 
152
156
  # Jitter the position of nodes in a Path randomly.
157
+ #
153
158
  # @param max The maximum offset
154
159
  # @param min The minimum offset (default 0)
155
160
  # @param fn The quantization number of sides
@@ -166,10 +171,13 @@ module VectorSalad
166
171
  )
167
172
  end
168
173
 
174
+ # Convert the path to a path (it returns self)
169
175
  def to_path
170
176
  self
171
177
  end
172
178
 
179
+ # Convert the complex path to a bezier path.
180
+ # This will convert any Spiro curve nodes into beziers.
173
181
  def to_bezier_path
174
182
  path = to_path
175
183
  spiro = false
@@ -180,7 +188,7 @@ module VectorSalad
180
188
  if spiro
181
189
  flat_spline_path = Spiro.spiros_to_splines(flat_path, @closed)
182
190
  if flat_spline_path.nil?
183
- fail 'Spiro failed, try different coordinates or using G2 nodes.'
191
+ fail "Spiro failed, try different coordinates or using G2 nodes."
184
192
  else
185
193
  path = Path.new(*flat_spline_path.map do |n|
186
194
  N.new(n[0], n[1], n[2])
@@ -190,6 +198,9 @@ module VectorSalad
190
198
  path
191
199
  end
192
200
 
201
+ # Convert the path into a cubic bezier path (no quadratics).
202
+ # This will convert any Spiro curve nodes into beziers and
203
+ # any quadratic beziers into cubics.
193
204
  def to_cubic_path
194
205
  path = to_bezier_path.nodes
195
206
  cubic_path = []
@@ -223,6 +234,7 @@ module VectorSalad
223
234
  Path.new(*cubic_path, closed: @closed, **@options)
224
235
  end
225
236
 
237
+ # Flatten any curves in the path into many small straight line segments.
226
238
  def to_simple_path(*_)
227
239
  # convert bezier curves and spiro splines
228
240
  path = to_cubic_path.nodes
@@ -246,10 +258,12 @@ module VectorSalad
246
258
  Path.new(*nodes, closed: @closed, **@options)
247
259
  end
248
260
 
261
+ # Wrap the path in a multi_path.
249
262
  def to_multi_path
250
263
  MultiPath.new(self)
251
264
  end
252
265
 
266
+ # Return the nodes as an array of coordinates.
253
267
  def to_a
254
268
  nodes.map(&:at)
255
269
  end