source: Dev/branches/rest-dojo-ui/client/dojox/gfx/path.js @ 273

Last change on this file since 273 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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