dyi 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,113 @@
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 'rexml/document'
23
+
24
+ module DYI #:nodoc:
25
+ module Formatter #:nodoc:
26
+
27
+ class SvgReader
28
+ class << self
29
+ def read(file_name)
30
+ doc = REXML::Document.new(open(file_name))
31
+ container = DYI::Shape::ShapeGroup.new
32
+ doc.root.elements.each do |element|
33
+ container.child_elements.push(create_shape(element))
34
+ end
35
+ container
36
+ end
37
+
38
+ private
39
+
40
+ def create_shape(element)
41
+ case element.name
42
+ when 'g' then create_shape_group(element)
43
+ when 'polygon' then create_polygon(element)
44
+ when 'path' then create_path(element)
45
+ end
46
+ end
47
+
48
+ def create_shape_group(element)
49
+ group = DYI::Shape::ShapeGroup.new
50
+ element.elements.each do |child|
51
+ child_element = create_shape(child)
52
+ group.child_elements.push(child_element) if child_element
53
+ end
54
+ group
55
+ end
56
+
57
+ def create_polygon(element)
58
+ color = DYI::Color.new(element.attributes['fill']) if element.attributes['fill'] != 'none'
59
+ points = element.attributes['points'].split(/\s+/).map {|pt| pt.scan(/-?[\.0-9]+/).map {|s| s.to_f}}
60
+ path = nil
61
+ points.each do |pt|
62
+ if path
63
+ path.line_to(pt)
64
+ else
65
+ path = DYI::Shape::Polygon.new(pt, :painting => {:fill => color})
66
+ end
67
+ end
68
+ path
69
+ end
70
+
71
+ def create_path(element)
72
+ color = DYI::Color.new(element.attributes['fill']) if element.attributes['fill'] != 'none'
73
+ pathes = element.attributes['d'].scan(/(M|L|l|H|h|V|v|C|c|z)\s*(\s*(?:\s*,?[\-\.0-9]+)*)/)
74
+ path = nil
75
+ if pathes.first.first == 'M'
76
+ pathes.each do |p_el|
77
+ coors = p_el[1].scan(/-?[\.0-9]+/).map {|s| s.to_f}
78
+ case p_el.first
79
+ when 'M'
80
+ if path
81
+ path.move_to(coors)
82
+ else
83
+ path = DYI::Shape::Path.new(coors, :painting => {:fill => color})
84
+ end
85
+ when 'L'
86
+ path.line_to(coors)
87
+ when 'l'
88
+ path.line_to(coors, true)
89
+ when 'H'
90
+ p 'H'
91
+ path.line_to([coors.first, path.current_point.y])
92
+ when 'h'
93
+ path.line_to([coors.first, 0], true)
94
+ when 'V'
95
+ p 'V'
96
+ path.line_to([path.current_point.x, coors.first])
97
+ when 'v'
98
+ path.line_to([0, coors.first], true)
99
+ when 'C'
100
+ path.curve_to([coors[0..1], coors[2..3], coors[4..5]])
101
+ when 'c'
102
+ path.curve_to([coors[0..1], coors[2..3], coors[4..5]], true)
103
+ when 'z'
104
+ path.close_path
105
+ end
106
+ end
107
+ end
108
+ path
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,317 @@
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 Formatter #:nodoc:
24
+
25
+ class XamlFormatter < XmlFormatter
26
+
27
+ FONT_STYLE = {'normal'=>'Normal','italic'=>'Italic'}
28
+ FONT_WEIGHT = {'normal'=>'Normal','bold'=>'Bold','100'=>'Thin','200'=>'ExtraLight','300'=>'Light','400'=>'Normal','500'=>'Medium','600'=>'SemiBold','700'=>'Bold','800'=>'ExtraBold','900'=>'Black'}
29
+ FONT_STRETCH = {'normal'=>'Normal','ultra-condensed'=>'UltraCondensed','extra-condensed'=>'ExtraCondensed','condensed'=>'Condensed','semi-condensed'=>'SemiCondensed','semi-expanded'=>'SemiExpanded','expanded'=>'Expanded','extra-expanded'=>'ExtraExpanded','ultra-expanded'=>'UltraExpanded'}
30
+ STROKE_LINE_CAP = {'butt'=>'Flat','round'=>'Round','square'=>'Square'}
31
+ STROKE_LINE_JOIN = {'miter'=>'Miter','round'=>'Round','bevel'=>'Bevel'}
32
+ TEXT_ANCHOR = {'start'=>'Left','middle'=>'Center','end'=>'Right'}
33
+ SPREAD_METHOD = {'pad'=>'Pad','reflect'=>'Reflect','repeat'=>'Repeat'}
34
+
35
+ def puts(io=$>)
36
+ StringFormat.set_default_formats(:coordinate => 'x,y') {
37
+ super
38
+ }
39
+ end
40
+
41
+ def write_canvas(canvas, io)
42
+ create_node(io, 'UserControl',
43
+ :xmlns => "http://schemas.microsoft.com/winfx/2006/xaml/presentation",
44
+ :"xmlns:x" => "http://schemas.microsoft.com/winfx/2006/xaml",
45
+ # :"xmlns:navigation" => "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation",
46
+ # :"xmlns:d" => "http://schemas.microsoft.com/expression/blend/2008",
47
+ # :"xmlns:mc" => "http://schemas.openxmlformats.org/markup-compatibility/2006",
48
+ :Width => canvas.width,
49
+ :Height => canvas.height) {
50
+ create_node(io, 'Canvas'){
51
+ canvas.child_elements.each do |element|
52
+ element.write_as(self, io)
53
+ end
54
+ }
55
+ }
56
+ end
57
+
58
+ def write_rectangle(shape, io)
59
+ attrs, attr_creator = common_attributes(shape, :shape)
60
+ attrs.merge!(:"Canvas.Left"=>shape.left, :"Canvas.Top"=>shape.top, :Width=>shape.width, :Height=>shape.height)
61
+ attrs[:RadiusX] = shape.attributes[:rx] if shape.attributes[:rx]
62
+ attrs[:RadiusY] = shape.attributes[:ry] if shape.attributes[:ry]
63
+ if attr_creator
64
+ create_node(io, 'Rectangle', attrs) {
65
+ attr_creator.call(io, 'Rectangle')
66
+ }
67
+ else
68
+ create_leaf_node(io, 'Rectangle', attrs)
69
+ end
70
+ end
71
+
72
+ def write_circle(shape, io)
73
+ attrs, attr_creator = common_attributes(shape, :shape)
74
+ attrs.merge!(:"Canvas.Left"=>shape.center.x - shape.radius, :"Canvas.Top"=>shape.center.y - shape.radius, :Width=>shape.radius * 2, :Height=>shape.radius * 2)
75
+ if attr_creator
76
+ create_node(io, 'Ellipse', attrs) {
77
+ attr_creator.call(io, 'Ellipse')
78
+ }
79
+ else
80
+ create_leaf_node(io, 'Ellipse', attrs)
81
+ end
82
+ end
83
+
84
+ def write_ellipse(shape, io)
85
+ attrs, attr_creator = common_attributes(shape, :shape)
86
+ attrs.merge!(:"Canvas.Left"=>shape.center.x - shape.radius, :"Canvas.Top"=>shape.center.y - shape.radius, :Width=>shape.radius_x * 2, :Height=>shape.radius_y * 2)
87
+ if attr_creator
88
+ create_node(io, 'Ellipse', attrs) {
89
+ attr_creator.call(io, 'Ellipse')
90
+ }
91
+ else
92
+ create_leaf_node(io, 'Ellipse', attrs)
93
+ end
94
+ end
95
+
96
+ def write_line(shape, io)
97
+ attrs, attr_creator = common_attributes(shape, :line)
98
+ attrs.merge!(:X1 => shape.start_point.x, :Y1 => shape.start_point.y, :X2 => shape.end_point.x, :Y2 => shape.end_point.y)
99
+ if attr_creator
100
+ create_node(io, 'Line', attrs) {
101
+ attr_creator.call(io, 'Line')
102
+ }
103
+ else
104
+ create_leaf_node(io, 'Line', attrs)
105
+ end
106
+ end
107
+
108
+ def write_polyline(shape, io)
109
+ attrs, attr_creator = common_attributes(shape, :line)
110
+ attrs.merge!(:Points => shape.points.join(' '))
111
+ if attr_creator
112
+ create_node(io, 'Polyline', attrs) {
113
+ attr_creator.call(io, 'Polyline')
114
+ }
115
+ else
116
+ create_leaf_node(io, 'Polyline', attrs)
117
+ end
118
+ end
119
+
120
+ def write_polygon(shape, io)
121
+ attrs, attr_creator = common_attributes(shape, :shape)
122
+ attrs.merge!(:Points => shape.points.join(' '))
123
+ if attr_creator
124
+ create_node(io, 'Polygon', attrs) {
125
+ attr_creator.call(io, 'Polygon')
126
+ }
127
+ else
128
+ create_leaf_node(io, 'Polygon', attrs)
129
+ end
130
+ end
131
+
132
+ def write_path(shape, io)
133
+ attrs, attr_creator = common_attributes(shape, :line)
134
+ attrs.merge!(:Data => shape.concise_path_data)
135
+ if attr_creator
136
+ create_node(io, 'Path', attrs) {
137
+ attr_creator.call(io, 'Path')
138
+ }
139
+ else
140
+ create_leaf_node(io, 'Path', attrs)
141
+ end
142
+ end
143
+
144
+ def write_text(shape, io)
145
+ attrs, attr_creator = common_attributes(shape, :text)
146
+ attrs.merge!(:"Canvas.Left" => shape.point.x, :"Canvas.Top" => shape.point.y)
147
+ # attrs[:"text-decoration"] = shape.attributes[:text_decoration] if shape.attributes[:text_decoration]
148
+ case shape.attributes[:alignment_baseline]
149
+ when nil then attrs[:"Canvas.Top"] -= shape.font_height * 0.85
150
+ when 'middle' then attrs[:"Canvas.Top"] -= shape.font_height * 0.5
151
+ when 'bottom' then attrs[:"Canvas.Top"] -= shape.font_height
152
+ end
153
+ case text_anchor = TEXT_ANCHOR[shape.attributes[:text_anchor]]
154
+ when 'Left'
155
+ attrs[:TextAlignment] = text_anchor
156
+ when 'Center'
157
+ attrs[:Width] = @canvas.width
158
+ attrs[:"Canvas.Left"] -= @canvas.width.quo(2)
159
+ attrs[:TextAlignment] = text_anchor
160
+ when 'Right'
161
+ attrs[:Width] = @canvas.width
162
+ attrs[:"Canvas.Left"] -= @canvas.width
163
+ attrs[:TextAlignment] = text_anchor
164
+ end
165
+ # attrs[:"writing-mode"] = shape.attributes[:writing_mode] if shape.attributes[:writing_mode]
166
+ text = shape.formated_text
167
+ if text =~ /(\r\n|\n|\r)/
168
+ attrs[:Text] = $`.strip
169
+ create_node(io, 'TextBlock', attrs) {
170
+ attr_creator.call(io, 'TextBlock') if attr_creator
171
+ create_leaf_node(io, 'LineBreak')
172
+ $'.each_line do |line|
173
+ create_leaf_node(io, 'Run', line.strip)
174
+ end
175
+ }
176
+ else
177
+ attrs[:Text] = text
178
+ if attr_creator
179
+ create_node(io, 'TextBlock', attrs, &attr_creator)
180
+ else
181
+ create_leaf_node(io, 'TextBlock', attrs)
182
+ end
183
+ end
184
+ end
185
+
186
+ def write_group(shape, io)
187
+ attrs, attr_creator = common_attributes(shape)
188
+ create_node(io, 'Canvas', attrs) {
189
+ attr_creator.call(io, 'Canvas') if attr_creator
190
+ create_node(io, 'Canvas.RenderTransform') {
191
+ create_transform_node(shape, io)
192
+ } unless shape.transform.empty?
193
+ shape.child_elements.each do |element|
194
+ element.write_as(self, io)
195
+ end
196
+ }
197
+ end
198
+
199
+ def write_linear_gradient(shape, io)
200
+ attr = {
201
+ :StartPoint => "#{shape.start_point[0]},#{shape.start_point[1]}",
202
+ :EndPoint => "#{shape.stop_point[0]},#{shape.stop_point[1]}"}
203
+ if spread_method = SPREAD_METHOD[shape.spread_method]
204
+ attr[:SpreadMethod] = spread_method
205
+ end
206
+ create_node(io, 'LinearGradientBrush', attr) {
207
+ shape.child_elements.each do |element|
208
+ element.write_as(self, io)
209
+ end
210
+ }
211
+ end
212
+
213
+ def write_gradient_stop(shape, io)
214
+ attrs = {:Offset=>shape.offset}
215
+ attrs[:Color] = shape.color.to_s16(shape.opacity) if shape.color
216
+ create_leaf_node(io, 'GradientStop', attrs)
217
+ end
218
+
219
+ def write_clip(shape, io)
220
+ # TODO
221
+ end
222
+
223
+ private
224
+
225
+ def common_attributes(shape, type=nil) # :nodoc:
226
+ attributes = {}
227
+ font = create_font_attr(shape)
228
+ painting, attr_creator = create_painting_attr(shape, type)
229
+ attributes.merge!(font) unless font.empty?
230
+ attributes.merge!(painting) unless painting.empty?
231
+ [attributes, attr_creator]
232
+ end
233
+
234
+ def create_font_attr(shape) # :nodoc:
235
+ attr = {}
236
+ if shape.respond_to?(:font) && (font = shape.font) && !font.empty?
237
+ attr[:FontFamily] = font.font_family if font.font_family
238
+ if font_style = FONT_STYLE[font.style]
239
+ attr[:FontStyle] = font_style
240
+ end
241
+ if font_weight = FONT_WEIGHT[font.weight]
242
+ attr[:FontWeight] = font_weight
243
+ end
244
+ if font_stretch = FONT_STRETCH[font.stretch]
245
+ attr[:FontStretch] = font_stretch
246
+ end
247
+ end
248
+ attr[:FontSize] = shape.font_height.to_user_unit if shape.respond_to?(:font_height)
249
+ attr
250
+ end
251
+
252
+ def create_painting_attr(shape, type) # :nodoc:
253
+ attr = {}
254
+ attr_creator = nil
255
+ if shape.respond_to?(:painting) && (painting = shape.painting) && !painting.empty?
256
+ if painting.fill
257
+ if painting.fill.respond_to?(:write_as)
258
+ case type
259
+ when :shape,:line
260
+ attr_creator = proc {|io, tag_name|
261
+ create_node(io, "#{tag_name}.Fill") {
262
+ painting.fill.write_as(self, io)
263
+ }
264
+ }
265
+ when :text
266
+ attr_creator = proc {|io, tag_name|
267
+ create_node(io, "#{tag_name}.Foreground") {
268
+ painting.fill.write_as(self, io)
269
+ }
270
+ }
271
+ end
272
+ else
273
+ case type
274
+ when :shape,:line then attr[:Fill] = painting.fill.to_s16(painting.fill_opacity)
275
+ when :text then attr[:Foreground] = painting.fill.to_s16(painting.fill_opacity)
276
+ end
277
+ end
278
+ end
279
+ attr[:Stroke] = painting.stroke.to_s16(painting.stroke_opacity) if painting.stroke
280
+ attr[:StrokeDashArray] = painting.stroke_dasharray.join(',') if painting.stroke_dasharray
281
+ attr[:StrokeDashOffset] = painting.stroke_dashoffset.to_user_unit if painting.stroke_dashoffset
282
+ if type == :line && linecap = STROKE_LINE_CAP[painting.stroke_linecap]
283
+ attr[:StrokeStartLineCap] = linecap
284
+ attr[:StrokeEndLineCap] = linecap
285
+ end
286
+ if linejoin = STROKE_LINE_JOIN[painting.stroke_linejoin]
287
+ attr[:StrokeLineJoin] = linejoin
288
+ end
289
+ attr[:StrokeMitterLimit] = painting.stroke_miterlimit if painting.stroke_miterlimit
290
+ if painting.stroke_width
291
+ attr[:StrokeThickness] = painting.stroke_width.to_user_unit
292
+ end
293
+ end
294
+ [attr, attr_creator]
295
+ end
296
+
297
+ def create_transform_node(shape, io) # :nodoc:
298
+ create_node(io, 'TransformGroup') {
299
+ shape.transform.each do |tr|
300
+ case tr.first
301
+ when :translate
302
+ create_leaf_node(io, 'TranslateTransform', :X=>tr[1], :Y=>tr[2])
303
+ when :scale
304
+ create_leaf_node(io, 'ScaleTransform', :ScaleX=>tr[1], :ScaleY=>tr[2])
305
+ when :rotate
306
+ create_leaf_node(io, 'RotateTransform', :Angle=>tr[1])
307
+ when :skewX
308
+ create_leaf_node(io, 'ScaleTransform', :AngleX=>tr[1])
309
+ when :skewY
310
+ create_leaf_node(io, 'ScaleTransform', :AngleY=>tr[1])
311
+ end
312
+ end
313
+ }
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,399 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ # Copyright (c) 2009-2011 Sound-F Co., Ltd. All rights reserved.
4
+ #
5
+ # Author:: Mamoru Yuo
6
+ # Documentation:: Mamoru Yuo
7
+ #
8
+ # This file is part of DYI.
9
+ #
10
+ # DYI is free software: you can redistribute it and/or modify it
11
+ # under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation, either version 3 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # DYI is distributed in the hope that it will be useful,
16
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with DYI. If not, see <http://www.gnu.org/licenses/>.
22
+ #
23
+ # == Overview
24
+ #
25
+ # This file provides the DYI::Length class, which provides length
26
+ # supports for DYI scripts. The length is a distance measurement.
27
+ #
28
+ # See the documentation to the DYI::Length class for more details and
29
+ # examples of usage.
30
+ #
31
+
32
+ module DYI #:nodoc:
33
+
34
+ # Class representing a length. See documentation for the file
35
+ # dyi/length.rb for an overview.
36
+ #
37
+ # == Introduction
38
+ #
39
+ # This class works with an amount and a unit. The lists of unit identifiers
40
+ # matches the list of unit identifiers in CSS: em, ex, px, pt, pc, cm, mm, in
41
+ # and percentages(%). When a unit is not given, then the length is assumed
42
+ # to be in user units (i.e., a value in the current user coordinate sytem).
43
+ #
44
+ # * As in CSS, the _em_ and _ex_ unit identifiers are relative to the current
45
+ # font's font-size and x-height, respectively.
46
+ # * One _px_ unit is defined to be equal to one user unit.
47
+ # * 1<em>pt</em> equals 1.25 user units.
48
+ # * 1<em>pc</em> equals 15 user units.
49
+ # * 1<em>mm</em> equals 3.543307 user units.
50
+ # * 1<em>cm</em> equals 35.43307 user units.
51
+ # * 1<em>in</em> equals 90 user units.
52
+ # * For percentage values that are defined to be relative to the size of
53
+ # parent element.
54
+ #
55
+ # == Ways of comparing and calculating
56
+ #
57
+ # This class include +Comparable+ module, therefore you can use the
58
+ # comparative operators. In the comparison between DYI::Length
59
+ # objects, the unit of each objects are arranged and it does. The equality
60
+ # operator '<tt>==</tt>' does not test equality instance but test equality
61
+ # value.
62
+ #
63
+ # This class suports following arithmetic operators and methods: <tt>+</tt>,
64
+ # <tt>-</tt>, <tt>*</tt>, <tt>/</tt>, <tt>%</tt>, <tt>**</tt>, +#div+, +#quo+,
65
+ # +#modulo+. The operators '<tt>+</tt>', '<tt>-</tt>' coerced right hand
66
+ # operand into Length, and then calculate.
67
+ #
68
+ # See the documentation to each operators and methods class for details.
69
+ #
70
+ # == Examples of use
71
+ #
72
+ # length1 = DYI::Length.new(10) # 10 user unit (equals to 10px).
73
+ # length2 = DYI::Length.new('10') # it is 10px too.
74
+ # DYI::Length.new('1in') > DYI::Length.new(50)
75
+ # # => true, 1in equals 90px.
76
+ # DYI::Length.new('1in') > DYI::Length.new('2em')
77
+ # # => Error, 'em' is not comparable unit.
78
+ # DYI::Length.new('10cm') == DYI::Length.new('100mm')
79
+ # # => true
80
+ class Length
81
+ include Comparable
82
+
83
+ # Array of unit that can be used.
84
+ UNITS = ['px', 'pt', '%', 'cm', 'mm', 'in', 'em', 'ex', 'pc']
85
+
86
+ @@units = {'px'=>1.0,'pt'=>1.25,'cm'=>35.43307,'mm'=>3.543307,'in'=>90.0,'pc'=>15.0}
87
+ @@default_format = '0.###U'
88
+
89
+ # Returns a new DYI::Length object.
90
+ #
91
+ # +length+ is instance of +Numeric+, +String+, or
92
+ # <tt>DYI::Length</tt> class.
93
+ def initialize(length)
94
+ case length
95
+ when Length
96
+ @value = length._num
97
+ @unit = length._unit
98
+ when Numeric
99
+ @value = length
100
+ @unit = nil
101
+ when String
102
+ unless /^\s*(-?[\d]*(?:\d\.|\.\d|\d)[0\d]*)(#{UNITS.join('|')})?\s*$/ =~ length
103
+ raise ArgumentError, "`#{length}' is string that could not be understand"
104
+ end
105
+ __value, __unit = $1, $2
106
+ @value = __value.include?('.') ? __value.to_f : __value.to_i
107
+ @unit = (__unit == 'px' || @value == 0) ? nil : __unit
108
+ else
109
+ raise TypeError, "#{length.class} can't be coerced into Length"
110
+ end
111
+ end
112
+
113
+ # This constant is zoro length.
114
+ ZERO = new(0)
115
+
116
+ # Returns the receiver's value.
117
+ def +@
118
+ self
119
+ end
120
+
121
+ # Returns the receiver's value, negated.
122
+ def -@
123
+ new_length(-@value)
124
+ end
125
+
126
+ # Returns a new length which is the sum of the receiver and +other+.
127
+ def +(other)
128
+ other = self.class.new(other)
129
+ if @unit == other._unit
130
+ new_length(@value + other._num)
131
+ else
132
+ self.class.new(to_f + other.to_f)
133
+ end
134
+ end
135
+
136
+ # Returns a new length which is the difference of the receiver and +other+.
137
+ def -(other)
138
+ other = self.class.new(other)
139
+ if @unit == other._unit
140
+ new_length(@value - other._num)
141
+ else
142
+ self.class.new(to_f - other.to_f)
143
+ end
144
+ end
145
+
146
+ # Returns a new muliplicative length of the receiver by +number+.
147
+ def *(number)
148
+ new_length(@value * number)
149
+ end
150
+
151
+ # Raises a length the number power.
152
+ def **(number)
153
+ new_length(@value ** number)
154
+ end
155
+
156
+ # Returns a new length which is the result of dividing the receiver by
157
+ # +other+.
158
+ def div(other)
159
+ case other
160
+ when Length
161
+ if @unit == other.unit
162
+ @value.div(other._num)
163
+ else
164
+ to_f.div(other.to_f)
165
+ end
166
+ else
167
+ raise TypeError, "#{other.class} can't be coerced into Length"
168
+ end
169
+ end
170
+
171
+ # Return a new length which is the modulo after division of the receiver by
172
+ # +other+.
173
+ def % (other)
174
+ case other
175
+ when Length
176
+ if @unit == other.unit
177
+ new_length(@value % other._num)
178
+ else
179
+ self.class.new(to_f % other.to_f)
180
+ end
181
+ else
182
+ raise TypeError, "#{other.class} can't be coerced into Length"
183
+ end
184
+ end
185
+
186
+ # If argument +number+ is a numeric, returns a new divisional length of the
187
+ # receiver by +other+. If argument +number+ is a length, returns a divisional
188
+ # float of the receiver by +other+.
189
+ #
190
+ # DYI::Length.new(10) / 4 # => 2.5px
191
+ # DYI::Length.new(10) / DYI::Length.new(4) # => 2.5
192
+ def /(number)
193
+ case number
194
+ when Numeric
195
+ new_length(@value.quo(number.to_f))
196
+ when Length
197
+ if @unit == number.unit
198
+ @value.quo(number._num.to_f)
199
+ else
200
+ to_f.quo(number.to_f)
201
+ end
202
+ else
203
+ raise TypeError, "#{number.class} can't be coerced into Numeric or Length"
204
+ end
205
+ end
206
+
207
+ alias quo /
208
+ alias modulo %
209
+
210
+ def clone #:nodoc:
211
+ raise TypeError, "allocator undefined for Length"
212
+ end
213
+
214
+ def dup #:nodoc:
215
+ raise TypeError, "allocator undefined for Length"
216
+ end
217
+
218
+ # Returns +true+ if the receiver has a zero length, +false+ otherwise.
219
+ def zero?
220
+ @value == 0
221
+ end
222
+
223
+ # Returns +self+ if the receiver is not zero, +nil+ otherwise.
224
+ def nonzero?
225
+ @value == 0 ? nil : self
226
+ end
227
+
228
+ # Returns the absolute length of the receiver.
229
+ def abs
230
+ @value >= 0 ? self : -self
231
+ end
232
+
233
+ # Returns +-1+, +0+, <tt>+1</tt> or +nil+ depending on whether the receiver
234
+ # is less than, equal to, greater than real or not comparable.
235
+ def <=>(other)
236
+ return nil unless other.kind_of?(Length)
237
+ if @unit == other._unit
238
+ @value <=> other._num
239
+ else
240
+ to_f <=> other.to_f rescue nil
241
+ end
242
+ end
243
+
244
+ # Returns the receiver's unit. If receiver has no unit, returns 'px'.
245
+ def unit
246
+ @unit.nil? ? 'px' : @unit
247
+ end
248
+
249
+ # :call-seq:
250
+ # step (limit, step) {|length| ...}
251
+ # step (limit, step)
252
+ #
253
+ # Invokes block with the sequence of length starting at receiver,
254
+ # incremented by +step+ on each call. The loop finishes when +length+ to be
255
+ # passed to the block is greater than +limit+ (if +step+ is positive) or
256
+ # less than +limit+ (if +step+ is negative).
257
+ #
258
+ # If no block is given, an enumerator is returned instead.
259
+ def step(limit, step)
260
+ if @unit == limit._unit && @unit == step._unit
261
+ self_value, limit_value, step_value = @value, limit._num, step._num
262
+ else
263
+ self_value, limit_value, step_value = to_f, limit.to_f, step.to_f
264
+ end
265
+ enum = Enumerator.new {|y|
266
+ self_value.step(limit_value, step_value) do |value|
267
+ self.new_length(value)
268
+ end
269
+ }
270
+ if block_given?
271
+ enum.each(&proc)
272
+ self
273
+ else
274
+ enum
275
+ end
276
+ end
277
+
278
+ # Returns a new length that converted into length of user unit.
279
+ def to_user_unit
280
+ @unit ? self.class.new(to_f) : self
281
+ end
282
+
283
+ # Returns a string representing obj.
284
+ #
285
+ # Format string can be specified for the argument. If no argument is given,
286
+ # +default_format+ of this class is used as format string. About format
287
+ # string, see the documentation to +default_format+ method.
288
+ def to_s(format=nil)
289
+ fmts = (format || @@default_format).split('\\\\')
290
+ fmts = fmts.map do |fmt|
291
+ fmt.gsub(/(?!\\U)(.|\G)U/, '\\1' + @unit.to_s).gsub(/(?!\\u)(.|\G)u/, '\\1' + unit)
292
+ end
293
+ @value.strfnum(fmts.join('\\\\'))
294
+ end
295
+
296
+ # Returns amount part of a length converted into given unit as float. If
297
+ # parameter +unit+ is given, converts into user unit.
298
+ def to_f(unit=nil)
299
+ unless self_ratio = @unit ? @@units[@unit] : 1.0
300
+ raise RuntimeError, "unit `#{@unit}' can not convert into user unit"
301
+ end
302
+ unless param_ratio = unit ? @@units[unit] : 1.0
303
+ if UNITS.include?(unit)
304
+ raise RuntimeError, "unit `#{@unit}' can not convert into user unit"
305
+ else
306
+ raise ArgumentError, "unit `#{@unit}' is unknown unit"
307
+ end
308
+ end
309
+ (@value * self_ratio.quo(param_ratio)).to_f
310
+ end
311
+
312
+ def inspect #:nodoc:
313
+ @value.to_s + @unit.to_s
314
+ end
315
+
316
+ protected
317
+
318
+ def _num #:nodoc:
319
+ @value
320
+ end
321
+
322
+ def _unit #:nodoc:
323
+ @unit
324
+ end
325
+
326
+ private
327
+
328
+ def new_length(value) #:nodoc:
329
+ other = self.class.allocate
330
+ other.instance_variable_set(:@value, value)
331
+ other.instance_variable_set(:@unit, @unit)
332
+ other
333
+ end
334
+
335
+ class << self
336
+
337
+ public
338
+
339
+ def new(*args) #:nodoc:
340
+ return args.first if args.size == 1 && args.first.instance_of?(self)
341
+ super
342
+ end
343
+
344
+ # Returns new instance as +new+ method when an argument is not +nil+.
345
+ # If an argument is +nil+, returns +nil+.
346
+ def new_or_nil(*args)
347
+ (args.size == 1 && args.first.nil?) ? nil : new(*args)
348
+ end
349
+
350
+ # Returns a coefficient that is used for conversion from any unit into
351
+ # user unit.
352
+ def unit_ratio(unit)
353
+ raise ArgumentError, "unit `#{unit}' can not convert other unit" unless ratio = @@units[unit.to_s]
354
+ ratio
355
+ end
356
+
357
+ # :call-seq:
358
+ # set_default_format (format) {...}
359
+ # set_default_format (format)
360
+ #
361
+ # Invokes block with given +format+ string as default format. After
362
+ # invokes block, formar format is used.
363
+ #
364
+ # If no block is given, sets default format setring as +default_format=+
365
+ # method.
366
+ def set_default_format(format)
367
+ if block_given?
368
+ org_format = @@default_format
369
+ self.default_format = format
370
+ yield
371
+ @@default_format = org_format
372
+ self
373
+ else
374
+ self.default_format = format
375
+ end
376
+ end
377
+
378
+ # Returns format that is used when called to_s. See the documentation to
379
+ # +default_format=+ method too.
380
+ def default_format
381
+ @@default_format
382
+ end
383
+
384
+ # Sets format that is used when called to_s.
385
+ #
386
+ # The format string is same as <tt>Numeric#strfnum</tt> format. See the
387
+ # documentation to <tt>Numeric#strfnum</tt> method. In addition to
388
+ # place-holder of +strfnum+, following placeholder can be used.
389
+ #
390
+ # +u+:: (unit placeholder) Placeholder '+u+' is replaced as a unit. If
391
+ # the unit is user unit, '+u+' is repleced as 'px'.
392
+ # +U+:: (unit placeholder) Placeholder '+U+' is replaced as a unit. If
393
+ # the unit is user unit, '+U+' is replece as empty string.
394
+ def default_format=(fromat)
395
+ @@default_format = fromat.dup
396
+ end
397
+ end
398
+ end
399
+ end