dynamic_images 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +27 -0
- data/README.rdoc +104 -0
- data/README_USAGE.rdoc +67 -0
- data/Rakefile +14 -0
- data/dynamic_images.gemspec +35 -0
- data/examples/gtk_window.rb +39 -0
- data/examples/named_colors_table.rb +13 -0
- data/init.rb +6 -0
- data/lib/dynamic_image.rb +240 -0
- data/lib/elements/block_element.rb +224 -0
- data/lib/elements/element_interface.rb +320 -0
- data/lib/elements/image_element.rb +82 -0
- data/lib/elements/table_cell_element.rb +34 -0
- data/lib/elements/table_element.rb +220 -0
- data/lib/elements/text_element.rb +195 -0
- data/lib/parsers/xml.dtd +154 -0
- data/lib/parsers/xml_parser.rb +135 -0
- data/lib/render_image.rb +60 -0
- data/lib/sources/color_source.rb +97 -0
- data/lib/sources/gradient_source.rb +200 -0
- data/lib/sources/source_factory.rb +36 -0
- data/test/asym.rb +54 -0
- data/test/performance.1.rb +86 -0
- data/test/performance.2.rb +159 -0
- data/test/performance.3.rb +236 -0
- data/test/performance.rb +51 -0
- data/test/units1.rb +185 -0
- data/test/units2.rb +162 -0
- metadata +115 -0
@@ -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
|