source: Dev/trunk/src/client/dojox/gfx/path.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 12.5 KB
Line 
1define(["./_base", "dojo/_base/lang","dojo/_base/declare", "./matrix", "./shape"],
2        function(g, lang, declare, matrix, shapeLib){
3
4        // module:
5        //              dojox/gfx/path
6
7        var Path = declare("dojox.gfx.path.Path", shapeLib.Shape, {
8                // summary:
9                //              a generalized path shape
10
11                constructor: function(rawNode){
12                        // summary:
13                        //              a path constructor
14                        // rawNode: Node
15                        //              a DOM node to be used by this path object
16                        this.shape = lang.clone(g.defaultPath);
17                        this.segments = [];
18                        this.tbbox = null;
19                        this.absolute = true;
20                        this.last = {};
21                        this.rawNode = rawNode;
22                        this.segmented = false;
23                },
24
25                // mode manipulations
26                setAbsoluteMode: function(mode){
27                        // summary:
28                        //              sets an absolute or relative mode for path points
29                        // mode: Boolean
30                        //              true/false or "absolute"/"relative" to specify the mode
31                        this._confirmSegmented();
32                        this.absolute = typeof mode == "string" ? (mode == "absolute") : mode;
33                        return this; // self
34                },
35                getAbsoluteMode: function(){
36                        // summary:
37                        //              returns a current value of the absolute mode
38                        this._confirmSegmented();
39                        return this.absolute; // Boolean
40                },
41
42                getBoundingBox: function(){
43                        // summary:
44                        //              returns the bounding box {x, y, width, height} or null
45                        this._confirmSegmented();
46                        return (this.bbox && ("l" in this.bbox)) ? {x: this.bbox.l, y: this.bbox.t, width: this.bbox.r - this.bbox.l, height: this.bbox.b - this.bbox.t} : null; // dojox/gfx.Rectangle
47                },
48
49                _getRealBBox: function(){
50                        // summary:
51                        //              returns an array of four points or null
52                        //              four points represent four corners of the untransformed bounding box
53                        this._confirmSegmented();
54                        if(this.tbbox){
55                                return this.tbbox;      // Array
56                        }
57                        var bbox = this.bbox, matrix = this._getRealMatrix();
58                        this.bbox = null;
59                        for(var i = 0, len = this.segments.length; i < len; ++i){
60                                this._updateWithSegment(this.segments[i], matrix);
61                        }
62                        var t = this.bbox;
63                        this.bbox = bbox;
64                        this.tbbox = t ? [
65                                {x: t.l, y: t.t},
66                                {x: t.r, y: t.t},
67                                {x: t.r, y: t.b},
68                                {x: t.l, y: t.b}
69                        ] : null;
70                        return this.tbbox;      // Array
71                },
72
73                getLastPosition: function(){
74                        // summary:
75                        //              returns the last point in the path, or null
76                        this._confirmSegmented();
77                        return "x" in this.last ? this.last : null; // Object
78                },
79
80                _applyTransform: function(){
81                        this.tbbox = null;
82                        return this.inherited(arguments);
83                },
84
85                // segment interpretation
86                _updateBBox: function(x, y, m){
87                        // summary:
88                        //              updates the bounding box of path with new point
89                        // x: Number
90                        //              an x coordinate
91                        // y: Number
92                        //              a y coordinate
93
94                        if(m){
95                                var t = matrix.multiplyPoint(m, x, y);
96                                x = t.x;
97                                y = t.y;
98                        }
99
100                        // we use {l, b, r, t} representation of a bbox
101                        if(this.bbox && ("l" in this.bbox)){
102                                if(this.bbox.l > x) this.bbox.l = x;
103                                if(this.bbox.r < x) this.bbox.r = x;
104                                if(this.bbox.t > y) this.bbox.t = y;
105                                if(this.bbox.b < y) this.bbox.b = y;
106                        }else{
107                                this.bbox = {l: x, b: y, r: x, t: y};
108                        }
109                },
110                _updateWithSegment: function(segment, matrix){
111                        // summary:
112                        //              updates the bounding box of path with new segment
113                        // segment: Object
114                        //              a segment
115                        var n = segment.args, l = n.length, i;
116                        // update internal variables: bbox, absolute, last
117                        switch(segment.action){
118                                case "M":
119                                case "L":
120                                case "C":
121                                case "S":
122                                case "Q":
123                                case "T":
124                                        for(i = 0; i < l; i += 2){
125                                                this._updateBBox(n[i], n[i + 1], matrix);
126                                        }
127                                        this.last.x = n[l - 2];
128                                        this.last.y = n[l - 1];
129                                        this.absolute = true;
130                                        break;
131                                case "H":
132                                        for(i = 0; i < l; ++i){
133                                                this._updateBBox(n[i], this.last.y, matrix);
134                                        }
135                                        this.last.x = n[l - 1];
136                                        this.absolute = true;
137                                        break;
138                                case "V":
139                                        for(i = 0; i < l; ++i){
140                                                this._updateBBox(this.last.x, n[i], matrix);
141                                        }
142                                        this.last.y = n[l - 1];
143                                        this.absolute = true;
144                                        break;
145                                case "m":
146                                        var start = 0;
147                                        if(!("x" in this.last)){
148                                                this._updateBBox(this.last.x = n[0], this.last.y = n[1], matrix);
149                                                start = 2;
150                                        }
151                                        for(i = start; i < l; i += 2){
152                                                this._updateBBox(this.last.x += n[i], this.last.y += n[i + 1], matrix);
153                                        }
154                                        this.absolute = false;
155                                        break;
156                                case "l":
157                                case "t":
158                                        for(i = 0; i < l; i += 2){
159                                                this._updateBBox(this.last.x += n[i], this.last.y += n[i + 1], matrix);
160                                        }
161                                        this.absolute = false;
162                                        break;
163                                case "h":
164                                        for(i = 0; i < l; ++i){
165                                                this._updateBBox(this.last.x += n[i], this.last.y, matrix);
166                                        }
167                                        this.absolute = false;
168                                        break;
169                                case "v":
170                                        for(i = 0; i < l; ++i){
171                                                this._updateBBox(this.last.x, this.last.y += n[i], matrix);
172                                        }
173                                        this.absolute = false;
174                                        break;
175                                case "c":
176                                        for(i = 0; i < l; i += 6){
177                                                this._updateBBox(this.last.x + n[i], this.last.y + n[i + 1], matrix);
178                                                this._updateBBox(this.last.x + n[i + 2], this.last.y + n[i + 3], matrix);
179                                                this._updateBBox(this.last.x += n[i + 4], this.last.y += n[i + 5], matrix);
180                                        }
181                                        this.absolute = false;
182                                        break;
183                                case "s":
184                                case "q":
185                                        for(i = 0; i < l; i += 4){
186                                                this._updateBBox(this.last.x + n[i], this.last.y + n[i + 1], matrix);
187                                                this._updateBBox(this.last.x += n[i + 2], this.last.y += n[i + 3], matrix);
188                                        }
189                                        this.absolute = false;
190                                        break;
191                                case "A":
192                                        for(i = 0; i < l; i += 7){
193                                                this._updateBBox(n[i + 5], n[i + 6], matrix);
194                                        }
195                                        this.last.x = n[l - 2];
196                                        this.last.y = n[l - 1];
197                                        this.absolute = true;
198                                        break;
199                                case "a":
200                                        for(i = 0; i < l; i += 7){
201                                                this._updateBBox(this.last.x += n[i + 5], this.last.y += n[i + 6], matrix);
202                                        }
203                                        this.absolute = false;
204                                        break;
205                        }
206                        // add an SVG path segment
207                        var path = [segment.action];
208                        for(i = 0; i < l; ++i){
209                                path.push(g.formatNumber(n[i], true));
210                        }
211                        if(typeof this.shape.path == "string"){
212                                this.shape.path += path.join("");
213                        }else{
214                                for(i = 0, l = path.length; i < l; ++i){
215                                        this.shape.path.push(path[i]);
216                                }
217                        }
218                },
219
220                // a dictionary, which maps segment type codes to a number of their arguments
221                _validSegments: {m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0},
222
223                _pushSegment: function(action, args){
224                        // summary:
225                        //              adds a segment
226                        // action: String
227                        //              valid SVG code for a segment's type
228                        // args: Array
229                        //              a list of parameters for this segment
230                        this.tbbox = null;
231                        var group = this._validSegments[action.toLowerCase()], segment;
232                        if(typeof group == "number"){
233                                if(group){
234                                        if(args.length >= group){
235                                                segment = {action: action, args: args.slice(0, args.length - args.length % group)};
236                                                this.segments.push(segment);
237                                                this._updateWithSegment(segment);
238                                        }
239                                }else{
240                                        segment = {action: action, args: []};
241                                        this.segments.push(segment);
242                                        this._updateWithSegment(segment);
243                                }
244                        }
245                },
246
247                _collectArgs: function(array, args){
248                        // summary:
249                        //              converts an array of arguments to plain numeric values
250                        // array: Array
251                        //              an output argument (array of numbers)
252                        // args: Array
253                        //              an input argument (can be values of Boolean, Number, dojox/gfx.Point, or an embedded array of them)
254                        for(var i = 0; i < args.length; ++i){
255                                var t = args[i];
256                                if(typeof t == "boolean"){
257                                        array.push(t ? 1 : 0);
258                                }else if(typeof t == "number"){
259                                        array.push(t);
260                                }else if(t instanceof Array){
261                                        this._collectArgs(array, t);
262                                }else if("x" in t && "y" in t){
263                                        array.push(t.x, t.y);
264                                }
265                        }
266                },
267
268                // segments
269                moveTo: function(){
270                        // summary:
271                        //              forms a move segment
272                        this._confirmSegmented();
273                        var args = [];
274                        this._collectArgs(args, arguments);
275                        this._pushSegment(this.absolute ? "M" : "m", args);
276                        return this; // self
277                },
278                lineTo: function(){
279                        // summary:
280                        //              forms a line segment
281                        this._confirmSegmented();
282                        var args = [];
283                        this._collectArgs(args, arguments);
284                        this._pushSegment(this.absolute ? "L" : "l", args);
285                        return this; // self
286                },
287                hLineTo: function(){
288                        // summary:
289                        //              forms a horizontal line segment
290                        this._confirmSegmented();
291                        var args = [];
292                        this._collectArgs(args, arguments);
293                        this._pushSegment(this.absolute ? "H" : "h", args);
294                        return this; // self
295                },
296                vLineTo: function(){
297                        // summary:
298                        //              forms a vertical line segment
299                        this._confirmSegmented();
300                        var args = [];
301                        this._collectArgs(args, arguments);
302                        this._pushSegment(this.absolute ? "V" : "v", args);
303                        return this; // self
304                },
305                curveTo: function(){
306                        // summary:
307                        //              forms a curve segment
308                        this._confirmSegmented();
309                        var args = [];
310                        this._collectArgs(args, arguments);
311                        this._pushSegment(this.absolute ? "C" : "c", args);
312                        return this; // self
313                },
314                smoothCurveTo: function(){
315                        // summary:
316                        //              forms a smooth curve segment
317                        this._confirmSegmented();
318                        var args = [];
319                        this._collectArgs(args, arguments);
320                        this._pushSegment(this.absolute ? "S" : "s", args);
321                        return this; // self
322                },
323                qCurveTo: function(){
324                        // summary:
325                        //              forms a quadratic curve segment
326                        this._confirmSegmented();
327                        var args = [];
328                        this._collectArgs(args, arguments);
329                        this._pushSegment(this.absolute ? "Q" : "q", args);
330                        return this; // self
331                },
332                qSmoothCurveTo: function(){
333                        // summary:
334                        //              forms a quadratic smooth curve segment
335                        this._confirmSegmented();
336                        var args = [];
337                        this._collectArgs(args, arguments);
338                        this._pushSegment(this.absolute ? "T" : "t", args);
339                        return this; // self
340                },
341                arcTo: function(){
342                        // summary:
343                        //              forms an elliptic arc segment
344                        this._confirmSegmented();
345                        var args = [];
346                        this._collectArgs(args, arguments);
347                        this._pushSegment(this.absolute ? "A" : "a", args);
348                        return this; // self
349                },
350                closePath: function(){
351                        // summary:
352                        //              closes a path
353                        this._confirmSegmented();
354                        this._pushSegment("Z", []);
355                        return this; // self
356                },
357
358                _confirmSegmented: function() {
359                        if (!this.segmented) {
360                                var path = this.shape.path;
361                                // switch to non-updating version of path building
362                                this.shape.path = [];
363                                this._setPath(path);
364                                // switch back to the string path
365                                this.shape.path = this.shape.path.join("");
366                                // become segmented
367                                this.segmented = true;
368                        }
369                },
370
371                // setShape
372                _setPath: function(path){
373                        // summary:
374                        //              forms a path using an SVG path string
375                        // path: String
376                        //              an SVG path string
377                        var p = lang.isArray(path) ? path : path.match(g.pathSvgRegExp);
378                        this.segments = [];
379                        this.absolute = true;
380                        this.bbox = {};
381                        this.last = {};
382                        if(!p) return;
383                        // create segments
384                        var action = "",        // current action
385                                args = [],              // current arguments
386                                l = p.length;
387                        for(var i = 0; i < l; ++i){
388                                var t = p[i], x = parseFloat(t);
389                                if(isNaN(x)){
390                                        if(action){
391                                                this._pushSegment(action, args);
392                                        }
393                                        args = [];
394                                        action = t;
395                                }else{
396                                        args.push(x);
397                                }
398                        }
399                        this._pushSegment(action, args);
400                },
401                setShape: function(newShape){
402                        // summary:
403                        //              forms a path using a shape
404                        // newShape: Object
405                        //              an SVG path string or a path object (see dojox/gfx.defaultPath)
406                        this.inherited(arguments, [typeof newShape == "string" ? {path: newShape} : newShape]);
407
408                        this.segmented = false;
409                        this.segments = [];
410                        if(!g.lazyPathSegmentation){
411                                this._confirmSegmented();
412                        }
413                        return this; // self
414                },
415
416                // useful constant for descendants
417                _2PI: Math.PI * 2
418        });
419
420        var TextPath = declare("dojox.gfx.path.TextPath", Path, {
421                // summary:
422                //              a generalized TextPath shape
423
424                constructor: function(rawNode){
425                        // summary:
426                        //              a TextPath shape constructor
427                        // rawNode: Node
428                        //              a DOM node to be used by this TextPath object
429                        if(!("text" in this)){
430                                this.text = lang.clone(g.defaultTextPath);
431                        }
432                        if(!("fontStyle" in this)){
433                                this.fontStyle = lang.clone(g.defaultFont);
434                        }
435                },
436                getText: function(){
437                        // summary:
438                        //              returns the current text object or null
439                        return this.text;       // Object
440                },
441                setText: function(newText){
442                        // summary:
443                        //              sets a text to be drawn along the path
444                        this.text = g.makeParameters(this.text,
445                                typeof newText == "string" ? {text: newText} : newText);
446                        this._setText();
447                        return this;    // self
448                },
449                getFont: function(){
450                        // summary:
451                        //              returns the current font object or null
452                        return this.fontStyle;  // Object
453                },
454                setFont: function(newFont){
455                        // summary:
456                        //              sets a font for text
457                        this.fontStyle = typeof newFont == "string" ?
458                                g.splitFontString(newFont) :
459                                g.makeParameters(g.defaultFont, newFont);
460                        this._setFont();
461                        return this;    // self
462                }
463        });
464
465        /*=====
466        g.Path = Path;
467        g.TextPath = TextPath;
468        =====*/
469
470        return g.path = {
471                // summary:
472                //              This module contains the core graphics Path API.
473                //              Path command format follows the W3C SVG 1.0 Path api.
474
475                Path: Path,
476                TextPath: TextPath
477        };
478});
Note: See TracBrowser for help on using the repository browser.