compass-canvas 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+ ===============
3
+
4
+ > Copyright (c) 2011 Stan Angeloff
5
+ >
6
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ > of this software and associated documentation files (the "Software"), to deal
8
+ > in the Software without restriction, including without limitation the rights
9
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ > copies of the Software, and to permit persons to whom the Software is
11
+ > furnished to do so, subject to the following conditions:
12
+ >
13
+ > The above copyright notice and this permission notice shall be included in
14
+ > all copies or substantial portions of the Software.
15
+ >
16
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ > THE SOFTWARE.
@@ -0,0 +1,78 @@
1
+ compass-canvas
2
+ ==============
3
+
4
+ ### Canvas drawing support for Compass with Cairo backend(s)
5
+
6
+ Description
7
+ -----------
8
+
9
+ Canvas is a Compass plugin that provides a drawing surface similar to the `<canvas>` element in JavaScript and [Turtle graphics][turtle] in other programming languages.
10
+ It uses [Cairo][cairo] as a back-end to perform all graphics operations.
11
+ Canvas supports anti-aliasing, vector graphics, gradients, masks, clipping, complex operations like drop shadow and many more.
12
+
13
+ [turtle]: http://en.wikipedia.org/wiki/Turtle_graphics
14
+ [cairo]: http://en.wikipedia.org/wiki/Cairo_(graphics)
15
+
16
+ Installation
17
+ ------------
18
+
19
+ Installation is done through [RubyGems][gems]:
20
+
21
+ gem install compass-canvas
22
+
23
+ ### Dependencies
24
+
25
+ The `compass-canvas` gem depends on the `cairo` gem. In order to install both gems, you must have Cairo's development files present on your system.
26
+ You can usually install these using your OS package manager.
27
+
28
+ #### Ubuntu
29
+
30
+ sudo apt-get install libcairo2-dev
31
+
32
+ [gems]: http://rubygems.org/
33
+
34
+ Example
35
+ -------
36
+
37
+ @import 'canvas';
38
+
39
+ $shape: triangle(10, 10, 310, 10, 160, 190);
40
+
41
+ html {
42
+ background: canvas(320, 200,
43
+ $shape
44
+ brush(10, 10, 160, 100, rgba(red, 0.5) 50%, rgba(red, 0.75))
45
+ fill
46
+ reset
47
+ save
48
+ translate(40, 20)
49
+ scale(0.75, 0.75)
50
+ $shape
51
+ brush(black)
52
+ stroke
53
+ brush(10, 10, 160, 100, rgba(blue, 0.75) 50%, rgba(blue, 0.5))
54
+ fill
55
+ restore
56
+ ) no-repeat 50% 50%;
57
+ }
58
+
59
+ License
60
+ -------
61
+
62
+ Canvas is licensed under the MIT License.
63
+
64
+ ## [Documentation](http://StanAngeloff.github.com/compass-canvas/)
65
+
66
+ [RDoc is available][rdoc] for the entire project.
67
+
68
+ For more information on Cairo, visit [The Cairo graphics tutorial][cairo-tutorial].
69
+
70
+ For a complete reference on Cairo methods, visit [Pycairo documentation][pycairo].
71
+
72
+ [rdoc]: http://rubydoc.info/gems/compass-canvas/frames
73
+ [cairo-tutorial]: http://zetcode.com/tutorials/cairographicstutorial/
74
+ [pycairo]: http://cairographics.org/documentation/pycairo/3/reference/context.html#class-context
75
+
76
+ ### Copyright
77
+
78
+ > Copyright (c) 2011 Stan Angeloff. See [LICENSE.md](https://github.com/StanAngeloff/compass-canvas/blob/master/LICENSE.md) for details.
@@ -0,0 +1,48 @@
1
+ # Canvas drawing support for Compass with Cairo backend(s).
2
+ #
3
+ # This module defines the current project version and useful helper functions.
4
+ #
5
+ # @author Stan Angeloff
6
+ module Compass::Canvas
7
+ # The project and Gem version. When building a Gem file for release, the
8
+ # version is stripped to X.Y.Z. If you are using a Git cloned-repository,
9
+ # the version will end in +.git+.
10
+ VERSION = '0.0.4.git'
11
+
12
+ # The default backend for drawing.
13
+ BACKEND = 'cairo'
14
+
15
+ # Helper function to construct an absolute path to a given directory in
16
+ # the project.
17
+ #
18
+ # @return [String] The absolute path to the directory.
19
+ def self.path_to(directory)
20
+ File.expand_path(File.join(File.dirname(__FILE__), '..', directory))
21
+ end
22
+
23
+ # Locations where plug-ins are installed. These paths are scanned for *.rb files
24
+ # and loaded in order.
25
+ PLUGINS_PATH = [
26
+ Compass::Canvas.path_to('plugins'),
27
+ File.join(ENV['HOME'], '.compass-canvas', 'plugins'),
28
+ File.join(Dir.getwd, 'plugins')
29
+ ]
30
+
31
+ # Default exception class.
32
+ class Exception < ::StandardError; end
33
+ end
34
+
35
+ require 'canvas/actions'
36
+ require 'canvas/constants'
37
+ require 'canvas/backend'
38
+ require 'canvas/configuration'
39
+ require 'canvas/plugins'
40
+ require 'canvas/functions'
41
+
42
+ # Register Canvas as a Compass framework.
43
+ #
44
+ # @see http://compass-style.org/docs/tutorials/extensions/
45
+ Compass::Frameworks.register('canvas',
46
+ :stylesheets_directory => Compass::Canvas.path_to('stylesheets'),
47
+ :templates_directory => Compass::Canvas.path_to('templates')
48
+ )
@@ -0,0 +1,39 @@
1
+ module Compass::Canvas
2
+ # This module contains all actions a backend must implement.
3
+ module Actions
4
+ ANTIALIAS = :antialias
5
+ ARC = :arc
6
+ ARC_REVERSE = :arc_reverse
7
+ BRUSH = :brush
8
+ CLIP = :clip
9
+ CLOSE = :close
10
+ CURVE = :curve
11
+ DASH_PATTERN = :dash_pattern
12
+ FILL = :fill
13
+ FILL_RULE = :fill_rule
14
+ GROUP = :group
15
+ LINE = :line
16
+ LINE_CAP = :line_cap
17
+ LINE_JOIN = :line_join
18
+ LINE_WIDTH = :line_width
19
+ MASK = :mask
20
+ MITER_LIMIT = :miter_limit
21
+ MOVE = :move
22
+ PAINT = :paint
23
+ POP = :pop
24
+ PUSH = :push
25
+ RESET = :reset
26
+ RESTORE = :restore
27
+ RETRIEVE = :retrieve
28
+ ROTATE = :rotate
29
+ SAVE = :save
30
+ SCALE = :scale
31
+ SLOW_BLUR = :slow_blur
32
+ STORE = :store
33
+ STROKE = :stroke
34
+ TOLERANCE = :tolerance
35
+ TRANSFORM = :transform
36
+ TRANSLATE = :translate
37
+ UNCLIP = :unclip
38
+ end
39
+ end
@@ -0,0 +1,145 @@
1
+ require 'base64'
2
+
3
+ # This module defines the base backend class for all implementations
4
+ module Compass::Canvas::Backend
5
+ # Base abstract backend class.
6
+ #
7
+ # Each implementation must respond to four methods:
8
+ # - {Compass::Canvas::Backend::Base::load_dependencies} - initializes the backend by loading third-party dependencies
9
+ # - {Compass::Canvas::Backend::Base::begin_canvas} - initialization code before the canvas is drawn
10
+ # - {Compass::Canvas::Backend::Base::execute_one} - executes a single action on the canvas
11
+ # - {Compass::Canvas::Backend::Base::to_blob} - clean up code, must return a
12
+ # +String+ representation of the canvas in a PNG format
13
+ class Base < Sass::Script::Literal
14
+ # @return [Fixnum] The width of the canvas, in pixels.
15
+ attr_accessor :width
16
+ # @return [Fixnum] The height of the canvas, in pixels.
17
+ attr_accessor :height
18
+ # @return [String] The external file where the backend will be loaded/saved in a PNG format.
19
+ attr_accessor :file
20
+
21
+ # Initializes a new instance of a backend class.
22
+ #
23
+ # @overload initialize(width, height, *actions)
24
+ # @param [Fixnum] width The width of the canvas, in pixels.
25
+ # @param [Fixnum] height The height of the canvas, in pixels.
26
+ # @param [Array<Object>] actions The actions to execute.
27
+ # @overload initialize(file, width, height, *actions)
28
+ # @param [String] file The file where the backend will be saved in a PNG format.
29
+ # @param [Fixnum] width The width of the canvas, in pixels.
30
+ # @param [Fixnum] height The height of the canvas, in pixels.
31
+ # @param [Array<Object>] actions The actions to execute.
32
+ # @overload initialize(file)
33
+ # @param [String] file An external file to read.
34
+ def initialize(*args)
35
+ load_dependencies
36
+ if args[0].is_a?(String)
37
+ file = args.shift
38
+ unless args[1].is_a?(Fixnum)
39
+ if file.include?('url(')
40
+ file = File.join(Compass.configuration.css_path, file.gsub(/^url\(['"]?|["']?\)$/, '').split('?').shift())
41
+ else
42
+ file = File.join(Compass.configuration.images_path, file.split('?').shift())
43
+ end
44
+ end
45
+ @file = file
46
+ end
47
+ if args[0].is_a?(Fixnum)
48
+ @width = args.shift
49
+ @height = args.shift
50
+ end
51
+ @actions = args
52
+ end
53
+
54
+ # Abstract method.
55
+ #
56
+ # Initializes the backend by loading third-party dependencies.
57
+ #
58
+ # @raise [Compass::Canvas::Exception] Backend implementation must override this method.
59
+ def load_dependencies
60
+ raise Compass::Canvas::Exception.new("(#{self.class}) Class must implement '#{this_method}'.")
61
+ end
62
+
63
+ # Abstract method.
64
+ #
65
+ # Initialization code before the canvas is drawn.
66
+ #
67
+ # @raise [Compass::Canvas::Exception] Backend implementation must override this method.
68
+ def begin_canvas
69
+ raise Compass::Canvas::Exception.new("(#{self.class}) Class must implement '#{this_method}'.")
70
+ end
71
+
72
+ # Abstract method.
73
+ #
74
+ # Executes a single action on the canvas.
75
+ #
76
+ # @raise [Compass::Canvas::Exception] Backend implementation must override this method.
77
+ def execute_one(action, *args)
78
+ raise Compass::Canvas::Exception.new("(#{self.class}) Class must implement '#{this_method}'.")
79
+ end
80
+
81
+ # Abstract method.
82
+ #
83
+ # Clean up code, must return a +String+ representation of the canvas in a PNG format.
84
+ #
85
+ # @raise [Compass::Canvas::Exception] Backend implementation must override this method.
86
+ def to_blob
87
+ raise Compass::Canvas::Exception.new("(#{self.class}) Class must implement '#{this_method}'.")
88
+ end
89
+
90
+ # Creates an empty canvas and executes all stored actions.
91
+ def execute
92
+ begin_canvas
93
+ execute_actions
94
+ end
95
+
96
+ # Returns the canvas as a Base64 encoded Data URI or as a file on disk
97
+ # depending on the configuration.
98
+ def value
99
+ execute
100
+ if @file
101
+ extension = '.png'
102
+ filename = @file.chomp(extension) + extension
103
+ path = File.join(Compass.configuration.images_path, filename)
104
+ FileUtils.mkpath(File.dirname(path))
105
+ File.open(path, 'wb') { |io| io << to_blob }
106
+ filename
107
+ else
108
+ "url('data:image/png;base64,#{ Base64.encode64(to_blob).gsub("\n", '') }')"
109
+ end
110
+ end
111
+
112
+ # Serializes the canvas as a Sass type
113
+ def to_s(options = {})
114
+ Sass::Script::String.new(value)
115
+ end
116
+
117
+ protected
118
+
119
+ def execute_actions(actions = nil)
120
+ actions ||= @actions
121
+ actions.each do |child|
122
+ if child.is_a?(Compass::Canvas::Backend::Interface::Base)
123
+ action = child.action
124
+ args = child.args
125
+ elsif child.is_a?(String)
126
+ action = child.to_sym
127
+ args = []
128
+ else
129
+ raise Compass::Canvas::Exception.new("(#{self.class}) Unsupported action: #{child.inspect}")
130
+ end
131
+ execute_one(action, *args)
132
+ end
133
+ self
134
+ end
135
+
136
+ private
137
+
138
+ def this_method
139
+ caller[0][/`([^']*)'/, 1]
140
+ end
141
+ end
142
+ end
143
+
144
+ require 'canvas/backend/interface';
145
+ require 'canvas/backend/cairo';
@@ -0,0 +1,176 @@
1
+ require 'stringio'
2
+
3
+ module Compass::Canvas::Backend
4
+ # Cairo backend implementation.
5
+ class Cairo < Base
6
+ # @return [::Cairo::ImageSurface] The internal image surface.
7
+ attr_accessor :surface
8
+
9
+ # Loads the +cairo+ gem dependency. If it is not on +$LOAD_PATH+, attempts
10
+ # to load RubyGems.
11
+ def load_dependencies
12
+ begin
13
+ require 'cairo'
14
+ rescue LoadError
15
+ require 'rubygems'
16
+ begin
17
+ require 'cairo'
18
+ rescue LoadError
19
+ puts "Compass::Canvas\n_______________\n\n"
20
+ puts "Unable to load Cairo backend. Please install it with the following command:\n\n"
21
+ puts " gem install cairo\n\n"
22
+ puts "For more information, please visit https://github.com/rcairo/rcairo"
23
+ raise
24
+ end
25
+ end
26
+ end
27
+
28
+ # Creates a new +ImageSurface+ and binds a new context to it.
29
+ def begin_canvas
30
+ if @width && @height
31
+ @surface = ::Cairo::ImageSurface.new(::Cairo::FORMAT_ARGB32, @width, @height)
32
+ else
33
+ @surface = ::Cairo::ImageSurface.from_png(@file)
34
+ end
35
+ @context = ::Cairo::Context.new(@surface)
36
+ @context.set_line_width(1)
37
+ @sources = []
38
+ end
39
+
40
+ # Executes a single action on the context bound to the surface.
41
+ def execute_one(action, *args)
42
+ case action
43
+ when Compass::Canvas::Actions::MOVE
44
+ @context.move_to(*args)
45
+ when Compass::Canvas::Actions::LINE
46
+ @context.line_to(*args)
47
+ when Compass::Canvas::Actions::CURVE
48
+ @context.curve_to(*args)
49
+ when Compass::Canvas::Actions::ARC
50
+ @context.arc(*args)
51
+ when Compass::Canvas::Actions::ARC_REVERSE
52
+ @context.arc_negative(*args)
53
+ when Compass::Canvas::Actions::PAINT
54
+ @context.paint(*args)
55
+ when Compass::Canvas::Actions::STROKE
56
+ @context.stroke_preserve(*args)
57
+ when Compass::Canvas::Actions::FILL
58
+ @context.fill_preserve(*args)
59
+ when Compass::Canvas::Actions::LINE_WIDTH
60
+ @context.set_line_width(*args)
61
+ when Compass::Canvas::Actions::PUSH
62
+ @context.push_group
63
+ when Compass::Canvas::Actions::POP
64
+ @context.pop_group_to_source
65
+ when Compass::Canvas::Actions::STORE
66
+ @sources.push(@context.source)
67
+ when Compass::Canvas::Actions::RETRIEVE
68
+ @context.set_source(@sources.pop)
69
+ when Compass::Canvas::Actions::GROUP
70
+ @context.new_sub_path
71
+ when Compass::Canvas::Actions::CLIP
72
+ @context.clip_preserve
73
+ when Compass::Canvas::Actions::UNCLIP
74
+ @context.reset_clip
75
+ when Compass::Canvas::Actions::CLOSE
76
+ @context.close_path
77
+ when Compass::Canvas::Actions::RESET
78
+ @context.new_path
79
+ when Compass::Canvas::Actions::SAVE
80
+ @context.save
81
+ when Compass::Canvas::Actions::RESTORE
82
+ @context.restore
83
+ when Compass::Canvas::Actions::ANTIALIAS
84
+ @context.set_antialias(constant('ANTIALIAS', args))
85
+ when Compass::Canvas::Actions::FILL_RULE
86
+ @context.set_fill_rule(constant('FILL_RULE', args))
87
+ when Compass::Canvas::Actions::TOLERANCE
88
+ @context.set_tolerance(*args)
89
+ when Compass::Canvas::Actions::LINE_CAP
90
+ @context.set_line_cap(constant('LINE_CAP', args))
91
+ when Compass::Canvas::Actions::LINE_JOIN
92
+ @context.set_line_join(constant('LINE_JOIN', args))
93
+ when Compass::Canvas::Actions::MITER_LIMIT
94
+ @context.set_miter_limit(*args)
95
+ when Compass::Canvas::Actions::DASH_PATTERN
96
+ # If at least two lengths exist, create a new pattern
97
+ if args.length > 1
98
+ @context.set_dash(args)
99
+ # Otherwise return to a solid stroke
100
+ else
101
+ @context.set_dash(nil, 0)
102
+ end
103
+ when Compass::Canvas::Actions::TRANSLATE
104
+ @context.translate(*args)
105
+ when Compass::Canvas::Actions::SCALE
106
+ @context.scale(*args)
107
+ when Compass::Canvas::Actions::ROTATE
108
+ @context.rotate(*args)
109
+ when Compass::Canvas::Actions::TRANSFORM
110
+ @context.transform(::Cairo::Matrix.new(*args))
111
+ when Compass::Canvas::Actions::MASK
112
+ type = args.shift
113
+ if type.is_a?(Compass::Canvas::Backend::Cairo)
114
+ surface = type.execute.surface
115
+ if args.length == 1
116
+ pattern = ::Cairo::SurfacePattern.new(surface)
117
+ pattern.set_extend(constant('EXTEND', args))
118
+ @context.mask(pattern)
119
+ else
120
+ x = args.shift if args.length
121
+ y = args.shift if args.length
122
+ @context.mask(surface, x || 0, y || 0)
123
+ end
124
+ elsif type == Compass::Canvas::Actions::RETRIEVE
125
+ @context.mask(@sources.pop)
126
+ else
127
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{action}) Unsupported canvas, Cairo can only mask with Cairo: #{type.inspect}")
128
+ end
129
+ when Compass::Canvas::Actions::BRUSH
130
+ type = args.shift
131
+ case type
132
+ when Compass::Canvas::Constants::SOLID
133
+ components = args.shift
134
+ @context.set_source_rgba(*components)
135
+ when Compass::Canvas::Constants::LINEAR, Compass::Canvas::Constants::RADIAL
136
+ coordinates = args.shift
137
+ stops = args.shift
138
+ gradient = ::Cairo::const_get("#{ type.to_s.sub(/^\w/) { |s| s.capitalize } }Pattern").new(*coordinates)
139
+ stops.each { |value| gradient.add_color_stop_rgba(*value) }
140
+ @context.set_source(gradient)
141
+ when Compass::Canvas::Constants::CANVAS
142
+ canvas = args.shift
143
+ if canvas.is_a?(Compass::Canvas::Backend::Cairo)
144
+ pattern = ::Cairo::SurfacePattern.new(canvas.execute.surface)
145
+ pattern.set_extend(constant('EXTEND', args)) if args.length
146
+ @context.set_source(pattern)
147
+ else
148
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{action}) Unsupported canvas, Cairo can only paint with Cairo: #{canvas.inspect}")
149
+ end
150
+ else
151
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{action}) Unsupported type (supported types are 'solid', 'linear', 'radial'): #{type.inspect}")
152
+ end
153
+ when Compass::Canvas::Actions::SLOW_BLUR
154
+ radius = args.shift
155
+ @context.pseudo_blur(radius) do
156
+ execute_actions(args)
157
+ end
158
+ else
159
+ raise Compass::Canvas::Exception.new("(#{self.class}) '#{action}' is not supported.")
160
+ end
161
+ end
162
+
163
+ # Serializes the +ImageSurface+ to a +String+.
164
+ def to_blob
165
+ stream = StringIO.new
166
+ @surface.write_to_png(stream)
167
+ stream.string
168
+ end
169
+
170
+ private
171
+
172
+ def constant(name, *args)
173
+ ::Cairo::const_get("#{ name.upcase }_#{ args.join('_').gsub('-', '_').upcase }")
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,42 @@
1
+ module Compass::Canvas::Backend
2
+ # This module defines classes for parsing Sass types and constructing Ruby
3
+ # objects for use in backend implementations.
4
+ module Interface
5
+ # Base class for all interface classes.
6
+ #
7
+ # This class unpacks Sass types to Ruby objects before passing them to a
8
+ # backend implementation.
9
+ class Base < Sass::Script::Literal
10
+ # @return [String] The action to take, e.g., +move+, +line+.
11
+ attr_accessor :action
12
+ # @return [Array] The arguments to pass, e.g., +X+ and +Y+ coordinates.
13
+ attr_accessor :args
14
+
15
+ # Initializes a new instance of an interface class.
16
+ #
17
+ # @param [String] action The action to take, e.g., +move+, +line+.
18
+ # @param [Array<Sass::Script::Literal>] args The arguments to pass.
19
+ def initialize(action, *args)
20
+ @action = action.value.to_sym
21
+ unless self.respond_to?(@action)
22
+ recognised = self.class.public_instance_methods - self.class.superclass.public_instance_methods
23
+ raise Compass::Canvas::Exception.new("(#{self.class}) '#{@action}' is not a recognised action. Did you mean any of the following: '#{ recognised.join("', '") }'?")
24
+ end
25
+ arity = self.method(@action).arity.abs
26
+ unless args.length >= arity
27
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{@action}) Wrong number of arguments (#{args.length} for #{arity}).")
28
+ end
29
+ @args = self.send(@action, *args)
30
+ end
31
+
32
+ # @raise [Compass::Canvas::Exception] Interface objects cannot be used as property values.
33
+ def to_s(options = {})
34
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{@action}) Not available in this context.")
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'canvas/backend/interface/context';
41
+ require 'canvas/backend/interface/path';
42
+ require 'canvas/backend/interface/pattern';
@@ -0,0 +1,68 @@
1
+ module Compass::Canvas::Backend::Interface
2
+ # Interface Context class.
3
+ class Context < Base
4
+ # Unpacks argument +width+ from Sass to a Ruby object.
5
+ def line_width(width)
6
+ [width.value]
7
+ end
8
+
9
+ # Unpacks argument +type+ from Sass to a Ruby object.
10
+ def line_cap(type)
11
+ [type.value]
12
+ end
13
+
14
+ # Unpacks argument +type+ from Sass to a Ruby object.
15
+ def line_join(type)
16
+ [type.value]
17
+ end
18
+
19
+ # Unpacks argument +limit+ from Sass to a Ruby object.
20
+ def miter_limit(limit)
21
+ [limit.value]
22
+ end
23
+
24
+ # Unpacks argument +type+ from Sass to a Ruby object.
25
+ def antialias(type)
26
+ if type.is_a?(Sass::Script::Color)
27
+ [type.to_s]
28
+ else
29
+ [type.value]
30
+ end
31
+ end
32
+
33
+ # Unpacks argument +type+ from Sass to a Ruby object.
34
+ def fill_rule(type)
35
+ [type.value]
36
+ end
37
+
38
+ # Unpacks argument +level+ from Sass to a Ruby object.
39
+ def tolerance(level)
40
+ [level.value]
41
+ end
42
+
43
+ # Unpacks arguments +X+ and +Y+ from Sass to Ruby objects.
44
+ def translate(x, y)
45
+ [x.value, y.value]
46
+ end
47
+
48
+ # Unpacks arguments +X+ and +Y+ from Sass to Ruby objects.
49
+ def scale(x, y)
50
+ [x.value, y.value]
51
+ end
52
+
53
+ # Unpacks argument +angle+ from Sass to a Ruby object.
54
+ def rotate(angle)
55
+ [angle.value * (Math::PI / 180.0)]
56
+ end
57
+
58
+ # Unpacks matrix arguments from Sass to Ruby objects.
59
+ def transform(xx, yx, xy, yy, x0, y0)
60
+ [xx.value, yx.value, xy.value, yy.value, x0.value, y0.value]
61
+ end
62
+
63
+ def slow_blur(*args)
64
+ radius = args.shift.value
65
+ [radius].concat(Compass::Canvas::Functions.unpack(args).flatten)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,29 @@
1
+ module Compass::Canvas::Backend::Interface
2
+ # Interface Path class.
3
+ class Path < Base
4
+ # Unpacks arguments +X+ and +Y+ from Sass to Ruby objects.
5
+ def move(x, y)
6
+ [x.value, y.value]
7
+ end
8
+
9
+ # Unpacks arguments +X+ and +Y+ from Sass to Ruby objects.
10
+ def line(x, y)
11
+ [x.value, y.value]
12
+ end
13
+
14
+ # Unpacks arguments +X+[1..3] and +Y+[1..3] from Sass to Ruby objects.
15
+ def curve(x1, y1, x2, y2, x3, y3)
16
+ [x1.value, y1.value, x2.value, y2.value, x3.value, y3.value]
17
+ end
18
+
19
+ # Unpacks arguments +X+, +Y+, +radius+ and +angle+[1..2] from Sass to Ruby objects.
20
+ def arc(x, y, radius, angle1, angle2)
21
+ [x.value, y.value, radius.value, angle1.value * (Math::PI / 180.0), angle2.value * (Math::PI / 180.0)]
22
+ end
23
+
24
+ # @see {arc}
25
+ def arc_reverse(*args)
26
+ arc(*args)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,100 @@
1
+ module Compass::Canvas::Backend::Interface
2
+ # Interface Pattern class.
3
+ class Pattern < Base
4
+ # Unpacks brush arguments to a Ruby object.
5
+ def brush(*args)
6
+ if args.length == 1
7
+ type = args.shift
8
+ if type.is_a?(Sass::Script::Color)
9
+ [Compass::Canvas::Constants::SOLID, Pattern.split(type)]
10
+ elsif type.is_a?(Sass::Script::String) && type.value == Compass::Canvas::Actions::RETRIEVE.to_s
11
+ [Compass::Canvas::Actions::RETRIEVE]
12
+ elsif type.is_a?(Compass::Canvas::Backend::Base)
13
+ [Compass::Canvas::Constants::CANVAS, type]
14
+ else
15
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{@action}) Unsupported solid brush type: #{type.inspect}")
16
+ end
17
+ elsif args.length == 2
18
+ canvas = args.shift
19
+ extends = args.shift
20
+ if canvas.is_a?(Compass::Canvas::Backend::Base) && extends.is_a?(Sass::Script::String)
21
+ [Compass::Canvas::Constants::CANVAS, canvas, extends.value]
22
+ else
23
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{@action}) Unsupported pattern brush type: #{canvas.inspect}")
24
+ end
25
+ elsif args.length > 4
26
+ index = 0
27
+ index = index + 1 while index < args.length && args[index].is_a?(Sass::Script::Number)
28
+ type = Compass::Canvas::Constants::LINEAR if index == 4
29
+ type = Compass::Canvas::Constants::RADIAL if index == 6
30
+ if type
31
+ [type, args.slice(0, index).map { |value| value.value }, Pattern.stops(args.slice(index, args.length))]
32
+ else
33
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{@action}) Unsupported gradient brush type: #{args.inspect}")
34
+ end
35
+ else
36
+ raise Compass::Canvas::Exception.new("(#{self.class}.#{@action}) Unsupported brush type: #{args.inspect}")
37
+ end
38
+ end
39
+
40
+ # Unpacks dash +pattern+ arguments to Ruby objects.
41
+ def dash_pattern(*pattern)
42
+ pattern.map { |value| value.value }
43
+ end
44
+
45
+ # Unpacks +canvas+ and optional arguments to a Ruby object.
46
+ def mask(*args)
47
+ type = args.shift
48
+ if type.is_a?(Sass::Script::String) && type.value == Compass::Canvas::Actions::RETRIEVE.to_s
49
+ type = Compass::Canvas::Actions::RETRIEVE
50
+ end
51
+ [type].concat(args.map { |value| value.value })
52
+ end
53
+
54
+ private
55
+
56
+ def self.split(value)
57
+ [value.red / 255.0, value.green / 255.0, value.blue / 255.0, value.alpha]
58
+ end
59
+
60
+ def self.stops(list)
61
+ result = []
62
+ last_offset = 0
63
+ list.each_with_index do |value, index|
64
+ if value.is_a?(Sass::Script::Color)
65
+ if index > 0
66
+ if index == list.length - 1
67
+ offset = 100
68
+ else
69
+ next_index = 0
70
+ next_offset = nil
71
+ list.slice(index, list.length).each do |next_value|
72
+ next_index = next_index + 1
73
+ if next_value.is_a?(Sass::Script::List)
74
+ next_value.value.each { |child| next_offset = child.value if child.is_a?(Sass::Script::Number) }
75
+ break
76
+ end
77
+ end
78
+ next_offset ||= 100
79
+ offset = last_offset + (next_offset - last_offset) / next_index
80
+ end
81
+ else
82
+ offset = 0
83
+ end
84
+ last_offset = offset
85
+ result.push([last_offset / 100.0].concat(Pattern.split(value)))
86
+ elsif value.is_a?(Sass::Script::List)
87
+ color = nil
88
+ value.value.each do |child|
89
+ color = child if child.is_a?(Sass::Script::Color)
90
+ last_offset = child.value if child.is_a?(Sass::Script::Number)
91
+ end
92
+ result.push([last_offset / 100.0].concat(Pattern.split(color))) if color
93
+ else
94
+ raise Compass::Canvas::Exception.new("(#{self.class}) Unsupported gradient brush color-stop: #{value.inspect}")
95
+ end
96
+ end
97
+ result
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,3 @@
1
+ Compass::Configuration.add_configuration_property(:canvas_backend, "The backend to use for all canvas drawing operations") do
2
+ Compass::Canvas::BACKEND
3
+ end
@@ -0,0 +1,9 @@
1
+ module Compass::Canvas
2
+ # This module contains all constants shared between interfaces and backends.
3
+ module Constants
4
+ CANVAS = :canvas
5
+ LINEAR = :linear
6
+ RADIAL = :radial
7
+ SOLID = :solid
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ require 'canvas/functions/canvas'
2
+ require 'canvas/functions/context'
3
+ require 'canvas/functions/path'
4
+ require 'canvas/functions/pattern'
5
+
6
+ module Compass::Canvas
7
+ # The +Functions+ module aggregates all exported functions.
8
+ #
9
+ # @see Compass::Canvas::Functions::Canvas
10
+ # @see Compass::Canvas::Functions::Context
11
+ # @see Compass::Canvas::Functions::Path
12
+ # @see Compass::Canvas::Functions::Pattern
13
+ module Functions
14
+ include Canvas
15
+ include Context
16
+ include Path
17
+ include Pattern
18
+ include Compass::Canvas::Plugins::Functions
19
+
20
+ def self.unpack(value)
21
+ if value.is_a?(Compass::Canvas::Backend::Interface::Base)
22
+ value
23
+ elsif value.is_a?(Sass::Script::Literal)
24
+ Compass::Canvas::Functions.unpack(value.value)
25
+ elsif value.is_a?(Array)
26
+ value.map { |child| Compass::Canvas::Functions.unpack(child) }
27
+ else
28
+ value
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Exports {Compass::Canvas::Functions} to Sass.
35
+ module Sass::Script::Functions
36
+ include Compass::Canvas::Functions
37
+ end
@@ -0,0 +1,21 @@
1
+ module Compass::Canvas
2
+ module Functions
3
+ # Functions for creating a canvas backend.
4
+ module Canvas
5
+ # Creates a new {Compass::Canvas::Backend}.
6
+ #
7
+ # This function cannot be created in Sass as it is a variadic function.
8
+ #
9
+ # @return [Compass::Canvas::Backend] A new backend instance.
10
+ def canvas(*args)
11
+ backend = Compass.configuration.canvas_backend.sub(/^\w/) { |s| s.capitalize }
12
+ begin
13
+ klass = Compass::Canvas::Backend.const_get(backend)
14
+ rescue NameError
15
+ raise Compass::Canvas::Exception.new("(Compass::Canvas) '#{backend}' backend is not installed.")
16
+ end
17
+ klass.new(*Compass::Canvas::Functions.unpack(args).flatten)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Compass::Canvas
2
+ module Functions
3
+ # Functions for creating a Context interface.
4
+ module Context
5
+ # Creates a new {Compass::Canvas::Backend::Interface::Context}.
6
+ #
7
+ # @return [Compass::Canvas::Backend::Interface::Context] A new Context interface.
8
+ def canvas_context(*args)
9
+ Compass::Canvas::Backend::Interface::Context.new(*args)
10
+ end
11
+
12
+ # Constructs a slow-blur group.
13
+ #
14
+ # This function cannot be created in Sass as it is a variadic function.
15
+ def slow_blur(radius, *args)
16
+ canvas_context(Sass::Script::String.new(Compass::Canvas::Actions::SLOW_BLUR.to_s), *[radius].concat(args))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module Compass::Canvas
2
+ module Functions
3
+ # Functions for creating a Path interface.
4
+ module Path
5
+ # Creates a new {Compass::Canvas::Backend::Interface::Path}.
6
+ #
7
+ # @return [Compass::Canvas::Backend::Interface::Path] A new Path interface.
8
+ def canvas_path(*args)
9
+ Compass::Canvas::Backend::Interface::Path.new(*args)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module Compass::Canvas
2
+ module Functions
3
+ # Functions for creating a Pattern interface.
4
+ module Pattern
5
+ # Creates a new {Compass::Canvas::Backend::Interface::Pattern}.
6
+ #
7
+ # @return [Compass::Canvas::Backend::Interface::Pattern] A new Pattern interface.
8
+ def canvas_pattern(*args)
9
+ Compass::Canvas::Backend::Interface::Pattern.new(*args)
10
+ end
11
+
12
+ # Constructs a paint pattern.
13
+ #
14
+ # This function cannot be created in Sass as it is a variadic function.
15
+ def brush(*args)
16
+ canvas_pattern(Sass::Script::String.new(Compass::Canvas::Actions::BRUSH), *args)
17
+ end
18
+
19
+ # Constructs a dash pattern array from positive On/Off lengths.
20
+ #
21
+ # This function cannot be created in Sass as it is a variadic function.
22
+ def dash_pattern(*args)
23
+ canvas_pattern(Sass::Script::String.new(Compass::Canvas::Actions::DASH_PATTERN), *args)
24
+ end
25
+
26
+ # Constructs a mask from a canvas.
27
+ #
28
+ # This function cannot be created in Sass as it is a variadic function.
29
+ def mask(*args)
30
+ canvas_pattern(Sass::Script::String.new(Compass::Canvas::Actions::MASK), *args)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module Compass::Canvas
2
+ # This module includes external files in registered locations.
3
+ #
4
+ # @see Compass::Canvas::PLUGINS_PATH
5
+ module Plugins
6
+ module Functions; end
7
+ end
8
+
9
+ PLUGINS_PATH.each do |path|
10
+ if File.exists?(path)
11
+ Dir.glob(File.join(path, '**', '*.rb')).each do |plugin|
12
+ require plugin
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module Compass::Canvas::Plugins::Functions
2
+ def slow_drop_shadow(x, y, radius, brush, *args)
3
+ Sass::Script::List.new(
4
+ [Sass::Script::String.new('push')].concat([canvas_context(Sass::Script::String.new('slow_blur'), radius, args)]).concat([Sass::Script::String.new('pop'), Sass::Script::String.new('store')]).concat([
5
+ brush,
6
+ Sass::Script::String.new('save'),
7
+ canvas_context(Sass::Script::String.new('translate'), x, y),
8
+ canvas_pattern(Sass::Script::String.new('mask'), Sass::Script::String.new('retrieve')),
9
+ Sass::Script::String.new('restore')
10
+ ].concat(args)),
11
+ :space
12
+ )
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ @import 'canvas/context';
2
+ @import 'canvas/path';
3
+ @import 'canvas/pattern';
@@ -0,0 +1,43 @@
1
+ @function line-width($width) {
2
+ @return canvas-context('line_width', $width);
3
+ }
4
+
5
+ @function line-cap($type) {
6
+ @return canvas-context('line_cap', $type);
7
+ }
8
+
9
+ @function line-join($type) {
10
+ @return canvas-context('line_join', $type);
11
+ }
12
+
13
+ @function miter-limit($limit) {
14
+ @return canvas-context('miter_limit', $limit);
15
+ }
16
+
17
+ @function antialias($type) {
18
+ @return canvas-context('antialias', $type);
19
+ }
20
+
21
+ @function fill-rule($type) {
22
+ @return canvas-context('fill_rule', $type);
23
+ }
24
+
25
+ @function tolerance($level) {
26
+ @return canvas-context('tolerance', $level);
27
+ }
28
+
29
+ @function translate($x, $y) {
30
+ @return canvas-context('translate', $x, $y);
31
+ }
32
+
33
+ @function scale($x, $y) {
34
+ @return canvas-context('scale', $x, $y);
35
+ }
36
+
37
+ @function rotate($angle) {
38
+ @return canvas-context('rotate', $angle);
39
+ }
40
+
41
+ @function transform($xx: 1.0, $yx: 0.0, $xy: 0.0, $yy: 1.0, $x0: 0.0, $y0: 0.0) {
42
+ @return canvas-context('transform', $xx, $yx, $xy, $yy, $x0, $y0);
43
+ }
@@ -0,0 +1,2 @@
1
+ @import 'canvas/path/primitives';
2
+ @import 'canvas/path/shapes';
@@ -0,0 +1,3 @@
1
+ // @function brush(*args) {}
2
+ // @function dash_pattern(*args) {}
3
+ // @function mask(*args) {}
@@ -0,0 +1,19 @@
1
+ @function move-to($x, $y) {
2
+ @return canvas-path('move', $x, $y);
3
+ }
4
+
5
+ @function line-to($x, $y) {
6
+ @return canvas-path('line', $x, $y);
7
+ }
8
+
9
+ @function arc($x, $y, $radius, $angle1, $angle2) {
10
+ @return canvas-path('arc', $x, $y, $radius, $angle1, $angle2);
11
+ }
12
+
13
+ @function arc-reverse($x, $y, $radius, $angle1, $angle2) {
14
+ @return canvas-path('arc_reverse', $x, $y, $radius, $angle1, $angle2);
15
+ }
16
+
17
+ @function curve-to($x1, $y1, $x2, $y2, $x3, $y3) {
18
+ @return canvas-path('curve', $x1, $y1, $x2, $y2, $x3, $y3);
19
+ }
@@ -0,0 +1,47 @@
1
+ @function rectangle($x1, $y1, $x2, $y2) {
2
+ @return (
3
+ move-to($x1, $y1)
4
+ line-to($x2, $y1)
5
+ line-to($x2, $y2)
6
+ line-to($x1, $y2)
7
+ close
8
+ );
9
+ }
10
+
11
+ @function circle($x, $y, $radius) {
12
+ @return arc($x, $y, $radius, 0, 360);
13
+ }
14
+
15
+ @function rounded-rectangle($x1, $y1, $x2, $y2, $x_radius, $y_radius: $x_radius) {
16
+ $max_x_radius: ($x2 - $x1) / 2.0;
17
+ $max_y_radius: ($y2 - $y1) / 2.0;
18
+ @if $x_radius > $max_x_radius {
19
+ $x_radius: $max_x_radius;
20
+ }
21
+ @if $y_radius > $max_y_radius {
22
+ $y_radius: $max_y_radius;
23
+ }
24
+ $half_x_radius: $x_radius / 2.0;
25
+ $half_y_radius: $y_radius / 2.0;
26
+ @return (
27
+ move-to($x1 + $x_radius, $y1)
28
+ line-to($x2 - $x_radius, $y1)
29
+ curve-to($x2 - $half_x_radius, $y1, $x2, $y1 + $half_y_radius, $x2, $y1 + $y_radius)
30
+ line-to($x2, $y2 - $y_radius)
31
+ curve-to($x2, $y2 - $half_y_radius, $x2 - $half_x_radius, $y2, $x2 - $x_radius, $y2)
32
+ line-to($x1 + $x_radius, $y2)
33
+ curve-to($x1 + $half_x_radius, $y2, $x1, $y2 - $half_y_radius, $x1, $y2 - $y_radius)
34
+ line-to($x1, $y1 + $y_radius)
35
+ curve-to($x1, $y1 + $half_y_radius, $x1 + $half_x_radius, $y1, $x1 + $x_radius, $y1)
36
+ close
37
+ );
38
+ }
39
+
40
+ @function triangle($x1, $y1, $x2, $y2, $x3, $y3) {
41
+ @return (
42
+ move-to($x1, $y1)
43
+ line-to($x2, $y2)
44
+ line-to($x3, $y3)
45
+ close
46
+ )
47
+ }
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: compass-canvas
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 4
10
+ version: 0.0.4
11
+ platform: ruby
12
+ authors:
13
+ - Stan Angeloff
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-14 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: compass
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 29
29
+ segments:
30
+ - 0
31
+ - 11
32
+ version: "0.11"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: cairo
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ hash: 27
44
+ segments:
45
+ - 1
46
+ - 10
47
+ version: "1.10"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description:
51
+ email:
52
+ - stanimir@angeloff.name
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files: []
58
+
59
+ files:
60
+ - README.md
61
+ - LICENSE.md
62
+ - lib/canvas.rb
63
+ - lib/canvas/functions/canvas.rb
64
+ - lib/canvas/functions/path.rb
65
+ - lib/canvas/functions/pattern.rb
66
+ - lib/canvas/functions/context.rb
67
+ - lib/canvas/backend.rb
68
+ - lib/canvas/functions.rb
69
+ - lib/canvas/backend/cairo.rb
70
+ - lib/canvas/backend/interface/path.rb
71
+ - lib/canvas/backend/interface/pattern.rb
72
+ - lib/canvas/backend/interface/context.rb
73
+ - lib/canvas/backend/interface.rb
74
+ - lib/canvas/constants.rb
75
+ - lib/canvas/configuration.rb
76
+ - lib/canvas/plugins.rb
77
+ - lib/canvas/actions.rb
78
+ - stylesheets/_canvas.scss
79
+ - stylesheets/canvas/_pattern.scss
80
+ - stylesheets/canvas/_path.scss
81
+ - stylesheets/canvas/_context.scss
82
+ - stylesheets/canvas/path/_primitives.scss
83
+ - stylesheets/canvas/path/_shapes.scss
84
+ - plugins/drop-shadow.rb
85
+ homepage: http://StanAngeloff.github.com/compass-canvas/
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.8.5
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Canvas drawing support for Compass with Cairo backend(s).
118
+ test_files: []
119
+