compass-canvas 0.0.4

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.
@@ -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
+