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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.yardopts +1 -0
- data/README.md +6 -5
- data/examples/birthday.png +0 -0
- data/examples/birthday.svg +604 -604
- data/examples/bunny_card.png +0 -0
- data/examples/bunny_card.svg +110 -110
- data/examples/circles.png +0 -0
- data/examples/circles.svg +2 -2
- data/examples/rects.rb +4 -4
- data/examples/squares.png +0 -0
- data/examples/squares.rb +4 -4
- data/examples/squares.svg +6 -6
- data/lib/contracts_contracts.rb +4 -4
- data/lib/vector_salad/canvas.rb +17 -0
- data/lib/vector_salad/dsl.rb +29 -28
- data/lib/vector_salad/export_with_magic.rb +1 -0
- data/lib/vector_salad/exporters/base_exporter.rb +1 -0
- data/lib/vector_salad/exporters/svg_exporter.rb +30 -22
- data/lib/vector_salad/interpolate.rb +13 -12
- data/lib/vector_salad/magic.rb +4 -3
- data/lib/vector_salad/mixins/at.rb +1 -0
- data/lib/vector_salad/mixins/mixins.rb +5 -0
- data/lib/vector_salad/monkeypatches.rb +4 -0
- data/lib/vector_salad/shape_proxy.rb +1 -0
- data/lib/vector_salad/standard_shapes/basic_shape.rb +25 -17
- data/lib/vector_salad/standard_shapes/circle.rb +9 -3
- data/lib/vector_salad/standard_shapes/clip.rb +10 -9
- data/lib/vector_salad/standard_shapes/custom.rb +23 -4
- data/lib/vector_salad/standard_shapes/difference.rb +5 -6
- data/lib/vector_salad/standard_shapes/exclusion.rb +5 -6
- data/lib/vector_salad/standard_shapes/flip.rb +8 -2
- data/lib/vector_salad/standard_shapes/heart.rb +3 -1
- data/lib/vector_salad/standard_shapes/hexagon.rb +3 -2
- data/lib/vector_salad/standard_shapes/intersection.rb +5 -6
- data/lib/vector_salad/standard_shapes/iso_tri.rb +3 -2
- data/lib/vector_salad/standard_shapes/jitter.rb +10 -7
- data/lib/vector_salad/standard_shapes/move.rb +7 -1
- data/lib/vector_salad/standard_shapes/multi_path.rb +17 -6
- data/lib/vector_salad/standard_shapes/n.rb +6 -3
- data/lib/vector_salad/standard_shapes/oval.rb +3 -1
- data/lib/vector_salad/standard_shapes/path.rb +28 -14
- data/lib/vector_salad/standard_shapes/pentagon.rb +2 -1
- data/lib/vector_salad/standard_shapes/polygon.rb +3 -1
- data/lib/vector_salad/standard_shapes/rect.rb +3 -1
- data/lib/vector_salad/standard_shapes/rotate.rb +3 -5
- data/lib/vector_salad/standard_shapes/scale.rb +8 -7
- data/lib/vector_salad/standard_shapes/square.rb +6 -21
- data/lib/vector_salad/standard_shapes/standard_shapes.rb +6 -0
- data/lib/vector_salad/standard_shapes/transform.rb +6 -5
- data/lib/vector_salad/standard_shapes/triangle.rb +3 -2
- data/lib/vector_salad/standard_shapes/union.rb +5 -6
- data/lib/vector_salad/version.rb +1 -1
- data/lib/vector_salad.rb +1 -1
- metadata +4 -2
@@ -1,21 +1,20 @@
|
|
1
|
-
require
|
2
|
-
require
|
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
|
-
#
|
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
|
-
#
|
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
|
2
|
-
require
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
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
|
-
#
|
9
|
+
# @example
|
9
10
|
# new(100)
|
10
11
|
def initialize(radius, **options)
|
11
12
|
super(6, radius, **options)
|
@@ -1,21 +1,20 @@
|
|
1
|
-
require
|
2
|
-
require
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
7
|
-
#
|
8
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
2
|
-
require
|
3
|
-
require
|
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
|
-
#
|
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
|
-
#
|
18
|
-
#
|
19
|
-
#
|
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
|
-
#
|
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
|
1
|
+
require "spiro"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
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
|
-
#
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|