dyi 0.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.
Files changed (48) hide show
  1. data/COPYING +674 -0
  2. data/README +28 -0
  3. data/examples/class_diagram.rb +151 -0
  4. data/examples/data/03311056.xlsx +0 -0
  5. data/examples/data/currency.xlsx +0 -0
  6. data/examples/data/money.csv +12 -0
  7. data/examples/line_and_bar.rb +26 -0
  8. data/examples/line_chart.rb +30 -0
  9. data/examples/logo.rb +68 -0
  10. data/examples/pie_chart.rb +19 -0
  11. data/examples/simple_shapes.rb +15 -0
  12. data/lib/dyi.rb +49 -0
  13. data/lib/dyi/chart.rb +34 -0
  14. data/lib/dyi/chart/array_reader.rb +136 -0
  15. data/lib/dyi/chart/base.rb +580 -0
  16. data/lib/dyi/chart/csv_reader.rb +93 -0
  17. data/lib/dyi/chart/excel_reader.rb +100 -0
  18. data/lib/dyi/chart/line_chart.rb +468 -0
  19. data/lib/dyi/chart/pie_chart.rb +141 -0
  20. data/lib/dyi/chart/table.rb +201 -0
  21. data/lib/dyi/color.rb +218 -0
  22. data/lib/dyi/coordinate.rb +224 -0
  23. data/lib/dyi/drawing.rb +32 -0
  24. data/lib/dyi/drawing/canvas.rb +100 -0
  25. data/lib/dyi/drawing/clipping.rb +61 -0
  26. data/lib/dyi/drawing/color_effect.rb +118 -0
  27. data/lib/dyi/drawing/filter.rb +74 -0
  28. data/lib/dyi/drawing/pen.rb +231 -0
  29. data/lib/dyi/drawing/pen_3d.rb +270 -0
  30. data/lib/dyi/font.rb +132 -0
  31. data/lib/dyi/formatter.rb +36 -0
  32. data/lib/dyi/formatter/base.rb +245 -0
  33. data/lib/dyi/formatter/emf_formatter.rb +253 -0
  34. data/lib/dyi/formatter/eps_formatter.rb +397 -0
  35. data/lib/dyi/formatter/svg_formatter.rb +260 -0
  36. data/lib/dyi/formatter/svg_reader.rb +113 -0
  37. data/lib/dyi/formatter/xaml_formatter.rb +317 -0
  38. data/lib/dyi/length.rb +399 -0
  39. data/lib/dyi/matrix.rb +122 -0
  40. data/lib/dyi/painting.rb +177 -0
  41. data/lib/dyi/shape.rb +1332 -0
  42. data/lib/dyi/svg_element.rb +149 -0
  43. data/lib/dyi/type.rb +104 -0
  44. data/lib/ironruby.rb +326 -0
  45. data/lib/util.rb +231 -0
  46. data/test/path_command_test.rb +217 -0
  47. data/test/test_length.rb +91 -0
  48. metadata +114 -0
@@ -0,0 +1,136 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ # Copyright (c) 2009-2011 Sound-F Co., Ltd. All rights reserved.
4
+ #
5
+ # Author:: Mamoru Yuo
6
+ #
7
+ # This file is part of DYI.
8
+ #
9
+ # DYI is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # DYI is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with DYI. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ module DYI #:nodoc:
23
+ module Chart #:nodoc:
24
+
25
+ class ArrayReader
26
+
27
+ def [](i, j)
28
+ @data[i][j]
29
+ end
30
+
31
+ def row_title(i)
32
+ @row_titles[i]
33
+ end
34
+
35
+ def column_title(j)
36
+ @col_titles[j]
37
+ end
38
+
39
+ def row_values(i)
40
+ @data[i].dup
41
+ end
42
+
43
+ def column_values(j)
44
+ @data.map{|r| r[j]}
45
+ end
46
+
47
+ def row_count
48
+ @data.size
49
+ end
50
+
51
+ def column_count
52
+ @data.first.size
53
+ end
54
+
55
+ def clear_data
56
+ @data.clear
57
+ @col_titles.clear
58
+ @row_titles.clear
59
+ end
60
+
61
+ def values
62
+ @data.dup
63
+ end
64
+
65
+ def column_titles
66
+ @col_titles.dup
67
+ end
68
+
69
+ def row_titles
70
+ @row_titles.dup
71
+ end
72
+
73
+ def initialize
74
+ @data = []
75
+ @col_titles = []
76
+ @row_titles = []
77
+ end
78
+
79
+ def read(array_of_array, options={})
80
+ clear_data
81
+ data_types = options[:data_types] || []
82
+ row_skip = (options[:row_skip].to_i rescue 0)
83
+ col_skip = (options[:column_skip].to_i rescue 0)
84
+ title_row = ((options[:title_row] ? options[:title_row].to_i : nil) rescue nil)
85
+ title_col = ((options[:title_column] ? options[:title_column].to_i : nil) rescue nil)
86
+ row_limit = ((options[:row_limit] ? options[:row_limit].to_i : nil) rescue nil)
87
+ row_proc = options[:row_proc]
88
+ array_of_array.each_with_index do |row, i|
89
+ unless options[:transposed]
90
+ if i == title_row
91
+ @col_titles.replace(row[col_skip..-1].map{|v| primitive_title_value(v)})
92
+ end
93
+ next if i < row_skip
94
+ break if row_limit && row_limit + row_skip <= i
95
+ next if row_proc.respond_to?(:call) && !row_proc.call(*row[col_skip..-1])
96
+ @row_titles << primitive_title_value(row[title_col]) if title_col
97
+ vals = []
98
+ row[col_skip..-1].each_with_index do |value, j|
99
+ vals << primitive_value(value, data_types[j])
100
+ end
101
+ @data << vals
102
+ else
103
+ row_limit_number = row_limit ? row_limit + row_skip - 1 : -1
104
+ if i == title_col
105
+ @row_titles.replace(row[row_skip..row_limit_number].map{|v| primitive_title_value(v)})
106
+ end
107
+ next if i < col_skip
108
+ vals = row[row_skip..row_limit_number]
109
+ @data.replace(vals.map{|value| []}) if @data.empty?
110
+ @col_titles << primitive_title_value(row[title_row]) if title_row
111
+ vals.each_with_index do |value, j|
112
+ @data[j] << primitive_value(value, data_types[i])
113
+ end
114
+ end
115
+ end
116
+ self
117
+ end
118
+
119
+ private
120
+
121
+ def primitive_value(value, type=nil)
122
+ value
123
+ end
124
+
125
+ def primitive_title_value(value, type=nil)
126
+ primitive_value(value, type)
127
+ end
128
+
129
+ class << self
130
+ def read(array_of_array, options={})
131
+ new.read(array_of_array, options)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,580 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ # Copyright (c) 2009-2011 Sound-F Co., Ltd. All rights reserved.
4
+ #
5
+ # Author:: Mamoru Yuo
6
+ #
7
+ # This file is part of DYI.
8
+ #
9
+ # DYI is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # DYI is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with DYI. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ require 'csv'
23
+
24
+ module DYI #:nodoc:
25
+ module Chart #:nodoc:
26
+
27
+ module Legend
28
+
29
+ private
30
+
31
+ def draw_legend(names, shapes=nil, records=nil, colors=nil) #:nodoc:
32
+ legend_canvas.translate(legend_point.x, legend_point.y)
33
+ if show_legend?
34
+ pen = Drawing::Pen.black_pen(:font => legend_font)
35
+ brush = Drawing::Brush.new
36
+ names.each_with_index do |name, index|
37
+ y = legend_font_size * (1.2 * (index + 1))
38
+ group = Shape::ShapeGroup.draw_on(legend_canvas)
39
+ case shapes && shapes[index]
40
+ when Shape::Base
41
+ shapes[index].draw_on(group)
42
+ when NilClass
43
+ brush.color = colors && colors[index] || chart_color(index)
44
+ brush.draw_rectangle(
45
+ group,
46
+ Coordinate.new(legend_font_size * 0.2, y - legend_font_size * 0.8),
47
+ legend_font_size * 0.8,
48
+ legend_font_size * 0.8)
49
+ end
50
+ pen.draw_text(
51
+ group,
52
+ Coordinate.new(legend_font_size * 0.2 + legend_font_size, y),
53
+ name)
54
+ end
55
+ end
56
+ end
57
+
58
+ def legend_font_size #:nodoc:
59
+ legend_font ? legend_font.draw_size : Font::DEFAULT_SIZE
60
+ end
61
+
62
+ def default_legend_point #:nodoc:
63
+ Coordinate.new(0,0)
64
+ end
65
+
66
+ def default_legend_format #:nodoc:
67
+ "{name}"
68
+ end
69
+
70
+ class << self
71
+
72
+ private
73
+
74
+ def included(klass) #:nodoc:
75
+ klass.__send__(:opt_accessor, :show_legend, :type => :boolean, :default => true)
76
+ klass.__send__(:opt_accessor, :legend_font, :type => :font)
77
+ klass.__send__(:opt_accessor, :legend_format, :type => :string, :default_method => :default_legend_format)
78
+ klass.__send__(:opt_accessor, :legend_point, :type => :point, :default_method => :default_legend_point)
79
+ end
80
+ end
81
+ end
82
+
83
+ module AxisUtil
84
+
85
+ private
86
+
87
+ def moderate_axis(data, axis_length, min=nil, max=nil, scale_count_limit=nil)
88
+ raise ArgumentError, 'no data' if (data = data.flatten.compact).empty?
89
+
90
+ axis_length = Length.new(axis_length)
91
+ data_min, data_max = chart_range(data, min, max)
92
+
93
+ base_value = base_value(data_min, data_max, min.nil?, max.nil?)
94
+ scale_count_limit ||= (axis_length / Length.new(30)).to_i
95
+ scale_count_limit = 10 if 10 < scale_count_limit
96
+ scale_count_limit = 2 if scale_count_limit < 2
97
+ scale_interval = scale_interval(base_value, data_min, data_max, scale_count_limit)
98
+ min_scale_value = nil
99
+ (base_value + scale_interval).step(data_min, -scale_interval) {|n| min_scale_value = n}
100
+ min ||= (min_scale_value.nil? ? base_value : min_scale_value - scale_interval)
101
+ min_scale_value ||= min + scale_interval
102
+ unless max
103
+ base_value.step(data_max, scale_interval) {|n| max = n}
104
+ max += scale_interval if max < data_max
105
+ end
106
+ {
107
+ :min => min || min_scale_value - scale_interval,
108
+ :max => max,
109
+ :axis_length => axis_length,
110
+ :min_scale_value => min_scale_value,
111
+ :scale_interval => scale_interval
112
+ }
113
+ end
114
+
115
+ def top_digit(num, n=1) #:nodoc:
116
+ num.div(10 ** (figures_count(num) - n + 1))
117
+ end
118
+
119
+ def suitable_1digit_value(a, b) #:nodoc:
120
+ return a if a == b
121
+ a, b = b, a if a > b
122
+ return 0 if a == 0
123
+ return 5 if a <= 5 && 5 <= b
124
+ return 2 if a <= 2 && 2 <= b
125
+ return 4 if a <= 4 && 4 <= b
126
+ return 6 if a <= 6 && 6 <= b
127
+ 8
128
+ end
129
+
130
+ def figures_count(num) #:nodoc:
131
+ Math.log10(num).floor
132
+ end
133
+
134
+ def base_value(a, b, allow_under=true, allow_over=true) #:nodoc:
135
+ return 0 if a * b <= 0 || a == b
136
+ a, b = -a, -b if negative = (a < 0)
137
+ a, b = b, a if a > b
138
+ return 0 if ((negative && allow_over) || (!negative && allow_under)) && a < b * 0.3
139
+ suitable_value_positive(a, b) * (negative ? -1 : 1)
140
+ end
141
+
142
+ def suitable_value_positive(a, b) #:nodoc:
143
+ if figures_count(a) != (dig = figures_count(b))
144
+ return 10 ** dig
145
+ end
146
+ n = 1
147
+ n += 1 while (dig_a = top_digit(a, n)) == (dig_b = top_digit(b, n))
148
+ (suitable_1digit_value(dig_a - dig_a.div(10) * 10 + (dig_a == dig_a.div(10) * 10 ? 0 : 1), dig_b - dig_b.div(10) * 10) + dig_a.div(10) * 10) * (10 ** (dig - figures_count(dig_a)))
149
+ end
150
+
151
+ def scale_interval(base_value, data_min, data_max, scale_count_limit) #:nodoc:
152
+ if base_value - data_min < data_max - base_value
153
+ allocate_scale_count = (data_max - base_value).div((data_max - data_min).quo(scale_count_limit))
154
+ scale_interval_base2edge(base_value, data_max, allocate_scale_count)
155
+ else
156
+ allocate_scale_count = (base_value - data_min).div((data_max - data_min).quo(scale_count_limit))
157
+ scale_interval_base2edge(base_value, data_min, allocate_scale_count)
158
+ end
159
+ end
160
+
161
+ def scale_interval_base2edge(base_value, edge_value, scale_count_limit) #:nodoc:
162
+ raise ArgumentError, 'base_value should not equal edge_value' if edge_value == base_value
163
+ range = (base_value - edge_value).abs
164
+
165
+ top_2_digits = top_digit(range, 2)
166
+ case scale_count_limit.to_i
167
+ when 1
168
+ case top_2_digits
169
+ when 10 then label_range = 10
170
+ when 11..20 then label_range = 20
171
+ when 21..40 then label_range = 40
172
+ when 41..50 then label_range = 50
173
+ when 51..99 then label_range = 100
174
+ end
175
+ when 2
176
+ case top_2_digits
177
+ when 10 then label_range = 5
178
+ when 11..20 then label_range = 10
179
+ when 21..40 then label_range = 20
180
+ when 41..50 then label_range = 25
181
+ when 51..99 then label_range = 50
182
+ end
183
+ when 3
184
+ case top_2_digits
185
+ when 10 then label_range = 4
186
+ when 11..15 then label_range = 5
187
+ when 16..30 then label_range = 10
188
+ when 31..60 then label_range = 20
189
+ when 61..75 then label_range = 25
190
+ when 76..99 then label_range = 40
191
+ end
192
+ when 4
193
+ case top_2_digits
194
+ when 10 then label_range = 2.5
195
+ when 11..15 then label_range = 4
196
+ when 16..20 then label_range = 5
197
+ when 21..40 then label_range = 10
198
+ when 41..80 then label_range = 20
199
+ when 81..99 then label_range = 25
200
+ end
201
+ when 5
202
+ case top_2_digits
203
+ when 10 then label_range = 2
204
+ when 11..12 then label_range = 2.5
205
+ when 13..15 then label_range = 4
206
+ when 16..25 then label_range = 5
207
+ when 26..50 then label_range = 10
208
+ when 51..99 then label_range = 20
209
+ end
210
+ when 6
211
+ case top_2_digits
212
+ when 10..12 then label_range = 2
213
+ when 13..15 then label_range = 2.5
214
+ when 16..20 then label_range = 4
215
+ when 21..30 then label_range = 5
216
+ when 31..60 then label_range = 10
217
+ when 61..99 then label_range = 20
218
+ end
219
+ when 7
220
+ case top_2_digits
221
+ when 10..14 then label_range = 2
222
+ when 15..17 then label_range = 2.5
223
+ when 18..20 then label_range = 4
224
+ when 21..35 then label_range = 5
225
+ when 35..70 then label_range = 10
226
+ when 71..99 then label_range = 20
227
+ end
228
+ when 8
229
+ case top_2_digits
230
+ when 10..16 then label_range = 2
231
+ when 17..20 then label_range = 2.5
232
+ when 21..25 then label_range = 4
233
+ when 26..40 then label_range = 5
234
+ when 41..80 then label_range = 10
235
+ when 81..99 then label_range = 20
236
+ end
237
+ when 9
238
+ case top_2_digits
239
+ when 10..18 then label_range = 2
240
+ when 19..22 then label_range = 2.5
241
+ when 23..30 then label_range = 4
242
+ when 31..45 then label_range = 5
243
+ when 46..90 then label_range = 10
244
+ when 91..99 then label_range = 20
245
+ end
246
+ else
247
+ case top_2_digits
248
+ when 10 then label_range = 1
249
+ when 11..20 then label_range = 2
250
+ when 21..25 then label_range = 2.5
251
+ when 26..30 then label_range = 4
252
+ when 31..50 then label_range = 5
253
+ when 51..99 then label_range = 10
254
+ end
255
+ end
256
+ label_range * (10 ** (figures_count(range) - 1))
257
+ end
258
+
259
+ def moderate_sub_axis(data, main_axis_settings, min=nil, max=nil)
260
+ if min && max
261
+ axis_ratio = (max - min).quo(main_axis_settings[:max] - main_axis_settings[:min])
262
+ return {
263
+ :max => max,
264
+ :min => min,
265
+ :min_scale_value => min + (main_axis_settings[:min_scale_value] - main_axis_settings[:min]) * axis_ratio,
266
+ :axis_length => main_axis_settings[:axis_length],
267
+ :scale_interval => main_axis_settings[:scale_interval] * axis_ratio
268
+ }
269
+ end
270
+ scale_count = (main_axis_settings[:max] - main_axis_settings[:min_scale_value]).div(main_axis_settings[:scale_interval]) + (main_axis_settings[:min_scale_value] == main_axis_settings[:min] ? 0 : 1)
271
+ data_min, data_max = chart_range(data, min, max)
272
+
273
+ base_value = base_value(data_min, data_max, min.nil?, max.nil?)
274
+
275
+ scale_interval = scale_interval(base_value, data_min, data_max, scale_count)
276
+ min_scale_value = nil
277
+ (base_value + scale_interval).step(data_min, -scale_interval) {|n| min_scale_value = n}
278
+ min ||= (min_scale_value.nil? ? base_value : min_scale_value - scale_interval)
279
+ min_scale_value ||= min + scale_interval
280
+ max = scale_interval * scale_count + min
281
+
282
+ {
283
+ :min => min || min_scale_value - scale_interval,
284
+ :max => max,
285
+ :axis_length => main_axis_settings[:axis_length],
286
+ :min_scale_value => min_scale_value,
287
+ :scale_interval => scale_interval
288
+ }
289
+ end
290
+
291
+ def chart_range(data, min=nil, max=nil)
292
+ data = data.compact.flatten
293
+ if min.nil? && max.nil?
294
+ data_min, data_max =
295
+ data.inject([nil, nil]) do |(_min, _max), value|
296
+ [value < (_min ||= value) ? value : _min, (_max ||= value) < value ? value : _max]
297
+ end
298
+ elsif min && max && max < min
299
+ data_min, data_max = max, min
300
+ else
301
+ data_min = min || [data.min, max].min
302
+ data_max = max || [data.max, min].max
303
+ end
304
+
305
+ if data_min == data_max
306
+ if data_min > 0
307
+ data_min = 0
308
+ elsif data_max < 0
309
+ data_max = 0
310
+ else
311
+ data_min = 0
312
+ data_max = 100
313
+ end
314
+ end
315
+ [data_min, data_max]
316
+ end
317
+
318
+ def value_position_on_chart(chart_margin, axis_settings, value, reverse_direction = false)
319
+ axis_settings[:axis_length] *
320
+ ((reverse_direction ? (axis_settings[:max] - value) : (value - axis_settings[:min])).to_f / (axis_settings[:max] - axis_settings[:min])) +
321
+ Length.new(chart_margin)
322
+ end
323
+
324
+ def order_position_on_chart(chart_margin, axis_length, count, index, type=:point, renge_width_ratio=0, reverse_direction=false)
325
+ chart_margin = Length.new(chart_margin)
326
+ pos =
327
+ case type
328
+ when :point then index.to_f / (count - 1)
329
+ when :range then (index + 0.5 - renge_width_ratio.to_f / 2) / count
330
+ else raise ArgumentError, "\"#{type}\" is invalid type"
331
+ end
332
+ axis_length * pos + chart_margin
333
+ end
334
+
335
+ def round_top_2_digit(max, min) #:nodoc:
336
+ digit = Math.log10([max.abs, min.abs].max).floor - 1
337
+ [max.quo(10 ** digit).ceil * (10 ** digit), min.quo(10 ** digit).floor * (10 ** digit)]
338
+ end
339
+
340
+ def min_scale_value(max, min, scale_interval) #:nodoc:
341
+ return scale_interval if min == 0
342
+ if (max_digit = Math.log10(max).to_i) != Math.log10(min).to_i
343
+ base_value = 10 ** max_digit
344
+ elsif max.div(10 ** max_digit) != min.div(10 ** max_digit)
345
+ base_value = 9 * 10 ** max_digit
346
+ else
347
+ range_digit = Math.log10(max - min).floor
348
+ base_value = max.div(10 ** range_digit) * (10 ** range_digit)
349
+ end
350
+ base_value - ((base_value - min).quo(scale_interval).ceil - 1) * scale_interval
351
+ end
352
+ end
353
+
354
+ class Base
355
+ DEFAULT_CHART_COLOR = ['#ff0f00', '#ff6600', '#ff9e01', '#fcd202', '#f8ff01', '#b0de09', '#04d215', '#0d8ecf', '#0d52d1', '#2a0cd0', '#8a0ccf', '#cd0d74']
356
+ attr_reader :options, :data, :canvas
357
+
358
+ def initialize(width, height, options={})
359
+ @canvas = Drawing::Canvas.new(width, height)
360
+ @options = {}
361
+ options.each do |key, value|
362
+ __send__("#{key}=", value) if respond_to?("#{key}=")
363
+ end
364
+ end
365
+
366
+ def width
367
+ @canvas.width
368
+ end
369
+
370
+ def width=(width)
371
+ @canvas.width = width
372
+ end
373
+
374
+ def height
375
+ @canvas.height
376
+ end
377
+
378
+ def height=(height)
379
+ @canvas.height = height
380
+ end
381
+
382
+ def set_real_size(width, height)
383
+ @canvas.real_width = Length.new(width)
384
+ @canvas.real_height = Length.new(height)
385
+ end
386
+
387
+ def clear_real_size
388
+ @canvas.real_width = nil
389
+ @canvas.real_height = nil
390
+ end
391
+
392
+ def load_data(reader)
393
+ @data = reader
394
+ create_vector_image
395
+ end
396
+
397
+ def save(file_name, format=nil, options={})
398
+ @canvas.save(file_name, format)
399
+ end
400
+
401
+ def puts_in_io(format=nil, io=$>)
402
+ @canvas.puts_in_io(format, io)
403
+ end
404
+
405
+ def string(format=nil)
406
+ @canvas.string(format)
407
+ end
408
+
409
+ private
410
+
411
+ def options #:nodoc:
412
+ @options
413
+ end
414
+
415
+ def chart_color(index) #:nodoc:
416
+ if respond_to?(:chart_colors) && chart_colors
417
+ result = chart_colors[index]
418
+ end
419
+ result || DEFAULT_CHART_COLOR[index % DEFAULT_CHART_COLOR.size]
420
+ end
421
+
422
+ class << self
423
+
424
+ private
425
+
426
+ def opt_reader(name, settings = {})
427
+ name = name.to_sym
428
+ getter_name = settings[:type] == :boolean ? name.to_s.gsub(/^(.*[^=\?])[=\?]*$/, '\1?') : name
429
+ if settings.key?(:default)
430
+ define_method(getter_name) {@options.key?(name) ? @options[name] : settings[:default]}
431
+ elsif settings.key?(:default_method)
432
+ define_method(getter_name) {@options.key?(name) ? @options[name] : __send__(settings[:default_method])}
433
+ elsif settings.key?(:default_proc)
434
+ define_method(getter_name) {@options.key?(name) ? @options[name] : settings[:default_proc].call(self)}
435
+ else
436
+ define_method(getter_name) {@options[name]}
437
+ end
438
+ end
439
+
440
+ def opt_writer(name, settings = {})
441
+ name = name.to_sym
442
+ setter_name = name.to_s.gsub(/^(.*[^=\?])[=\?]*$/, '\1=')
443
+
444
+ convertor =
445
+ case settings[:type]
446
+ when :boolen then proc {|value| not not value}
447
+ when :string then proc {|value| value.to_s}
448
+ when :symbol then proc {|value| value.to_sym}
449
+ when :integer then proc {|value| value.to_i}
450
+ when :float then proc {|value| value.to_f}
451
+ when :length then proc {|value| Length.new(value)}
452
+ when :point then proc {|value| Coordinate.new(value)}
453
+ when :color then proc {|value| Color.new(value)}
454
+ when :font then proc {|value| Font.new(value)}
455
+ else proc {|value| value} if !settings.key?(:map_method) && !settings.key?(:mapper) && !settings.key?(:item_type)
456
+ end
457
+
458
+ validator =
459
+ case settings[:type]
460
+ when :symbol
461
+ if settings.key?(:valid_values)
462
+ proc {|value| raise ArgumentError, "\"#{value}\" is invalid value" unless settings[:valid_values].include?(convertor.call(value))}
463
+ end
464
+ when :integer, :float
465
+ if settings.key?(:range)
466
+ proc {|value| raise ArgumentError, "\"#{value}\" is invalid value" unless settings[:range].include?(convertor.call(value))}
467
+ end
468
+ end
469
+
470
+ case settings[:type]
471
+ when :hash
472
+ raise ArgumentError, "keys is not specified" unless settings.key?(:keys)
473
+ define_method(setter_name) {|values|
474
+ if values.nil? || values.empty?
475
+ @options.delete(name)
476
+ else
477
+ @options[name] =
478
+ settings[:keys].inject({}) do |hash, key|
479
+ hash[key] =
480
+ if convertor
481
+ convertor.call(values[key])
482
+ elsif settings.key?(:map_method)
483
+ __send__(settings[:map_method], values[key])
484
+ elsif settings.key?(:mapper)
485
+ settings[:mapper].call(values[key], self)
486
+ elsif settings.key?(:item_type)
487
+ case settings[:item_type]
488
+ when :boolen then not not values[key]
489
+ when :string then values[key].to_s
490
+ when :symbol then values[key].to_sym
491
+ when :integer then values[key].to_i
492
+ when :float then values[key].to_f
493
+ when :length then Length.new(values[key])
494
+ when :point then Coordinate.new(values[key])
495
+ when :color then value[key].respond_to?(:format) ? value[key] : Color.new(values[key])
496
+ when :font then Font.new(values[key])
497
+ else values[key]
498
+ end
499
+ end if values[key]
500
+ hash
501
+ end
502
+ end
503
+ values
504
+ }
505
+ when :array
506
+ define_method(setter_name) {|values|
507
+ if values.nil? || values.empty?
508
+ @options.delete(name)
509
+ else
510
+ @options[name] =
511
+ Array(values).to_a.map {|item|
512
+ if convertor
513
+ convertor.call(item)
514
+ elsif settings.key?(:map_method)
515
+ __send__(settings[:map_method], item)
516
+ elsif settings.key?(:mapper)
517
+ settings[:mapper].call(item, self)
518
+ elsif settings.key?(:item_type)
519
+ case settings[:item_type]
520
+ when :boolen then not not item
521
+ when :string then item.to_s
522
+ when :symbol then item.to_sym
523
+ when :integer then item.to_i
524
+ when :float then item.to_f
525
+ when :length then Length.new(item)
526
+ when :point then Coordinate.new(item)
527
+ when :color then item.respond_to?(:write_as) ? item : Color.new(item)
528
+ when :font then Font.new(item)
529
+ else item
530
+ end
531
+ else
532
+ item
533
+ end
534
+ }
535
+ end
536
+ values
537
+ }
538
+ else
539
+ define_method(setter_name) {|value|
540
+ if value.nil?
541
+ @options.delete(name)
542
+ else
543
+ validator && validator.call(value)
544
+ @options[name] =
545
+ if convertor
546
+ convertor.call(value)
547
+ elsif settings.key?(:map_method)
548
+ __send__(settings[:map_method], value)
549
+ elsif ettings.key?(:mapper)
550
+ settings[:mapper].call(value, self)
551
+ elsif settings.key?(:item_type)
552
+ case settings[:item_type]
553
+ when :boolen then not not value
554
+ when :string then value.to_s
555
+ when :symbol then value.to_sym
556
+ when :integer then value.to_i
557
+ when :float then value.to_f
558
+ when :length then Length.new(value)
559
+ when :point then Coordinate.new(value)
560
+ when :color then Color.new(value)
561
+ when :font then Font.new(value)
562
+ else value
563
+ end
564
+ else
565
+ value
566
+ end
567
+ end
568
+ value
569
+ }
570
+ end
571
+ end
572
+
573
+ def opt_accessor(name, settings = {})
574
+ opt_reader(name, settings)
575
+ opt_writer(name, settings)
576
+ end
577
+ end
578
+ end
579
+ end
580
+ end