path.js 12 KB

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