dyi 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +674 -0
- data/README +28 -0
- data/examples/class_diagram.rb +151 -0
- data/examples/data/03311056.xlsx +0 -0
- data/examples/data/currency.xlsx +0 -0
- data/examples/data/money.csv +12 -0
- data/examples/line_and_bar.rb +26 -0
- data/examples/line_chart.rb +30 -0
- data/examples/logo.rb +68 -0
- data/examples/pie_chart.rb +19 -0
- data/examples/simple_shapes.rb +15 -0
- data/lib/dyi.rb +49 -0
- data/lib/dyi/chart.rb +34 -0
- data/lib/dyi/chart/array_reader.rb +136 -0
- data/lib/dyi/chart/base.rb +580 -0
- data/lib/dyi/chart/csv_reader.rb +93 -0
- data/lib/dyi/chart/excel_reader.rb +100 -0
- data/lib/dyi/chart/line_chart.rb +468 -0
- data/lib/dyi/chart/pie_chart.rb +141 -0
- data/lib/dyi/chart/table.rb +201 -0
- data/lib/dyi/color.rb +218 -0
- data/lib/dyi/coordinate.rb +224 -0
- data/lib/dyi/drawing.rb +32 -0
- data/lib/dyi/drawing/canvas.rb +100 -0
- data/lib/dyi/drawing/clipping.rb +61 -0
- data/lib/dyi/drawing/color_effect.rb +118 -0
- data/lib/dyi/drawing/filter.rb +74 -0
- data/lib/dyi/drawing/pen.rb +231 -0
- data/lib/dyi/drawing/pen_3d.rb +270 -0
- data/lib/dyi/font.rb +132 -0
- data/lib/dyi/formatter.rb +36 -0
- data/lib/dyi/formatter/base.rb +245 -0
- data/lib/dyi/formatter/emf_formatter.rb +253 -0
- data/lib/dyi/formatter/eps_formatter.rb +397 -0
- data/lib/dyi/formatter/svg_formatter.rb +260 -0
- data/lib/dyi/formatter/svg_reader.rb +113 -0
- data/lib/dyi/formatter/xaml_formatter.rb +317 -0
- data/lib/dyi/length.rb +399 -0
- data/lib/dyi/matrix.rb +122 -0
- data/lib/dyi/painting.rb +177 -0
- data/lib/dyi/shape.rb +1332 -0
- data/lib/dyi/svg_element.rb +149 -0
- data/lib/dyi/type.rb +104 -0
- data/lib/ironruby.rb +326 -0
- data/lib/util.rb +231 -0
- data/test/path_command_test.rb +217 -0
- data/test/test_length.rb +91 -0
- metadata +114 -0
data/lib/dyi/matrix.rb
ADDED
@@ -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
|
data/lib/dyi/painting.rb
ADDED
@@ -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
|
data/lib/dyi/shape.rb
ADDED
@@ -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
|