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,122 @@
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
+
24
+ class Matrix
25
+ attr_accessor :xx, :yx, :xy, :yy, :x0, :y0
26
+
27
+ # :call-seq:
28
+ # new (xx, yx, xy, yy, x0, y0)
29
+ #
30
+ def initialize(*args)
31
+ case args.first
32
+ when :translate
33
+ @xx = @yy = 1
34
+ @xy = @yx = 0
35
+ @x0 = args[1]
36
+ @y0 = args[2] || 0
37
+ when :scale
38
+ @xx = args[1]
39
+ @yy = args[2] || args[1]
40
+ @xy = @yx = @x0 = @y0 = 0
41
+ when :rotate
42
+ radian = args[1] * 2 * Math::PI / 360
43
+ @xx = @yy = Math.cos(radian)
44
+ @xy = -(@yx = Math.sin(radian))
45
+ @x0 = @y0 = 0
46
+ when :skewX
47
+ @xx = @yy = 1
48
+ @xy = Math.tan(args[1] * 2 * Math::PI / 360)
49
+ @yx = @x0 = @y0 = 0
50
+ when :skewY
51
+ @xx = @yy = 1
52
+ @yx = Math.tan(args[1] * 2 * Math::PI / 360)
53
+ @xy = @x0 = @y0 = 0
54
+ else
55
+ raise ArgumentError unless args.size == 6
56
+ @xx, @yx, @xy, @yy, @x0, @y0 = args
57
+ end
58
+ end
59
+
60
+ def *(other)
61
+ self.class.new(
62
+ xx * other.xx + xy * other.yx, yx * other.xx + yy * other.yx,
63
+ xx * other.xy + xy * other.yy, yx * other.xy + yy * other.yy,
64
+ xx * other.x0 + xy * other.y0 + x0, yx * other.x0 + yy * other.y0 + y0)
65
+ end
66
+
67
+ def ==(other)
68
+ xx == other.xx && yx == other.yx && xy == other.xy && yy == other.yy && x0 == other.x0 && y0 == other.y0
69
+ end
70
+
71
+ def translate(tx, ty)
72
+ self * Matrix.translate(tx, ty)
73
+ end
74
+
75
+ def scale(sx, xy)
76
+ self * Matrix.scale(sx, xy)
77
+ end
78
+
79
+ def rotate(angle)
80
+ self * Matrix.rotate(angle)
81
+ end
82
+
83
+ def skew_x(angle)
84
+ self * Matrix.skew_x(angle)
85
+ end
86
+
87
+ def skew_y(angle)
88
+ self * Matrix.skew_y(angle)
89
+ end
90
+
91
+ def transform(coordinate)
92
+ Coordinate.new(coordinate.x * @xx + coordinate.y * @xy + @x0, coordinate.x * @yx + coordinate.y * @yy + @y0)
93
+ end
94
+
95
+ class << self
96
+
97
+ def identity
98
+ new(1, 0, 0, 1, 0, 0)
99
+ end
100
+
101
+ def translate(tx, ty)
102
+ new(:translate, tx, ty)
103
+ end
104
+
105
+ def scale(sx, sy)
106
+ new(:scale, sx, sy)
107
+ end
108
+
109
+ def rotate(angle)
110
+ new(:rotate, angle)
111
+ end
112
+
113
+ def skew_x(angle)
114
+ new(:skewX, angle)
115
+ end
116
+
117
+ def skew_y(angle)
118
+ new(:skewY, angle)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,177 @@
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
+
24
+ class Painting
25
+ IMPLEMENT_ATTRIBUTES = [:fill,:fill_opacity,:fill_rule,:stroke,:stroke_dasharray,:stroke_dashoffset,:stroke_linecap,:stroke_linejoin,:stroke_miterlimit,:stroke_opacity,:stroke_width]
26
+ VALID_VALUES = {
27
+ :fill_rule => ['nonzero','evenodd'],
28
+ :stroke_linecap => ['butt','round','square'],
29
+ :stroke_linejoin => ['miter','round','bevel']
30
+ }
31
+
32
+ ##
33
+ # :method: fill
34
+
35
+ ##
36
+ # :method: fill_opacity
37
+
38
+ ##
39
+ # :method: fill_rule
40
+
41
+ ##
42
+ # :method: stroke
43
+
44
+ ##
45
+ # :method: stroke_dasharray
46
+
47
+ ##
48
+ # :method: stroke_dashoffset
49
+
50
+ ##
51
+ # :method: stroke_linecap
52
+
53
+ ##
54
+ # :method: stroke_linejoin
55
+
56
+ ##
57
+ # :method: stroke_miterlimit
58
+
59
+ ##
60
+ # :method: stroke_opacity
61
+
62
+ ##
63
+ # :method: stroke_width
64
+
65
+ ##
66
+ attr_reader *IMPLEMENT_ATTRIBUTES
67
+
68
+ def initialize(options={})
69
+ case options
70
+ when Painting
71
+ IMPLEMENT_ATTRIBUTES.each do |attr|
72
+ instance_variable_set("@#{attr}", options.__send__(attr))
73
+ end
74
+ when Hash
75
+ options.each do |attr, value|
76
+ __send__("#{attr}=", value) if IMPLEMENT_ATTRIBUTES.include?(attr.to_sym)
77
+ end
78
+ else
79
+ raise TypeError, "#{options.class} can't be coerced into #{self.class}"
80
+ end
81
+ end
82
+
83
+ ##
84
+ # :method: fill_rule=
85
+ #
86
+ # :call-seq:
87
+ # fill_rule= (value)
88
+ #
89
+
90
+ ##
91
+ # :method: stroke_linecap=
92
+ #
93
+ # :call-seq:
94
+ # stroke_linecap= (value)
95
+ #
96
+
97
+ ##
98
+ # :method: stroke_linejoin=
99
+ #
100
+ # :call-seq:
101
+ # stroke_linejoin= (value)
102
+ #
103
+
104
+ ##
105
+ VALID_VALUES.each do |attr, valid_values|
106
+ define_method("#{attr.to_s}=") {|value|
107
+ if (value = value.to_s).size == 0
108
+ instance_variable_set("@#{attr}", nil)
109
+ else
110
+ raise ArgumentError, "`#{value}' is invalid #{attr}" unless VALID_VALUES[attr].include?(value)
111
+ instance_variable_set("@#{attr}", value)
112
+ end
113
+ }
114
+ end
115
+
116
+ def fill=(color)
117
+ @fill = color.respond_to?(:color?) && color.color? ? color : Color.new_or_nil(color)
118
+ end
119
+
120
+ def stroke=(color)
121
+ @stroke = color.respond_to?(:color?) && color.color? ? color : Color.new_or_nil(color)
122
+ end
123
+
124
+ def fill_opacity=(opacity)
125
+ @fill_opacity = opacity.nil? ? nil : opacity.to_f
126
+ end
127
+
128
+ def stroke_opacity=(opacity)
129
+ @stroke_opacity = opacity.nil? ? nil : opacity.to_f
130
+ end
131
+
132
+ def stroke_width=(width)
133
+ @stroke_width = Length.new_or_nil(width)
134
+ end
135
+
136
+ def stroke_miterlimit=(miterlimit)
137
+ @stroke_miterlimit = miterlimit.nil? ? nil : miterlimit.to_f
138
+ end
139
+
140
+ def stroke_dasharray=(array)
141
+ if (array.nil? || array.empty?)
142
+ @stroke_dasharray = nil
143
+ elsif array.kind_of?(String)
144
+ @stroke_dasharray = array.split(/\s*,\s*/).map {|len| Length.new(len)}
145
+ else
146
+ @stroke_dasharray = array.map {|len| Length.new(len)}
147
+ end
148
+ end
149
+
150
+ def stroke_dashoffset=(offset)
151
+ @stroke_dashoffset = Length.new_or_nil(offset)
152
+ end
153
+
154
+ def attributes
155
+ IMPLEMENT_ATTRIBUTES.inject({}) do |hash, attr|
156
+ value = instance_variable_get("@#{attr}")
157
+ unless value.nil?
158
+ hash[attr] = value # value.respond_to?(:join) ? value.join(',') : value.to_s
159
+ end
160
+ hash
161
+ end
162
+ end
163
+
164
+ def empty?
165
+ IMPLEMENT_ATTRIBUTES.all? do |attr|
166
+ not instance_variable_get("@#{attr}")
167
+ end
168
+ end
169
+
170
+ class << self
171
+
172
+ def new_or_nil(*args)
173
+ (args.size == 1 && args.first.nil?) ? nil : new(*args)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,1332 @@
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 'enumerator'
23
+
24
+ module DYI #:nodoc:
25
+ module Shape #:nodoc:
26
+
27
+ class Base
28
+ extend AttributeCreator
29
+ attr_reader :attributes, :clipping
30
+
31
+ def draw_on(parent)
32
+ parent.child_elements.push(self)
33
+ self
34
+ end
35
+
36
+ def write_as(formatter, io=$>)
37
+ end
38
+
39
+ def root_node?
40
+ false
41
+ end
42
+
43
+ def transform
44
+ @transform ||= []
45
+ end
46
+
47
+ def translate(x, y=0)
48
+ x = Length.new(x)
49
+ y = Length.new(y)
50
+ return if x.zero? && y.zero?
51
+ lt = transform.last
52
+ if lt && lt.first == :translate
53
+ lt[1] += x
54
+ lt[2] += y
55
+ transform.pop if lt[1].zero? && lt[2].zero?
56
+ else
57
+ transform.push([:translate, x, y])
58
+ end
59
+ end
60
+
61
+ def scale(x, y=nil, base_point=Coordinate::ZERO)
62
+ y ||= x
63
+ return if x == 1 && y == 1
64
+ base_point = Coordinate.new(base_point)
65
+ translate(base_point.x, base_point.y) if base_point.nonzero?
66
+ lt = transform.last
67
+ if lt && lt.first == :scale
68
+ lt[1] *= x
69
+ lt[2] *= y
70
+ transform.pop if lt[1] == 1 && lt[2] == 1
71
+ else
72
+ transform.push([:scale, x, y])
73
+ end
74
+ translate(- base_point.x, - base_point.y) if base_point.nonzero?
75
+ end
76
+
77
+ def rotate(angle, base_point=Coordinate::ZERO)
78
+ angle %= 360
79
+ return if angle == 0
80
+ base_point = Coordinate.new(base_point)
81
+ translate(base_point.x, base_point.y) if base_point.nonzero?
82
+ lt = transform.last
83
+ if lt && lt.first == :rotate
84
+ lt[1] = (lt[1] + angle) % 360
85
+ transform.pop if lt[1] == 0
86
+ else
87
+ transform.push([:rotate, angle])
88
+ end
89
+ translate(- base_point.x, - base_point.y) if base_point.nonzero?
90
+ end
91
+
92
+ def skew_x(angle, base_point=Coordinate::ZERO)
93
+ angle %= 180
94
+ return if angle == 0
95
+ base_point = Coordinate.new(base_point)
96
+ translate(base_point.x, base_point.y) if base_point.nonzero?
97
+ transform.push([:skewX, angle])
98
+ translate(- base_point.x, - base_point.y) if base_point.nonzero?
99
+ end
100
+
101
+ def skew_y(angle, base_point=Coordinate::ZERO)
102
+ angle %= 180
103
+ return if angle == 0
104
+ base_point = Coordinate.new(base_point)
105
+ translate(base_point.x, base_point.y) if base_point.nonzero?
106
+ lt = transform.last
107
+ transform.push([:skewY, angle])
108
+ translate(- base_point.x, - base_point.y) if base_point.nonzero?
109
+ end
110
+
111
+ def set_clipping(clipping)
112
+ @clipping = clipping
113
+ end
114
+
115
+ def clear_clipping
116
+ @clipping = nil
117
+ end
118
+
119
+ def set_clipping_shapes(*shapes)
120
+ @clipping = Drawing::Compositing::Clip.new(*shapes)
121
+ end
122
+
123
+ private
124
+
125
+ def init_attributes(options)
126
+ options = options.clone
127
+ @font = Font.new_or_nil(options.delete(:font)) if respond_to?(:font)
128
+ @painting = Painting.new_or_nil(options.delete(:painting)) if respond_to?(:painting)
129
+ options
130
+ end
131
+ end
132
+
133
+ class Rectangle < Base
134
+ attr_painting :painting
135
+ attr_length :width, :height
136
+
137
+ def initialize(left_top, width, height, options={})
138
+ width = Length.new(width)
139
+ height = Length.new(height)
140
+ @lt_pt = Coordinate.new(left_top)
141
+ @lt_pt += Coordinate.new(width, 0) if width < Length::ZERO
142
+ @lt_pt += Coordinate.new(0, height) if height < Length::ZERO
143
+ @width = width.abs
144
+ @height = height.abs
145
+ @attributes = init_attributes(options)
146
+ end
147
+
148
+ def left
149
+ @lt_pt.x
150
+ end
151
+
152
+ def right
153
+ @lt_pt.x + width
154
+ end
155
+
156
+ def top
157
+ @lt_pt.y
158
+ end
159
+
160
+ def bottom
161
+ @lt_pt.y + height
162
+ end
163
+
164
+ def center
165
+ @lt_pt + Coordinate.new(width.quo(2), height.quo(2))
166
+ end
167
+
168
+ def write_as(formatter, io=$>)
169
+ formatter.write_rectangle(self, io, &(block_given? ? Proc.new : nil))
170
+ end
171
+
172
+ class << self
173
+
174
+ public
175
+
176
+ def create_on_width_height(left_top, width, height, options={})
177
+ new(left_top, width, height, options)
178
+ end
179
+
180
+ def create_on_corner(top, right, bottom, left, options={})
181
+ left_top = Coordinate.new([left, right].min, [top, bottom].min)
182
+ width = (Length.new(right) - Length.new(left)).abs
183
+ height = (Length.new(bottom) - Length.new(top)).abs
184
+ new(left_top, width, height, options)
185
+ end
186
+ end
187
+ end
188
+
189
+ class Circle < Base
190
+ attr_painting :painting
191
+ attr_coordinate :center
192
+ attr_length :radius
193
+
194
+ def initialize(center, radius, options={})
195
+ @center = Coordinate.new(center)
196
+ @radius = Length.new(radius).abs
197
+ @attributes = init_attributes(options)
198
+ end
199
+
200
+ def left
201
+ @center.x - @radius
202
+ end
203
+
204
+ def right
205
+ @center.x + @radius
206
+ end
207
+
208
+ def top
209
+ @center.y - @radius
210
+ end
211
+
212
+ def bottom
213
+ @center.y + @radius
214
+ end
215
+
216
+ def width
217
+ @radius * 2
218
+ end
219
+
220
+ def height
221
+ @radius * 2
222
+ end
223
+
224
+ def write_as(formatter, io=$>)
225
+ formatter.write_circle(self, io, &(block_given? ? Proc.new : nil))
226
+ end
227
+
228
+ class << self
229
+
230
+ public
231
+
232
+ def create_on_center_radius(center, radius, options={})
233
+ new(center, radius, options)
234
+ end
235
+ end
236
+ end
237
+
238
+ class Ellipse < Base
239
+ attr_painting :painting
240
+ attr_coordinate :center
241
+ attr_length :radius_x, :radius_y
242
+
243
+ def initialize(center, radius_x, radius_y, options={})
244
+ @center = Coordinate.new(center)
245
+ @radius_x = Length.new(radius_x).abs
246
+ @radius_y = Length.new(radius_y).abs
247
+ @attributes = init_attributes(options)
248
+ end
249
+
250
+ def left
251
+ @center.x - @radius_x
252
+ end
253
+
254
+ def right
255
+ @center.x + @radius_x
256
+ end
257
+
258
+ def top
259
+ @center.y - @radius_y
260
+ end
261
+
262
+ def bottom
263
+ @center.y + @radius_y
264
+ end
265
+
266
+ def width
267
+ @radius_x * 2
268
+ end
269
+
270
+ def height
271
+ @radius_y * 2
272
+ end
273
+
274
+ def write_as(formatter, io=$>)
275
+ formatter.write_ellipse(self, io, &(block_given? ? Proc.new : nil))
276
+ end
277
+
278
+ class << self
279
+
280
+ public
281
+
282
+ def create_on_center_radius(center, radius_x, radius_y, options={})
283
+ new(center, radius_x, radius_y, options)
284
+ end
285
+ end
286
+ end
287
+
288
+ class Line < Base
289
+ attr_painting :painting
290
+ attr_coordinate :start_point, :end_point
291
+
292
+ def initialize(start_point, end_point, options={})
293
+ @start_point = Coordinate.new(start_point)
294
+ @end_point = Coordinate.new(end_point)
295
+ @attributes = init_attributes(options)
296
+ end
297
+
298
+ def left
299
+ [@start_point.x, @end_point.x].min
300
+ end
301
+
302
+ def right
303
+ [@start_point.x, @end_point.x].max
304
+ end
305
+
306
+ def top
307
+ [@start_point.y, @end_point.y].min
308
+ end
309
+
310
+ def bottom
311
+ [@start_point.y, @end_point.y].max
312
+ end
313
+
314
+ def write_as(formatter, io=$>)
315
+ formatter.write_line(self, io, &(block_given? ? Proc.new : nil))
316
+ end
317
+
318
+ class << self
319
+
320
+ public
321
+
322
+ def create_on_start_end(start_point, end_point, options={})
323
+ new(start_point, end_point, options)
324
+ end
325
+
326
+ def create_on_direction(start_point, direction_x, direction_y, options={})
327
+ start_point = Coordinate.new(start_point)
328
+ end_point = start_point + Coordinate.new(direction_x, direction_y)
329
+ new(start_point, end_point, options)
330
+ end
331
+ end
332
+ end
333
+
334
+ class Polyline < Base
335
+ attr_painting :painting
336
+
337
+ def initialize(start_point, options={})
338
+ @points = [Coordinate.new(start_point)]
339
+ @attributes = init_attributes(options)
340
+ end
341
+
342
+ def line_to(point, relative=false)
343
+ @points.push(relative ? current_point + point : Coordinate.new(point))
344
+ end
345
+
346
+ def current_point
347
+ @points.last
348
+ end
349
+
350
+ def start_point
351
+ @points.first
352
+ end
353
+
354
+ def points
355
+ @points.dup
356
+ end
357
+
358
+ def undo
359
+ @points.pop if @points.size > 1
360
+ end
361
+
362
+ def left
363
+ @points.min {|a, b| a.x <=> b.x}.x
364
+ end
365
+
366
+ def right
367
+ @points.max {|a, b| a.x <=> b.x}.x
368
+ end
369
+
370
+ def top
371
+ @points.min {|a, b| a.y <=> b.y}.y
372
+ end
373
+
374
+ def bottom
375
+ @points.max {|a, b| a.y <=> b.y}.y
376
+ end
377
+
378
+ def write_as(formatter, io=$>)
379
+ formatter.write_polyline(self, io, &(block_given? ? Proc.new : nil))
380
+ end
381
+ end
382
+
383
+ class Polygon < Polyline
384
+
385
+ def write_as(formatter, io=$>)
386
+ formatter.write_polygon(self, io, &(block_given? ? Proc.new : nil))
387
+ end
388
+ end
389
+
390
+ class Path < Base
391
+ attr_painting :painting
392
+
393
+ def initialize(start_point, options={})
394
+ @path_data = PathData.new(start_point)
395
+ @attributes = init_attributes(options)
396
+ end
397
+
398
+ def move_to(*points)
399
+ push_command(:move_to, *points)
400
+ end
401
+
402
+ def rmove_to(*points)
403
+ push_command(:rmove_to, *points)
404
+ end
405
+
406
+ def line_to(*points)
407
+ push_command(:line_to, *points)
408
+ end
409
+
410
+ def rline_to(*points)
411
+ push_command(:rline_to, *points)
412
+ end
413
+
414
+ def quadratic_curve_to(*points)
415
+ raise ArgumentError, "number of points must be 2 or more" if points.size < 2
416
+ push_command(:quadratic_curve_to, points[0], points[1])
417
+ push_command(:shorthand_quadratic_curve_to, *points[2..-1]) if points.size > 2
418
+ end
419
+
420
+ def rquadratic_curve_to(*points)
421
+ raise ArgumentError, "number of points must be 2 or more" if points.size < 2
422
+ push_command(:rquadratic_curve_to, points[0], points[1])
423
+ push_command(:rshorthand_quadratic_curve_to, *points[2..-1]) if points.size > 2
424
+ end
425
+
426
+ def curve_to(*points)
427
+ raise ArgumentError, "number of points must be odd number of 3 or more" if points.size % 2 == 0 || points.size < 3
428
+ push_command(:curve_to, points[0], points[1], points[2])
429
+ push_command(:shorthand_curve_to, *points[3..-1]) if points.size > 3
430
+ end
431
+
432
+ def rcurve_to(*points)
433
+ raise ArgumentError, "number of points must be odd number of 3 or more" if points.size % 2 == 0 || points.size < 3
434
+ push_command(:rcurve_to, points[0], points[1], points[2])
435
+ push_command(:rshorthand_curve_to, *points[3..-1]) if points.size > 3
436
+ end
437
+
438
+ def arc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true)
439
+ push_command(:arc_to, radius_x, radius_y, rotation, is_large_arc, is_clockwise, point)
440
+ end
441
+
442
+ def rarc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true)
443
+ push_command(:rarc_to, radius_x, radius_y, rotation, is_large_arc, is_clockwise, point)
444
+ end
445
+
446
+ def close?
447
+ @path_data.close?
448
+ end
449
+
450
+ def close_path
451
+ push_command(:close_path)
452
+ end
453
+
454
+ def start_point
455
+ @path_data.start_point
456
+ end
457
+
458
+ def current_point
459
+ @path_data.current_point
460
+ end
461
+
462
+ def current_start_point
463
+ @path_data.current_start_point
464
+ end
465
+
466
+ def push_command(command_type, *args)
467
+ @path_data.push_command(command_type, *args)
468
+ end
469
+
470
+ def pop_command
471
+ @path_data.pop
472
+ end
473
+
474
+ def path_points
475
+ @path_data.path_points
476
+ end
477
+
478
+ def path_data
479
+ @path_data
480
+ end
481
+
482
+ def compatible_path_data
483
+ @path_data.compatible_path_data
484
+ end
485
+
486
+ def concise_path_data
487
+ @path_data.to_concise_syntax
488
+ end
489
+
490
+ def left
491
+ edge_coordinate(:left)
492
+ end
493
+
494
+ def right
495
+ edge_coordinate(:right)
496
+ end
497
+
498
+ def top
499
+ edge_coordinate(:top)
500
+ end
501
+
502
+ def bottom
503
+ edge_coordinate(:bottom)
504
+ end
505
+ =begin
506
+ def line_bezier_paths
507
+ start_point = Coordinate::ZERO
508
+ current_point = Coordinate::ZERO
509
+ last_ctrl_point = nil
510
+ @path_data.inject([]) do |result, path_point|
511
+ case path_point.first
512
+ when 'M', 'L', 'C'
513
+ last_ctrl_point = path_point[2]
514
+ current_point = path_point.last
515
+ result << path_point
516
+ start_point = current_point if path_point.first == 'M'
517
+ when 'm', 'l'
518
+ result << [path_point.first.upcase, (current_point += path_point.last)]
519
+ start_point = current_point if path_point.first == 'm'
520
+ when 'c'
521
+ result << [path_point.first.upcase, current_point + path_point[1], (last_ctrl_point = current_point + path_point[2]), (current_point += path_point.last)]
522
+ when 'Z'
523
+ result << path_point
524
+ current_point = start_point
525
+ when 'Q', 'q', 'T', 't'
526
+ case path_point.first
527
+ when 'Q'
528
+ last_ctrl_point = path_point[1]
529
+ last_point = path_point[2]
530
+ when 'q'
531
+ last_ctrl_point = current_point + path_point[1]
532
+ last_point = current_point + path_point[2]
533
+ when 'T'
534
+ last_ctrl_point = current_point * 2 - last_ctrl_point
535
+ last_point = path_point[1]
536
+ when 't'
537
+ last_ctrl_point = current_point * 2 - last_ctrl_point
538
+ last_point = current_point + path_point[1]
539
+ end
540
+ ctrl_point1 = (current_point + last_ctrl_point * 2).quo(3)
541
+ ctrl_point2 = (last_point + last_ctrl_point * 2).quo(3)
542
+ result << ['C', ctrl_point1, ctrl_point2, (current_point = last_point)]
543
+ when 'S', 's'
544
+ case path_point.first
545
+ when 'S'
546
+ ctrl_point1 = current_point * 2 - last_ctrl_point
547
+ ctrl_point2 = path_point[1]
548
+ last_point = path_point[2]
549
+ when 's'
550
+ ctrl_point1 = current_point * 2 - last_ctrl_point
551
+ ctrl_point2 = current_point + path_point[1]
552
+ last_point = current_point + path_point[2]
553
+ end
554
+ result << ['C', ctrl_point1, (last_ctrl_point = ctrl_point2), (current_point = last_point)]
555
+ when 'A', 'a'
556
+ rx, ry, lotate, large_arc, clockwise, last_point = path_point[1..-1]
557
+ last_point += current_point if path_point.first == 'a'
558
+ rx = rx.to_f
559
+ ry = ry.to_f
560
+ lotate = lotate * Math::PI / 180
561
+ cu_pt = Coordinate.new(
562
+ current_point.x * Math.cos(lotate) / rx + current_point.y * Math.sin(lotate) / rx,
563
+ current_point.y * Math.cos(lotate) / ry - current_point.x * Math.sin(lotate) / ry)
564
+ en_pt = Coordinate.new(
565
+ last_point.x * Math.cos(lotate) / rx + last_point.y * Math.sin(lotate) / rx,
566
+ last_point.y * Math.cos(lotate) / ry - last_point.x * Math.sin(lotate) / ry)
567
+ begin
568
+ k = Math.sqrt(4.quo((en_pt.x.to_f - cu_pt.x.to_f) ** 2 + (en_pt.y.to_f - cu_pt.y.to_f) ** 2) - 1) * (large_arc == clockwise ? 1 : -1)
569
+ center_pt = Coordinate.new(
570
+ cu_pt.x - cu_pt.y * k + en_pt.x + en_pt.y * k,
571
+ cu_pt.y + cu_pt.x * k + en_pt.y - en_pt.x * k) * 0.5
572
+ cu_pt -= center_pt
573
+ en_pt -= center_pt
574
+ theta = Math.acos(cu_pt.x.to_f * en_pt.x.to_f + cu_pt.y.to_f * en_pt.y.to_f)
575
+ theta = 2 * Math::PI - theta if large_arc == 1
576
+ rescue
577
+ center_pt = Coordinate.new(cu_pt.x + en_pt.x, cu_pt.y + en_pt.y) * 0.5
578
+ cu_pt -= center_pt
579
+ en_pt -= center_pt
580
+ theta = Math::PI
581
+ end
582
+ d_count = theta.quo(Math::PI / 8).ceil
583
+ d_t = theta / d_count * (clockwise == 1 ? 1 : -1)
584
+ curves = []
585
+ cos = Math.cos(d_t)
586
+ sin = Math.sin(d_t)
587
+ tan = Math.tan(d_t / 4)
588
+ mat = Matrix.new(
589
+ rx * Math.cos(lotate), rx * Math.sin(lotate),
590
+ -ry * Math.sin(lotate), ry * Math.cos(lotate),
591
+ center_pt.x * rx * Math.cos(lotate) - center_pt.y * ry * Math.sin(lotate),
592
+ center_pt.y * ry * Math.cos(lotate) + center_pt.x * rx * Math.sin(lotate))
593
+ d_count.times do |i|
594
+ ne_pt = Coordinate.new(cu_pt.x * cos - cu_pt.y * sin, cu_pt.y * cos + cu_pt.x * sin)
595
+ curves << [
596
+ mat.translate(Coordinate.new(cu_pt.x - cu_pt.y * 4 * tan / 3, cu_pt.y + cu_pt.x * 4 * tan / 3)),
597
+ mat.translate(Coordinate.new(ne_pt.x + ne_pt.y * 4 * tan / 3, ne_pt.y - ne_pt.x * 4 * tan / 3)),
598
+ mat.translate(ne_pt)]
599
+ cu_pt = ne_pt
600
+ end
601
+ curves.last[2] = last_point
602
+ current_point = last_point
603
+ curves.each do |c|
604
+ result << ['C', c[0], c[1], c[2]]
605
+ end
606
+ end
607
+ result
608
+ end
609
+ end
610
+ =end
611
+ def write_as(formatter, io=$>)
612
+ formatter.write_path(self, io, &(block_given? ? Proc.new : nil))
613
+ end
614
+
615
+ private
616
+ =begin
617
+ def edge_coordinate(edge_type)
618
+ case edge_type
619
+ when :left
620
+ element_type = :x
621
+ amount_type = :min
622
+ when :right
623
+ element_type = :x
624
+ amount_type = :max
625
+ when :top
626
+ element_type = :y
627
+ amount_type = :min
628
+ when :bottom
629
+ element_type = :y
630
+ amount_type = :max
631
+ else
632
+ raise ArgumentError, "unknown edge_tpe `#{edge_type}'"
633
+ end
634
+ current_pt = nil
635
+ line_bezier_paths.inject(nil) do |result, path_point|
636
+ case path_point.first
637
+ when 'M', 'L'
638
+ current_pt = path_point.last
639
+ [result, current_pt.__send__(element_type)].compact.__send__(amount_type)
640
+ when 'C'
641
+ pts = [current_pt.__send__(element_type), path_point[1].__send__(element_type), path_point[2].__send__(element_type), path_point[3].__send__(element_type)]
642
+ nums = pts.map {|pt| pt.to_f}
643
+ current_pt = path_point.last
644
+ delta = (nums[2] - nums[1] * 2 + nums[0]) ** 2 - (nums[3] - nums[2] * 3 + nums[1] * 3 - nums[0]) * (nums[1] - nums[0])
645
+ if delta >= 0
646
+ res0 = ((nums[2] - nums[1] * 2 + nums[0]) * (-1) + Math.sqrt(delta)).quo(nums[3] - nums[2] * 3 + nums[1] * 3 - nums[0])
647
+ res1 = ((nums[2] - nums[1] * 2 + nums[0]) * (-1) - Math.sqrt(delta)).quo(nums[3] - nums[2] * 3 + nums[1] * 3 - nums[0])
648
+ res0 = (0..1).include?(res0) ? Length.new(res0) : nil
649
+ res1 = (0..1).include?(res1) ? Length.new(res1) : nil
650
+ [result, pts[0], pts[3], res0, res1].compact.__send__(amount_type)
651
+ else
652
+ [result, pts[0], pts[3]].conpact.__send__(amount_type)
653
+ end
654
+ else
655
+ result
656
+ end
657
+ end
658
+ end
659
+ =end
660
+ class << self
661
+
662
+ public
663
+
664
+ def draw(start_point, options={}, &block)
665
+ path = new(start_point, options)
666
+ yield path
667
+ path
668
+ end
669
+
670
+ def draw_and_close(start_point, options={}, &block)
671
+ path = draw(start_point, options, &block)
672
+ path.close_path unless path.close?
673
+ path
674
+ end
675
+ end
676
+
677
+ class PathData #:nodoc:
678
+ include Enumerable
679
+
680
+ def initialize(*points)
681
+ raise ArgumentError, 'wrong number of arguments (0 for 1)' if points.empty?
682
+ @commands = MoveCommand.absolute_commands(nil, *points)
683
+ end
684
+
685
+ def each
686
+ if block_given?
687
+ @commands.each{|command| yield command}
688
+ else
689
+ @commands.each
690
+ end
691
+ end
692
+
693
+ def push_command(command_type, *args)
694
+ case command_type
695
+ when :move_to
696
+ @commands.push(*MoveCommand.absolute_commands(@commands.last, *args))
697
+ when :rmove_to
698
+ @commands.push(*MoveCommand.relative_commands(@commands.last, *args))
699
+ when :close_path
700
+ @commands.push(*CloseCommand.commands(@commands.last))
701
+ when :line_to
702
+ @commands.push(*LineCommand.absolute_commands(@commands.last, *args))
703
+ when :rline_to
704
+ @commands.push(*LineCommand.relative_commands(@commands.last, *args))
705
+ when :horizontal_lineto_to
706
+ @commands.push(*HorizontalLineCommand.absolute_commands(@commands.last, *args))
707
+ when :rhorizontal_lineto_to
708
+ @commands.push(*HorizontalLineCommand.relative_commands(@commands.last, *args))
709
+ when :vertical_lineto_to
710
+ @commands.push(*VerticalLineCommand.absolute_commands(@commands.last, *args))
711
+ when :rvertical_lineto_to
712
+ @commands.push(*VerticalLineCommand.relative_commands(@commands.last, *args))
713
+ when :curve_to
714
+ @commands.push(*CurveCommand.absolute_commands(@commands.last, *args))
715
+ when :rcurve_to
716
+ @commands.push(*CurveCommand.relative_commands(@commands.last, *args))
717
+ when :shorthand_curve_to
718
+ @commands.push(*ShorthandCurveCommand.absolute_commands(@commands.last, *args))
719
+ when :rshorthand_curve_to
720
+ @commands.push(*ShorthandCurveCommand.relative_commands(@commands.last, *args))
721
+ when :quadratic_curve_to
722
+ @commands.push(*QuadraticCurveCommand.absolute_commands(@commands.last, *args))
723
+ when :rquadratic_curve_to
724
+ @commands.push(*QuadraticCurveCommand.relative_commands(@commands.last, *args))
725
+ when :shorthand_quadratic_curve_to
726
+ @commands.push(*ShorthandQuadraticCurveCommand.absolute_commands(@commands.last, *args))
727
+ when :rshorthand_quadratic_curve_to
728
+ @commands.push(*ShorthandQuadraticCurveCommand.relative_commands(@commands.last, *args))
729
+ when :arc_to
730
+ @commands.push(*ArcCommand.absolute_commands(@commands.last, *args))
731
+ when :rarc_to
732
+ @commands.push(*ArcCommand.relative_commands(@commands.last, *args))
733
+ else
734
+ raise ArgumentError, "unknown command type `#{command_type}'"
735
+ end
736
+ end
737
+
738
+ def pop_command
739
+ @commands.pop
740
+ end
741
+
742
+ def compatible_path_data
743
+ new_instance = clone
744
+ new_instance.commands = compatible_path_commands
745
+ new_instance
746
+ end
747
+
748
+ def compatible_path_data!
749
+ @commands = compatible_path_commands
750
+ self
751
+ end
752
+
753
+ def start_point
754
+ @commands.first.start_point
755
+ end
756
+
757
+ def current_point
758
+ @commands.last.last_point
759
+ end
760
+
761
+ def current_start_point
762
+ @commands.last.start_point
763
+ end
764
+
765
+ def path_points
766
+ @commands.map{|command| command.points}.flatten
767
+ end
768
+
769
+ def close?
770
+ @commands.last.is_a?(CloseCommand)
771
+ end
772
+
773
+ def to_concise_syntax
774
+ @commands.map{|command| command.to_concise_syntax_fragments}.join(' ')
775
+ end
776
+
777
+ protected
778
+
779
+ def commands=(value)
780
+ @commands = value
781
+ end
782
+
783
+ private
784
+
785
+ def compatible_path_commands
786
+ @commands.inject([]) do |compat_cmds, command|
787
+ compat_cmds.push(*command.to_compatible_commands(compat_cmds.last))
788
+ end
789
+ end
790
+ end
791
+
792
+ class CommandBase #:nodoc:
793
+ attr_reader :preceding_command, :point
794
+
795
+ def initialize(relative, preceding_command, point)
796
+ @relative = relative
797
+ @preceding_command = preceding_command
798
+ @point = Coordinate.new(point)
799
+ end
800
+
801
+ def relative?
802
+ @relative
803
+ end
804
+
805
+ def absolute?
806
+ !relative?
807
+ end
808
+
809
+ def start_point
810
+ preceding_command.start_point
811
+ end
812
+
813
+ def last_point
814
+ relative? ? preceding_point + @point : @point
815
+ end
816
+
817
+ def preceding_point
818
+ preceding_command && preceding_command.last_point
819
+ end
820
+
821
+ def to_compatible_commands(preceding_command)
822
+ compat_commands = clone
823
+ compat_commands.preceding_command = preceding_command
824
+ compat_commands
825
+ end
826
+
827
+ def used_same_command?
828
+ preceding_command.instructions_char == instructions_char
829
+ end
830
+
831
+ protected
832
+
833
+ def preceding_command=(value)
834
+ @preceding_command = preceding_command
835
+ end
836
+
837
+ class << self
838
+ def relative_commands(preceding_command, *args)
839
+ commands(true, preceding_command, *args)
840
+ end
841
+
842
+ def absolute_commands(preceding_command, *args)
843
+ commands(false, preceding_command, *args)
844
+ end
845
+ end
846
+ end
847
+
848
+ class MoveCommand < CommandBase #:nodoc:
849
+
850
+ def start_point
851
+ last_point
852
+ end
853
+
854
+ def last_point
855
+ (relative? && preceding_command.nil?) ? preceding_command : super
856
+ end
857
+
858
+ def relative?
859
+ preceding_command.nil? ? false : super
860
+ end
861
+
862
+ def to_concise_syntax_fragments
863
+ instructions_char + @point.to_s
864
+ end
865
+
866
+ def instructions_char
867
+ relative? ? 'm' : 'M'
868
+ end
869
+
870
+ class << self
871
+ def commands(relative, preceding_command, *points)
872
+ raise ArgumentError, 'wrong number of arguments (2 for 3)' if points.empty?
873
+ commands = [new(relative, preceding_command, points.first)]
874
+ points[1..-1].inject(commands) do |cmds, pt|
875
+ cmds << LineCommand.new(relative, cmds.last, pt)
876
+ end
877
+ end
878
+ end
879
+ end
880
+
881
+ class CloseCommand < CommandBase #:nodoc:
882
+ def initialize(preceding_command)
883
+ raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
884
+ @relative = nil
885
+ @preceding_command = preceding_command
886
+ @point = nil
887
+ end
888
+
889
+ def last_point
890
+ start_point
891
+ end
892
+
893
+ def relative?
894
+ nil
895
+ end
896
+
897
+ def absolute?
898
+ nil
899
+ end
900
+
901
+ def to_concise_syntax_fragments
902
+ instructions_char
903
+ end
904
+
905
+ def instructions_char
906
+ 'Z'
907
+ end
908
+
909
+ class << self
910
+ undef relative_commands, absolute_commands
911
+
912
+ def commands(preceding_command)
913
+ [new(preceding_command)]
914
+ end
915
+ end
916
+ end
917
+
918
+ class LineCommand < CommandBase #:nodoc:
919
+ def initialize(relative, preceding_command, point)
920
+ raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
921
+ super
922
+ end
923
+
924
+ def to_concise_syntax_fragments
925
+ used_same_command? ? @point.to_s : (instructions_char + @point.to_s)
926
+ end
927
+
928
+ def instructions_char
929
+ relative? ? 'l' : 'L'
930
+ end
931
+
932
+ class << self
933
+ def commands(relative, preceding_command, *points)
934
+ raise ArgumentError, 'wrong number of arguments (2 for 3)' if points.empty?
935
+ cmd = preceding_command
936
+ points.inject([]) do |cmds, pt|
937
+ cmds << (cmd = new(relative, cmd, pt))
938
+ end
939
+ end
940
+ end
941
+ end
942
+
943
+ class HorizontalLineCommand < LineCommand #:nodoc:
944
+ def initialize(relative, preceding_command, x)
945
+ super(relative, preceding_command, Coordinate.new(x, relative ? 0 : preceding_command.last_point.y))
946
+ end
947
+
948
+ def to_compatible_commands(preceding_command)
949
+ LineCommand.new(relative?, preceding_command, pt)
950
+ end
951
+
952
+ def to_concise_syntax_fragments
953
+ used_same_command? ? @point.x.to_s : (instructions_char + @point.x.to_s)
954
+ end
955
+
956
+ def instructions_char
957
+ relative? ? 'h' : 'H'
958
+ end
959
+ end
960
+
961
+ class VerticalLineCommand < LineCommand #:nodoc:
962
+ def initialize(relative, preceding_command, y)
963
+ super(relative, preceding_command, Coordinate.new(relative ? 0 : preceding_command.last_point.x, y))
964
+ end
965
+
966
+ def to_compatible_commands(preceding_command)
967
+ LineCommand.new(relative?, preceding_command, pt)
968
+ end
969
+
970
+ def to_concise_syntax_fragments
971
+ used_same_command? ? @point.y.to_s : (instructions_char + @point.y.to_s)
972
+ end
973
+
974
+ def instructions_char
975
+ relative? ? 'v' : 'V'
976
+ end
977
+ end
978
+
979
+ class CurveCommandBase < CommandBase #:nodoc:
980
+ def initialize(relative, preceding_command, *points)
981
+ raise ArgumentError, "wrong number of arguments (2 for #{pt_cnt + 2})" if points.size != pt_cnt
982
+ raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
983
+ @relative = relative
984
+ @preceding_command = preceding_command
985
+ @point = Coordinate.new(points.last)
986
+ @control_points = points[0..-2].map{|pt| Coordinate.new(pt)}
987
+ end
988
+
989
+ def last_control_point
990
+ relative? ? (preceding_point + @control_points.last) : @control_points.last
991
+ end
992
+
993
+ def to_concise_syntax_fragments
994
+ used_same_command? ? @point.to_s : (instructions_char + @point.to_s)
995
+ end
996
+
997
+ def to_concise_syntax_fragments
998
+ fragments = @control_points.map{|ctrl_pt| ctrl_pt.to_s}.push(@point.to_s)
999
+ fragments[0] = instructions_char + fragments[0] unless used_same_command?
1000
+ fragments
1001
+ end
1002
+
1003
+ private
1004
+
1005
+ def pt_cnt
1006
+ self.class.pt_cnt
1007
+ end
1008
+
1009
+ class << self
1010
+ def commands(relative, preceding_command, *points)
1011
+ raise ArgumentError, "number of points must be a multipule of #{pt_cnt}" if points.size % pt_cnt != 0
1012
+ cmd = preceding_command
1013
+ points.each_slice(pt_cnt).inject([]) do |cmds, pts|
1014
+ cmds << (cmd = new(relative, cmd, *pts))
1015
+ end
1016
+ end
1017
+ end
1018
+ end
1019
+
1020
+ class CurveCommand < CurveCommandBase #:nodoc:
1021
+ def preceding_control_point
1022
+ if preceding_command.is_a?(CurveCommand)
1023
+ preceding_command.last_control_point
1024
+ else
1025
+ preceding_command.last_point
1026
+ end
1027
+ end
1028
+
1029
+ def control_point1
1030
+ @control_points[0]
1031
+ end
1032
+
1033
+ def control_point2
1034
+ @control_points[1]
1035
+ end
1036
+
1037
+ def instructions_char
1038
+ relative? ? 'c' : 'C'
1039
+ end
1040
+
1041
+ class << self
1042
+ def pt_cnt
1043
+ 3
1044
+ end
1045
+ end
1046
+ end
1047
+
1048
+ class ShorthandCurveCommand < CurveCommand #:nodoc:
1049
+ def control_point1
1050
+ if relative?
1051
+ preceding_point - preceding_control_point
1052
+ else
1053
+ preceding_point * 2 - preceding_control_point
1054
+ end
1055
+ end
1056
+
1057
+ def control_point2
1058
+ @control_points[0]
1059
+ end
1060
+
1061
+ def to_compatible_commands(preceding_command)
1062
+ CurveCommand.new(relative?, preceding_command, control_point1, control_point2, @point)
1063
+ end
1064
+
1065
+ def instructions_char
1066
+ relative? ? 's' : 'S'
1067
+ end
1068
+
1069
+ class << self
1070
+ def pt_cnt
1071
+ 2
1072
+ end
1073
+ end
1074
+ end
1075
+
1076
+ class QuadraticCurveCommand < CurveCommandBase #:nodoc:
1077
+ def preceding_control_point
1078
+ if preceding_command.is_a?(QuadraticCurveCommand)
1079
+ preceding_command.last_control_point
1080
+ else
1081
+ preceding_command.last_point
1082
+ end
1083
+ end
1084
+
1085
+ def control_point
1086
+ @control_points[0]
1087
+ end
1088
+
1089
+ def to_compatible_commands(preceding_command)
1090
+ ctrl_pt1 = relative? ? control_point * 2.0 / 3.0 : (preceding_point + control_point * 2.0) / 3.0
1091
+ ctrl_pt2 = (control_point * 2.0 + point) / 3.0
1092
+ CurveCommand.new(relative?, preceding_command, ctrl_pt1, ctrl_pt2, @point)
1093
+ end
1094
+
1095
+ def instructions_char
1096
+ relative? ? 'q' : 'Q'
1097
+ end
1098
+
1099
+ class << self
1100
+ def pt_cnt
1101
+ 2
1102
+ end
1103
+ end
1104
+ end
1105
+
1106
+ class ShorthandQuadraticCurveCommand < QuadraticCurveCommand #:nodoc:
1107
+ def control_point
1108
+ if relative?
1109
+ preceding_point - preceding_control_point
1110
+ else
1111
+ preceding_point * 2 - preceding_control_point
1112
+ end
1113
+ end
1114
+
1115
+ def last_control_point
1116
+ preceding_point * 2 - preceding_control_point
1117
+ end
1118
+
1119
+ def instructions_char
1120
+ relative? ? 't' : 'T'
1121
+ end
1122
+
1123
+ class << self
1124
+ def pt_cnt
1125
+ 1
1126
+ end
1127
+ end
1128
+ end
1129
+
1130
+ class ArcCommand < CommandBase #:nodoc:
1131
+ attr_reader :rx, :ry, :rotation
1132
+
1133
+ def initialize(relative, preceding_command, rx, ry, rotation, is_large_arc, is_clockwise, point)
1134
+ raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
1135
+ @relative = relative
1136
+ @preceding_command = preceding_command
1137
+ @point = Coordinate.new(point)
1138
+ @rx = Length.new(rx)
1139
+ @ry = Length.new(ry)
1140
+ @rotation = rotation
1141
+ @is_large_arc = is_large_arc
1142
+ @is_clockwise = is_clockwise
1143
+ end
1144
+
1145
+ def large_arc?
1146
+ @is_large_arc
1147
+ end
1148
+
1149
+ def clockwise?
1150
+ @is_clockwise
1151
+ end
1152
+
1153
+ def to_compatible_commands(preceding_command)
1154
+ division_count = (center_angle / 30.0).ceil
1155
+ division_angle = center_angle / division_count * (clockwise? ? 1 : -1)
1156
+ current_point = start_angle_point
1157
+ compat_commands = []
1158
+ division_count.times do |i|
1159
+ end_point = if i == division_count - 1
1160
+ end_angle_point
1161
+ else
1162
+ Matrix.rotate(division_angle).transform(current_point)
1163
+ end
1164
+ control_point1 = control_point_of_curve(current_point, division_angle, true)
1165
+ control_point2 = control_point_of_curve(end_point, division_angle, false)
1166
+ preceding_command = CurveCommand.new(relative?,
1167
+ preceding_command,
1168
+ control_point1,
1169
+ control_point2,
1170
+ (i == division_count - 1) ?
1171
+ point : transform_orginal_shape(end_point))
1172
+ compat_commands << preceding_command
1173
+ current_point = end_point
1174
+ end
1175
+ compat_commands
1176
+ end
1177
+
1178
+ def to_concise_syntax_fragments
1179
+ [used_same_command? ? rx.to_s : instructions_char + rx.to_s,
1180
+ ry, rotation, large_arc? ? 1 : 0, clockwise? ? 1 : 0, point.to_s]
1181
+ end
1182
+
1183
+ def instructions_char
1184
+ relative? ? 'a' : 'A'
1185
+ end
1186
+
1187
+ def center_point
1188
+ Matrix.rotate(rotation).transform(modified_center_point) + (preceding_point + point) * 0.5
1189
+ end
1190
+
1191
+ private
1192
+
1193
+ def modified_mid_point
1194
+ Matrix.rotate(-rotation).transform((preceding_point - point) * 0.5)
1195
+ end
1196
+
1197
+ def modified_center_point
1198
+ pt = modified_mid_point
1199
+ Coordinate.new(pt.y * (rx / ry), -pt.x * (ry / rx)) *
1200
+ Math.sqrt(((rx.to_f * ry.to_f) ** 2 - (rx.to_f * pt.y.to_f) ** 2 - (ry.to_f * pt.x.to_f) ** 2) /
1201
+ ((rx.to_f * pt.y.to_f) ** 2 + (ry.to_f * pt.x.to_f) ** 2)) *
1202
+ ((large_arc? == clockwise?) ? -1 : 1)
1203
+ end
1204
+
1205
+ def start_angle_point
1206
+ Coordinate.new((modified_mid_point.x - modified_center_point.x) / rx,
1207
+ (modified_mid_point.y - modified_center_point.y) / ry)
1208
+ end
1209
+
1210
+ def end_angle_point
1211
+ Coordinate.new((-modified_mid_point.x - modified_center_point.x) / rx,
1212
+ (-modified_mid_point.y - modified_center_point.y) / ry)
1213
+ end
1214
+
1215
+ def center_angle
1216
+ angle = Math.acos(start_angle_point.x.to_f * end_angle_point.x.to_f +
1217
+ start_angle_point.y.to_f * end_angle_point.y.to_f) * 180.0 / Math::PI
1218
+ large_arc? ? 360.0 - angle : angle
1219
+ end
1220
+
1221
+ def transform_matrix
1222
+ Matrix.translate(center_point.x.to_f, center_point.y.to_f).rotate(rotation).scale(rx.to_f, ry.to_f)
1223
+ end
1224
+
1225
+ def transform_orginal_shape(modified_point)
1226
+ transform_matrix.transform(modified_point)
1227
+ end
1228
+
1229
+ def control_point_of_curve(point, center_angle, is_start_point)
1230
+ handle_length = Math.tan(center_angle * Math::PI / 180.0 / 4.0) * 4.0 / 3.0
1231
+ handle = is_start_point ? handle_length : -handle_length
1232
+ transform_matrix.transform(Matrix.new(1, handle, -handle, 1, 0, 0).transform(point))
1233
+ end
1234
+
1235
+ class << self
1236
+ def commands(relative, preceding_command, *args)
1237
+ new(relative, preceding_command, *args)
1238
+ end
1239
+ end
1240
+ end
1241
+ end
1242
+
1243
+ class Text < Base
1244
+ UNPRIMITIVE_OPTIONS = [:line_height, :alignment_baseline, :format]
1245
+ BASELINE_VALUES = ['baseline', 'top', 'middle', 'bottom']
1246
+ DEFAULT_LINE_HEIGHT = 1
1247
+ attr_font :font
1248
+ attr_painting :painting
1249
+ attr_coordinate :point
1250
+ attr_coordinate :line_height
1251
+ attr_accessor :text
1252
+ attr_reader :format
1253
+ attr_reader *UNPRIMITIVE_OPTIONS
1254
+
1255
+ def initialize(point, text=nil, options={})
1256
+ @point = Coordinate.new(point || [0,0])
1257
+ @text = text
1258
+ @attributes = init_attributes(options)
1259
+ end
1260
+
1261
+ def format=(value)
1262
+ @format = value && value.to_s
1263
+ end
1264
+
1265
+ def font_height
1266
+ font.draw_size
1267
+ end
1268
+
1269
+ def dy
1270
+ font_height * (line_height || DEFAULT_LINE_HEIGHT)
1271
+ end
1272
+
1273
+ def formated_text
1274
+ if @format
1275
+ if @text.kind_of?(Numeric)
1276
+ @text.strfnum(@format)
1277
+ elsif @text.respond_to?(:strftime)
1278
+ @text.strftime(@format)
1279
+ else
1280
+ @text.to_s
1281
+ end
1282
+ else
1283
+ @text.to_s
1284
+ end
1285
+ end
1286
+
1287
+ def write_as(formatter, io=$>)
1288
+ formatter.write_text(self, io, &(block_given? ? Proc.new : nil))
1289
+ end
1290
+
1291
+ private
1292
+
1293
+ def init_attributes(options)
1294
+ options = super
1295
+ format = options.delete(:format)
1296
+ @format = format && format.to_s
1297
+ line_height = options.delete(:line_height)
1298
+ @line_height = line_height || DEFAULT_LINE_HEIGHT
1299
+ options
1300
+ end
1301
+ end
1302
+
1303
+ class ShapeGroup < Base
1304
+ attr_reader :child_elements
1305
+
1306
+ def initialize(options={})
1307
+ @attributes = options
1308
+ @child_elements = []
1309
+ end
1310
+
1311
+ def width
1312
+ Length.new_or_nil(@attributes[:width])
1313
+ end
1314
+
1315
+ def height
1316
+ Length.new_or_nil(@attributes[:height])
1317
+ end
1318
+
1319
+ def write_as(formatter, io=$>)
1320
+ formatter.write_group(self, io, &(block_given? ? Proc.new : nil))
1321
+ end
1322
+
1323
+ class << self
1324
+ public
1325
+
1326
+ def draw_on(canvas, options = {})
1327
+ new(options).draw_on(canvas)
1328
+ end
1329
+ end
1330
+ end
1331
+ end
1332
+ end