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,13 +1,14 @@
|
|
1
|
-
require_relative
|
2
|
-
Dir.glob(File.expand_path(
|
3
|
-
require
|
1
|
+
require_relative "base_exporter.rb"
|
2
|
+
Dir.glob(File.expand_path("../../standard_shapes/*.rb", __FILE__)).each do |f|
|
3
|
+
require f
|
4
4
|
end
|
5
5
|
|
6
6
|
module VectorSalad
|
7
7
|
module Exporters
|
8
|
+
# @api private
|
8
9
|
class SvgExporter < BaseExporter
|
9
10
|
def header
|
10
|
-
puts <<-END.gsub(/^ {10}/,
|
11
|
+
puts <<-END.gsub(/^ {10}/, "")
|
11
12
|
<svg version="1.1"
|
12
13
|
xmlns="http://www.w3.org/2000/svg"
|
13
14
|
width="#{canvas_width}"
|
@@ -16,13 +17,13 @@ module VectorSalad
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def footer
|
19
|
-
puts
|
20
|
+
puts "</svg>"
|
20
21
|
end
|
21
22
|
|
22
23
|
def self.options(options)
|
23
|
-
out =
|
24
|
+
out = ""
|
24
25
|
options.each do |k, v|
|
25
|
-
out << " #{k.to_s.gsub(/_/,
|
26
|
+
out << " #{k.to_s.gsub(/_/, '-')}=\"#{v.to_s.gsub(/_/, '-')}\""
|
26
27
|
end
|
27
28
|
out
|
28
29
|
end
|
@@ -30,59 +31,62 @@ module VectorSalad
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
34
|
module VectorSalad
|
35
35
|
module StandardShapes
|
36
36
|
class Path
|
37
|
+
# Export the shape to an svg string
|
37
38
|
def to_svg
|
38
39
|
svg = '<path d="'
|
39
40
|
svg << to_svg_d_attribute
|
40
41
|
svg << '"'
|
41
42
|
svg << VectorSalad::Exporters::SvgExporter.options(@options)
|
42
|
-
svg <<
|
43
|
+
svg << "/>"
|
43
44
|
end
|
44
45
|
|
46
|
+
# @api private
|
45
47
|
def to_svg_d_attribute
|
46
48
|
nodes = to_bezier_path.nodes
|
47
|
-
svg =
|
49
|
+
svg = ""
|
48
50
|
svg << "M #{nodes[0].x} #{nodes[0].y}"
|
49
51
|
|
50
52
|
nodes[1..-1].each_index do |j|
|
51
|
-
i = j+1
|
53
|
+
i = j + 1
|
52
54
|
|
53
55
|
n = nodes[i]
|
54
56
|
case n.type
|
55
57
|
when :cubic
|
56
|
-
if nodes[i-1].type == :node
|
58
|
+
if nodes[i - 1].type == :node
|
57
59
|
svg << " C #{n.x} #{n.y}"
|
58
|
-
elsif nodes[i-2].type == :node && nodes[i-1].type == :cubic
|
60
|
+
elsif nodes[i - 2].type == :node && nodes[i - 1].type == :cubic
|
59
61
|
svg << ", #{n.x} #{n.y}"
|
60
62
|
end
|
61
63
|
when :quadratic
|
62
64
|
svg << " Q #{n.x} #{n.y}"
|
63
65
|
when :node
|
64
|
-
if nodes[i-1].type == :cubic || nodes[i-1].type == :quadratic
|
66
|
+
if nodes[i - 1].type == :cubic || nodes[i - 1].type == :quadratic
|
65
67
|
svg << ", #{n.x} #{n.y}"
|
66
|
-
elsif nodes[i-1].type == :node
|
68
|
+
elsif nodes[i - 1].type == :node
|
67
69
|
svg << " L #{n.x} #{n.y}"
|
68
70
|
end
|
69
71
|
else
|
70
|
-
|
72
|
+
fail "The SVG exporter doesn't support #{n.type} node type."
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
74
|
-
svg <<
|
76
|
+
svg << " Z" if @closed
|
75
77
|
svg
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
79
81
|
class BasicShape
|
82
|
+
# Export the shape to an svg string
|
80
83
|
def to_svg
|
81
84
|
to_path.to_svg
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
88
|
class MultiPath
|
89
|
+
# Export the shape to an svg string
|
86
90
|
def to_svg
|
87
91
|
svg = '<path d="'
|
88
92
|
paths.each do |path|
|
@@ -90,42 +94,46 @@ module VectorSalad
|
|
90
94
|
end
|
91
95
|
svg << '"'
|
92
96
|
svg << VectorSalad::Exporters::SvgExporter.options(@options)
|
93
|
-
svg <<
|
97
|
+
svg << "/>"
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
97
101
|
class Circle
|
102
|
+
# Export the shape to an svg string
|
98
103
|
def to_svg
|
99
104
|
svg = "<circle cx=\"#{at[0]}\" cy=\"#{at[1]}\" r=\"#{radius}\""
|
100
105
|
svg << VectorSalad::Exporters::SvgExporter.options(@options)
|
101
|
-
svg <<
|
106
|
+
svg << "/>"
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
105
110
|
class Oval
|
111
|
+
# Export the shape to an svg string
|
106
112
|
def to_svg
|
107
113
|
svg = "<ellipse cx=\"#{at[0]}\" cy=\"#{at[1]}\""
|
108
114
|
svg << " rx=\"#{width}\" ry=\"#{height}\""
|
109
115
|
svg << VectorSalad::Exporters::SvgExporter.options(@options)
|
110
|
-
svg <<
|
116
|
+
svg << "/>"
|
111
117
|
end
|
112
118
|
end
|
113
119
|
|
114
120
|
class Square
|
121
|
+
# Export the shape to an svg string
|
115
122
|
def to_svg
|
116
123
|
svg = "<rect x=\"#{at[0]}\" y=\"#{at[1]}\""
|
117
124
|
svg << " width=\"#{size}\" height=\"#{size}\""
|
118
125
|
svg << VectorSalad::Exporters::SvgExporter.options(@options)
|
119
|
-
svg <<
|
126
|
+
svg << "/>"
|
120
127
|
end
|
121
128
|
end
|
122
129
|
|
123
130
|
class Rect
|
131
|
+
# Export the shape to an svg string
|
124
132
|
def to_svg
|
125
133
|
svg = "<rect x=\"#{at[0]}\" y=\"#{at[1]}\""
|
126
134
|
svg << " width=\"#{width}\" height=\"#{height}\""
|
127
135
|
svg << VectorSalad::Exporters::SvgExporter.options(@options)
|
128
|
-
svg <<
|
136
|
+
svg << "/>"
|
129
137
|
end
|
130
138
|
end
|
131
139
|
end
|
@@ -1,11 +1,12 @@
|
|
1
|
-
# Code adapted from:
|
2
|
-
# http://jeremykun.com/2013/05/11/bezier-curves-and-picasso/
|
3
|
-
#
|
4
|
-
# Interpolate a cubic bezier spline into straight line segments,
|
5
|
-
# using the De Casteljau algorithm.
|
6
1
|
module VectorSalad
|
2
|
+
# Code adapted from:
|
3
|
+
# http://jeremykun.com/2013/05/11/bezier-curves-and-picasso/
|
4
|
+
#
|
5
|
+
# Interpolate a cubic bezier spline into straight line segments,
|
6
|
+
# using the De Casteljau algorithm.
|
7
|
+
#
|
8
|
+
# @api private
|
7
9
|
class Interpolate
|
8
|
-
#TOLERANCE = 10 # anything below 50 is roughly good-looking
|
9
10
|
TOLERANCE = 0.2 # anything below 50 is roughly good-looking
|
10
11
|
|
11
12
|
def initialize
|
@@ -25,10 +26,10 @@ module VectorSalad
|
|
25
26
|
# Early stopping function for the Casteljau algorithm.
|
26
27
|
# Is the curve flat enough for visual purposes?
|
27
28
|
def flat_enough?(curve)
|
28
|
-
ax = (3.0*curve[1][0] - 2.0*curve[0][0] - curve[3][0])**2
|
29
|
-
ay = (3.0*curve[1][1] - 2.0*curve[0][1] - curve[3][1])**2
|
30
|
-
bx = (3.0*curve[2][0] - curve[0][0] - 2.0*curve[3][0])**2
|
31
|
-
by = (3.0*curve[2][1] - curve[0][1] - 2.0*curve[3][1])**2
|
29
|
+
ax = (3.0 * curve[1][0] - 2.0 * curve[0][0] - curve[3][0])**2
|
30
|
+
ay = (3.0 * curve[1][1] - 2.0 * curve[0][1] - curve[3][1])**2
|
31
|
+
bx = (3.0 * curve[2][0] - curve[0][0] - 2.0 * curve[3][0])**2
|
32
|
+
by = (3.0 * curve[2][1] - curve[0][1] - 2.0 * curve[3][1])**2
|
32
33
|
|
33
34
|
[ax, bx].max + [ay, by].max <= TOLERANCE
|
34
35
|
end
|
@@ -40,7 +41,7 @@ module VectorSalad
|
|
40
41
|
def midpoints(points)
|
41
42
|
midpoints = Array.new(points.length - 1)
|
42
43
|
midpoints.each_index do |i|
|
43
|
-
midpoints[i] = midpoint(points[i], points[i+1])
|
44
|
+
midpoints[i] = midpoint(points[i], points[i + 1])
|
44
45
|
end
|
45
46
|
midpoints
|
46
47
|
end
|
@@ -51,7 +52,7 @@ module VectorSalad
|
|
51
52
|
third = midpoints(second)
|
52
53
|
|
53
54
|
[[curve[0], first[0], second[0], third[0]],
|
54
|
-
|
55
|
+
[third[0], second[1], first[2], curve[3]]]
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
data/lib/vector_salad/magic.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
Dir.glob(File.expand_path(
|
1
|
+
require "vector_salad/canvas"
|
2
|
+
require "vector_salad/dsl"
|
3
|
+
Dir.glob(File.expand_path("../standard_shapes/*.rb", __FILE__)).each do |file|
|
4
4
|
require file
|
5
5
|
end
|
6
6
|
|
7
7
|
module VectorSalad
|
8
|
+
# @api private
|
8
9
|
module Magic
|
9
10
|
def canvas
|
10
11
|
@vs_canvas ||= VectorSalad::Canvas.new
|
@@ -1,10 +1,14 @@
|
|
1
|
+
# Monkeypatches to core Ruby Fixnum class
|
1
2
|
class Fixnum
|
3
|
+
# Multiply number self by UNIT constant
|
2
4
|
def ~
|
3
5
|
self * UNIT
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
9
|
+
# Monkeypatches to core Ruby Float class
|
7
10
|
class Float
|
11
|
+
# Multiply number self by UNIT constant
|
8
12
|
def ~
|
9
13
|
self * UNIT
|
10
14
|
end
|
@@ -1,29 +1,37 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "forwardable"
|
2
|
+
require "contracts"
|
3
|
+
require "contracts_contracts"
|
4
4
|
|
5
5
|
module VectorSalad
|
6
6
|
module StandardShapes
|
7
|
+
# All shapes must inherit from BasicShape,
|
8
|
+
# with the exception of N node.
|
9
|
+
# It alows many usefulmethods in {Path} to be called on the shape
|
10
|
+
# by delegating them, so see {Path} for more information on all the
|
11
|
+
# methods that shapes can use.
|
12
|
+
#
|
13
|
+
# You can't use BasicShape directly.
|
7
14
|
class BasicShape
|
8
15
|
extend Forwardable
|
9
16
|
include Contracts
|
10
17
|
|
11
18
|
attr_accessor :options
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
# @!macro [attach] property
|
21
|
+
# @!method $2
|
22
|
+
# @see Path#$2
|
23
|
+
def_instance_delegator :to_path, :flip
|
24
|
+
def_instance_delegator :to_path, :flip_x
|
25
|
+
def_instance_delegator :to_path, :flip_y
|
26
|
+
def_instance_delegator :to_path, :rotate
|
27
|
+
def_instance_delegator :to_path, :move
|
28
|
+
def_instance_delegator :to_path, :jitter
|
29
|
+
def_instance_delegator :to_path, :scale
|
30
|
+
def_instance_delegator :to_path, :to_simple_path
|
31
|
+
def_instance_delegator :to_path, :to_bezier_path
|
32
|
+
def_instance_delegator :to_path, :to_cubic_path
|
33
|
+
def_instance_delegator :to_path, :to_multi_path
|
34
|
+
def_instance_delegator :to_path, :to_a
|
27
35
|
end
|
28
36
|
end
|
29
37
|
end
|
@@ -4,13 +4,14 @@ require "vector_salad/mixins/at"
|
|
4
4
|
|
5
5
|
module VectorSalad
|
6
6
|
module StandardShapes
|
7
|
-
|
7
|
+
# Perfect circle shape.
|
8
|
+
class Circle < BasicShape # Used to inherit from Path, check still works
|
8
9
|
include VectorSalad::Mixins::At
|
9
10
|
attr_reader :radius
|
10
11
|
|
11
12
|
# Create a perfectly round circle.
|
12
13
|
#
|
13
|
-
#
|
14
|
+
# @example
|
14
15
|
# new(100)
|
15
16
|
Contract Pos, {} => Circle
|
16
17
|
def initialize(radius, **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
|
@@ -46,8 +48,12 @@ module VectorSalad
|
|
46
48
|
)
|
47
49
|
end
|
48
50
|
|
51
|
+
# Flatten the circle into many small straight line segments.
|
52
|
+
#
|
53
|
+
# @param fn The number of segments
|
54
|
+
Contract Maybe[Num] => Path
|
49
55
|
def to_simple_path(fn = nil)
|
50
|
-
fn ||= (@radius *
|
56
|
+
fn ||= (@radius * 4).ceil
|
51
57
|
|
52
58
|
nodes = []
|
53
59
|
arc = (2.0 * Math::PI) / fn
|
@@ -1,21 +1,20 @@
|
|
1
|
-
require
|
1
|
+
require "clipper"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "vector_salad/dsl"
|
4
|
+
require "vector_salad/canvas"
|
5
|
+
require "vector_salad/standard_shapes/multi_path"
|
6
6
|
|
7
7
|
module VectorSalad
|
8
8
|
module StandardShapes
|
9
|
+
# Perform a clipping operation on the contained paths or shapes.
|
10
|
+
# It's easier to use one of the subclasses;
|
11
|
+
# (see {Difference}, {Intersection}, {Union}, {Exclusion}).
|
9
12
|
class Clip < BasicShape
|
10
13
|
include VectorSalad::DSL
|
11
14
|
include VectorSalad::StandardShapes
|
12
15
|
|
13
|
-
# Perform a clipping operation on a set of paths or shapes.
|
14
16
|
# The first path is used as the subject, subsequent paths are applied to
|
15
17
|
# the first using the specified operation.
|
16
|
-
#
|
17
|
-
# It's easier to use one of the subclasses;
|
18
|
-
# (see {Difference}, {Intersection}, {Union}, {Exclusion}).
|
19
18
|
Contract Or[*%i(difference intersection union xor)], {}, Proc => MultiPath
|
20
19
|
def initialize(operation, **options, &block)
|
21
20
|
instance_eval(&block) # canvas is populated
|
@@ -24,7 +23,7 @@ module VectorSalad
|
|
24
23
|
|
25
24
|
i = 0
|
26
25
|
canvas.each do |shape|
|
27
|
-
method = i == 0 ?
|
26
|
+
method = i == 0 ? "subject" : "clip"
|
28
27
|
path = shape.to_simple_path.to_a
|
29
28
|
if path[0][0].instance_of? Array # MultiPath
|
30
29
|
clipper.send("add_#{method}_polygons".to_sym, path)
|
@@ -39,10 +38,12 @@ module VectorSalad
|
|
39
38
|
)
|
40
39
|
end
|
41
40
|
|
41
|
+
# The canvas the clipping is done on.
|
42
42
|
def canvas
|
43
43
|
@canvas ||= VectorSalad::Canvas.new
|
44
44
|
end
|
45
45
|
|
46
|
+
# Convert the shape to a path
|
46
47
|
def to_path
|
47
48
|
@path
|
48
49
|
end
|
@@ -1,11 +1,30 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "vector_salad/standard_shapes/path"
|
2
|
+
require "vector_salad/standard_shapes/n"
|
3
|
+
require "vector_salad/dsl"
|
4
|
+
require "vector_salad/canvas"
|
5
5
|
|
6
6
|
module VectorSalad
|
7
7
|
module StandardShapes
|
8
|
+
# Make your own custom shape.
|
8
9
|
class Custom < BasicShape
|
10
|
+
# You must name your custom shape to be able to use it later.
|
11
|
+
# The block passed to custom shape is what will create your shape,
|
12
|
+
# it should create just a single shape or MultiPath.
|
13
|
+
# This means you should use {MultiPath} or one of the {Clip} operations
|
14
|
+
# which return MultiPaths to create complex shapes.
|
15
|
+
#
|
16
|
+
# @example Using DSL:
|
17
|
+
# custom(:donut) do |size| # name shape and specify parameters
|
18
|
+
# difference do # return a single shape
|
19
|
+
# circle(size / 2)
|
20
|
+
# circle(size / 3)
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# donut(100)
|
25
|
+
#
|
26
|
+
# @param name The name of your shape in snake_case
|
27
|
+
Contract Symbol, {}, Proc => Custom
|
9
28
|
def initialize(name, **options, &block)
|
10
29
|
::VectorSalad::StandardShapes.const_set(name.to_s.capitalize.to_sym, Class.new(BasicShape) do
|
11
30
|
include VectorSalad::DSL
|