dyi 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/COPYING +674 -0
  2. data/README +28 -0
  3. data/examples/class_diagram.rb +151 -0
  4. data/examples/data/03311056.xlsx +0 -0
  5. data/examples/data/currency.xlsx +0 -0
  6. data/examples/data/money.csv +12 -0
  7. data/examples/line_and_bar.rb +26 -0
  8. data/examples/line_chart.rb +30 -0
  9. data/examples/logo.rb +68 -0
  10. data/examples/pie_chart.rb +19 -0
  11. data/examples/simple_shapes.rb +15 -0
  12. data/lib/dyi.rb +49 -0
  13. data/lib/dyi/chart.rb +34 -0
  14. data/lib/dyi/chart/array_reader.rb +136 -0
  15. data/lib/dyi/chart/base.rb +580 -0
  16. data/lib/dyi/chart/csv_reader.rb +93 -0
  17. data/lib/dyi/chart/excel_reader.rb +100 -0
  18. data/lib/dyi/chart/line_chart.rb +468 -0
  19. data/lib/dyi/chart/pie_chart.rb +141 -0
  20. data/lib/dyi/chart/table.rb +201 -0
  21. data/lib/dyi/color.rb +218 -0
  22. data/lib/dyi/coordinate.rb +224 -0
  23. data/lib/dyi/drawing.rb +32 -0
  24. data/lib/dyi/drawing/canvas.rb +100 -0
  25. data/lib/dyi/drawing/clipping.rb +61 -0
  26. data/lib/dyi/drawing/color_effect.rb +118 -0
  27. data/lib/dyi/drawing/filter.rb +74 -0
  28. data/lib/dyi/drawing/pen.rb +231 -0
  29. data/lib/dyi/drawing/pen_3d.rb +270 -0
  30. data/lib/dyi/font.rb +132 -0
  31. data/lib/dyi/formatter.rb +36 -0
  32. data/lib/dyi/formatter/base.rb +245 -0
  33. data/lib/dyi/formatter/emf_formatter.rb +253 -0
  34. data/lib/dyi/formatter/eps_formatter.rb +397 -0
  35. data/lib/dyi/formatter/svg_formatter.rb +260 -0
  36. data/lib/dyi/formatter/svg_reader.rb +113 -0
  37. data/lib/dyi/formatter/xaml_formatter.rb +317 -0
  38. data/lib/dyi/length.rb +399 -0
  39. data/lib/dyi/matrix.rb +122 -0
  40. data/lib/dyi/painting.rb +177 -0
  41. data/lib/dyi/shape.rb +1332 -0
  42. data/lib/dyi/svg_element.rb +149 -0
  43. data/lib/dyi/type.rb +104 -0
  44. data/lib/ironruby.rb +326 -0
  45. data/lib/util.rb +231 -0
  46. data/test/path_command_test.rb +217 -0
  47. data/test/test_length.rb +91 -0
  48. metadata +114 -0
@@ -0,0 +1,397 @@
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 'nkf'
23
+
24
+ module DYI #:nodoc:
25
+ module Formatter #:nodoc:
26
+
27
+ class EpsFormatter < Base
28
+
29
+ def header_comment
30
+ <<-EOS
31
+ %!PS-Adobe-3.0 EPSF-3.0
32
+ %%CreationDate: #{Time.now.strftime('%a %b %d %H:%M:%S %Y')}
33
+ %%BoundingBox: 0 0 #{@canvas.real_width.to_f('pt').ceil} #{@canvas.real_height.to_f('pt').ceil}
34
+ %%EndComments
35
+ EOS
36
+ end
37
+
38
+ def prolog
39
+ <<-EOS
40
+ %%BeginProlog
41
+ /px { } bind def
42
+ /pt { #{Length.unit_ratio('pt')} mul } bind def
43
+ /cm { #{Length.unit_ratio('cm')} mul } bind def
44
+ /mm { #{Length.unit_ratio('mm')} mul } bind def
45
+ /in { #{Length.unit_ratio('in')} mul } bind def
46
+ /pc { #{Length.unit_ratio('pc')} mul } bind def
47
+ %%EndProlog
48
+ EOS
49
+ end
50
+
51
+ def footer_comment
52
+ <<-EOS
53
+ %%EOF
54
+ EOS
55
+ end
56
+
57
+ def base_transform
58
+ pt_ratio = 1.quo(Length.unit_ratio('pt'))
59
+ <<-EOS
60
+ [#{pt_ratio} 0 0 -#{pt_ratio} 0 #{@canvas.real_height * pt_ratio}] concat
61
+ EOS
62
+ end
63
+
64
+ def puts(io=$>)
65
+ StringFormat.set_default_formats(:color=>['%.3f %.3f %.3f', true], :length=>'0.### U', :coordinate=>'x y') {
66
+ if @canvas.root_node?
67
+ io << header_comment
68
+ io << prolog
69
+ io << base_transform
70
+ end
71
+ @canvas.write_as(self, io)
72
+ if @canvas.root_node?
73
+ io << footer_comment
74
+ end
75
+ }
76
+ end
77
+
78
+ def write_canvas(canvas, io)
79
+ canvas.child_elements.each do |element|
80
+ element.write_as(self, io)
81
+ end
82
+ end
83
+
84
+ def write_rectangle(shape, io)
85
+ command_block(io) {
86
+ transform_path(io, shape)
87
+ clip_path(io, shape)
88
+ puts_line(io, 'newpath')
89
+ puts_line(io, shape.left, shape.top, 'moveto')
90
+ puts_line(io, shape.width, 0, 'rlineto')
91
+ puts_line(io, 0, shape.height, 'rlineto')
92
+ puts_line(io, -shape.width, 0, 'rlineto')
93
+ puts_line(io, 'closepath')
94
+ if block_given?
95
+ yield
96
+ else
97
+ fill_current_path(io, shape)
98
+ stroke_current_path(io, shape)
99
+ end
100
+ }
101
+ end
102
+
103
+ def write_circle(shape, io)
104
+ command_block(io) {
105
+ transform_path(io, shape)
106
+ clip_path(io, shape)
107
+ puts_line(io, 'newpath')
108
+ puts_line(io, shape.center.x + shape.radius, shape.center.y, 'moveto')
109
+ puts_line(io, shape.center.x, shape.center.y, shape.radius, 0, 360, 'arc')
110
+ if block_given?
111
+ yield
112
+ else
113
+ fill_current_path(io, shape)
114
+ stroke_current_path(io, shape)
115
+ end
116
+ }
117
+ end
118
+
119
+ def write_ellipse(shape, io)
120
+ ratio = shape.radius_x.to_f / shape.radius_y.to_f
121
+ command_block(io) {
122
+ transform_path(io, shape)
123
+ clip_path(io, shape)
124
+ puts_line(io, 1, 1.0 / ratio, 'scale')
125
+ puts_line(io, 'newpath')
126
+ puts_line(io, shape.center.x + shape.radius_x, shape.center.y * ratio, 'moveto')
127
+ puts_line(io, shape.center.x, shape.center.y * ratio, shape.radius_x, 0, 360, 'arc')
128
+ if block_given?
129
+ yield
130
+ else
131
+ fill_current_path(io, shape)
132
+ stroke_current_path(io, shape)
133
+ end
134
+ }
135
+ end
136
+
137
+ def write_line(shape, io)
138
+ command_block(io) {
139
+ transform_path(io, shape)
140
+ clip_path(io, shape)
141
+ puts_line(io, 'newpath')
142
+ write_lines(io, [shape.start_point, shape.end_point])
143
+ if block_given?
144
+ yield
145
+ else
146
+ stroke_current_path(io, shape)
147
+ end
148
+ }
149
+ end
150
+
151
+ def write_polyline(shape, io)
152
+ command_block(io) {
153
+ transform_path(io, shape)
154
+ clip_path(io, shape)
155
+ puts_line(io, 'newpath')
156
+ write_lines(io, shape.points)
157
+ if block_given?
158
+ yield
159
+ else
160
+ fill_current_path(io, shape)
161
+ stroke_current_path(io, shape)
162
+ end
163
+ }
164
+ end
165
+
166
+ def write_polygon(shape, io)
167
+ command_block(io) {
168
+ transform_path(io, shape)
169
+ clip_path(io, shape)
170
+ puts_line(io, 'newpath')
171
+ write_lines(io, shape.points)
172
+ puts_line(io, 'closepath')
173
+ if block_given?
174
+ yield
175
+ else
176
+ fill_current_path(io, shape)
177
+ stroke_current_path(io, shape)
178
+ end
179
+ }
180
+ end
181
+
182
+ def write_path(shape, io)
183
+ command_block(io) {
184
+ transform_path(io, shape)
185
+ clip_path(io, shape)
186
+ puts_line(io, 'newpath')
187
+ shape.compatible_path_data.each do |cmd|
188
+ case cmd
189
+ when Shape::Path::MoveCommand
190
+ puts_line(io, cmd.point.x, cmd.point.y, cmd.relative? ? 'rmoveto' : 'moveto')
191
+ when Shape::Path::CloseCommand
192
+ puts_line(io, 'closepath')
193
+ when Shape::Path::LineCommand
194
+ puts_line(io, cmd.point.x, cmd.point.y, cmd.relative? ? 'rlineto' : 'lineto')
195
+ when Shape::Path::CurveCommand
196
+ puts_line(io,
197
+ cmd.control_point1.x, cmd.control_point1.y,
198
+ cmd.control_point2.x, cmd.control_point2.y,
199
+ cmd.point.x, cmd.point.y,
200
+ cmd.relative? ? 'rcurveto' : 'curveto')
201
+ else
202
+ raise TypeError, "unknown command: #{cmd.class}"
203
+ end
204
+ end
205
+ if block_given?
206
+ yield
207
+ else
208
+ fill_current_path(io, shape)
209
+ stroke_current_path(io, shape)
210
+ end
211
+ }
212
+ end
213
+
214
+ def write_text(shape, io)
215
+ command_block(io) {
216
+ puts_line(io, '/GothicBBB-Medium-RKSJ-H findfont', shape.font.draw_size, 'scalefont setfont')
217
+ text = NKF.nkf('-s -W', shape.formated_text).unpack('H*').first
218
+ case shape.attributes[:text_anchor]
219
+ when 'middle' then dx = "<#{text}> stringwidth pop -0.5 mul"
220
+ when 'end' then dx = "<#{text}> stringwidth pop -1 mul"
221
+ else dx = "0"
222
+ end
223
+ case shape.attributes[:alignment_baseline]
224
+ when 'top' then y = shape.point.y - shape.font_height * 0.85
225
+ when 'middle' then y = shape.point.y - shape.font_height * 0.35
226
+ when 'bottom' then y = shape.point.y + shape.font_height * 0.15
227
+ else y = shape.point.y
228
+ end
229
+ puts_line(io, "[ 1 0 0 -1 #{dx}", shape.point.y * 2, '] concat')
230
+ puts_line(io, shape.point.x, y, 'moveto')
231
+ puts_line(io, "<#{text}>", 'show')
232
+ }
233
+ end
234
+
235
+ def write_group(shape, io)
236
+ command_block(io) {
237
+ transform_path(io, shape)
238
+ clip_path(io, shape)
239
+ shape.child_elements.each do |element|
240
+ element.write_as(self, io)
241
+ end
242
+ } unless shape.child_elements.empty?
243
+ end
244
+
245
+ private
246
+
247
+ def puts_line(io, *args) #:nodoc:
248
+ io << args.flatten.join(' ') << "\n"
249
+ end
250
+
251
+ def command_block(io) #:nodoc:
252
+ puts_line(io, 'gsave')
253
+ yield
254
+ puts_line(io, 'grestore')
255
+ end
256
+
257
+ def stroke_path(io, shape) #:nodoc:
258
+ cmds = []
259
+ if shape.respond_to?(:painting) && (attrs = shape.painting)
260
+ cmds.push([attrs.stroke, 'setrgbcolor']) if attrs.stroke
261
+ cmds.push([attrs.stroke_width, 'setlinewidth']) if attrs.stroke_width && attrs.stroke_width.to_f > 0
262
+ cmds.push(['[', attrs.stroke_dasharray, ']', attrs.stroke_dashoffset || 0, 'setdash']) if attrs.stroke_dasharray
263
+ cmds.push([linecap_to_num(attrs.stroke_linecap), 'setlinecap']) if linecap_to_num(attrs.stroke_linecap)
264
+ cmds.push([linejoin_to_num(attrs.stroke_linejoin), 'setlinejoin']) if linejoin_to_num(attrs.stroke_linejoin)
265
+ cmds.push([attrs.stroke_miterlimit, 'setmiterlimit']) if attrs.stroke_miterlimit
266
+ end
267
+ return if cmds.empty?
268
+ command_block(io) {
269
+ cmds.each do |cmd|
270
+ puts_line(io, *cmd)
271
+ end
272
+ yield
273
+ }
274
+ end
275
+
276
+ def stroke_current_path(io, shape) #:nodoc:
277
+ stroke_path(io, shape) {
278
+ puts_line(io, 'stroke')
279
+ } if shape.painting.stroke
280
+ end
281
+
282
+ def fill_path(io, shape) #:nodoc:
283
+ cmds = []
284
+ if shape.respond_to?(:painting) && (attrs = shape.painting)
285
+ if attrs.fill
286
+ if attrs.fill.respond_to?(:write_as)
287
+ cmds.push(linear_gradiant_commands(shape, attrs.fill))
288
+ else
289
+ cmds.push([attrs.fill, 'setrgbcolor'])
290
+ end
291
+ end
292
+ end
293
+ return if cmds.empty?
294
+ command_block(io) {
295
+ cmds.each do |cmd|
296
+ puts_line(io, *cmd)
297
+ end
298
+ yield
299
+ }
300
+ end
301
+
302
+ def fill_current_path(io, shape) #:nodoc:
303
+ fill_path(io, shape) {
304
+ puts_line(io, shape.painting.fill_rule == 'evenodd' ? 'eofill' : 'fill')
305
+ } if shape.painting.fill
306
+ end
307
+
308
+ def linecap_to_num(linecap) #:nodoc:
309
+ case linecap
310
+ when 'butt' then 0
311
+ when 'round' then 1
312
+ when 'square' then 2
313
+ end
314
+ end
315
+
316
+ def linejoin_to_num(linejoin) #:nodoc:
317
+ case linejoin
318
+ when 'miter' then 0
319
+ when 'round' then 1
320
+ when 'bevel' then 2
321
+ end
322
+ end
323
+
324
+ def write_lines(io, points) #:nodoc:
325
+ puts_line(io, points.first.x, points.first.y, 'moveto')
326
+ points[1..-1].each do |pt|
327
+ pt = Coordinate.new(pt)
328
+ puts_line(io, pt.x, pt.y, 'lineto')
329
+ end
330
+ end
331
+
332
+ def linear_gradiant_commands(shape, lg)
333
+ x1 = shape.left * (1 - lg.start_point[0]) + shape.right * lg.start_point[0]
334
+ y1 = shape.top * (1 - lg.start_point[1]) + shape.bottom * lg.start_point[1]
335
+ x2 = shape.left * (1 - lg.stop_point[0]) + shape.right * lg.stop_point[0]
336
+ y2 = shape.top * (1 - lg.stop_point[1]) + shape.bottom * lg.stop_point[1]
337
+
338
+ last_stop = lg.child_elements.first
339
+
340
+ cmds = []
341
+ if last_stop
342
+ lg.child_elements[1..-1].each do |g_stop|
343
+ c1 = last_stop.color
344
+ c2 = g_stop.color
345
+ next unless c1 && c2
346
+ s_x1 = x1 * (1 - last_stop.offset) + x2 * last_stop.offset
347
+ s_y1 = y1 * (1 - last_stop.offset) + y2 * last_stop.offset
348
+ s_x2 = x1 * (1 - g_stop.offset) + x2 * g_stop.offset
349
+ s_y2 = y1 * (1 - g_stop.offset) + y2 * g_stop.offset
350
+ cmds.push(['<< /PatternType 2 /Shading << /ShadingType 2 /ColorSpace /DeviceRGB'])
351
+ cmds.push(['/Coords [', s_x1, s_y1, s_x2, s_y2, ']'])
352
+ cmds.push(['/Function << /FunctionType 2 /Domain [ 0 1 ]'])
353
+ cmds.push(['/C0 [', c1, ']'])
354
+ cmds.push(['/C1 [', c2, ']'])
355
+ cmds.push(['/N 1 >> >> >> matrix makepattern setpattern fill'])
356
+ current_stop = g_stop
357
+ end
358
+ end
359
+ cmds
360
+ end
361
+
362
+ def transform_path(io, shape) #:nodoc:
363
+ if shape.respond_to?(:transform) && !(tr = shape.transform).empty?
364
+ tr.each do |t|
365
+ case t[0]
366
+ when :translate
367
+ puts_line(io, t[1], t[2], 'translate')
368
+ when :scale
369
+ puts_line(io, t[1], t[2], 'scale')
370
+ when :rotate
371
+ puts_line(io, t[1], t[2], 'rotate')
372
+ when :skewX
373
+ tan = Math.tan(t[1] * Math::PI / 180)
374
+ puts_line(io, '[', 1, 0, tan, 1, 0, 0, ']', 'concat')
375
+ when :skewY
376
+ tan = Math.tan(t[1] * Math::PI / 180)
377
+ puts_line(io, '[', 1, tan, 0, 1, 0, 0, ']', 'concat')
378
+ end
379
+ end
380
+ end
381
+ end
382
+
383
+ def clip_path(io, shape) #:nodoc:
384
+ if shape.respond_to?(:clipping) && shape.clipping
385
+ shape.clipping.each_shapes do |shape, rule|
386
+ s = shape.dup
387
+ s.painting.fill = nil
388
+ s.painting.stroke = nil
389
+ s.write_as(self, io) {
390
+ puts_line(io, rule == 'evenodd' ? 'eoclip' : 'clip')
391
+ }
392
+ end
393
+ end
394
+ end
395
+ end
396
+ end
397
+ end
@@ -0,0 +1,260 @@
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 'stringio'
23
+
24
+ module DYI #:nodoc:
25
+ module Formatter #:nodoc:
26
+
27
+ class SvgFormatter < XmlFormatter
28
+
29
+ def initialize(canvas, indent=0, level=0, version='1.1')
30
+ super(canvas, indent, level)
31
+ raise ArgumentError, "version `#{version}' is unknown version" unless ['1.0', '1.1'].include?(@version = version.to_s)
32
+ @defs = {}
33
+ end
34
+
35
+ def declaration
36
+ case @version
37
+ when '1.0' then %Q{<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2000/CR-SVG-20001102/DTD/svg-20001102.dtd">}
38
+ when '1.1' then %Q{<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">}
39
+ end
40
+ end
41
+
42
+ def puts(io=$>)
43
+ StringFormat.set_default_formats(:coordinate => 'x,y') {
44
+ super
45
+ }
46
+ end
47
+
48
+ def write_canvas(canvas, io)
49
+ @defs = {}
50
+ sio = StringIO.new
51
+ create_node(sio, 'svg',
52
+ :xmlns => "http://www.w3.org/2000/svg",
53
+ :version => @version,
54
+ :width => canvas.real_width,
55
+ :height => canvas.real_height,
56
+ :viewBox => canvas.view_box,
57
+ :preserveAspectRatio => canvas.preserve_aspect_ratio) {
58
+ @root_info = [sio.pos, @level]
59
+ canvas.child_elements.each do |element|
60
+ element.write_as(self, sio)
61
+ end
62
+ }
63
+
64
+ if @defs.empty?
65
+ io << sio.string
66
+ else
67
+ sio.rewind
68
+ io << sio.read(@root_info[0])
69
+
70
+ _level = @level
71
+ @level = @root_info[1]
72
+ create_node(io, 'defs') {
73
+ @defs.each do |def_id, def_item|
74
+ def_item.write_as(self, io)
75
+ end
76
+ }
77
+ @level = _level
78
+
79
+ io << sio.read
80
+ end
81
+ end
82
+
83
+ def write_rectangle(shape, io)
84
+ attrs = {:x=>shape.left, :y=>shape.top, :width=>shape.width, :height=>shape.height}
85
+ attrs.merge!(common_attributes(shape))
86
+ attrs[:rx] = shape.attributes[:rx] if shape.attributes[:rx]
87
+ attrs[:ry] = shape.attributes[:ry] if shape.attributes[:ry]
88
+ create_leaf_node(io, 'rect', attrs)
89
+ end
90
+
91
+ def write_circle(shape, io)
92
+ attrs = {:cx=>shape.center.x, :cy=>shape.center.y, :r=>shape.radius}
93
+ attrs.merge!(common_attributes(shape))
94
+ create_leaf_node(io, 'circle', attrs)
95
+ end
96
+
97
+ def write_ellipse(shape, io)
98
+ attrs = {:cx=>shape.center.x, :cy=>shape.center.y, :rx=>shape.radius_x, :ry=>shape.radius_y}
99
+ attrs.merge!(common_attributes(shape))
100
+ create_leaf_node(io, 'ellipse', attrs)
101
+ end
102
+
103
+ def write_line(shape, io)
104
+ attrs = {:x1 => shape.start_point.x, :y1 => shape.start_point.y, :x2 => shape.end_point.x, :y2 => shape.end_point.y}
105
+ attrs.merge!(common_attributes(shape))
106
+ create_leaf_node(io, 'line', attrs)
107
+ end
108
+
109
+ def write_polyline(shape, io)
110
+ attrs = {:points => shape.points.join(' ')}
111
+ attrs.merge!(common_attributes(shape))
112
+ create_leaf_node(io, 'polyline', attrs)
113
+ end
114
+
115
+ def write_polygon(shape, io)
116
+ attrs = {:points => shape.points.join(' ')}
117
+ attrs.merge!(common_attributes(shape))
118
+ create_leaf_node(io, 'polygon', attrs)
119
+ end
120
+
121
+ def write_path(shape, io)
122
+ # attrs = {:d => shape.concise_path_data}
123
+ attrs = {:d => shape.compatible_path_data.to_concise_syntax}
124
+ attrs.merge!(common_attributes(shape))
125
+ create_leaf_node(io, 'path', attrs)
126
+ end
127
+
128
+ def write_text(shape, io)
129
+ attrs = {:x => shape.point.x, :y => shape.point.y}
130
+ attrs.merge!(common_attributes(shape))
131
+ attrs[:"text-decoration"] = shape.attributes[:text_decoration] if shape.attributes[:text_decoration]
132
+ # attrs[:"alignment-baseline"] = shape.attributes[:alignment_baseline] if shape.attributes[:alignment_baseline]
133
+ case shape.attributes[:alignment_baseline]
134
+ when 'top' then attrs[:y] += shape.font_height * 0.85
135
+ when 'middle' then attrs[:y] += shape.font_height * 0.35
136
+ when 'bottom' then attrs[:y] -= shape.font_height * 0.15
137
+ end
138
+ attrs[:"text-anchor"] = shape.attributes[:text_anchor] if shape.attributes[:text_anchor]
139
+ attrs[:"writing-mode"] = shape.attributes[:writing_mode] if shape.attributes[:writing_mode]
140
+ attrs[:textLength] = shape.attributes[:textLength] if shape.attributes[:textLength]
141
+ attrs[:lengthAdjust] = shape.attributes[:lengthAdjust] if shape.attributes[:lengthAdjust]
142
+ text = shape.formated_text
143
+ if text =~ /(\r\n|\n|\r)/
144
+ create_node(io, 'text', attrs) {
145
+ create_leaf_node(io, 'tspan', $`.strip, :x => shape.point.x)
146
+ $'.each_line do |line|
147
+ create_leaf_node(io, 'tspan', line.strip, :x => shape.point.x, :dy => shape.dy)
148
+ end
149
+ }
150
+ else
151
+ create_leaf_node(io, 'text', text, attrs)
152
+ end
153
+ end
154
+
155
+ def write_group(shape, io)
156
+ create_node(io, 'g', common_attributes(shape)) {
157
+ shape.child_elements.each do |element|
158
+ element.write_as(self, io)
159
+ end
160
+ } unless shape.child_elements.empty?
161
+ end
162
+
163
+ def write_linear_gradient(shape, io)
164
+ attr = {
165
+ :id => @defs.find{|key, value| value==shape}[0],
166
+ :gradientUnit => 'objectBoundingBox',
167
+ :x1 => shape.start_point[0],
168
+ :y1 => shape.start_point[1],
169
+ :x2 => shape.stop_point[0],
170
+ :y2 => shape.stop_point[1]}
171
+ attr[:"spreadMethod"] = shape.spread_method if shape.spread_method
172
+ create_node(io, 'linearGradient', attr) {
173
+ shape.child_elements.each do |element|
174
+ element.write_as(self, io)
175
+ end
176
+ }
177
+ end
178
+
179
+ def write_gradient_stop(shape, io)
180
+ attrs = {:offset=>shape.offset}
181
+ attrs[:"stop-color"] = shape.color if shape.color
182
+ attrs[:"stop-opacity"] = shape.opacity if shape.opacity
183
+ create_leaf_node(io, 'stop', attrs)
184
+ end
185
+
186
+ def write_clipping(clipping, io)
187
+ attr = {:id => @defs.find{|key, value| value==clipping}[0]}
188
+ create_node(io, 'clipPath', attr) {
189
+ clipping.shapes.each_with_index do |shape, i|
190
+ shape.write_as(self, io)
191
+ end
192
+ }
193
+ end
194
+
195
+ private
196
+
197
+ def name_to_attribute(name) #:nodoc:
198
+ name.to_s.gsub(/_/,'-').to_sym
199
+ end
200
+
201
+ def common_attributes(shape) #:nodoc:
202
+ attributes = {}
203
+ style = create_style(shape)
204
+ transform = create_transform(shape)
205
+ clip_path = create_clip_path(shape)
206
+ attributes[:style] = style if style
207
+ attributes[:transform] = transform if transform
208
+ attributes[:'clip-path'] = clip_path if clip_path
209
+ attributes
210
+ end
211
+
212
+ def create_style(shape) #:nodoc:
213
+ styles = {}
214
+ if shape.respond_to?(:font) && shape.font && !shape.font.empty?
215
+ styles.merge!(shape.font.attributes)
216
+ end
217
+ if shape.respond_to?(:painting) && shape.painting && !shape.painting.empty?
218
+ attributes = shape.painting.attributes
219
+ attributes[:stroke_dasharray] = attributes[:stroke_dasharray].join(',') if attributes.key?(:stroke_dasharray)
220
+ attributes[:fill] = 'none' unless attributes.key?(:fill)
221
+ styles.merge!(attributes)
222
+ end
223
+ styles.empty? ? nil : styles.map {|key, value| "#{attribute_name(key)}:#{attribute_string(value)}"}.join(';')
224
+ end
225
+
226
+ def attribute_string(value) #:nodoc:
227
+ value.respond_to?(:write_as) ? "url(##{add_defs(value)})" : value.to_s
228
+ end
229
+
230
+ def create_transform(shape) #:nodoc:
231
+ if shape.respond_to?(:transform) && !shape.transform.empty?
232
+ shape.transform.map{|item| "#{item[0]}(#{item[1...item.size].join(',')})"}.join(' ')
233
+ end
234
+ end
235
+
236
+ def create_clip_path(shape) #:nodoc:
237
+ if shape.respond_to?(:clipping) && shape.clipping
238
+ "url(##{add_defs(shape.clipping)})"
239
+ end
240
+ end
241
+
242
+ def add_defs(value) #:nodoc:
243
+ @defs.each do |def_id, def_item|
244
+ return def_id if def_item == value
245
+ end
246
+ def_id = create_def_id(@defs.size)
247
+ @defs[def_id] = value
248
+ def_id
249
+ end
250
+
251
+ def create_def_id(index) #:nodoc:
252
+ 'def%03d' % index
253
+ end
254
+
255
+ def attribute_name(key) #:nodoc:
256
+ key.to_s.gsub(/_/,'-')
257
+ end
258
+ end
259
+ end
260
+ end