dynamic_images 1.0.0

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,27 @@
1
+ README.rdoc
2
+ README_USAGE.rdoc
3
+ Rakefile
4
+ examples/gtk_window.rb
5
+ examples/named_colors_table.rb
6
+ init.rb
7
+ lib/dynamic_image.rb
8
+ lib/elements/block_element.rb
9
+ lib/elements/element_interface.rb
10
+ lib/elements/image_element.rb
11
+ lib/elements/table_cell_element.rb
12
+ lib/elements/table_element.rb
13
+ lib/elements/text_element.rb
14
+ lib/parsers/xml.dtd
15
+ lib/parsers/xml_parser.rb
16
+ lib/render_image.rb
17
+ lib/sources/color_source.rb
18
+ lib/sources/gradient_source.rb
19
+ lib/sources/source_factory.rb
20
+ test/asym.rb
21
+ test/performance.1.rb
22
+ test/performance.2.rb
23
+ test/performance.3.rb
24
+ test/performance.rb
25
+ test/units1.rb
26
+ test/units2.rb
27
+ Manifest
@@ -0,0 +1,104 @@
1
+ = Dynamic Images
2
+
3
+ Ruby library providing image rendering described by dynamic templates
4
+
5
+ == Dependencies
6
+
7
+ Library is using these libraries:
8
+ * cairo
9
+ * pango
10
+ * rexml
11
+
12
+ == How to install & use
13
+
14
+ Download this repository and place it into your folder.
15
+
16
+ Require init.rb file from library's folder.
17
+
18
+ Optionaly you can load gtk2 libraty too. It's supporting another image formats.
19
+
20
+ For more information how to use or about elements read documentation.
21
+
22
+
23
+
24
+
25
+ = Usage of Library
26
+
27
+ == In general about options
28
+ Options should be in base given as hash. Format of keys in hash is not fixed. You can use +Symbol+ as good as +String+. There is also no difference between "-" and "_" chars.
29
+
30
+ If option accepts more arguments you can specify they in +Array+ and also in +String+. In case you choose +String+ it's necessary to seperate arguments by space char.
31
+
32
+ === Example
33
+ These <tt>Hash</tt>es are considered as absolutelly same.
34
+ * <tt>{:vertical_align => :middle}</tt>, <tt>{'vertical_align' => 'middle'}</tt>, <tt>{"vertical-align" => :middle}</tt>, <tt>{:vertical_align => "middle"}</tt>, <tt>{:vertical_align => :middle}</tt>, etc.
35
+ * <tt>{:to_fit => [:crop, :sentences, 3, :resize]}</tt>, <tt>{:to_fit => "crop sentences 3 resize"}</tt>, <tt>{'to-fit' => "crop sentences 3 resize"}</tt>, etc.
36
+
37
+ == Passing a Block
38
+ In block you can accept object to call methods on it or if you don't accept any argument, block is called in object instance.
39
+
40
+ === Example
41
+ These examples are considered as same.
42
+ DynamicImage.new do |img|
43
+ img.text "<b>Warning</b>"
44
+ img.save! "warning.png"
45
+ end
46
+
47
+ DynamicImage.new do
48
+ text "<b>Warning</b>"
49
+ save! "warning.png"
50
+ end
51
+
52
+ == Image formats
53
+ In base you can save and load all as PNG images. You can enable more formats by loading gtk library. DynamicImage will automatically detect it's loaded.
54
+
55
+ require 'gtk2'
56
+
57
+ == Using with Rails
58
+ To use within Rails application just download this library as plugin.
59
+
60
+ rails plugin install git://github.com/malis/dynamic_images.git
61
+
62
+ You need to add <tt>cairo</tt> and <tt>pango</tt> gems to your Gemfile or environment.rb file.
63
+
64
+ Then just update your controller like this:
65
+
66
+ def show
67
+ @article = Article.find(params[:id])
68
+ respond_to do |format|
69
+ format.html
70
+ format.png { render_image } #or render_image("show.jpg"), find more in doc
71
+ end
72
+ end
73
+
74
+ Do not forgot to add mime types for used image formats to your environment file.
75
+
76
+ Mime::Type.register "image/png", :png
77
+
78
+ Create view articles/show.png.xml.erb like this:
79
+
80
+ <?xml version="1.0" encoding="UTF-8" ?>
81
+ <!DOCTYPE dynamic_images PUBLIC "-//malis//dynamic_images//EN" "https://raw.github.com/malis/dynamic_images/master/lib/parsers/xml.dtd">
82
+ <dynamic_images>
83
+ <dynamic_image width="500" align="center" background="blue 0.5">
84
+ <text font="Arial bold 20"><%= @article.title %></text>
85
+ <text indent="30"><%= @article.text %></text>
86
+ </dynamic_image>
87
+ </dynamic_images>
88
+
89
+ That's all!
90
+
91
+ Library is tested under Rails 2.3.11 (ruby 1.8.7) and 3.1.1 (ruby 1.9.2)
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ == Copying
101
+ Copyright (c) 2012 Dominik Mališ
102
+
103
+ This program is free software.
104
+ You can distribute/modify this program under the terms of the GNU LESSER GENERAL PUBLIC LICENSE.
@@ -0,0 +1,67 @@
1
+ = Usage of Library
2
+
3
+ == In general about options
4
+ Options should be in base given as hash. Format of keys in hash is not fixed. You can use +Symbol+ as good as +String+. There is also no difference between "-" and "_" chars.
5
+
6
+ If option accepts more arguments you can specify they in +Array+ and also in +String+. In case you choose +String+ it's necessary to seperate arguments by space char.
7
+
8
+ === Example
9
+ These <tt>Hash</tt>es are considered as absolutelly same.
10
+ * <tt>{:vertical_align => :middle}</tt>, <tt>{'vertical_align' => 'middle'}</tt>, <tt>{"vertical-align" => :middle}</tt>, <tt>{:vertical_align => "middle"}</tt>, <tt>{:vertical_align => :middle}</tt>, etc.
11
+ * <tt>{:to_fit => [:crop, :sentences, 3, :resize]}</tt>, <tt>{:to_fit => "crop sentences 3 resize"}</tt>, <tt>{'to-fit' => "crop sentences 3 resize"}</tt>, etc.
12
+
13
+ == Passing a Block
14
+ In block you can accept object to call methods on it or if you don't accept any argument, block is called in object instance.
15
+
16
+ === Example
17
+ These examples are considered as same.
18
+ DynamicImage.new do |img|
19
+ img.text "<b>Warning</b>"
20
+ img.save! "warning.png"
21
+ end
22
+
23
+ DynamicImage.new do
24
+ text "<b>Warning</b>"
25
+ save! "warning.png"
26
+ end
27
+
28
+ == Image formats
29
+ In base you can save and load all as PNG images. You can enable more formats by loading gtk library. DynamicImage will automatically detect it's loaded.
30
+
31
+ require 'gtk2'
32
+
33
+ == Using with Rails
34
+ To use within Rails application just download this library as plugin.
35
+
36
+ rails plugin install git://github.com/malis/dynamic_images.git
37
+
38
+ You need to add <tt>cairo</tt> and <tt>pango</tt> gems to your Gemfile or environment.rb file.
39
+
40
+ Then just update your controller like this:
41
+
42
+ def show
43
+ @article = Article.find(params[:id])
44
+ respond_to do |format|
45
+ format.html
46
+ format.png { render_image } #or render_image("show.jpg"), find more in doc
47
+ end
48
+ end
49
+
50
+ Do not forgot to add mime types for used image formats to your environment file.
51
+
52
+ Mime::Type.register "image/png", :png
53
+
54
+ Create view articles/show.png.xml.erb like this:
55
+
56
+ <?xml version="1.0" encoding="UTF-8" ?>
57
+ <!DOCTYPE dynamic_images PUBLIC "-//malis//dynamic_images//EN" "https://raw.github.com/malis/dynamic_images/master/lib/parsers/xml.dtd">
58
+ <dynamic_images>
59
+ <dynamic_image width="500" align="center" background="blue 0.5">
60
+ <text font="Arial bold 20"><%= @article.title %></text>
61
+ <text indent="30"><%= @article.text %></text>
62
+ </dynamic_image>
63
+ </dynamic_images>
64
+
65
+ That's all!
66
+
67
+ Library is tested under Rails 2.3.11 (ruby 1.8.7) and 3.1.1 (ruby 1.9.2)
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('dynamic_images', '1.0.0') do |p|
6
+ p.description = "Ruby library providing image rendering described by dynamic templates"
7
+ p.url = "http://github.com/malis/dynamic_images"
8
+ p.author = "Dominik Malis"
9
+ p.email = "dominik.malis@gmail.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.runtime_dependencies = ["cairo", "pango"]
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "dynamic_images"
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Dominik Malis"]
9
+ s.date = "2012-06-19"
10
+ s.description = "Ruby library providing image rendering described by dynamic templates"
11
+ s.email = "dominik.malis@gmail.com"
12
+ s.extra_rdoc_files = ["README.rdoc", "README_USAGE.rdoc", "lib/dynamic_image.rb", "lib/elements/block_element.rb", "lib/elements/element_interface.rb", "lib/elements/image_element.rb", "lib/elements/table_cell_element.rb", "lib/elements/table_element.rb", "lib/elements/text_element.rb", "lib/parsers/xml.dtd", "lib/parsers/xml_parser.rb", "lib/render_image.rb", "lib/sources/color_source.rb", "lib/sources/gradient_source.rb", "lib/sources/source_factory.rb"]
13
+ s.files = ["README.rdoc", "README_USAGE.rdoc", "Rakefile", "examples/gtk_window.rb", "examples/named_colors_table.rb", "init.rb", "lib/dynamic_image.rb", "lib/elements/block_element.rb", "lib/elements/element_interface.rb", "lib/elements/image_element.rb", "lib/elements/table_cell_element.rb", "lib/elements/table_element.rb", "lib/elements/text_element.rb", "lib/parsers/xml.dtd", "lib/parsers/xml_parser.rb", "lib/render_image.rb", "lib/sources/color_source.rb", "lib/sources/gradient_source.rb", "lib/sources/source_factory.rb", "test/asym.rb", "test/performance.1.rb", "test/performance.2.rb", "test/performance.3.rb", "test/performance.rb", "test/units1.rb", "test/units2.rb", "Manifest", "dynamic_images.gemspec"]
14
+ s.homepage = "http://github.com/malis/dynamic_images"
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Dynamic_images", "--main", "README_USAGE.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = "dynamic_images"
18
+ s.rubygems_version = "1.8.10"
19
+ s.summary = "Ruby library providing image rendering described by dynamic templates"
20
+
21
+ if s.respond_to? :specification_version then
22
+ s.specification_version = 3
23
+
24
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
+ s.add_runtime_dependency(%q<cairo>, [">= 0"])
26
+ s.add_runtime_dependency(%q<pango>, [">= 0"])
27
+ else
28
+ s.add_dependency(%q<cairo>, [">= 0"])
29
+ s.add_dependency(%q<pango>, [">= 0"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<cairo>, [">= 0"])
33
+ s.add_dependency(%q<pango>, [">= 0"])
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'gtk2'
3
+ require File.dirname(__FILE__) + '/../init.rb'
4
+
5
+ class Win < Gtk::Window
6
+ def initialize
7
+ super
8
+ set_title "DynamicImage Gtk Test"
9
+ signal_connect "destroy" do
10
+ Gtk.main_quit
11
+ end
12
+ resize 300, 300
13
+
14
+ @darea = Gtk::DrawingArea.new
15
+ @darea.signal_connect "expose-event" do
16
+ expose
17
+ end
18
+ add @darea
19
+
20
+ show_all
21
+ end
22
+
23
+ private
24
+ def expose
25
+ w = allocation.width
26
+ h = allocation.height
27
+
28
+ DynamicImage.from @darea.window.create_cairo_context do
29
+ block :w => w, :h => h, :bg => [:gradient_radial_repeat, "225deg", "50%", "0%", :lime, "50%", :red, "100%", :orange], :align => :center, :valign => :middle do
30
+ text "Try to resize me!", :font => "Arial bold 48", :color => [:gradient_repeat, "0%", :blue, "100%", :yellow]
31
+ end
32
+ save!
33
+ end
34
+ end
35
+ end
36
+
37
+ Gtk.init
38
+ window = Win.new
39
+ Gtk.main
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../init.rb'
3
+
4
+ DynamicImage.new do
5
+ table :cols => 9 do
6
+ for color in DynamicImageSources::ColorSource.named_colors
7
+ cell :padding => 5, :margin => 2, :background => color do
8
+ text color
9
+ end
10
+ end
11
+ end
12
+ save! "named_colors_table.png"
13
+ end
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ require File.dirname(__FILE__) + '/lib/dynamic_image.rb'
2
+
3
+ if defined? ActionController::Base
4
+ require File.dirname(__FILE__) + '/lib/render_image.rb'
5
+ ActionController::Base.send :include, RenderImage
6
+ end
@@ -0,0 +1,240 @@
1
+ require 'cairo'
2
+ require 'pango'
3
+ require File.dirname(__FILE__) + '/elements/block_element.rb'
4
+ require File.dirname(__FILE__) + '/parsers/xml_parser.rb'
5
+
6
+ # DynamicImage provides interface to create an image in ruby code.
7
+ #
8
+ # :include:../README_USAGE.rdoc
9
+ class DynamicImage < DynamicImageElements::BlockElement
10
+ # Gets original Cairo::Context of Cairo::ImageSurface object if width and height was given in options or it's created from existing source.
11
+ attr_reader :context
12
+
13
+ # DynamicImage accepts options +Hash+. If block is given destroy method is called automatically after a block if source of image isn't Cairo object. Otherwise you have to call destroy manually.
14
+ #
15
+ # === Options
16
+ # Image accepts also all options like BlockElement. See it first.
17
+ #
18
+ # [:auto_destroy]
19
+ # Sets whether to automatically destroy surface if you are using block. Default is true.
20
+ #
21
+ # [:format]
22
+ # Sets the memory format of image data.
23
+ #
24
+ # Valid values are :a1, :a8, :rgb24 and :argb32. See http://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t for details.
25
+ #
26
+ # [:from_source]
27
+ # Creates new DynamicImage from given source if it's supported. It has same behavior as DynamicImage.from.
28
+ #
29
+ def initialize(options = {}, &block) # :yields: block_element
30
+ treat_options options
31
+ @options = options
32
+ use_options :margin
33
+ use_options :padding
34
+ if options[:width] && options[:height] || options[:from_source]
35
+ [:width, :height].each {|key| @options[key] = nil } if options[:from_source] #remove forbidden options
36
+ create_surface
37
+ end
38
+ super options, &block
39
+ destroy_by_block if block
40
+ end
41
+
42
+ private
43
+ def create_surface(use_from_source = true)
44
+ if @options[:from_source] && use_from_source
45
+ if @options[:from_source].is_a? Cairo::Surface
46
+ set_surface_and_context @options[:from_source]
47
+ @options[:auto_destroy] = false
48
+ elsif @options[:from_source].is_a? Cairo::Context
49
+ set_surface_and_context nil, @options[:from_source]
50
+ @options[:auto_destroy] = false
51
+ elsif @options[:from_source].to_s =~ /\.png$/i
52
+ image = Cairo::ImageSurface.from_png @options[:from_source]
53
+ set_surface_and_context image
54
+ set_width image.width, false
55
+ set_height image.height, false
56
+ else
57
+ if defined? Gdk
58
+ pixbuf = Gdk::Pixbuf.new @options[:from_source]
59
+ set_width pixbuf.width, false
60
+ set_height pixbuf.height, false
61
+ create_surface false
62
+ context.save
63
+ context.set_source_pixbuf pixbuf, 0, 0
64
+ context.rectangle 0, 0, pixbuf.width, pixbuf.height
65
+ context.clip
66
+ context.paint
67
+ context.restore
68
+ else
69
+ raise "Unsupported source format of: #{@options[:from_source]}"
70
+ end
71
+ end
72
+ else
73
+ w, h = @options[:width].to_i, @options[:height].to_i
74
+ if @padding
75
+ w += @padding[1] + @padding[3]
76
+ h += @padding[0] + @padding[2]
77
+ end
78
+ if @margin
79
+ w += @margin[1] + @margin[3]
80
+ h += @margin[0] + @margin[2]
81
+ end
82
+ if @border && !@border.empty?
83
+ w += @border[:left][0].to_i if @border[:left]
84
+ w += @border[:right][0].to_i if @border[:right]
85
+ h += @border[:top][0].to_i if @border[:top]
86
+ h += @border[:bottom][0].to_i if @border[:bottom]
87
+ end
88
+ surface_args = [w, h]
89
+ surface_args.unshift({
90
+ :a1 => Cairo::Format::A1,
91
+ :a8 => Cairo::Format::A8,
92
+ :rgb24 => Cairo::Format::RGB24,
93
+ :argb32 => Cairo::Format::ARGB32
94
+ }[@options[:format].to_sym]) if @options[:format]
95
+ @surface = Cairo::ImageSurface.new *surface_args
96
+ @context = Cairo::Context.new @surface
97
+ @context.set_antialias({
98
+ :default => Cairo::ANTIALIAS_DEFAULT,
99
+ :gray => Cairo::ANTIALIAS_GRAY,
100
+ :none => Cairo::ANTIALIAS_NONE,
101
+ :subpixel => Cairo::ANTIALIAS_SUBPIXEL
102
+ }[@options[:antialias].to_sym]) if @options[:antialias]
103
+ end
104
+ end
105
+
106
+ def set_surface_and_context(surface, context = nil)
107
+ @surface = surface
108
+ @context = context || Cairo::Context.new(surface)
109
+ end
110
+
111
+ # Call this if block is given to destroy surface
112
+ def destroy_by_block
113
+ self.destroy if @options[:auto_destroy] != false
114
+ end
115
+
116
+ public
117
+ # Gets left and bottom borders of drawing canvas
118
+ def canvas_border
119
+ x, y = [@options[:width], @options[:height]]
120
+ if @padding
121
+ x += @padding[3]
122
+ y += @padding[0]
123
+ end
124
+ if @margin
125
+ x += @margin[3]
126
+ y += @margin[0]
127
+ end
128
+ if @border && !@border.empty?
129
+ x += @border[:left][0].to_i if @border[:left]
130
+ y += @border[:top][0].to_i if @border[:top]
131
+ end
132
+ [x, y]
133
+ end
134
+
135
+ # Creates new DynamicImage from given source if it's supported. Use it in same way as DynamicImage.new.
136
+ #
137
+ # PNG is always supported as source.
138
+ #
139
+ # If there is +Gdk+ loaded you can use any from <tt>Gdk::Pixbuf.formats</tt> as source. By default, "jpeg", "png" and "ico" are possible file formats to load from, but more formats may be installed.
140
+ #
141
+ def self.from(source, options = {}, &block) # :yields: block_element
142
+ options[:from_source] = source
143
+ DynamicImage.new options, &block
144
+ end
145
+
146
+ # Saves image into file or given IO object (you have to speficify :format option like file extension, f.e. <tt>png</tt>). Image will be drawed only if no file is given. It's usable when you drawing into prepared Cairo object.
147
+ #
148
+ # Block can be given to draw into context directly.
149
+ #
150
+ # PNG format is always supported.
151
+ #
152
+ # If there is +Gdk+ loaded you can use any from <tt>Gdk::Pixbuf.formats</tt> as source. By default, "jpeg", "png" and "ico" are possible file formats to save in, but more formats may be installed.
153
+ #
154
+ # When saving into JPEG format you can pass :quality into options. Valid values are in 0 - 100.
155
+ #
156
+ def save!(file = nil, options = {}, &block) # :yields: context
157
+ treat_options options
158
+ unless context
159
+ canvas_size = final_size
160
+ canvas_size[0] = @options[:width] if @options[:width]
161
+ canvas_size[1] = @options[:height] if @options[:height]
162
+ set_width canvas_size[0], false
163
+ set_height canvas_size[1], false
164
+ create_surface
165
+ end
166
+ draw!
167
+ block.call context if block
168
+ write_to file, options if file
169
+ end
170
+
171
+ private
172
+ def write_to(file, options)
173
+ ext = options[:format]
174
+ ext ||= file.scan(/\.([a-z]+)$/i).flatten.first.downcase
175
+ if ext.to_s == "png"
176
+ context.target.write_to_png file
177
+ else
178
+ raise "Unsupported file type #{ext}" unless defined? Gdk
179
+ w, h = @options[:width], @options[:height]
180
+ pixmap = Gdk::Pixmap.new nil, w, h, 24
181
+ context = pixmap.create_cairo_context
182
+ context.set_source @context.target, 0, 0
183
+ context.paint
184
+ #pixbuf = Gdk::Pixbuf.new gtk.gdk.COLORSPACE_RGB, True, 8, w, h
185
+ pixbuf = Gdk::Pixbuf.from_drawable Gdk::Colormap.system, pixmap, 0, 0, w, h
186
+ begin
187
+ format = Gdk::Pixbuf.formats.select{|f| f.extensions.include? ext.to_s}.first.name
188
+ rescue
189
+ raise "Unsupported file type #{ext}"
190
+ end
191
+ pixbuf.save file, format, (format == "jpeg" && options[:quality] ? {'quality' => options[:quality]} : {})
192
+ end
193
+ end
194
+
195
+ public
196
+ # Saves image content into more images if content is bigger than given image size.
197
+ # Image is cut between elements in first level of elements hierarchy. In case of table it's cut between rows of table.
198
+ #
199
+ # Method accepts limit of pages to be rendered. If no number is given or 0 is passed it's not limited.
200
+ # Give a block returning filename or given IO object (you have to speficify :format option like file extension, f.e. <tt>png</tt>) to saving in it. Block provides index of page which is currently rendered. Index starting at 0.
201
+ #
202
+ # PNG format is always supported.
203
+ #
204
+ # If there is +Gdk+ loaded you can use any from <tt>Gdk::Pixbuf.formats</tt> as source. By default, "jpeg", "png" and "ico" are possible file formats to save in, but more formats may be installed.
205
+ #
206
+ # When saving into JPEG format you can pass :quality into options. Valid values are in 0 - 100.
207
+ #
208
+ # === Example
209
+ # save_endless 4 do |index|
210
+ # "image-#{index}.png"
211
+ # end
212
+ #
213
+ def save_endless!(limit = 0, options = {}, &block) # :yields: index
214
+ raise "Width and height must be set when you saving endless" unless @options[:width] || @options[:height]
215
+ treat_options options
216
+ @drawing_endless = index = 0
217
+ loop do
218
+ draw! 0, 0, index
219
+ write_to block.call(index), options
220
+ destroy
221
+ create_surface
222
+ index += 1
223
+ @drawing_endless = index
224
+ break if index == limit || is_drawed?
225
+ end
226
+ end
227
+
228
+ # Gets index of drawing endless from canvas
229
+ def drawing_endless #:nodoc:
230
+ @drawing_endless
231
+ end
232
+
233
+ # Destroys source objects to free a memory. It's important to call this method when it's finished to avoid a memory leaks.
234
+ #
235
+ # If you passed a block to DynamicImage.new or DynamicImage.from it's called automatically after block is finished.
236
+ def destroy
237
+ @surface.destroy if @surface
238
+ @context.destroy if @context
239
+ end
240
+ end