dyi 0.0.2 → 1.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/lib/dyi/painting.rb CHANGED
@@ -22,7 +22,7 @@
22
22
  module DYI #:nodoc:
23
23
 
24
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]
25
+ IMPLEMENT_ATTRIBUTES = [:opacity,:fill,:fill_opacity,:fill_rule,:stroke,:stroke_dasharray,:stroke_dashoffset,:stroke_linecap,:stroke_linejoin,:stroke_miterlimit,:stroke_opacity,:stroke_width]
26
26
  VALID_VALUES = {
27
27
  :fill_rule => ['nonzero','evenodd'],
28
28
  :stroke_linecap => ['butt','round','square'],
@@ -121,6 +121,11 @@ module DYI #:nodoc:
121
121
  @stroke = color.respond_to?(:color?) && color.color? ? color : Color.new_or_nil(color)
122
122
  end
123
123
 
124
+ # @since 1.0.0
125
+ def opacity=(opacity)
126
+ @opacity = opacity.nil? ? nil : opacity.to_f
127
+ end
128
+
124
129
  def fill_opacity=(opacity)
125
130
  @fill_opacity = opacity.nil? ? nil : opacity.to_f
126
131
  end
data/lib/dyi/script.rb ADDED
@@ -0,0 +1,29 @@
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
+ %w(
23
+
24
+ simple_script
25
+ ecmascript
26
+
27
+ ).each do |file_name|
28
+ require File.join(File.dirname(__FILE__), 'script', file_name)
29
+ end
@@ -0,0 +1,273 @@
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
+ # == Overview
23
+ #
24
+ # This file provides the classes of client side scripting. The script becomes
25
+ # effective only when it is output by SVG format.
26
+ #
27
+ # @since 1.0.0
28
+
29
+ module DYI
30
+ module Script
31
+ # Module for using ECMAScript.
32
+ module EcmaScript
33
+
34
+ # This Module includes helper methods for generating a client-script.
35
+ # These methods generate a script that conforms to DOM Level 2 (W3C
36
+ # Recommendation).
37
+ module DomLevel2
38
+ def get_element(element)
39
+ parts = []
40
+ parts << 'document.getElementById("' << (element.respond_to?(:publish_id) ? element.id : element) << '")'
41
+ parts.join
42
+ end
43
+
44
+ def add_event_listener(event, listener)
45
+ parts = []
46
+ parts << get_element(event.target)
47
+ parts << '.addEventListener("' << event.event_name
48
+ parts << '", function(' << listener.arguments.join(', ') << ") {\n"
49
+ parts << listener.body
50
+ parts << '})'
51
+ parts.join
52
+ end
53
+
54
+ def dispatch_evnet(event)
55
+ parts = []
56
+ parts << get_element(event.target)
57
+ parts << '.dispatchEvent("' << event.event_name << '")'
58
+ parts.join
59
+ end
60
+
61
+ def draw_text_border(*elements)
62
+ parts = []
63
+ parts << " (function(){\n"
64
+ parts << " var elms = ["
65
+ script_elements =
66
+ elements.map do |element|
67
+ el_parts = []
68
+ el_parts << '{el:'
69
+ el_parts << get_element(element)
70
+ el_parts << ',hp:'
71
+ el_parts << (element.attributes[:horizontal_padding] ||
72
+ element.attributes[:padding] || 0)
73
+ el_parts << ',vp:'
74
+ el_parts << (element.attributes[:vertical_padding] ||
75
+ element.attributes[:padding] || 0)
76
+ el_parts << '}'
77
+ el_parts.join
78
+ end
79
+ parts << script_elements.join(",\n ")
80
+ parts << "];\n"
81
+ parts << " for(var i=0; i<#{elements.size}; i++){\n"
82
+ parts << " var elm = elms[i];\n"
83
+ parts << " var top=null,right=null,bottom=null,left=null,rect=null;\n"
84
+ parts << " for(var j=0, len=elm.el.childNodes.length; j<len; j++){\n"
85
+ parts << " var node = elm.el.childNodes.item(j);\n"
86
+ parts << " if(node.nodeName == \"text\") {\n"
87
+ parts << " var text_width = node.getComputedTextLength();\n"
88
+ parts << " var ext = node.getExtentOfChar(0);\n"
89
+ parts << " if(top == null || ext.y < top)\n"
90
+ parts << " top = ext.y;\n"
91
+ parts << " if(right == null || right < ext.x + text_width)\n"
92
+ parts << " right = ext.x + text_width;\n"
93
+ parts << " if(bottom == null || bottom < ext.y + ext.height)\n"
94
+ parts << " bottom = ext.y + ext.height;\n"
95
+ parts << " if(left == null || ext.x < left)\n"
96
+ parts << " left = ext.x;\n"
97
+ parts << " }\n"
98
+ parts << " else if(node.nodeName == \"rect\")\n"
99
+ parts << " rect = node;\n"
100
+ parts << " }\n"
101
+ parts << " rect.setAttribute(\"x\", left - elm.hp);\n"
102
+ parts << " rect.setAttribute(\"y\", top - elm.vp);\n"
103
+ parts << " rect.setAttribute(\"width\", right - left + elm.hp * 2);\n"
104
+ parts << " rect.setAttribute(\"height\", bottom - top + elm.vp * 2);\n"
105
+ parts << " }\n"
106
+ parts << " })();\n"
107
+ parts.join
108
+ end
109
+
110
+ def form_legend_labels(legend)
111
+ parts = []
112
+ parts << " (function(){\n"
113
+ parts << " var legend = #{get_element(legend)}\n"
114
+ parts << " var lengths = [];\n"
115
+ parts << " var groups = legend.childNodes;\n"
116
+ parts << " for(var i=0,lenI=groups.length; i<lenI; i++){\n"
117
+ parts << " if(groups.item(i).nodeName == \"g\"){\n"
118
+ parts << " var lens = [];\n"
119
+ parts << " var texts = groups.item(i).childNodes;\n"
120
+ parts << " for(var j=0,lenJ=texts.length; j<lenJ; j++){\n"
121
+ parts << " if(texts.item(j).nodeName == \"text\"){\n"
122
+ parts << " lens.push(texts.item(j).getComputedTextLength());\n"
123
+ parts << " }\n"
124
+ parts << " }\n"
125
+ parts << " lengths.push(lens);\n"
126
+ parts << " }\n"
127
+ parts << " }\n"
128
+ parts << " var max_lengths = [];\n"
129
+ parts << " lengths.forEach(function(lens, i, lengths){\n"
130
+ parts << " if(i == 0){\n"
131
+ parts << " max_lengths = lens;\n"
132
+ parts << " return;\n"
133
+ parts << " }\n"
134
+ parts << " for(j=0; j<max_lengths.length; j++){\n"
135
+ parts << " if(max_lengths[j] < lens[j])\n"
136
+ parts << " max_lengths[j] = lens[j];\n"
137
+ parts << " }\n"
138
+ parts << " });\n"
139
+ parts << " for(i=0; i<lenI; i++){\n"
140
+ parts << " if(groups.item(i).nodeName == \"g\"){\n"
141
+ parts << " var lens = [];\n"
142
+ parts << " var texts = groups.item(i).childNodes;\n"
143
+ parts << " var k = 0, x = 0;\n"
144
+ parts << " for(j=0,lenJ=texts.length; j<lenJ; j++){\n"
145
+ parts << " var node = texts.item(j);\n"
146
+ parts << " if(node.nodeName == \"rect\"){\n"
147
+ parts << " x = Number(node.getAttribute(\"x\")) + Number(node.getAttribute(\"width\"));\n"
148
+ parts << " }\n"
149
+ parts << " else if(node.nodeName == \"line\"){\n"
150
+ parts << " x = Number(node.getAttribute(\"x2\"));\n"
151
+ parts << " }\n"
152
+ parts << " else if(node.nodeName == \"text\"){\n"
153
+ parts << " x += node.getExtentOfChar(0).height * 0.5;\n"
154
+ parts << " if(node.getAttribute(\"text-anchor\") == \"middle\"){\n"
155
+ parts << " x += max_lengths[k] / 2.0;\n"
156
+ parts << " node.setAttribute(\"x\", x);\n"
157
+ parts << " x += max_lengths[k] / 2.0;\n"
158
+ parts << " }\n"
159
+ parts << " else if(node.getAttribute(\"text-anchor\") == \"end\"){\n"
160
+ parts << " x += max_lengths[k];\n"
161
+ parts << " node.setAttribute(\"x\", x);\n"
162
+ parts << " }\n"
163
+ parts << " else {\n"
164
+ parts << " node.setAttribute(\"x\", x);\n"
165
+ parts << " x += max_lengths[k];\n"
166
+ parts << " }\n"
167
+ parts << " k++;\n"
168
+ parts << " }\n"
169
+ parts << " }\n"
170
+ parts << " lengths.push(lens);\n"
171
+ parts << " }\n"
172
+ parts << " }\n"
173
+ parts << " })();\n"
174
+ parts.join
175
+ end
176
+ end
177
+
178
+ # Class representing a function of ECMAScript. The scripting becomes
179
+ # effective only when it is output by SVG format.
180
+ class Function < SimpleScript
181
+ attr_reader :name, :arguments
182
+
183
+ # @param [String] body body of client scripting
184
+ # @param [String] name a function name
185
+ # @param [Array] arguments a list of argument's name
186
+ def initialize(body, name=nil, *arguments)
187
+ super(body)
188
+ if name && name !~ /\A[\$A-Z_a-z][\$0-9A-Z_a-z]*\z/
189
+ raise ArgumentError, "illegal identifier: `#{name}'"
190
+ end
191
+ @name = name
192
+ @arguments = arguments.map do |arg|
193
+ if arg.to_s !~ /\A[\$A-Z_a-z][\$0-9A-Z_a-z]*\z/
194
+ raise ArgumentError, "illegal identifier: `#{arg}'"
195
+ end
196
+ arg.to_s
197
+ end
198
+ end
199
+
200
+ # (see SimpleScript#body)
201
+ def body
202
+ parts = []
203
+ parts << 'function'
204
+ parts << " #{name}" if name
205
+ parts << '('
206
+ parts << arguments.join(', ')
207
+ parts << ") {\n"
208
+ parts << @body
209
+ parts << "}\n"
210
+ parts.join
211
+ end
212
+ end
213
+
214
+ # Class representing a event listener of ECMAScript. The scripting
215
+ # becomes effective only when it is output by SVG format.
216
+ class EventListener < Function
217
+
218
+ # @param [String] body body of client scripting
219
+ # @param [String] name a function name
220
+ # @param [String] argument argument's name
221
+ def initialize(body, name=nil, argument='evt')
222
+ super
223
+ @events = []
224
+ end
225
+
226
+ # Relates this object to an event.
227
+ # @param [Event] event an event that is related to
228
+ # @return [void]
229
+ def related_to(event)
230
+ @events << event
231
+ end
232
+
233
+ # Removes the relation to an event.
234
+ # @param [Event] event an event that is removed the relation to
235
+ # @return [void]
236
+ def unrelated_to(event)
237
+ @events.delete(event)
238
+ end
239
+
240
+ # (see SimpleScript#body)
241
+ def body
242
+ if name
243
+ super
244
+ else
245
+ parts = []
246
+ parts << "setTimeout(function() {\n"
247
+ @events.each do |event|
248
+ if event.event_name == :load
249
+ parts << @body
250
+ elsif
251
+ if event.target.root_element?
252
+ parts << ' document.rootElement.addEventListener("'
253
+ else
254
+ parts << ' document.getElementById("'
255
+ parts << event.target.id
256
+ parts << '").addEventListener("'
257
+ end
258
+ parts << event.event_name
259
+ parts << '", function('
260
+ parts << arguments.join(', ')
261
+ parts << ") {\n"
262
+ parts << @body
263
+ parts << " }, false);\n"
264
+ end
265
+ end
266
+ parts << "}, 0);\n"
267
+ parts.join
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,94 @@
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
+ # == Overview
23
+ #
24
+ # This file provides the classes of client side scripting. The event becomes
25
+ # effective only when it is output by SVG format.
26
+ #
27
+ # @since 1.0.0
28
+
29
+ module DYI
30
+ module Script
31
+
32
+ # Class representing a inline-client-script. The scripting becomes
33
+ # effective only when it is output by SVG format.
34
+ class SimpleScript
35
+
36
+ # @return [String] content-type of script
37
+ attr_reader :content_type
38
+ # @return [String] body of client scripting
39
+ attr_reader :body
40
+
41
+ # @param [String] body body of client scripting
42
+ # @param [String] content_type content-type of script
43
+ def initialize(body, content_type = 'application/ecmascript')
44
+ @content_type = content_type
45
+ @body = body
46
+ end
47
+
48
+ # Returns this script includes reference of external script file.
49
+ # @return [Boolean] always returns false
50
+ def include_external_file?
51
+ false
52
+ end
53
+
54
+ # Returns this script includes reference of external script file.
55
+ # @return [Boolean] always returns false
56
+ def has_uri_reference?
57
+ false
58
+ end
59
+
60
+ # Writes the buffer contents of the object.
61
+ # @param [Formatter::Base] a formatter for export
62
+ # @param [IO] io a buffer that is written
63
+ # @return [void]
64
+ def write_as(formatter, io=$>)
65
+ formatter.write_script(self, io)
66
+ end
67
+ end
68
+
69
+ # Class representing a referenct of external client-script-file.
70
+ # The scripting becomes effective only when it is output by SVG format.
71
+ class ScriptReference < SimpleScript
72
+
73
+ # @return [String] a path of external script file
74
+ attr_reader :href
75
+
76
+ def initialize(href, content_type = 'application/ecmascript')
77
+ super(nil, content_type)
78
+ @href = href
79
+ end
80
+
81
+ # Returns whether this script contains reference of external script file.
82
+ # @return [Boolean] always returns true
83
+ def include_external_file?
84
+ true
85
+ end
86
+
87
+ # Returns whether this script contains reference of external script file.
88
+ # @return [Boolean] always returns true
89
+ def has_uri_reference?
90
+ true
91
+ end
92
+ end
93
+ end
94
+ end
data/lib/dyi/shape.rb CHANGED
@@ -19,1337 +19,11 @@
19
19
  # You should have received a copy of the GNU General Public License
20
20
  # along with DYI. If not, see <http://www.gnu.org/licenses/>.
21
21
 
22
- require 'enumerator'
22
+ %w(
23
23
 
24
- module DYI #:nodoc:
25
- module Shape #:nodoc:
24
+ base
25
+ path
26
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::Clipping.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 = case start_point
395
- when PathData then start_point
396
- else PathData.new(start_point)
397
- end
398
- @attributes = init_attributes(options)
399
- end
400
-
401
- def move_to(*points)
402
- push_command(:move_to, *points)
403
- end
404
-
405
- def rmove_to(*points)
406
- push_command(:rmove_to, *points)
407
- end
408
-
409
- def line_to(*points)
410
- push_command(:line_to, *points)
411
- end
412
-
413
- def rline_to(*points)
414
- push_command(:rline_to, *points)
415
- end
416
-
417
- def quadratic_curve_to(*points)
418
- raise ArgumentError, "number of points must be 2 or more" if points.size < 2
419
- push_command(:quadratic_curve_to, points[0], points[1])
420
- push_command(:shorthand_quadratic_curve_to, *points[2..-1]) if points.size > 2
421
- end
422
-
423
- def rquadratic_curve_to(*points)
424
- raise ArgumentError, "number of points must be 2 or more" if points.size < 2
425
- push_command(:rquadratic_curve_to, points[0], points[1])
426
- push_command(:rshorthand_quadratic_curve_to, *points[2..-1]) if points.size > 2
427
- end
428
-
429
- def curve_to(*points)
430
- raise ArgumentError, "number of points must be odd number of 3 or more" if points.size % 2 == 0 || points.size < 3
431
- push_command(:curve_to, points[0], points[1], points[2])
432
- push_command(:shorthand_curve_to, *points[3..-1]) if points.size > 3
433
- end
434
-
435
- def rcurve_to(*points)
436
- raise ArgumentError, "number of points must be odd number of 3 or more" if points.size % 2 == 0 || points.size < 3
437
- push_command(:rcurve_to, points[0], points[1], points[2])
438
- push_command(:rshorthand_curve_to, *points[3..-1]) if points.size > 3
439
- end
440
-
441
- def arc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true)
442
- push_command(:arc_to, radius_x, radius_y, rotation, is_large_arc, is_clockwise, point)
443
- end
444
-
445
- def rarc_to(point, radius_x, radius_y, rotation=0, is_large_arc=false, is_clockwise=true)
446
- push_command(:rarc_to, radius_x, radius_y, rotation, is_large_arc, is_clockwise, point)
447
- end
448
-
449
- def close?
450
- @path_data.close?
451
- end
452
-
453
- def close_path
454
- push_command(:close_path)
455
- end
456
-
457
- def start_point
458
- @path_data.start_point
459
- end
460
-
461
- def current_point
462
- @path_data.current_point
463
- end
464
-
465
- def current_start_point
466
- @path_data.current_start_point
467
- end
468
-
469
- def push_command(command_type, *args)
470
- @path_data.push_command(command_type, *args)
471
- end
472
-
473
- def pop_command
474
- @path_data.pop
475
- end
476
-
477
- def path_points
478
- @path_data.path_points
479
- end
480
-
481
- def path_data
482
- @path_data
483
- end
484
-
485
- def compatible_path_data
486
- @path_data.compatible_path_data
487
- end
488
-
489
- def concise_path_data
490
- @path_data.to_concise_syntax
491
- end
492
-
493
- def left
494
- edge_coordinate(:left)
495
- end
496
-
497
- def right
498
- edge_coordinate(:right)
499
- end
500
-
501
- def top
502
- edge_coordinate(:top)
503
- end
504
-
505
- def bottom
506
- edge_coordinate(:bottom)
507
- end
508
- =begin
509
- def line_bezier_paths
510
- start_point = Coordinate::ZERO
511
- current_point = Coordinate::ZERO
512
- last_ctrl_point = nil
513
- @path_data.inject([]) do |result, path_point|
514
- case path_point.first
515
- when 'M', 'L', 'C'
516
- last_ctrl_point = path_point[2]
517
- current_point = path_point.last
518
- result << path_point
519
- start_point = current_point if path_point.first == 'M'
520
- when 'm', 'l'
521
- result << [path_point.first.upcase, (current_point += path_point.last)]
522
- start_point = current_point if path_point.first == 'm'
523
- when 'c'
524
- result << [path_point.first.upcase, current_point + path_point[1], (last_ctrl_point = current_point + path_point[2]), (current_point += path_point.last)]
525
- when 'Z'
526
- result << path_point
527
- current_point = start_point
528
- when 'Q', 'q', 'T', 't'
529
- case path_point.first
530
- when 'Q'
531
- last_ctrl_point = path_point[1]
532
- last_point = path_point[2]
533
- when 'q'
534
- last_ctrl_point = current_point + path_point[1]
535
- last_point = current_point + path_point[2]
536
- when 'T'
537
- last_ctrl_point = current_point * 2 - last_ctrl_point
538
- last_point = path_point[1]
539
- when 't'
540
- last_ctrl_point = current_point * 2 - last_ctrl_point
541
- last_point = current_point + path_point[1]
542
- end
543
- ctrl_point1 = (current_point + last_ctrl_point * 2).quo(3)
544
- ctrl_point2 = (last_point + last_ctrl_point * 2).quo(3)
545
- result << ['C', ctrl_point1, ctrl_point2, (current_point = last_point)]
546
- when 'S', 's'
547
- case path_point.first
548
- when 'S'
549
- ctrl_point1 = current_point * 2 - last_ctrl_point
550
- ctrl_point2 = path_point[1]
551
- last_point = path_point[2]
552
- when 's'
553
- ctrl_point1 = current_point * 2 - last_ctrl_point
554
- ctrl_point2 = current_point + path_point[1]
555
- last_point = current_point + path_point[2]
556
- end
557
- result << ['C', ctrl_point1, (last_ctrl_point = ctrl_point2), (current_point = last_point)]
558
- when 'A', 'a'
559
- rx, ry, lotate, large_arc, clockwise, last_point = path_point[1..-1]
560
- last_point += current_point if path_point.first == 'a'
561
- rx = rx.to_f
562
- ry = ry.to_f
563
- lotate = lotate * Math::PI / 180
564
- cu_pt = Coordinate.new(
565
- current_point.x * Math.cos(lotate) / rx + current_point.y * Math.sin(lotate) / rx,
566
- current_point.y * Math.cos(lotate) / ry - current_point.x * Math.sin(lotate) / ry)
567
- en_pt = Coordinate.new(
568
- last_point.x * Math.cos(lotate) / rx + last_point.y * Math.sin(lotate) / rx,
569
- last_point.y * Math.cos(lotate) / ry - last_point.x * Math.sin(lotate) / ry)
570
- begin
571
- 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)
572
- center_pt = Coordinate.new(
573
- cu_pt.x - cu_pt.y * k + en_pt.x + en_pt.y * k,
574
- cu_pt.y + cu_pt.x * k + en_pt.y - en_pt.x * k) * 0.5
575
- cu_pt -= center_pt
576
- en_pt -= center_pt
577
- theta = Math.acos(cu_pt.x.to_f * en_pt.x.to_f + cu_pt.y.to_f * en_pt.y.to_f)
578
- theta = 2 * Math::PI - theta if large_arc == 1
579
- rescue
580
- center_pt = Coordinate.new(cu_pt.x + en_pt.x, cu_pt.y + en_pt.y) * 0.5
581
- cu_pt -= center_pt
582
- en_pt -= center_pt
583
- theta = Math::PI
584
- end
585
- d_count = theta.quo(Math::PI / 8).ceil
586
- d_t = theta / d_count * (clockwise == 1 ? 1 : -1)
587
- curves = []
588
- cos = Math.cos(d_t)
589
- sin = Math.sin(d_t)
590
- tan = Math.tan(d_t / 4)
591
- mat = Matrix.new(
592
- rx * Math.cos(lotate), rx * Math.sin(lotate),
593
- -ry * Math.sin(lotate), ry * Math.cos(lotate),
594
- center_pt.x * rx * Math.cos(lotate) - center_pt.y * ry * Math.sin(lotate),
595
- center_pt.y * ry * Math.cos(lotate) + center_pt.x * rx * Math.sin(lotate))
596
- d_count.times do |i|
597
- ne_pt = Coordinate.new(cu_pt.x * cos - cu_pt.y * sin, cu_pt.y * cos + cu_pt.x * sin)
598
- curves << [
599
- mat.translate(Coordinate.new(cu_pt.x - cu_pt.y * 4 * tan / 3, cu_pt.y + cu_pt.x * 4 * tan / 3)),
600
- mat.translate(Coordinate.new(ne_pt.x + ne_pt.y * 4 * tan / 3, ne_pt.y - ne_pt.x * 4 * tan / 3)),
601
- mat.translate(ne_pt)]
602
- cu_pt = ne_pt
603
- end
604
- curves.last[2] = last_point
605
- current_point = last_point
606
- curves.each do |c|
607
- result << ['C', c[0], c[1], c[2]]
608
- end
609
- end
610
- result
611
- end
612
- end
613
- =end
614
- def write_as(formatter, io=$>)
615
- formatter.write_path(self, io, &(block_given? ? Proc.new : nil))
616
- end
617
-
618
- private
619
- =begin
620
- def edge_coordinate(edge_type)
621
- case edge_type
622
- when :left
623
- element_type = :x
624
- amount_type = :min
625
- when :right
626
- element_type = :x
627
- amount_type = :max
628
- when :top
629
- element_type = :y
630
- amount_type = :min
631
- when :bottom
632
- element_type = :y
633
- amount_type = :max
634
- else
635
- raise ArgumentError, "unknown edge_tpe `#{edge_type}'"
636
- end
637
- current_pt = nil
638
- line_bezier_paths.inject(nil) do |result, path_point|
639
- case path_point.first
640
- when 'M', 'L'
641
- current_pt = path_point.last
642
- [result, current_pt.__send__(element_type)].compact.__send__(amount_type)
643
- when 'C'
644
- pts = [current_pt.__send__(element_type), path_point[1].__send__(element_type), path_point[2].__send__(element_type), path_point[3].__send__(element_type)]
645
- nums = pts.map {|pt| pt.to_f}
646
- current_pt = path_point.last
647
- delta = (nums[2] - nums[1] * 2 + nums[0]) ** 2 - (nums[3] - nums[2] * 3 + nums[1] * 3 - nums[0]) * (nums[1] - nums[0])
648
- if delta >= 0
649
- res0 = ((nums[2] - nums[1] * 2 + nums[0]) * (-1) + Math.sqrt(delta)).quo(nums[3] - nums[2] * 3 + nums[1] * 3 - nums[0])
650
- res1 = ((nums[2] - nums[1] * 2 + nums[0]) * (-1) - Math.sqrt(delta)).quo(nums[3] - nums[2] * 3 + nums[1] * 3 - nums[0])
651
- res0 = (0..1).include?(res0) ? Length.new(res0) : nil
652
- res1 = (0..1).include?(res1) ? Length.new(res1) : nil
653
- [result, pts[0], pts[3], res0, res1].compact.__send__(amount_type)
654
- else
655
- [result, pts[0], pts[3]].conpact.__send__(amount_type)
656
- end
657
- else
658
- result
659
- end
660
- end
661
- end
662
- =end
663
- class << self
664
-
665
- public
666
-
667
- def draw(start_point, options={}, &block)
668
- path = new(start_point, options)
669
- yield path
670
- path
671
- end
672
-
673
- def draw_and_close(start_point, options={}, &block)
674
- path = draw(start_point, options, &block)
675
- path.close_path unless path.close?
676
- path
677
- end
678
- end
679
-
680
- class PathData #:nodoc:
681
- include Enumerable
682
-
683
- def initialize(*points)
684
- raise ArgumentError, 'wrong number of arguments (0 for 1)' if points.empty?
685
- @commands = MoveCommand.absolute_commands(nil, *points)
686
- end
687
-
688
- def each
689
- if block_given?
690
- @commands.each{|command| yield command}
691
- else
692
- @commands.each
693
- end
694
- end
695
-
696
- def push_command(command_type, *args)
697
- case command_type
698
- when :move_to
699
- @commands.push(*MoveCommand.absolute_commands(@commands.last, *args))
700
- when :rmove_to
701
- @commands.push(*MoveCommand.relative_commands(@commands.last, *args))
702
- when :close_path
703
- @commands.push(*CloseCommand.commands(@commands.last))
704
- when :line_to
705
- @commands.push(*LineCommand.absolute_commands(@commands.last, *args))
706
- when :rline_to
707
- @commands.push(*LineCommand.relative_commands(@commands.last, *args))
708
- when :horizontal_lineto_to
709
- @commands.push(*HorizontalLineCommand.absolute_commands(@commands.last, *args))
710
- when :rhorizontal_lineto_to
711
- @commands.push(*HorizontalLineCommand.relative_commands(@commands.last, *args))
712
- when :vertical_lineto_to
713
- @commands.push(*VerticalLineCommand.absolute_commands(@commands.last, *args))
714
- when :rvertical_lineto_to
715
- @commands.push(*VerticalLineCommand.relative_commands(@commands.last, *args))
716
- when :curve_to
717
- @commands.push(*CurveCommand.absolute_commands(@commands.last, *args))
718
- when :rcurve_to
719
- @commands.push(*CurveCommand.relative_commands(@commands.last, *args))
720
- when :shorthand_curve_to
721
- @commands.push(*ShorthandCurveCommand.absolute_commands(@commands.last, *args))
722
- when :rshorthand_curve_to
723
- @commands.push(*ShorthandCurveCommand.relative_commands(@commands.last, *args))
724
- when :quadratic_curve_to
725
- @commands.push(*QuadraticCurveCommand.absolute_commands(@commands.last, *args))
726
- when :rquadratic_curve_to
727
- @commands.push(*QuadraticCurveCommand.relative_commands(@commands.last, *args))
728
- when :shorthand_quadratic_curve_to
729
- @commands.push(*ShorthandQuadraticCurveCommand.absolute_commands(@commands.last, *args))
730
- when :rshorthand_quadratic_curve_to
731
- @commands.push(*ShorthandQuadraticCurveCommand.relative_commands(@commands.last, *args))
732
- when :arc_to
733
- @commands.push(*ArcCommand.absolute_commands(@commands.last, *args))
734
- when :rarc_to
735
- @commands.push(*ArcCommand.relative_commands(@commands.last, *args))
736
- else
737
- raise ArgumentError, "unknown command type `#{command_type}'"
738
- end
739
- end
740
-
741
- def pop_command
742
- @commands.pop
743
- end
744
-
745
- def compatible_path_data
746
- new_instance = clone
747
- new_instance.commands = compatible_path_commands
748
- new_instance
749
- end
750
-
751
- def compatible_path_data!
752
- @commands = compatible_path_commands
753
- self
754
- end
755
-
756
- def start_point
757
- @commands.first.start_point
758
- end
759
-
760
- def current_point
761
- @commands.last.last_point
762
- end
763
-
764
- def current_start_point
765
- @commands.last.start_point
766
- end
767
-
768
- def path_points
769
- @commands.map{|command| command.points}.flatten
770
- end
771
-
772
- def close?
773
- @commands.last.is_a?(CloseCommand)
774
- end
775
-
776
- def to_concise_syntax
777
- @commands.map{|command| command.to_concise_syntax_fragments}.join(' ')
778
- end
779
-
780
- protected
781
-
782
- def commands=(value)
783
- @commands = value
784
- end
785
-
786
- private
787
-
788
- def compatible_path_commands
789
- @commands.inject([]) do |compat_cmds, command|
790
- compat_cmds.push(*command.to_compatible_commands(compat_cmds.last))
791
- end
792
- end
793
- end
794
-
795
- class CommandBase #:nodoc:
796
- attr_reader :preceding_command, :point
797
-
798
- def initialize(relative, preceding_command, point)
799
- @relative = relative
800
- @preceding_command = preceding_command
801
- @point = Coordinate.new(point)
802
- end
803
-
804
- def relative?
805
- @relative
806
- end
807
-
808
- def absolute?
809
- !relative?
810
- end
811
-
812
- def start_point
813
- preceding_command.start_point
814
- end
815
-
816
- def last_point
817
- relative? ? preceding_point + @point : @point
818
- end
819
-
820
- def preceding_point
821
- preceding_command && preceding_command.last_point
822
- end
823
-
824
- def to_compatible_commands(preceding_command)
825
- compat_commands = clone
826
- compat_commands.preceding_command = preceding_command
827
- compat_commands
828
- end
829
-
830
- def used_same_command?
831
- preceding_command.instructions_char == instructions_char
832
- end
833
-
834
- protected
835
-
836
- def preceding_command=(value)
837
- @preceding_command = preceding_command
838
- end
839
-
840
- class << self
841
- def relative_commands(preceding_command, *args)
842
- commands(true, preceding_command, *args)
843
- end
844
-
845
- def absolute_commands(preceding_command, *args)
846
- commands(false, preceding_command, *args)
847
- end
848
- end
849
- end
850
-
851
- class MoveCommand < CommandBase #:nodoc:
852
-
853
- def start_point
854
- last_point
855
- end
856
-
857
- def last_point
858
- (relative? && preceding_command.nil?) ? point : super
859
- end
860
-
861
- def relative?
862
- preceding_command.nil? ? false : super
863
- end
864
-
865
- def to_concise_syntax_fragments
866
- instructions_char + @point.to_s
867
- end
868
-
869
- def instructions_char
870
- relative? ? 'm' : 'M'
871
- end
872
-
873
- class << self
874
- def commands(relative, preceding_command, *points)
875
- raise ArgumentError, 'wrong number of arguments (2 for 3)' if points.empty?
876
- commands = [new(relative, preceding_command, points.first)]
877
- points[1..-1].inject(commands) do |cmds, pt|
878
- cmds << LineCommand.new(relative, cmds.last, pt)
879
- end
880
- end
881
- end
882
- end
883
-
884
- class CloseCommand < CommandBase #:nodoc:
885
- def initialize(preceding_command)
886
- raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
887
- @relative = nil
888
- @preceding_command = preceding_command
889
- @point = nil
890
- end
891
-
892
- def last_point
893
- start_point
894
- end
895
-
896
- def relative?
897
- nil
898
- end
899
-
900
- def absolute?
901
- nil
902
- end
903
-
904
- def to_concise_syntax_fragments
905
- instructions_char
906
- end
907
-
908
- def instructions_char
909
- 'Z'
910
- end
911
-
912
- class << self
913
- undef relative_commands, absolute_commands
914
-
915
- def commands(preceding_command)
916
- [new(preceding_command)]
917
- end
918
- end
919
- end
920
-
921
- class LineCommand < CommandBase #:nodoc:
922
- def initialize(relative, preceding_command, point)
923
- raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
924
- super
925
- end
926
-
927
- def to_concise_syntax_fragments
928
- used_same_command? ? @point.to_s : (instructions_char + @point.to_s)
929
- end
930
-
931
- def instructions_char
932
- relative? ? 'l' : 'L'
933
- end
934
-
935
- class << self
936
- def commands(relative, preceding_command, *points)
937
- raise ArgumentError, 'wrong number of arguments (2 for 3)' if points.empty?
938
- cmd = preceding_command
939
- points.inject([]) do |cmds, pt|
940
- cmds << (cmd = new(relative, cmd, pt))
941
- end
942
- end
943
- end
944
- end
945
-
946
- class HorizontalLineCommand < LineCommand #:nodoc:
947
- def initialize(relative, preceding_command, x)
948
- super(relative, preceding_command, Coordinate.new(x, relative ? 0 : preceding_command.last_point.y))
949
- end
950
-
951
- def to_compatible_commands(preceding_command)
952
- LineCommand.new(relative?, preceding_command, @point)
953
- end
954
-
955
- def to_concise_syntax_fragments
956
- used_same_command? ? @point.x.to_s : (instructions_char + @point.x.to_s)
957
- end
958
-
959
- def instructions_char
960
- relative? ? 'h' : 'H'
961
- end
962
- end
963
-
964
- class VerticalLineCommand < LineCommand #:nodoc:
965
- def initialize(relative, preceding_command, y)
966
- super(relative, preceding_command, Coordinate.new(relative ? 0 : preceding_command.last_point.x, y))
967
- end
968
-
969
- def to_compatible_commands(preceding_command)
970
- LineCommand.new(relative?, preceding_command, @point)
971
- end
972
-
973
- def to_concise_syntax_fragments
974
- used_same_command? ? @point.y.to_s : (instructions_char + @point.y.to_s)
975
- end
976
-
977
- def instructions_char
978
- relative? ? 'v' : 'V'
979
- end
980
- end
981
-
982
- class CurveCommandBase < CommandBase #:nodoc:
983
- def initialize(relative, preceding_command, *points)
984
- raise ArgumentError, "wrong number of arguments (2 for #{pt_cnt + 2})" if points.size != pt_cnt
985
- raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
986
- @relative = relative
987
- @preceding_command = preceding_command
988
- @point = Coordinate.new(points.last)
989
- @control_points = points[0..-2].map{|pt| Coordinate.new(pt)}
990
- end
991
-
992
- def last_control_point
993
- relative? ? (preceding_point + @control_points.last) : @control_points.last
994
- end
995
-
996
- def to_concise_syntax_fragments
997
- used_same_command? ? @point.to_s : (instructions_char + @point.to_s)
998
- end
999
-
1000
- def to_concise_syntax_fragments
1001
- fragments = @control_points.map{|ctrl_pt| ctrl_pt.to_s}.push(@point.to_s)
1002
- fragments[0] = instructions_char + fragments[0] unless used_same_command?
1003
- fragments
1004
- end
1005
-
1006
- private
1007
-
1008
- def pt_cnt
1009
- self.class.pt_cnt
1010
- end
1011
-
1012
- class << self
1013
- def commands(relative, preceding_command, *points)
1014
- raise ArgumentError, "number of points must be a multipule of #{pt_cnt}" if points.size % pt_cnt != 0
1015
- cmd = preceding_command
1016
- points.each_slice(pt_cnt).inject([]) do |cmds, pts|
1017
- cmds << (cmd = new(relative, cmd, *pts))
1018
- end
1019
- end
1020
- end
1021
- end
1022
-
1023
- class CurveCommand < CurveCommandBase #:nodoc:
1024
- def preceding_control_point
1025
- if preceding_command.is_a?(CurveCommand)
1026
- preceding_command.last_control_point
1027
- else
1028
- preceding_command.last_point
1029
- end
1030
- end
1031
-
1032
- def control_point1
1033
- @control_points[0]
1034
- end
1035
-
1036
- def control_point2
1037
- @control_points[1]
1038
- end
1039
-
1040
- def instructions_char
1041
- relative? ? 'c' : 'C'
1042
- end
1043
-
1044
- class << self
1045
- def pt_cnt
1046
- 3
1047
- end
1048
- end
1049
- end
1050
-
1051
- class ShorthandCurveCommand < CurveCommand #:nodoc:
1052
- def control_point1
1053
- if relative?
1054
- preceding_point - preceding_control_point
1055
- else
1056
- preceding_point * 2 - preceding_control_point
1057
- end
1058
- end
1059
-
1060
- def control_point2
1061
- @control_points[0]
1062
- end
1063
-
1064
- def to_compatible_commands(preceding_command)
1065
- CurveCommand.new(relative?, preceding_command, control_point1, control_point2, @point)
1066
- end
1067
-
1068
- def instructions_char
1069
- relative? ? 's' : 'S'
1070
- end
1071
-
1072
- class << self
1073
- def pt_cnt
1074
- 2
1075
- end
1076
- end
1077
- end
1078
-
1079
- class QuadraticCurveCommand < CurveCommandBase #:nodoc:
1080
- def preceding_control_point
1081
- if preceding_command.is_a?(QuadraticCurveCommand)
1082
- preceding_command.last_control_point
1083
- else
1084
- preceding_command.last_point
1085
- end
1086
- end
1087
-
1088
- def control_point
1089
- @control_points[0]
1090
- end
1091
-
1092
- def to_compatible_commands(preceding_command)
1093
- ctrl_pt1 = relative? ? control_point * 2.0 / 3.0 : (preceding_point + control_point * 2.0) / 3.0
1094
- ctrl_pt2 = (control_point * 2.0 + point) / 3.0
1095
- CurveCommand.new(relative?, preceding_command, ctrl_pt1, ctrl_pt2, @point)
1096
- end
1097
-
1098
- def instructions_char
1099
- relative? ? 'q' : 'Q'
1100
- end
1101
-
1102
- class << self
1103
- def pt_cnt
1104
- 2
1105
- end
1106
- end
1107
- end
1108
-
1109
- class ShorthandQuadraticCurveCommand < QuadraticCurveCommand #:nodoc:
1110
- def control_point
1111
- if relative?
1112
- preceding_point - preceding_control_point
1113
- else
1114
- preceding_point * 2 - preceding_control_point
1115
- end
1116
- end
1117
-
1118
- def last_control_point
1119
- preceding_point * 2 - preceding_control_point
1120
- end
1121
-
1122
- def instructions_char
1123
- relative? ? 't' : 'T'
1124
- end
1125
-
1126
- class << self
1127
- def pt_cnt
1128
- 1
1129
- end
1130
- end
1131
- end
1132
-
1133
- class ArcCommand < CommandBase #:nodoc:
1134
- attr_reader :rx, :ry, :rotation
1135
-
1136
- def initialize(relative, preceding_command, rx, ry, rotation, is_large_arc, is_clockwise, point)
1137
- raise ArgumentError, 'preceding_command is nil' if preceding_command.nil?
1138
- @relative = relative
1139
- @preceding_command = preceding_command
1140
- @point = Coordinate.new(point)
1141
- @rotation = rotation
1142
- @is_large_arc = is_large_arc
1143
- @is_clockwise = is_clockwise
1144
- @rx = Length.new(rx).abs
1145
- @ry = Length.new(ry).abs
1146
- l = (modified_mid_point.x.to_f / @rx.to_f) ** 2 + (modified_mid_point.y.to_f / @ry.to_f) ** 2
1147
- if 1 < l
1148
- @rx *= Math.sqrt(l)
1149
- @ry *= Math.sqrt(l)
1150
- end
1151
- end
1152
-
1153
- def large_arc?
1154
- @is_large_arc
1155
- end
1156
-
1157
- def clockwise?
1158
- @is_clockwise
1159
- end
1160
-
1161
- def to_compatible_commands(preceding_command)
1162
- return LineCommand.new(relative?, preceding_command, point) if rx.zero? || ry.zero?
1163
- division_count = (center_angle / 30.0).ceil
1164
- division_angle = center_angle / division_count * (clockwise? ? 1 : -1)
1165
- current_point = start_angle_point
1166
- compat_commands = []
1167
- division_count.times do |i|
1168
- end_point = if i == division_count - 1
1169
- end_angle_point
1170
- else
1171
- Matrix.rotate(division_angle).transform(current_point)
1172
- end
1173
- control_point1 = control_point_of_curve(current_point, division_angle, true)
1174
- control_point2 = control_point_of_curve(end_point, division_angle, false)
1175
- path_point = (i == division_count - 1) ? point : transform_orginal_shape(end_point)
1176
- if relative?
1177
- control_point1 += preceding_point
1178
- control_point2 += preceding_point
1179
- path_point += preceding_point
1180
- end
1181
- preceding_command = CurveCommand.absolute_commands(preceding_command,
1182
- control_point1,
1183
- control_point2,
1184
- path_point).first
1185
- compat_commands << preceding_command
1186
- current_point = end_point
1187
- end
1188
- compat_commands
1189
- end
1190
-
1191
- def to_concise_syntax_fragments
1192
- [used_same_command? ? rx.to_s : instructions_char + rx.to_s,
1193
- ry, rotation, large_arc? ? 1 : 0, clockwise? ? 1 : 0, point.to_s]
1194
- end
1195
-
1196
- def instructions_char
1197
- relative? ? 'a' : 'A'
1198
- end
1199
-
1200
- def center_point
1201
- st_pt = relative? ? Coordinate::ZERO : preceding_point
1202
- Matrix.rotate(rotation).transform(modified_center_point) + (st_pt + point) * 0.5
1203
- end
1204
-
1205
- private
1206
-
1207
- def modified_mid_point
1208
- st_pt = relative? ? Coordinate::ZERO : preceding_point
1209
- Matrix.rotate(-rotation).transform((st_pt - point) * 0.5)
1210
- end
1211
-
1212
- def modified_center_point
1213
- pt = modified_mid_point
1214
- Coordinate.new(pt.y * (rx / ry), -pt.x * (ry / rx)) *
1215
- 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) /
1216
- ((rx.to_f * pt.y.to_f) ** 2 + (ry.to_f * pt.x.to_f) ** 2)) *
1217
- ((large_arc? == clockwise?) ? -1 : 1)
1218
- end
1219
-
1220
- def start_angle_point
1221
- Coordinate.new((modified_mid_point.x - modified_center_point.x) / rx,
1222
- (modified_mid_point.y - modified_center_point.y) / ry)
1223
- end
1224
-
1225
- def end_angle_point
1226
- Coordinate.new((-modified_mid_point.x - modified_center_point.x) / rx,
1227
- (-modified_mid_point.y - modified_center_point.y) / ry)
1228
- end
1229
-
1230
- def center_angle
1231
- angle = Math.acos(start_angle_point.x.to_f * end_angle_point.x.to_f +
1232
- start_angle_point.y.to_f * end_angle_point.y.to_f) * 180.0 / Math::PI
1233
- large_arc? ? 360.0 - angle : angle
1234
- end
1235
-
1236
- def transform_matrix
1237
- Matrix.translate(center_point.x.to_f, center_point.y.to_f).rotate(rotation).scale(rx.to_f, ry.to_f)
1238
- end
1239
-
1240
- def transform_orginal_shape(modified_point)
1241
- transform_matrix.transform(modified_point)
1242
- end
1243
-
1244
- def control_point_of_curve(point, center_angle, is_start_point)
1245
- handle_length = Math.tan(center_angle * Math::PI / 180.0 / 4.0) * 4.0 / 3.0
1246
- handle = is_start_point ? handle_length : -handle_length
1247
- transform_matrix.transform(Matrix.new(1, handle, -handle, 1, 0, 0).transform(point))
1248
- end
1249
-
1250
- class << self
1251
- def commands(relative, preceding_command, *args)
1252
- raise ArgumentError, "number of arguments must be a multipule of 6" if args.size % 6 != 0
1253
- cmd = preceding_command
1254
- args.each_slice(6).inject([]) do |cmds, ars|
1255
- if ars[0].zero? || ars[1].zero?
1256
- cmds << (cmd = LineCommand.new(relative, cmd, ars.last))
1257
- else
1258
- cmds << (cmd = new(relative, cmd, *ars))
1259
- end
1260
- end
1261
- end
1262
- end
1263
- end
1264
- end
1265
-
1266
- class Text < Base
1267
- UNPRIMITIVE_OPTIONS = [:line_height, :alignment_baseline, :format]
1268
- BASELINE_VALUES = ['baseline', 'top', 'middle', 'bottom']
1269
- DEFAULT_LINE_HEIGHT = 1
1270
- attr_font :font
1271
- attr_painting :painting
1272
- attr_coordinate :point
1273
- attr_coordinate :line_height
1274
- attr_accessor :text
1275
- attr_reader :format
1276
- attr_reader *UNPRIMITIVE_OPTIONS
1277
-
1278
- def initialize(point, text=nil, options={})
1279
- @point = Coordinate.new(point || [0,0])
1280
- @text = text
1281
- @attributes = init_attributes(options)
1282
- end
1283
-
1284
- def format=(value)
1285
- @format = value && value.to_s
1286
- end
1287
-
1288
- def font_height
1289
- font.draw_size
1290
- end
1291
-
1292
- def dy
1293
- font_height * (line_height || DEFAULT_LINE_HEIGHT)
1294
- end
1295
-
1296
- def formated_text
1297
- if @format
1298
- if @text.kind_of?(Numeric)
1299
- @text.strfnum(@format)
1300
- elsif @text.respond_to?(:strftime)
1301
- @text.strftime(@format)
1302
- else
1303
- @text.to_s
1304
- end
1305
- else
1306
- @text.to_s
1307
- end
1308
- end
1309
-
1310
- def write_as(formatter, io=$>)
1311
- formatter.write_text(self, io, &(block_given? ? Proc.new : nil))
1312
- end
1313
-
1314
- private
1315
-
1316
- def init_attributes(options)
1317
- options = super
1318
- format = options.delete(:format)
1319
- @format = format && format.to_s
1320
- line_height = options.delete(:line_height)
1321
- @line_height = line_height || DEFAULT_LINE_HEIGHT
1322
- options
1323
- end
1324
- end
1325
-
1326
- class ShapeGroup < Base
1327
- attr_reader :child_elements
1328
-
1329
- def initialize(options={})
1330
- @attributes = options
1331
- @child_elements = []
1332
- end
1333
-
1334
- def width
1335
- Length.new_or_nil(@attributes[:width])
1336
- end
1337
-
1338
- def height
1339
- Length.new_or_nil(@attributes[:height])
1340
- end
1341
-
1342
- def write_as(formatter, io=$>)
1343
- formatter.write_group(self, io, &(block_given? ? Proc.new : nil))
1344
- end
1345
-
1346
- class << self
1347
- public
1348
-
1349
- def draw_on(canvas, options = {})
1350
- new(options).draw_on(canvas)
1351
- end
1352
- end
1353
- end
1354
- end
27
+ ).each do |file_name|
28
+ require File.join(File.dirname(__FILE__), 'shape', file_name)
1355
29
  end