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/CHANGES +13 -0
- data/examples/animation.rb +28 -0
- data/examples/class_diagram.rb +1 -1
- data/examples/css.rb +34 -0
- data/examples/line_and_bar.rb +1 -2
- data/examples/line_chart.rb +5 -6
- data/examples/logo.rb +1 -1
- data/examples/pie_chart.rb +11 -4
- data/examples/simple_shapes.rb +1 -1
- data/lib/dyi.rb +8 -2
- data/lib/dyi/animation.rb +259 -0
- data/lib/dyi/canvas.rb +202 -0
- data/lib/dyi/chart.rb +2 -0
- data/lib/dyi/chart/array_reader.rb +112 -63
- data/lib/dyi/chart/axis_util.rb +298 -0
- data/lib/dyi/chart/base.rb +188 -466
- data/lib/dyi/chart/csv_reader.rb +0 -4
- data/lib/dyi/chart/legend.rb +82 -0
- data/lib/dyi/chart/line_chart.rb +31 -23
- data/lib/dyi/chart/pie_chart.rb +237 -30
- data/lib/dyi/color.rb +1 -1
- data/lib/dyi/drawing.rb +8 -1
- data/lib/dyi/drawing/clipping.rb +14 -1
- data/lib/dyi/drawing/pen.rb +12 -2
- data/lib/dyi/element.rb +167 -0
- data/lib/dyi/event.rb +148 -0
- data/lib/dyi/formatter/base.rb +43 -3
- data/lib/dyi/formatter/eps_formatter.rb +2 -2
- data/lib/dyi/formatter/svg_formatter.rb +430 -74
- data/lib/dyi/painting.rb +6 -1
- data/lib/dyi/script.rb +29 -0
- data/lib/dyi/script/ecmascript.rb +273 -0
- data/lib/dyi/script/simple_script.rb +94 -0
- data/lib/dyi/shape.rb +5 -1331
- data/lib/dyi/shape/base.rb +612 -0
- data/lib/dyi/shape/path.rb +902 -0
- data/lib/dyi/stylesheet.rb +86 -0
- data/lib/ironruby.rb +11 -11
- metadata +17 -4
- data/lib/dyi/drawing/canvas.rb +0 -100
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
|
-
|
22
|
+
%w(
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
base
|
25
|
+
path
|
26
26
|
|
27
|
-
|
28
|
-
|
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
|