Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/graphics/path.js
2868 views
1
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
16
/**
17
* @fileoverview Represents a path used with a Graphics implementation.
18
* @author [email protected] (Erik Arvidsson)
19
*/
20
21
goog.provide('goog.graphics.Path');
22
goog.provide('goog.graphics.Path.Segment');
23
24
goog.require('goog.array');
25
goog.require('goog.graphics.AffineTransform');
26
goog.require('goog.math');
27
28
29
30
/**
31
* Creates a path object. A path is a sequence of segments and may be open or
32
* closed. Path uses the EVEN-ODD fill rule for determining the interior of the
33
* path. A path must start with a moveTo command.
34
*
35
* A "simple" path does not contain any arcs and may be transformed using
36
* the {@code transform} method.
37
*
38
* @constructor
39
*/
40
goog.graphics.Path = function() {
41
/**
42
* The segment types that constitute this path.
43
* @type {!Array<number>}
44
* @private
45
*/
46
this.segments_ = [];
47
48
/**
49
* The number of repeated segments of the current type.
50
* @type {!Array<number>}
51
* @private
52
*/
53
this.count_ = [];
54
55
/**
56
* The arguments corresponding to each of the segments.
57
* @type {!Array<number>}
58
* @private
59
*/
60
this.arguments_ = [];
61
};
62
63
64
/**
65
* The coordinates of the point which closes the path (the point of the
66
* last moveTo command).
67
* @type {Array<number>?}
68
* @private
69
*/
70
goog.graphics.Path.prototype.closePoint_ = null;
71
72
73
/**
74
* The coordinates most recently added to the end of the path.
75
* @type {Array<number>?}
76
* @private
77
*/
78
goog.graphics.Path.prototype.currentPoint_ = null;
79
80
81
/**
82
* Flag for whether this is a simple path (contains no arc segments).
83
* @type {boolean}
84
* @private
85
*/
86
goog.graphics.Path.prototype.simple_ = true;
87
88
89
/**
90
* Path segment types.
91
* @enum {number}
92
*/
93
goog.graphics.Path.Segment = {
94
MOVETO: 0,
95
LINETO: 1,
96
CURVETO: 2,
97
ARCTO: 3,
98
CLOSE: 4
99
};
100
101
102
/**
103
* The number of points for each segment type.
104
* @type {!Array<number>}
105
* @private
106
*/
107
goog.graphics.Path.segmentArgCounts_ = (function() {
108
var counts = [];
109
counts[goog.graphics.Path.Segment.MOVETO] = 2;
110
counts[goog.graphics.Path.Segment.LINETO] = 2;
111
counts[goog.graphics.Path.Segment.CURVETO] = 6;
112
counts[goog.graphics.Path.Segment.ARCTO] = 6;
113
counts[goog.graphics.Path.Segment.CLOSE] = 0;
114
return counts;
115
})();
116
117
118
/**
119
* Returns the number of points for a segment type.
120
*
121
* @param {number} segment The segment type.
122
* @return {number} The number of points.
123
*/
124
goog.graphics.Path.getSegmentCount = function(segment) {
125
return goog.graphics.Path.segmentArgCounts_[segment];
126
};
127
128
129
/**
130
* Appends another path to the end of this path.
131
*
132
* @param {!goog.graphics.Path} path The path to append.
133
* @return {!goog.graphics.Path} This path.
134
*/
135
goog.graphics.Path.prototype.appendPath = function(path) {
136
if (path.currentPoint_) {
137
Array.prototype.push.apply(this.segments_, path.segments_);
138
Array.prototype.push.apply(this.count_, path.count_);
139
Array.prototype.push.apply(this.arguments_, path.arguments_);
140
this.currentPoint_ = path.currentPoint_.concat();
141
this.closePoint_ = path.closePoint_.concat();
142
this.simple_ = this.simple_ && path.simple_;
143
}
144
return this;
145
};
146
147
148
/**
149
* Clears the path.
150
*
151
* @return {!goog.graphics.Path} The path itself.
152
*/
153
goog.graphics.Path.prototype.clear = function() {
154
this.segments_.length = 0;
155
this.count_.length = 0;
156
this.arguments_.length = 0;
157
delete this.closePoint_;
158
delete this.currentPoint_;
159
delete this.simple_;
160
return this;
161
};
162
163
164
/**
165
* Adds a point to the path by moving to the specified point. Repeated moveTo
166
* commands are collapsed into a single moveTo.
167
*
168
* @param {number} x X coordinate of destination point.
169
* @param {number} y Y coordinate of destination point.
170
* @return {!goog.graphics.Path} The path itself.
171
*/
172
goog.graphics.Path.prototype.moveTo = function(x, y) {
173
if (goog.array.peek(this.segments_) == goog.graphics.Path.Segment.MOVETO) {
174
this.arguments_.length -= 2;
175
} else {
176
this.segments_.push(goog.graphics.Path.Segment.MOVETO);
177
this.count_.push(1);
178
}
179
this.arguments_.push(x, y);
180
this.currentPoint_ = this.closePoint_ = [x, y];
181
return this;
182
};
183
184
185
/**
186
* Adds points to the path by drawing a straight line to each point.
187
*
188
* @param {...number} var_args The coordinates of each destination point as x, y
189
* value pairs.
190
* @return {!goog.graphics.Path} The path itself.
191
*/
192
goog.graphics.Path.prototype.lineTo = function(var_args) {
193
var lastSegment = goog.array.peek(this.segments_);
194
if (lastSegment == null) {
195
throw Error('Path cannot start with lineTo');
196
}
197
if (lastSegment != goog.graphics.Path.Segment.LINETO) {
198
this.segments_.push(goog.graphics.Path.Segment.LINETO);
199
this.count_.push(0);
200
}
201
for (var i = 0; i < arguments.length; i += 2) {
202
var x = arguments[i];
203
var y = arguments[i + 1];
204
this.arguments_.push(x, y);
205
}
206
this.count_[this.count_.length - 1] += i / 2;
207
this.currentPoint_ = [x, y];
208
return this;
209
};
210
211
212
/**
213
* Adds points to the path by drawing cubic Bezier curves. Each curve is
214
* specified using 3 points (6 coordinates) - two control points and the end
215
* point of the curve.
216
*
217
* @param {...number} var_args The coordinates specifying each curve in sets of
218
* 6 points: {@code [x1, y1]} the first control point, {@code [x2, y2]} the
219
* second control point and {@code [x, y]} the end point.
220
* @return {!goog.graphics.Path} The path itself.
221
*/
222
goog.graphics.Path.prototype.curveTo = function(var_args) {
223
var lastSegment = goog.array.peek(this.segments_);
224
if (lastSegment == null) {
225
throw Error('Path cannot start with curve');
226
}
227
if (lastSegment != goog.graphics.Path.Segment.CURVETO) {
228
this.segments_.push(goog.graphics.Path.Segment.CURVETO);
229
this.count_.push(0);
230
}
231
for (var i = 0; i < arguments.length; i += 6) {
232
var x = arguments[i + 4];
233
var y = arguments[i + 5];
234
this.arguments_.push(
235
arguments[i], arguments[i + 1], arguments[i + 2], arguments[i + 3], x,
236
y);
237
}
238
this.count_[this.count_.length - 1] += i / 6;
239
this.currentPoint_ = [x, y];
240
return this;
241
};
242
243
244
/**
245
* Adds a path command to close the path by connecting the
246
* last point to the first point.
247
*
248
* @return {!goog.graphics.Path} The path itself.
249
*/
250
goog.graphics.Path.prototype.close = function() {
251
var lastSegment = goog.array.peek(this.segments_);
252
if (lastSegment == null) {
253
throw Error('Path cannot start with close');
254
}
255
if (lastSegment != goog.graphics.Path.Segment.CLOSE) {
256
this.segments_.push(goog.graphics.Path.Segment.CLOSE);
257
this.count_.push(1);
258
this.currentPoint_ = this.closePoint_;
259
}
260
return this;
261
};
262
263
264
/**
265
* Adds a path command to draw an arc centered at the point {@code (cx, cy)}
266
* with radius {@code rx} along the x-axis and {@code ry} along the y-axis from
267
* {@code startAngle} through {@code extent} degrees. Positive rotation is in
268
* the direction from positive x-axis to positive y-axis.
269
*
270
* @param {number} cx X coordinate of center of ellipse.
271
* @param {number} cy Y coordinate of center of ellipse.
272
* @param {number} rx Radius of ellipse on x axis.
273
* @param {number} ry Radius of ellipse on y axis.
274
* @param {number} fromAngle Starting angle measured in degrees from the
275
* positive x-axis.
276
* @param {number} extent The span of the arc in degrees.
277
* @param {boolean} connect If true, the starting point of the arc is connected
278
* to the current point.
279
* @return {!goog.graphics.Path} The path itself.
280
* @deprecated Use {@code arcTo} or {@code arcToAsCurves} instead.
281
*/
282
goog.graphics.Path.prototype.arc = function(
283
cx, cy, rx, ry, fromAngle, extent, connect) {
284
var startX = cx + goog.math.angleDx(fromAngle, rx);
285
var startY = cy + goog.math.angleDy(fromAngle, ry);
286
if (connect) {
287
if (!this.currentPoint_ || startX != this.currentPoint_[0] ||
288
startY != this.currentPoint_[1]) {
289
this.lineTo(startX, startY);
290
}
291
} else {
292
this.moveTo(startX, startY);
293
}
294
return this.arcTo(rx, ry, fromAngle, extent);
295
};
296
297
298
/**
299
* Adds a path command to draw an arc starting at the path's current point,
300
* with radius {@code rx} along the x-axis and {@code ry} along the y-axis from
301
* {@code startAngle} through {@code extent} degrees. Positive rotation is in
302
* the direction from positive x-axis to positive y-axis.
303
*
304
* This method makes the path non-simple.
305
*
306
* @param {number} rx Radius of ellipse on x axis.
307
* @param {number} ry Radius of ellipse on y axis.
308
* @param {number} fromAngle Starting angle measured in degrees from the
309
* positive x-axis.
310
* @param {number} extent The span of the arc in degrees.
311
* @return {!goog.graphics.Path} The path itself.
312
*/
313
goog.graphics.Path.prototype.arcTo = function(rx, ry, fromAngle, extent) {
314
var cx = this.currentPoint_[0] - goog.math.angleDx(fromAngle, rx);
315
var cy = this.currentPoint_[1] - goog.math.angleDy(fromAngle, ry);
316
var ex = cx + goog.math.angleDx(fromAngle + extent, rx);
317
var ey = cy + goog.math.angleDy(fromAngle + extent, ry);
318
this.segments_.push(goog.graphics.Path.Segment.ARCTO);
319
this.count_.push(1);
320
this.arguments_.push(rx, ry, fromAngle, extent, ex, ey);
321
this.simple_ = false;
322
this.currentPoint_ = [ex, ey];
323
return this;
324
};
325
326
327
/**
328
* Same as {@code arcTo}, but approximates the arc using bezier curves.
329
.* As a result, this method does not affect the simplified status of this path.
330
* The algorithm is adapted from {@code java.awt.geom.ArcIterator}.
331
*
332
* @param {number} rx Radius of ellipse on x axis.
333
* @param {number} ry Radius of ellipse on y axis.
334
* @param {number} fromAngle Starting angle measured in degrees from the
335
* positive x-axis.
336
* @param {number} extent The span of the arc in degrees.
337
* @return {!goog.graphics.Path} The path itself.
338
*/
339
goog.graphics.Path.prototype.arcToAsCurves = function(
340
rx, ry, fromAngle, extent) {
341
var cx = this.currentPoint_[0] - goog.math.angleDx(fromAngle, rx);
342
var cy = this.currentPoint_[1] - goog.math.angleDy(fromAngle, ry);
343
var extentRad = goog.math.toRadians(extent);
344
var arcSegs = Math.ceil(Math.abs(extentRad) / Math.PI * 2);
345
var inc = extentRad / arcSegs;
346
var angle = goog.math.toRadians(fromAngle);
347
for (var j = 0; j < arcSegs; j++) {
348
var relX = Math.cos(angle);
349
var relY = Math.sin(angle);
350
var z = 4 / 3 * Math.sin(inc / 2) / (1 + Math.cos(inc / 2));
351
var c0 = cx + (relX - z * relY) * rx;
352
var c1 = cy + (relY + z * relX) * ry;
353
angle += inc;
354
relX = Math.cos(angle);
355
relY = Math.sin(angle);
356
this.curveTo(
357
c0, c1, cx + (relX + z * relY) * rx, cy + (relY - z * relX) * ry,
358
cx + relX * rx, cy + relY * ry);
359
}
360
return this;
361
};
362
363
364
/**
365
* Iterates over the path calling the supplied callback once for each path
366
* segment. The arguments to the callback function are the segment type and
367
* an array of its arguments.
368
*
369
* The {@code LINETO} and {@code CURVETO} arrays can contain multiple
370
* segments of the same type. The number of segments is the length of the
371
* array divided by the segment length (2 for lines, 6 for curves).
372
*
373
* As a convenience the {@code ARCTO} segment also includes the end point as the
374
* last two arguments: {@code rx, ry, fromAngle, extent, x, y}.
375
*
376
* @param {function(number, Array)} callback The function to call with each
377
* path segment.
378
*/
379
goog.graphics.Path.prototype.forEachSegment = function(callback) {
380
var points = this.arguments_;
381
var index = 0;
382
for (var i = 0, length = this.segments_.length; i < length; i++) {
383
var seg = this.segments_[i];
384
var n = goog.graphics.Path.segmentArgCounts_[seg] * this.count_[i];
385
callback(seg, points.slice(index, index + n));
386
index += n;
387
}
388
};
389
390
391
/**
392
* Returns the coordinates most recently added to the end of the path.
393
*
394
* @return {Array<number>?} An array containing the ending coordinates of the
395
* path of the form {@code [x, y]}.
396
*/
397
goog.graphics.Path.prototype.getCurrentPoint = function() {
398
return this.currentPoint_ && this.currentPoint_.concat();
399
};
400
401
402
/**
403
* @return {!goog.graphics.Path} A copy of this path.
404
*/
405
goog.graphics.Path.prototype.clone = function() {
406
var path = new this.constructor();
407
path.segments_ = this.segments_.concat();
408
path.count_ = this.count_.concat();
409
path.arguments_ = this.arguments_.concat();
410
path.closePoint_ = this.closePoint_ && this.closePoint_.concat();
411
path.currentPoint_ = this.currentPoint_ && this.currentPoint_.concat();
412
path.simple_ = this.simple_;
413
return path;
414
};
415
416
417
/**
418
* Returns true if this path contains no arcs. Simplified paths can be
419
* created using {@code createSimplifiedPath}.
420
*
421
* @return {boolean} True if the path contains no arcs.
422
*/
423
goog.graphics.Path.prototype.isSimple = function() {
424
return this.simple_;
425
};
426
427
428
/**
429
* A map from segment type to the path function to call to simplify a path.
430
* @type {!Object}
431
* @private
432
* @suppress {deprecated} goog.graphics.Path is deprecated.
433
*/
434
goog.graphics.Path.simplifySegmentMap_ = (function() {
435
var map = {};
436
map[goog.graphics.Path.Segment.MOVETO] = goog.graphics.Path.prototype.moveTo;
437
map[goog.graphics.Path.Segment.LINETO] = goog.graphics.Path.prototype.lineTo;
438
map[goog.graphics.Path.Segment.CLOSE] = goog.graphics.Path.prototype.close;
439
map[goog.graphics.Path.Segment.CURVETO] =
440
goog.graphics.Path.prototype.curveTo;
441
map[goog.graphics.Path.Segment.ARCTO] =
442
goog.graphics.Path.prototype.arcToAsCurves;
443
return map;
444
})();
445
446
447
/**
448
* Creates a copy of the given path, replacing {@code arcTo} with
449
* {@code arcToAsCurves}. The resulting path is simplified and can
450
* be transformed.
451
*
452
* @param {!goog.graphics.Path} src The path to simplify.
453
* @return {!goog.graphics.Path} A new simplified path.
454
* @suppress {deprecated} goog.graphics is deprecated.
455
*/
456
goog.graphics.Path.createSimplifiedPath = function(src) {
457
if (src.isSimple()) {
458
return src.clone();
459
}
460
var path = new goog.graphics.Path();
461
src.forEachSegment(function(segment, args) {
462
goog.graphics.Path.simplifySegmentMap_[segment].apply(path, args);
463
});
464
return path;
465
};
466
467
468
// TODO(chrisn): Delete this method
469
/**
470
* Creates a transformed copy of this path. The path is simplified
471
* {@see #createSimplifiedPath} prior to transformation.
472
*
473
* @param {!goog.graphics.AffineTransform} tx The transformation to perform.
474
* @return {!goog.graphics.Path} A new, transformed path.
475
*/
476
goog.graphics.Path.prototype.createTransformedPath = function(tx) {
477
var path = goog.graphics.Path.createSimplifiedPath(this);
478
path.transform(tx);
479
return path;
480
};
481
482
483
/**
484
* Transforms the path. Only simple paths are transformable. Attempting
485
* to transform a non-simple path will throw an error.
486
*
487
* @param {!goog.graphics.AffineTransform} tx The transformation to perform.
488
* @return {!goog.graphics.Path} The path itself.
489
*/
490
goog.graphics.Path.prototype.transform = function(tx) {
491
if (!this.isSimple()) {
492
throw Error('Non-simple path');
493
}
494
tx.transform(
495
this.arguments_, 0, this.arguments_, 0, this.arguments_.length / 2);
496
if (this.closePoint_) {
497
tx.transform(this.closePoint_, 0, this.closePoint_, 0, 1);
498
}
499
if (this.currentPoint_ && this.closePoint_ != this.currentPoint_) {
500
tx.transform(this.currentPoint_, 0, this.currentPoint_, 0, 1);
501
}
502
return this;
503
};
504
505
506
/**
507
* @return {boolean} Whether the path is empty.
508
*/
509
goog.graphics.Path.prototype.isEmpty = function() {
510
return this.segments_.length == 0;
511
};
512
513