vector_salad 0.0.7 → 0.0.8

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.
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