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