vector_salad 0.0.1

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 (131) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +89 -0
  6. data/Rakefile +2 -0
  7. data/bin/vector_salad +72 -0
  8. data/examples/birthday.png +0 -0
  9. data/examples/birthday.rb +45 -0
  10. data/examples/birthday.svg +630 -0
  11. data/examples/boolean_operations.png +0 -0
  12. data/examples/boolean_operations.rb +23 -0
  13. data/examples/boolean_operations.svg +151 -0
  14. data/examples/bp_logo.png +0 -0
  15. data/examples/bp_logo.rb +18 -0
  16. data/examples/bp_logo.svg +25 -0
  17. data/examples/bunny_card.png +0 -0
  18. data/examples/bunny_card.rb +219 -0
  19. data/examples/bunny_card.svg +134 -0
  20. data/examples/chill.png +0 -0
  21. data/examples/chill.rb +16 -0
  22. data/examples/chill.svg +86 -0
  23. data/examples/circle_line_segments.png +0 -0
  24. data/examples/circle_line_segments.rb +11 -0
  25. data/examples/circle_line_segments.svg +28 -0
  26. data/examples/circles.png +0 -0
  27. data/examples/circles.rb +14 -0
  28. data/examples/circles.svg +11 -0
  29. data/examples/clip_operations.png +0 -0
  30. data/examples/clip_operations.rb +14 -0
  31. data/examples/clip_operations.svg +8 -0
  32. data/examples/cog_menu.png +0 -0
  33. data/examples/cog_menu.rb +32 -0
  34. data/examples/cog_menu.svg +37 -0
  35. data/examples/cubic_bezier_handles.png +0 -0
  36. data/examples/cubic_bezier_handles.rb +21 -0
  37. data/examples/cubic_bezier_handles.svg +14 -0
  38. data/examples/cubic_circle.png +0 -0
  39. data/examples/cubic_circle.rb +26 -0
  40. data/examples/cubic_circle.svg +29 -0
  41. data/examples/face.png +0 -0
  42. data/examples/face.rb +4 -0
  43. data/examples/face.svg +10 -0
  44. data/examples/flower.png +0 -0
  45. data/examples/flower.rb +23 -0
  46. data/examples/flower.svg +207 -0
  47. data/examples/fox.png +0 -0
  48. data/examples/fox.rb +110 -0
  49. data/examples/fox.svg +31 -0
  50. data/examples/fresh_vector_salad_gui.png +0 -0
  51. data/examples/galaxies.png +0 -0
  52. data/examples/galaxies.rb +60 -0
  53. data/examples/galaxies.svg +5806 -0
  54. data/examples/gold_stars.png +0 -0
  55. data/examples/gold_stars.rb +9 -0
  56. data/examples/gold_stars.svg +12 -0
  57. data/examples/paths.png +0 -0
  58. data/examples/paths.rb +87 -0
  59. data/examples/paths.svg +13 -0
  60. data/examples/pepsi_logo.png +0 -0
  61. data/examples/pepsi_logo.rb +21 -0
  62. data/examples/pepsi_logo.svg +10 -0
  63. data/examples/polygons.png +0 -0
  64. data/examples/polygons.rb +9 -0
  65. data/examples/polygons.svg +13 -0
  66. data/examples/quadratic_bezier_handle.png +0 -0
  67. data/examples/quadratic_bezier_handle.rb +18 -0
  68. data/examples/quadratic_bezier_handle.svg +13 -0
  69. data/examples/rects.png +0 -0
  70. data/examples/rects.rb +10 -0
  71. data/examples/rects.svg +11 -0
  72. data/examples/simple_path.png +0 -0
  73. data/examples/simple_path.rb +29 -0
  74. data/examples/simple_path.svg +8 -0
  75. data/examples/space.png +0 -0
  76. data/examples/space.rb +171 -0
  77. data/examples/space.svg +46453 -0
  78. data/examples/spiro_nodes.png +0 -0
  79. data/examples/spiro_nodes.rb +20 -0
  80. data/examples/spiro_nodes.svg +13 -0
  81. data/examples/squares.png +0 -0
  82. data/examples/squares.rb +14 -0
  83. data/examples/squares.svg +11 -0
  84. data/examples/stars.png +0 -0
  85. data/examples/stars.rb +3 -0
  86. data/examples/stars.svg +30006 -0
  87. data/examples/transforms.png +0 -0
  88. data/examples/transforms.rb +58 -0
  89. data/examples/transforms.svg +121 -0
  90. data/examples/triangles.png +0 -0
  91. data/examples/triangles.rb +8 -0
  92. data/examples/triangles.svg +9 -0
  93. data/lib/contracts_contracts.rb +32 -0
  94. data/lib/vector_salad.rb +5 -0
  95. data/lib/vector_salad/canvas.rb +27 -0
  96. data/lib/vector_salad/dsl.rb +41 -0
  97. data/lib/vector_salad/export_with_magic.rb +29 -0
  98. data/lib/vector_salad/exporters/base_exporter.rb +92 -0
  99. data/lib/vector_salad/exporters/svg_exporter.rb +174 -0
  100. data/lib/vector_salad/interpolate.rb +57 -0
  101. data/lib/vector_salad/magic.rb +17 -0
  102. data/lib/vector_salad/mixins/at.rb +28 -0
  103. data/lib/vector_salad/shape_proxy.rb +14 -0
  104. data/lib/vector_salad/standard_shapes/basic_shape.rb +29 -0
  105. data/lib/vector_salad/standard_shapes/circle.rb +64 -0
  106. data/lib/vector_salad/standard_shapes/clip.rb +51 -0
  107. data/lib/vector_salad/standard_shapes/custom.rb +28 -0
  108. data/lib/vector_salad/standard_shapes/difference.rb +28 -0
  109. data/lib/vector_salad/standard_shapes/exclusion.rb +28 -0
  110. data/lib/vector_salad/standard_shapes/flip.rb +24 -0
  111. data/lib/vector_salad/standard_shapes/hexagon.rb +15 -0
  112. data/lib/vector_salad/standard_shapes/intersection.rb +28 -0
  113. data/lib/vector_salad/standard_shapes/iso_tri.rb +39 -0
  114. data/lib/vector_salad/standard_shapes/jitter.rb +33 -0
  115. data/lib/vector_salad/standard_shapes/move.rb +24 -0
  116. data/lib/vector_salad/standard_shapes/multi_path.rb +82 -0
  117. data/lib/vector_salad/standard_shapes/n.rb +112 -0
  118. data/lib/vector_salad/standard_shapes/oval.rb +51 -0
  119. data/lib/vector_salad/standard_shapes/path.rb +249 -0
  120. data/lib/vector_salad/standard_shapes/pentagon.rb +15 -0
  121. data/lib/vector_salad/standard_shapes/polygon.rb +37 -0
  122. data/lib/vector_salad/standard_shapes/rect.rb +34 -0
  123. data/lib/vector_salad/standard_shapes/rotate.rb +24 -0
  124. data/lib/vector_salad/standard_shapes/scale.rb +34 -0
  125. data/lib/vector_salad/standard_shapes/square.rb +34 -0
  126. data/lib/vector_salad/standard_shapes/transform.rb +20 -0
  127. data/lib/vector_salad/standard_shapes/triangle.rb +15 -0
  128. data/lib/vector_salad/standard_shapes/union.rb +28 -0
  129. data/lib/vector_salad/version.rb +3 -0
  130. data/vector_salad.gemspec +34 -0
  131. metadata +262 -0
@@ -0,0 +1,57 @@
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
+ module VectorSalad
7
+ class Interpolate
8
+ #TOLERANCE = 10 # anything below 50 is roughly good-looking
9
+ TOLERANCE = 0.2 # anything below 50 is roughly good-looking
10
+
11
+ def initialize
12
+ @nodes = []
13
+ end
14
+
15
+ def casteljau(curve)
16
+ if flat_enough? curve
17
+ @nodes << curve[3]
18
+ else
19
+ halves = subdivide(curve)
20
+ casteljau(halves[0])
21
+ casteljau(halves[1])
22
+ end
23
+ end
24
+
25
+ # Early stopping function for the Casteljau algorithm.
26
+ # Is the curve flat enough for visual purposes?
27
+ 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
32
+
33
+ [ax, bx].max + [ay, by].max <= TOLERANCE
34
+ end
35
+
36
+ def midpoint(p, q)
37
+ [(p[0] + q[0]) / 2.0, (p[1] + q[1]) / 2.0]
38
+ end
39
+
40
+ def midpoints(points)
41
+ midpoints = Array.new(points.length - 1)
42
+ midpoints.each_index do |i|
43
+ midpoints[i] = midpoint(points[i], points[i+1])
44
+ end
45
+ midpoints
46
+ end
47
+
48
+ def subdivide(curve)
49
+ first = midpoints(curve)
50
+ second = midpoints(first)
51
+ third = midpoints(second)
52
+
53
+ [[curve[0], first[0], second[0], third[0]],
54
+ [third[0], second[1], first[2], curve[3]]]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ require 'vector_salad/canvas'
2
+ require 'vector_salad/dsl'
3
+ Dir.glob(File.expand_path('../standard_shapes/*.rb', __FILE__)).each do |file|
4
+ require file
5
+ end
6
+
7
+ module VectorSalad
8
+ module Magic
9
+ def canvas
10
+ @vs_canvas ||= VectorSalad::Canvas.new
11
+ end
12
+ end
13
+ end
14
+
15
+ extend VectorSalad::DSL
16
+ extend VectorSalad::Magic
17
+ include VectorSalad::StandardShapes
@@ -0,0 +1,28 @@
1
+ module VectorSalad
2
+ module Mixins
3
+ module At
4
+ include Contracts
5
+
6
+ # Change the absolute x, y coordinates of the shape.
7
+ Contract Num, Num => Any
8
+ def [](x, y)
9
+ @x, @y = x, y
10
+ self
11
+ end
12
+
13
+ # Get the x, y coordinates of the shape.
14
+ Contract None => Coords
15
+ def at
16
+ [@x, @y]
17
+ end
18
+
19
+ # Move the shape relatively.
20
+ Contract Num, Num => Any
21
+ def move(x, y)
22
+ shape = clone
23
+ shape[shape.at[0] + x, shape.at[1] + y]
24
+ shape
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ module VectorSalad
2
+ class ShapeProxy
3
+ attr_reader :shape
4
+
5
+ def initialize(shape)
6
+ @shape = shape
7
+ end
8
+
9
+ def method_missing(name, *args, &block)
10
+ @shape = shape.send(name, *args, &block)
11
+ self
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ require 'forwardable'
2
+ require 'contracts'
3
+ require 'contracts_contracts'
4
+
5
+ module VectorSalad
6
+ module StandardShapes
7
+ class BasicShape
8
+ extend Forwardable
9
+ include Contracts
10
+
11
+ attr_accessor :options
12
+
13
+ delegate [
14
+ :flip,
15
+ :flip_x,
16
+ :flip_y,
17
+ :rotate,
18
+ :move,
19
+ :jitter,
20
+ :scale,
21
+ :to_simple_path,
22
+ :to_bezier_path,
23
+ :to_cubic_path,
24
+ :to_multi_path,
25
+ :to_a
26
+ ] => :to_path
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,64 @@
1
+ require "vector_salad/standard_shapes/path"
2
+ require "vector_salad/standard_shapes/n"
3
+ require "vector_salad/mixins/at"
4
+
5
+ module VectorSalad
6
+ module StandardShapes
7
+ class Circle < Path
8
+ include VectorSalad::Mixins::At
9
+ attr_reader :radius
10
+
11
+ # Create a perfectly round circle.
12
+ #
13
+ # Examples do
14
+ # new(100)
15
+ Contract Pos, {} => Circle
16
+ def initialize(radius, **options)
17
+ @options = options
18
+ @radius = radius
19
+ @x, @y = 0, 0
20
+ self
21
+ end
22
+
23
+ def to_path
24
+ # http://stackoverflow.com/a/13338311
25
+ # c = 4 * (Math.sqrt(2) - 1) / 3
26
+ # c = 0.5522847498307936
27
+ #
28
+ # http://spencermortensen.com/articles/bezier-circle/
29
+ c = 0.551915024494
30
+ d = c * @radius
31
+ Path.new(
32
+ N.n(@x + @radius, @y),
33
+ N.c(@x + @radius, @y + d),
34
+ N.c(@x + d, @y + @radius),
35
+ N.n(@x, @y + @radius),
36
+ N.c(@x - d, @y + @radius),
37
+ N.c(@x - @radius, @y + d),
38
+ N.n(@x - @radius, @y),
39
+ N.c(@x - @radius, @y - d),
40
+ N.c(@x - d, @y - @radius),
41
+ N.n(@x, @y - @radius),
42
+ N.c(@x + d, @y - @radius),
43
+ N.c(@x + @radius, @y - d),
44
+ N.n(@x + @radius, @y),
45
+ **@options
46
+ )
47
+ end
48
+
49
+ def to_simple_path(fn = nil)
50
+ fn ||= (@radius * 2).ceil
51
+
52
+ nodes = []
53
+ arc = (2.0 * Math::PI) / fn
54
+ fn.times do |t|
55
+ a = arc * t
56
+ x = @radius * Math.cos(a) + @x
57
+ y = @radius * Math.sin(a) + @y
58
+ nodes << N.n(x, y)
59
+ end
60
+ Path.new(*nodes, **@options)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,51 @@
1
+ require 'clipper'
2
+
3
+ require 'vector_salad/dsl'
4
+ require 'vector_salad/canvas'
5
+ require 'vector_salad/standard_shapes/multi_path'
6
+
7
+ module VectorSalad
8
+ module StandardShapes
9
+ class Clip < BasicShape
10
+ include VectorSalad::DSL
11
+ include VectorSalad::StandardShapes
12
+
13
+ # Perform a clipping operation on a set of paths or shapes.
14
+ # The first path is used as the subject, subsequent paths are applied to
15
+ # the first using the specified operation.
16
+ #
17
+ # It's easier to use one of the subclasses;
18
+ # (see {Difference}, {Intersection}, {Union}, {Exclusion}).
19
+ Contract Or[*%i(difference intersection union xor)], {}, Proc => MultiPath
20
+ def initialize(operation, **options, &block)
21
+ instance_eval(&block) # canvas is populated
22
+
23
+ clipper = Clipper::Clipper.new
24
+
25
+ i = 0
26
+ canvas.each do |shape|
27
+ method = i == 0 ? 'subject' : 'clip'
28
+ path = shape.to_simple_path.to_a
29
+ if path[0][0].instance_of? Array # MultiPath
30
+ clipper.send("add_#{method}_polygons".to_sym, path)
31
+ else # Path
32
+ clipper.send("add_#{method}_polygon".to_sym, path)
33
+ end
34
+ i += 1
35
+ end
36
+
37
+ @path = MultiPath.new(
38
+ *clipper.send(operation, :non_zero, :non_zero), **options
39
+ )
40
+ end
41
+
42
+ def canvas
43
+ @canvas ||= VectorSalad::Canvas.new
44
+ end
45
+
46
+ def to_path
47
+ @path
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
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
+
6
+ module VectorSalad
7
+ module StandardShapes
8
+ class Custom < BasicShape
9
+ def initialize(name, &block)
10
+ #instance_eval(&block)
11
+ ::VectorSalad::StandardShapes.const_set(name.to_s.capitalize.to_sym, Class.new(BasicShape) do
12
+ include VectorSalad::DSL
13
+ include VectorSalad::StandardShapes
14
+
15
+ define_method(:initialize, &block)
16
+
17
+ def canvas
18
+ @canvas ||= VectorSalad::Canvas.new
19
+ end
20
+
21
+ def to_path
22
+ canvas[0]
23
+ end
24
+ end)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'vector_salad/standard_shapes/clip'
2
+ require 'vector_salad/standard_shapes/multi_path'
3
+
4
+ module VectorSalad
5
+ module StandardShapes
6
+ class Difference < Clip
7
+ # Subtract paths.
8
+ # The first path is used as the subject, subsequent paths are subtracted
9
+ # from the first.
10
+ #
11
+ # Examples:
12
+ #
13
+ # Difference.new do
14
+ # canvas << Path.new([0,0], [90,90], [0,90])
15
+ # canvas << Path.new([50,0], [95,0], [50, 70])
16
+ # end
17
+ #
18
+ # # Using DSL:
19
+ # difference do
20
+ # path([0,0], [90,90], [0,90])
21
+ # path([50,0], [95,0], [50, 70])
22
+ # end
23
+ def initialize(**options, &block)
24
+ super(:difference, **options, &block)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'vector_salad/standard_shapes/clip'
2
+ require 'vector_salad/standard_shapes/multi_path'
3
+
4
+ module VectorSalad
5
+ module StandardShapes
6
+ class Exclusion < Clip
7
+ # Exclude paths.
8
+ # The first path is used as the subject, subsequent paths are excluded
9
+ # from the first.
10
+ #
11
+ # Examples:
12
+ #
13
+ # Exclusion.new do
14
+ # canvas << Path.new([0,0], [90,90], [0,90])
15
+ # canvas << Path.new([50,0], [95,0], [50, 70])
16
+ # end
17
+ #
18
+ # # Using DSL:
19
+ # exclusion do
20
+ # path([0,0], [90,90], [0,90])
21
+ # path([50,0], [95,0], [50, 70])
22
+ # end
23
+ def initialize(**options, &block)
24
+ super(:xor, **options, &block)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require 'vector_salad/standard_shapes/transform'
2
+
3
+ module VectorSalad
4
+ module StandardShapes
5
+ class Flip < Transform
6
+ # Flip the contained shapes on the specified axis.
7
+ #
8
+ # Examples:
9
+ #
10
+ # flip(:x) do
11
+ # triangle(30, at: [50, -50])
12
+ # pentagon(40, at: [50, -100])
13
+ # end
14
+ Contract Or[:x, :y], { canvas: VectorSalad::Canvas }, Proc => Any
15
+ def initialize(axis, canvas:, **_options, &block)
16
+ instance_eval(&block) # inner_canvas is populated
17
+
18
+ @canvas.each do |shape|
19
+ canvas << shape.flip(axis)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ require 'vector_salad/standard_shapes/polygon'
2
+
3
+ module VectorSalad
4
+ module StandardShapes
5
+ class Hexagon < Polygon
6
+ # Create a regular hexagon.
7
+ #
8
+ # Examples:
9
+ # new(100)
10
+ def initialize(radius, **options)
11
+ super(6, radius, **options)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ require 'vector_salad/standard_shapes/clip'
2
+ require 'vector_salad/standard_shapes/multi_path'
3
+
4
+ module VectorSalad
5
+ module StandardShapes
6
+ class Intersection < Clip
7
+ # Intersect paths.
8
+ # The first path is used as the subject, subsequent paths are intersected
9
+ # with the first.
10
+ #
11
+ # Examples:
12
+ #
13
+ # Intersection.new do
14
+ # canvas << Path.new([0,0], [90,90], [0,90])
15
+ # canvas << Path.new([50,0], [95,0], [50, 70])
16
+ # end
17
+ #
18
+ # # Using DSL:
19
+ # intersection do
20
+ # path([0,0], [90,90], [0,90])
21
+ # path([50,0], [95,0], [50, 70])
22
+ # end
23
+ def initialize(**options, &block)
24
+ super(:intersection, **options, &block)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,39 @@
1
+ require "vector_salad/standard_shapes/path"
2
+ require "vector_salad/standard_shapes/n"
3
+ require "vector_salad/mixins/at"
4
+
5
+ module VectorSalad
6
+ module StandardShapes
7
+ class IsoTri < BasicShape
8
+ include VectorSalad::Mixins::At
9
+
10
+ attr_reader :height
11
+
12
+ # Create an isosceles or right-angle triangle.
13
+ #
14
+ # Examples:
15
+ #
16
+ # new(100)
17
+ # new(100, 150)
18
+ #
19
+ # @param width (defaults to height*2).
20
+ Contract Args[Pos], {} => IsoTri
21
+ def initialize(width = nil, height, **options)
22
+ width = height * 2 if width.nil?
23
+ @width, @height = width, height
24
+ @options = options
25
+ @x, @y = 0, 0
26
+ self
27
+ end
28
+
29
+ def to_path
30
+ Path.new(
31
+ N.n(@x, @y),
32
+ N.n(@x - @width / 2, @y + @height),
33
+ N.n(@x + @width / 2, @y + @height),
34
+ **@options
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end