dynamic_images 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,224 @@
1
+ require File.dirname(__FILE__) + '/element_interface.rb'
2
+
3
+ module DynamicImageElements
4
+ # Element is used for compositing another elements in it. All inner elements are positioned relative to block element position.
5
+ class BlockElement
6
+ include ElementInterface
7
+
8
+ # Block element accepts options +Hash+. Block can be given and class provides element self to composite elements in it.
9
+ #
10
+ # Use methods provided by class to add elements.
11
+ #
12
+ # === Options
13
+ # You can use also aliases provided by ElementInterface::OPTIONS_ALIASES in all options +Hash+es.
14
+ #
15
+ # [:align]
16
+ # Sets the align of inner elements. Valid values are :left, :center and :right. Default is :left.
17
+ #
18
+ # It's automatically propagated to directly inner TextElements.
19
+ # [:background]
20
+ # Sets background of element. Accepts value for DynamicImageSources::SourceFactory.
21
+ #
22
+ # Default is transparent.
23
+ # [:border, :border_top, :border_right, :border_bottom, :border_left]
24
+ # Creates border around element. You can specify all sides same by :border or each side separately. It's possible to set all same by :border and override one or more sides by f.e. :border_top.
25
+ #
26
+ # Specify border by +Array+ or +String+ separated by space chars in format <tt>[line_width line_style *color]</tt>. See accepted value for DynamicImageSources::SourceFactory.parse for accepted color sources.
27
+ #
28
+ # Valid <tt>line_style</tt>s are:
29
+ # :solid ───────────────────────────────────
30
+ # :dotted ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪
31
+ # :dashed ─── ─── ─── ─── ─── ─── ─── ─── ───
32
+ # :dashed_bigger ───── ───── ───── ───── ───── ─────
33
+ # :dashed_big ──────── ──────── ──────── ────────
34
+ # :dashed_dot ─── ▪ ─── ▪ ─── ▪ ─── ▪ ─── ▪ ─── ▪
35
+ # :dashed_double_dot ─── ▪ ▪ ─── ▪ ▪ ─── ▪ ▪ ─── ▪ ▪ ───
36
+ # :dashed_big_bigger ───── ─── ───── ─── ───── ─── ─────
37
+ # :double_dotted ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪
38
+ # :triple_dotted ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪
39
+ # :double_dashed_dot ─── ─── ▪ ─── ─── ▪ ─── ─── ▪ ─── ─
40
+ # :double_dashed_double_dot ─── ─── ▪ ▪ ─── ─── ▪ ▪ ─── ─── ▪ ▪
41
+ # :double_dashed_triple_dot ─── ─── ▪ ▪ ▪ ─── ─── ▪ ▪ ▪ ─── ───
42
+ # :triple_dashed_dot ─── ─── ─── ▪ ─── ─── ─── ▪ ─── ───
43
+ # :triple_dashed_double_dot ─── ─── ─── ▪ ▪ ─── ─── ─── ▪ ▪ ───
44
+ #
45
+ # You can add number to +line_style+ name to multiple space size. F.e.: <tt>[1, dotted3, :red]</tt>.
46
+ # [:color]
47
+ # Sets foreground of inner text elements. Accepts value for DynamicImageSources::SourceFactory.
48
+ # [:padding, :padding_top, :padding_right, :padding_bottom, :padding_left]
49
+ # Creates gap between element's border and its content (inner elements). You can specify all sides same by :padding or each side separately.
50
+ #
51
+ # You can specify all sides by :padding key too as an Array or String separated by space chars. Values has to be in this order: top, right, bottom, left.
52
+ # When top equals bottom and right equals left you can use only two numbers to specify both pairs.
53
+ #
54
+ # See :margin for examples. It's similar.
55
+ #
56
+ # [:vertical_align]
57
+ # Sets vertical align of inner elements. Valid values are :top, :middle and :bottom. Default is :top.
58
+ #
59
+ # ==== Common options
60
+ # These options can be given to any element in composition.
61
+ #
62
+ # [:height]
63
+ # Sets height of element. Please note that real height is calculated as height + paddings + margins.
64
+ #
65
+ # You can use percentage as +String+ or +Float+. It will calculate height according to parent's element height. F.e.: <tt>"100%"</tt> or <tt>1.0</tt>.
66
+ # [:margin, :margin_top, :margin_right, :margin_bottom, :margin_left]
67
+ # Creates gap around element and other elements in canvas. You can specify all sides same by :margin or each side separately.
68
+ #
69
+ # You can specify all sides by :margin key too as an Array or String separated by space chars. Values has to be in this order: top, right, bottom, left.
70
+ # When top equals bottom and right equals left you can use only two numbers to specify both pairs.
71
+ #
72
+ # ==== Example
73
+ # * <tt>:margin_top => 10</tt> will create 10 px gap above element, other sides will have no gap
74
+ # * <tt>:margin => 10</tt> will create gap 10 px at all sides
75
+ # * <tt>:margin => [5, 10, 10, 5]</tt> will create gaps 5 px above, 10 px at right side and below, 5 px at left side
76
+ # * <tt>:margin => [5, 10, 10, 5]</tt> is same as <tt>:margin => "5 10 10 5"</tt>
77
+ # * <tt>:margin => [5, 10, 5, 10]</tt> is same as <tt>:margin => [5, 10]</tt> and <tt>:margin => "5 10"</tt>
78
+ #
79
+ # [:position]
80
+ # Moves element from its position. Valid values are :static, :relative, :absolute. Default is :static.
81
+ #
82
+ # Static position doesn't move element from its position. Even if x or y is given.
83
+ #
84
+ # Relative position moves element from its position. Amount of move is given by x and y options and it's added to original position. Other elements are not affected by it.
85
+ #
86
+ # Absolute position removes element from document flow and places element to the position given by x and y into parent element.
87
+ # [:width]
88
+ # Sets width of element. Please note that real width is calculated as width + paddings + margins.
89
+ #
90
+ # You can use percentage as +String+ or +Float+. It will calculate width according to parent's element width. F.e.: <tt>"100%"</tt> or <tt>1.0</tt>.
91
+ # [:x]
92
+ # Horizontal position in parent container (block element)
93
+ # [:y]
94
+ # Vertical position in parent container
95
+ # [:z]
96
+ # Z-order of objects in parent element. Default is 0 and elements are ordered by order they was created in.
97
+ #
98
+ def initialize(options, parent = nil, &block) # :yields: block_element
99
+ @options = options
100
+ @parent = parent
101
+ @elements = [] #should looks like [[:x => int, :y => int, z => int, :obj => Element], ...]
102
+ use_options :margin
103
+ use_options :padding
104
+ use_options :border
105
+ process self, &block if block
106
+ end
107
+
108
+ # Gets width for inner elements
109
+ def width #:nodoc:
110
+ @drawing ? inner_size[0] : (@options[:width] || (@parent && @parent.width ? @parent.width - @padding[1] - @padding[3] - @margin[1] - @margin[3] : nil))
111
+ end
112
+ # Gets height for inner elements
113
+ def height #:nodoc:
114
+ @drawing ? inner_size[1] : (@options[:height] || (@parent && @parent.height ? @parent.height - @padding[0] - @padding[2] - @margin[0] - @margin[2] : nil))
115
+ end
116
+
117
+ private
118
+ def inner_size
119
+ unless @size
120
+ size = [0, 0]
121
+ size = content_size unless @options[:width] && @options[:height]
122
+ size[0] = @options[:width] if @options[:width]
123
+ size[1] = @options[:height] if @options[:height]
124
+ @size = size
125
+ end
126
+ @size
127
+ end
128
+
129
+ def content_size
130
+ size = [0, 0]
131
+ @elements.each do |element|
132
+ element[:obj].final_size.each_with_index do |value, index|
133
+ pos = element[[:x, :y][index]]
134
+ pos = pos.call if pos.class == Proc
135
+ size[index] = value+pos if value+pos > size[index]
136
+ end
137
+ end
138
+ size
139
+ end
140
+
141
+ def draw(x, y, endless)
142
+ final_size
143
+ original_source = context.source
144
+ @drawing = true
145
+ draw_background x, y
146
+ draw_border x, y
147
+ if @padding
148
+ x += @padding[3]
149
+ y += @padding[0]
150
+ end
151
+ content_height = content_size[1]
152
+ @elements.sort{|a, b| a[:z] <=> b[:z]}.each do |element|
153
+ next if element[:obj].is_drawed?
154
+ element[:obj].set_width((width * element[:width]).to_i, false) if element[:width].class == Float
155
+ element[:obj].set_height((height * element[:height]).to_i, false) if element[:height].class == Float
156
+ if element[:position] == :absolute || (element[:obj].class == TextElement && !element[:width])
157
+ x_pos = element[:x].class == Proc ? element[:x].call : element[:x]
158
+ else
159
+ case @options[:align].to_s
160
+ when "right"
161
+ x_pos = inner_size[0] - element[:obj].final_size[0]
162
+ when "center"
163
+ x_pos = (inner_size[0] - element[:obj].final_size[0])/2
164
+ else #left
165
+ x_pos = 0
166
+ end
167
+ end
168
+ y_pos = element[:y].class == Proc ? element[:y].call : element[:y]
169
+ y_pos += (inner_size[1]-content_height)/2 if @options[:vertical_align].to_s == "middle"
170
+ y_pos += inner_size[1]-content_height if @options[:vertical_align].to_s == "bottom"
171
+ @options[:color].set_source context if @options[:color]
172
+ element[:obj].draw! x_pos+x, y_pos+y, endless
173
+ end
174
+ @drawing = false
175
+ context.set_source original_source
176
+ end
177
+
178
+ def add_element(e, options)
179
+ @size = nil
180
+ x = (options[:position].to_s.to_sym == :absolute ? options[:x] : nil) || 0
181
+ last_element = @last_element
182
+ y = (options[:position].to_s.to_sym == :absolute ? options[:y] : nil) || (last_element ? lambda{(last_element[:y].class == Proc ? last_element[:y].call : last_element[:y]) + (last_element[:obj].is_drawed? ? 0 : last_element[:obj].final_size[1])} : 0)
183
+ z = options[:z] || 0
184
+ element = {:x => x, :y => y, :z => z, :position => options[:position].to_s.to_sym, :width => options[:width], :height => options[:height], :obj => e}
185
+ @last_element = element unless element[:position] == :absolute
186
+ e.set_width(width ? (width * options[:width]).to_i : nil, false) if options[:width].class == Float
187
+ e.set_height(height ? (height * options[:height]).to_i : nil, false) if options[:height].class == Float
188
+ @elements << element
189
+ e
190
+ end
191
+
192
+ public
193
+ # Creates new BlockElement as a child of its composite. See BlockElement.new for arguments information.
194
+ def block(options = {}, &block) # :yields: block_element
195
+ treat_options options
196
+ add_element BlockElement.new(options, self, &block), options
197
+ end
198
+
199
+ # Creates new ImageElement as a child of its composite. See ImageElement.new for arguments information.
200
+ def image(source, options = {})
201
+ treat_options options
202
+ add_element ImageElement.new(source, options, self), options
203
+ end
204
+
205
+ # Creates new TableElement as a child of its composite. See TableElement.new for arguments information.
206
+ def table(options = {}, &block) # :yields: table_element
207
+ treat_options options
208
+ add_element TableElement.new(options, self, &block), options
209
+ end
210
+
211
+ # Creates new TextElement as a child of its composite. See TextElement.new for arguments information.
212
+ def text(content, options = {}, &block) # :yields: pango_layout
213
+ treat_options options
214
+ options[:align] = @options[:align] unless options[:align] # propagate align if no one is given
215
+ add_element TextElement.new(content, options, self, &block), options
216
+ end
217
+
218
+
219
+ end
220
+ end
221
+
222
+ require File.dirname(__FILE__) + '/image_element.rb'
223
+ require File.dirname(__FILE__) + '/table_element.rb'
224
+ require File.dirname(__FILE__) + '/text_element.rb'
@@ -0,0 +1,320 @@
1
+ require File.dirname(__FILE__) + '/../sources/source_factory.rb'
2
+
3
+ # Module keeps all elements that can be placed into composition of canvas.
4
+ module DynamicImageElements
5
+ # Interface providing default methods for all elements in composite. Also contain some private methods which helps element to process common tasks.
6
+ module ElementInterface
7
+ protected
8
+ # Gets original Cairo::Context object if width and height was given in options or it's created from existing source.
9
+ def context #:nodoc:
10
+ @parent.context
11
+ end
12
+
13
+ # Gets left and bottom borders of drawing canvas
14
+ def canvas_border #:nodoc:
15
+ @parent.canvas_border
16
+ end
17
+
18
+ # Gets index of drawing endless from canvas
19
+ def drawing_endless #:nodoc:
20
+ @parent.drawing_endless
21
+ end
22
+
23
+ # Draws element and its inner element if there are any
24
+ def draw!(x = 0, y = 0, endless = false) #:nodoc:
25
+ return if is_drawed?
26
+ if @margin
27
+ x += @margin[3]
28
+ y += @margin[0]
29
+ end
30
+ if @options[:position].to_sym == :relative
31
+ x += @options[:x].to_i
32
+ y += @options[:y].to_i
33
+ end
34
+ if @border && !@border.empty?
35
+ x += @border[:left][0] if @border[:left]
36
+ y += @border[:top][0] if @border[:top]
37
+ end
38
+ unless endless && @parent
39
+ draw x, y, endless
40
+ @drawed = endless
41
+ else
42
+ element_border = final_size.each_with_index.map {|value, index| value + [x, y][index] }
43
+ if element_border[0] <= canvas_border[0] && element_border[1] <= canvas_border[1]
44
+ draw x, y, endless
45
+ @drawed = endless
46
+ end
47
+ end
48
+ end
49
+
50
+ # Gets whether element is drawed
51
+ def is_drawed? #:nodoc:
52
+ if @elements && !@elements.size.zero?
53
+ @elements.map{|e| e[:obj].is_drawed? }.inject(&:&)
54
+ else
55
+ @drawed && @drawed != drawing_endless
56
+ end
57
+ end
58
+
59
+ private
60
+ def draw(x, y, endless)
61
+ raise Exception.new "not implemented in #{self.class}, but should be"
62
+ end
63
+
64
+ # Gives array that contains size of dimensions provided for inner elements.
65
+ # It's calculated as #element_size - <i>padding</i>
66
+ def inner_size
67
+ raise Exception.new "not implemented in #{self.class}, but should be"
68
+ end
69
+
70
+ # Gives array that contains real size of element.
71
+ def element_size
72
+ w, h = inner_size
73
+ if @padding
74
+ w += @padding[1] + @padding[3]
75
+ h += @padding[0] + @padding[2]
76
+ end
77
+ [w, h]
78
+ end
79
+
80
+ public
81
+ # Gives array that contains size of space occupied of element.
82
+ # It's calculated as #element_size + <i>margin</i>
83
+ def final_size
84
+ w, h = element_size
85
+ if @margin
86
+ w += @margin[1] + @margin[3]
87
+ h += @margin[0] + @margin[2]
88
+ end
89
+ if @border && !@border.empty?
90
+ w += @border[:left][0] if @border[:left]
91
+ w += @border[:right][0] if @border[:right]
92
+ h += @border[:top][0] if @border[:top]
93
+ h += @border[:bottom][0] if @border[:bottom]
94
+ end
95
+ [w, h]
96
+ end
97
+
98
+ # Changes element's width option
99
+ def set_width(width, inner = true) #:nodoc:
100
+ if width.nil?
101
+ @options[:width] = nil
102
+ elsif width > 0
103
+ unless inner
104
+ width -= @padding[1] + @padding[3] if @padding
105
+ width -= @margin[1] + @margin[3] if @margin
106
+ if @border && !@border.empty?
107
+ width -= @border[:left][0] if @border[:left]
108
+ width -= @border[:right][0] if @border[:right]
109
+ end
110
+ end
111
+ @options[:width] = width < 0 ? 0 : width
112
+ recalculate_size!
113
+ end
114
+ end
115
+ # Changes element's height option
116
+ def set_height(height, inner = true) #:nodoc:
117
+ if height.nil?
118
+ @options[:height] = nil
119
+ elsif height > 0
120
+ unless inner
121
+ height -= @padding[0] + @padding[2] if @padding
122
+ height -= @margin[0] + @margin[2] if @margin
123
+ if @border && !@border.empty?
124
+ height -= @border[:top][0] if @border[:top]
125
+ height -= @border[:bottom][0] if @border[:bottom]
126
+ end
127
+ end
128
+ @options[:height] = height < 0 ? 0 : height
129
+ recalculate_size!
130
+ end
131
+ end
132
+
133
+ protected
134
+ # Clears cached size to force recalculating
135
+ def recalculate_size! #:nodoc:
136
+ @size = nil #nullify any calculated size
137
+ @elements.each {|e| e[:obj].recalculate_size! } if @elements #propagate size change to inner elements if there is any
138
+ end
139
+
140
+ private
141
+ # Processes a given block. Yields objects if the block expects any arguments.
142
+ # Otherwise evaluates the block in the context of first object.
143
+ def process(*objects, &block)
144
+ if block.arity > 0
145
+ yield *objects
146
+ else
147
+ objects.first.instance_eval &block
148
+ end
149
+ end
150
+
151
+ # Parse common options from @options +Hash+ by meta-key
152
+ def use_options(metakey)
153
+ if metakey == :margin || metakey == :padding
154
+ value = [0, 0, 0, 0]
155
+ if @options[metakey].is_a? Array
156
+ value = (@options[metakey].flatten.map{|v| v.class == Fixnum || v.class == Float || v.class == String ? v.to_i : 0}*4)[0..3]
157
+ else
158
+ value = (@options[metakey].to_s.scan(/\-?\d+/).flatten.map(&:to_i)*4)[0..3] if @options[metakey] && @options[metakey].to_s =~ /\d/
159
+ end
160
+ [:top, :right, :bottom, :left].each_with_index {|side, index| value[index] = @options["#{metakey}_#{side}".to_sym].to_i if @options["#{metakey}_#{side}".to_sym].to_s =~ /\-?\d+(.\d+)?/ }
161
+ instance_variable_set "@#{metakey}", value
162
+ elsif metakey == :border
163
+ border_sides_order = [:top, :right, :bottom, :left]
164
+ @options.keys.each do |key|
165
+ border_sides_order.push(border_sides_order.delete($1.to_sym)) if key.to_s =~ /\Aborder_(top|right|bottom|left)\Z/
166
+ end
167
+ @border = {}
168
+ [:top, :right, :bottom, :left].each do |side|
169
+ @options["#{metakey}_#{side}".to_sym] = @options[:border] unless @options["#{metakey}_#{side}".to_sym]
170
+ next unless @options["#{metakey}_#{side}".to_sym]
171
+ border = @options["#{metakey}_#{side}".to_sym].is_a?(Array) ? @options["#{metakey}_#{side}".to_sym].flatten : @options["#{metakey}_#{side}".to_sym].to_s.split(/\s+/)
172
+ if border[0].to_i > 0
173
+ source = DynamicImageSources::SourceFactory.parse(border[2..-1])
174
+ @border[side] = [border[0].to_i, border[1], source] if source
175
+ end
176
+ @border_sides_order = border_sides_order
177
+ end
178
+ end
179
+ end
180
+
181
+ # Aliases for option's keys. You can use these shortcuts:
182
+ # * :w to specify :width
183
+ # * :h to specify :height
184
+ # * :bg to specify :background
185
+ # * :valign to specify :vertical_align
186
+ OPTIONS_ALIASES = {:w => :width, :h => :height, :bg => :background, :valign => :vertical_align}
187
+
188
+ # Treats options Hash to generalize it and parse sources
189
+ def treat_options(options)
190
+ #convert all keys to symbols
191
+ options.keys.each do |key|
192
+ next if key.class == Symbol
193
+ options[key.to_s.gsub("-", "_").downcase.to_sym] = options[key]
194
+ end
195
+ #use aliases
196
+ OPTIONS_ALIASES.each do |alias_key, key|
197
+ next unless options[alias_key]
198
+ options[key] = options[alias_key]
199
+ end
200
+ #check values that must be numeric
201
+ [:x, :y, :z, :cols, :colspan, :rowspan].each do |key|
202
+ if options[key] && (options[key].class == Fixnum || options[key].class == Float || options[key].class == String)
203
+ options[key] = options[key].to_i
204
+ else
205
+ options[key] = nil
206
+ end
207
+ end
208
+ #check values that must be numeric, but can be in percentage
209
+ [:width, :height, :alpha].each do |key|
210
+ next unless options[key]
211
+ next if options[key].class == Float
212
+ next if options[key].class == Fixnum && options[key] > 0
213
+ if options[key] =~ /\A\s*(\d+)%\s*\Z$/
214
+ options[key] = $1.to_f/100.0
215
+ elsif options[key] =~ /\A\s*(\d+\.\d+)\s*\Z$/
216
+ options[key] = $1.to_f
217
+ elsif options[key] =~ /\A\s*(\d+)\s*\Z/
218
+ options[key] = $1.to_i
219
+ else
220
+ options[key] = nil
221
+ end
222
+ end
223
+ #check values that must be positive numeric
224
+ [:cols, :colspan, :rowspan].each do |key|
225
+ options[key] = nil if options[key] && options[key] <= 0
226
+ end
227
+ #check value that are true/false
228
+ [:auto_dir, :justify].each do |key|
229
+ next unless options[key]
230
+ next if options[key].class == TrueClass || options[key].class == FalseClass
231
+ options[key] = !(options[key].to_s == "" || options[key].to_s == "false" || options[key].to_s == "0")
232
+ end
233
+ options[:width] = nil if options[:width] && options[:width] <= 0
234
+ options[:height] = nil if options[:height] && options[:height] <= 0
235
+ options[:position] = :static unless options[:position]
236
+ options[:background] = DynamicImageSources::SourceFactory.parse options[:background] unless options[:background].to_s.empty?
237
+ options[:color] = DynamicImageSources::SourceFactory.parse options[:color] unless options[:color].to_s.empty?
238
+ end
239
+
240
+ #drawing helpers
241
+ def draw_background(x, y)
242
+ if @options[:background]
243
+ context.save
244
+ w, h = element_size
245
+ @options[:background].set_source context, x, y, w, h
246
+ context.rectangle x, y, w, h
247
+ context.clip
248
+ context.paint
249
+ context.restore
250
+ end
251
+ end
252
+
253
+ # Predefined styles for line drawing
254
+ LINE_STYLES = {
255
+ :solid => [],
256
+ :dotted => [1.0, 1.0],
257
+ :dashed => [3.0, 1.0],
258
+ :dashed_bigger => [5.0, 1.0],
259
+ :dashed_big => [8.0, 1.0],
260
+ :dashed_dot => [3.0, 1.0, 1.0, 1.0],
261
+ :dashed_double_dot => [3.0, 1.0, 1.0, 1.0, 1.0, 1.0],
262
+ :dashed_big_bigger => [5.0, 1.0, 3.0, 1.0],
263
+ :double_dotted => [1.0, 1.0, 1.0, 3.0],
264
+ :triple_dotted => [1.0, 1.0, 1.0, 1.0, 1.0, 3.0],
265
+ :double_dashed_dot => [3.0, 1.0, 3.0, 1.0, 1.0, 1.0],
266
+ :double_dashed_double_dot => [3.0, 1.0, 3.0, 1.0, 1.0, 1.0, 1.0, 1.0],
267
+ :double_dashed_triple_dot => [3.0, 1.0, 3.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
268
+ :triple_dashed_dot => [3.0, 1.0, 3.0, 1.0, 3.0, 1.0, 1.0, 1.0],
269
+ :triple_dashed_double_dot => [3.0, 1.0, 3.0, 1.0, 3.0, 1.0, 1.0, 1.0, 1.0, 1.0]
270
+ }
271
+
272
+ def draw_border(x, y)
273
+ return unless @border_sides_order
274
+ original_source = context.source
275
+ lines_width = {
276
+ :top => (@border[:top][0] rescue 0),
277
+ :right => (@border[:right][0] rescue 0),
278
+ :bottom => (@border[:bottom][0] rescue 0),
279
+ :left => (@border[:left][0] rescue 0)
280
+ }
281
+ @border_sides_order.each do |key|
282
+ next unless @border && @border[key]
283
+ #line width
284
+ width = lines_width[key]
285
+ context.set_line_width width
286
+ #line style
287
+ multiple_gap = nil
288
+ if @border[key][1].to_s =~ /\A([a-z_]+)(\d+)\Z/
289
+ style = $1.to_sym
290
+ multiple_gap = $2.to_i
291
+ else
292
+ style = @border[key][1].to_sym rescue :solid
293
+ end
294
+ style = :solid unless LINE_STYLES[style]
295
+ style = LINE_STYLES[style].map{|i| i*width}
296
+ style = style.each_with_index.map{|i, index| index%2==1 ? i*multiple_gap : i} if multiple_gap
297
+ context.set_dash style, style.size
298
+ #line color
299
+ w, h = element_size
300
+ @border[key][2].set_source context, x, y, w, h
301
+ case key
302
+ when :top
303
+ context.move_to x-lines_width[:left], y-lines_width[:top]/2
304
+ context.line_to x+w+lines_width[:right], y-lines_width[:top]/2
305
+ when :right
306
+ context.move_to x+w+lines_width[:right]/2, y-lines_width[:top]
307
+ context.line_to x+w+lines_width[:right]/2, y+h+lines_width[:bottom]
308
+ when :bottom
309
+ context.move_to x+w+lines_width[:right], y+h+lines_width[:bottom]/2
310
+ context.line_to x-lines_width[:left], y+h+lines_width[:bottom]/2
311
+ when :left
312
+ context.move_to x-lines_width[:left]/2, y+h+lines_width[:bottom]
313
+ context.line_to x-lines_width[:left]/2, y-lines_width[:top]
314
+ end
315
+ context.stroke
316
+ end
317
+ context.set_source original_source
318
+ end
319
+ end
320
+ end