path.js 12 KB

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