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