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