dyi 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +674 -0
- data/README +28 -0
- data/examples/class_diagram.rb +151 -0
- data/examples/data/03311056.xlsx +0 -0
- data/examples/data/currency.xlsx +0 -0
- data/examples/data/money.csv +12 -0
- data/examples/line_and_bar.rb +26 -0
- data/examples/line_chart.rb +30 -0
- data/examples/logo.rb +68 -0
- data/examples/pie_chart.rb +19 -0
- data/examples/simple_shapes.rb +15 -0
- data/lib/dyi.rb +49 -0
- data/lib/dyi/chart.rb +34 -0
- data/lib/dyi/chart/array_reader.rb +136 -0
- data/lib/dyi/chart/base.rb +580 -0
- data/lib/dyi/chart/csv_reader.rb +93 -0
- data/lib/dyi/chart/excel_reader.rb +100 -0
- data/lib/dyi/chart/line_chart.rb +468 -0
- data/lib/dyi/chart/pie_chart.rb +141 -0
- data/lib/dyi/chart/table.rb +201 -0
- data/lib/dyi/color.rb +218 -0
- data/lib/dyi/coordinate.rb +224 -0
- data/lib/dyi/drawing.rb +32 -0
- data/lib/dyi/drawing/canvas.rb +100 -0
- data/lib/dyi/drawing/clipping.rb +61 -0
- data/lib/dyi/drawing/color_effect.rb +118 -0
- data/lib/dyi/drawing/filter.rb +74 -0
- data/lib/dyi/drawing/pen.rb +231 -0
- data/lib/dyi/drawing/pen_3d.rb +270 -0
- data/lib/dyi/font.rb +132 -0
- data/lib/dyi/formatter.rb +36 -0
- data/lib/dyi/formatter/base.rb +245 -0
- data/lib/dyi/formatter/emf_formatter.rb +253 -0
- data/lib/dyi/formatter/eps_formatter.rb +397 -0
- data/lib/dyi/formatter/svg_formatter.rb +260 -0
- data/lib/dyi/formatter/svg_reader.rb +113 -0
- data/lib/dyi/formatter/xaml_formatter.rb +317 -0
- data/lib/dyi/length.rb +399 -0
- data/lib/dyi/matrix.rb +122 -0
- data/lib/dyi/painting.rb +177 -0
- data/lib/dyi/shape.rb +1332 -0
- data/lib/dyi/svg_element.rb +149 -0
- data/lib/dyi/type.rb +104 -0
- data/lib/ironruby.rb +326 -0
- data/lib/util.rb +231 -0
- data/test/path_command_test.rb +217 -0
- data/test/test_length.rb +91 -0
- 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
|
data/lib/dyi/length.rb
ADDED
@@ -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
|