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