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,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