dyi 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/COPYING +674 -0
  2. data/README +28 -0
  3. data/examples/class_diagram.rb +151 -0
  4. data/examples/data/03311056.xlsx +0 -0
  5. data/examples/data/currency.xlsx +0 -0
  6. data/examples/data/money.csv +12 -0
  7. data/examples/line_and_bar.rb +26 -0
  8. data/examples/line_chart.rb +30 -0
  9. data/examples/logo.rb +68 -0
  10. data/examples/pie_chart.rb +19 -0
  11. data/examples/simple_shapes.rb +15 -0
  12. data/lib/dyi.rb +49 -0
  13. data/lib/dyi/chart.rb +34 -0
  14. data/lib/dyi/chart/array_reader.rb +136 -0
  15. data/lib/dyi/chart/base.rb +580 -0
  16. data/lib/dyi/chart/csv_reader.rb +93 -0
  17. data/lib/dyi/chart/excel_reader.rb +100 -0
  18. data/lib/dyi/chart/line_chart.rb +468 -0
  19. data/lib/dyi/chart/pie_chart.rb +141 -0
  20. data/lib/dyi/chart/table.rb +201 -0
  21. data/lib/dyi/color.rb +218 -0
  22. data/lib/dyi/coordinate.rb +224 -0
  23. data/lib/dyi/drawing.rb +32 -0
  24. data/lib/dyi/drawing/canvas.rb +100 -0
  25. data/lib/dyi/drawing/clipping.rb +61 -0
  26. data/lib/dyi/drawing/color_effect.rb +118 -0
  27. data/lib/dyi/drawing/filter.rb +74 -0
  28. data/lib/dyi/drawing/pen.rb +231 -0
  29. data/lib/dyi/drawing/pen_3d.rb +270 -0
  30. data/lib/dyi/font.rb +132 -0
  31. data/lib/dyi/formatter.rb +36 -0
  32. data/lib/dyi/formatter/base.rb +245 -0
  33. data/lib/dyi/formatter/emf_formatter.rb +253 -0
  34. data/lib/dyi/formatter/eps_formatter.rb +397 -0
  35. data/lib/dyi/formatter/svg_formatter.rb +260 -0
  36. data/lib/dyi/formatter/svg_reader.rb +113 -0
  37. data/lib/dyi/formatter/xaml_formatter.rb +317 -0
  38. data/lib/dyi/length.rb +399 -0
  39. data/lib/dyi/matrix.rb +122 -0
  40. data/lib/dyi/painting.rb +177 -0
  41. data/lib/dyi/shape.rb +1332 -0
  42. data/lib/dyi/svg_element.rb +149 -0
  43. data/lib/dyi/type.rb +104 -0
  44. data/lib/ironruby.rb +326 -0
  45. data/lib/util.rb +231 -0
  46. data/test/path_command_test.rb +217 -0
  47. data/test/test_length.rb +91 -0
  48. metadata +114 -0
@@ -0,0 +1,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