vector_salad 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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