not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1233 lines
44 KiB

  1. // Copyright (c) 2015,16 Walter Bender
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the The GNU Affero General Public
  5. // License as published by the Free Software Foundation; either
  6. // version 3 of the License, or (at your option) any later version.
  7. //
  8. // You should have received a copy of the GNU Affero General Public
  9. // License along with this library; if not, write to the Free Software
  10. // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
  11. // Borrowing loosely from tasprite_factory.py in the Python version.
  12. function SVG() {
  13. // Interface to the graphical representation of blocks, turtles,
  14. // palettes, etc. on screen.
  15. // Terms used here:
  16. // docks -- list of connection points of a block to other blocks
  17. // innies -- right hand side docks of a block, argument slots
  18. // outie -- left hand side dock of a block
  19. // slot -- top dock of a block that can be attached to other blocks
  20. // cap -- top dock of a block that cannot be attached to other blocks
  21. // tab -- bottom dock of a block if other blocks can be attached
  22. // tail -- bottom dock of a block if no other blocks can be attached
  23. // arm -- connection point of a branching block (if-then, loops) where
  24. // inner blocks are attached
  25. // else -- optional second `arm' for if-then-else blocks
  26. this.init = function() {
  27. this._x = 0;
  28. this._y = 0;
  29. this._minX = 10000;
  30. this._minY = 10000;
  31. this._maxX = -10000;
  32. this._maxY = -10000;
  33. this._width = 0;
  34. this._height = 0;
  35. this.docks = [];
  36. this._scale = 1;
  37. this._orientation = 0;
  38. this._radius = 8;
  39. this._strokeWidth = 1;
  40. this._innies = [];
  41. this._outie = false;
  42. this._innieX1 = (9 - this._strokeWidth) / 2;
  43. this._innieY1 = 3;
  44. this._innieX2 = (9 - this._strokeWidth) / 2;
  45. this._innieY2 = (9 - this._strokeWidth) / 2;
  46. this._inniesSpacer = 9;
  47. this._padding = this._innieY1 + this._strokeWidth;
  48. this._slot = true;
  49. this._cap = false;
  50. this._tab = true;
  51. this._bool = false;
  52. this._slotX = 10;
  53. this._slotY = 2;
  54. this._tail = false;
  55. this._porch = false;
  56. this._porchX = this._innieX1 + this._innieX2 + 4 * this._strokeWidth;
  57. this._porchY = this._innieY2;
  58. this._expandX = 30;
  59. this._expandX2 = 0;
  60. this._expandY = 0;
  61. this._expandY2 = 0;
  62. this._clampCount = 1;
  63. this._clampSlots = [1];
  64. this._slotSize = 21; // TODO: Compute this.
  65. this._arm = true;
  66. this._else = false;
  67. this._draw_inniess = true;
  68. this._fill = 'fill_color';
  69. this._stroke = 'stroke_color';
  70. this.margins = [0, 0, 0, 0];
  71. this._fontSize = 10;
  72. }
  73. // Attribute methods
  74. this.setFontSize = function (fontSize) {
  75. this._fontSize = fontSize;
  76. };
  77. this.setDrawInniess = function (flag) {
  78. this._draw_inniess = flag;
  79. };
  80. this.getWidth = function () {
  81. return this._width;
  82. };
  83. this.getHeight = function () {
  84. return this._height;
  85. };
  86. this.clearDocks = function () {
  87. this.docks = [];
  88. };
  89. this.setScale = function (scale) {
  90. this._scale = scale;
  91. };
  92. this.setOrientation = function (orientation) {
  93. this._orientation = orientation;
  94. };
  95. this.setClampCount = function (number) {
  96. this._clampCount = number;
  97. var n = this._clampSlots.length;
  98. if (n < number) {
  99. for (var i = 0; i < number - n; i++) {
  100. this._clampSlots.push(1);
  101. }
  102. }
  103. };
  104. this.setClampSlots = function (clamp, number) {
  105. if (clamp > this._clampCount.length - 1) {
  106. this.setClampCount(clamp + 1);
  107. }
  108. this._clampSlots[clamp] = number;
  109. };
  110. this.setExpand = function (w, h, w2, h2) {
  111. // TODO: make this an array
  112. this._expandX = w;
  113. this._expandY = h;
  114. this._expandX2 = w2;
  115. this._expandY2 = h2;
  116. };
  117. this.setstrokeWidth = function (stroke_width) {
  118. this._strokeWidth = stroke_width;
  119. this._calc_porch_params();
  120. };
  121. this.setColors = function (colors) {
  122. this._fill = colors[0];
  123. this._stroke = colors[1];
  124. };
  125. this.setFillColor = function (color) {
  126. this._fill = color;
  127. };
  128. this.setStrokeColor = function (color) {
  129. this._stroke = color;
  130. };
  131. this.setInnies = function (inniesArray) {
  132. for (var i = 0; i < inniesArray.length; i++) {
  133. this._innies.push(inniesArray[i]);
  134. }
  135. };
  136. this.setOutie = function (flag) {
  137. // Only one outie.
  138. this._outie = flag;
  139. };
  140. this.setSlot = function (flag) {
  141. this._slot = flag;
  142. if (flag) {
  143. this._cap = false;
  144. }
  145. };
  146. this.setCap = function (flag) {
  147. this._cap = flag;
  148. if (flag) {
  149. this._slot = false;
  150. }
  151. };
  152. this.setTab = function (flag) {
  153. this._tab = flag;
  154. if (flag) {
  155. this._tail = false;
  156. }
  157. };
  158. this.setTail = function (flag) {
  159. this._tail = flag;
  160. if (flag) {
  161. this._tab = false;
  162. }
  163. };
  164. this.setPorch = function (flag) {
  165. this._porch = flag;
  166. };
  167. this.setBoolean = function (flag) {
  168. this._bool = flag;
  169. };
  170. this.setElse = function (flag) {
  171. this._else = flag;
  172. };
  173. this.setArm = function (flag) {
  174. this._arm = flag;
  175. };
  176. // SVG-related helper methods
  177. this._resetMinMax = function () {
  178. this._minX = 10000;
  179. this._minY = 10000;
  180. this._maxX = -10000;
  181. this._maxY = -10000;
  182. };
  183. this._checkMinMax = function () {
  184. if (this._x < this._minX) {
  185. this._minX = this._x;
  186. }
  187. if (this._y < this._minY) {
  188. this._minY = this._y;
  189. }
  190. if (this._x > this._maxX) {
  191. this._maxX = this._x;
  192. }
  193. if (this._y > this._maxY) {
  194. this._maxY = this._y;
  195. }
  196. };
  197. this._calculateXY = function () {
  198. var x = this._strokeWidth / 2.0;
  199. var y = this._strokeWidth / 2.0 + this._radius;
  200. this.margins[0] = x + this._strokeWidth + 0.5;
  201. this.margins[1] = this._strokeWidth + 0.5;
  202. if (this._outie) {
  203. x += this._innieX1 + this._innieX2;
  204. this.margins[0] += this._innieX1 + this._innieX2;
  205. }
  206. if (this._cap) {
  207. y += this._slotY * 3.0;
  208. this.margins[1] += this._slotY * 3.0;
  209. } else if (this._slot) {
  210. this.margins[1] += this._slotY;
  211. }
  212. this.margins[0] *= this._scale;
  213. this.margins[1] *= this._scale;
  214. return([x, y]);
  215. };
  216. this._calculateWH = function (addstrokeWidth) {
  217. if (addstrokeWidth) {
  218. this._width = (this._maxX - this._minX + this._strokeWidth) * this._scale;
  219. } else {
  220. this._width = (this._maxX - this._minX) * this._scale;
  221. }
  222. if (this.margins[2] === 0) {
  223. this.margins[2] = (this._strokeWidth + 0.5) * this._scale;
  224. } else {
  225. this.margins[2] = this._width - this.margins[2];
  226. }
  227. if (addstrokeWidth) {
  228. this._height = (this._maxY - this._minY + this._strokeWidth) * this._scale;
  229. } else {
  230. this._height = (this._maxY - this._minY) * this._scale;
  231. }
  232. if (this.margins[3] === 0) {
  233. if (this._tab) {
  234. this.margins[3] = (this._slotY + this._strokeWidth + 0.5) * this._scale;
  235. } else {
  236. this.margins[3] = (this._slotY * 2 + this._strokeWidth + 0.5) * this._scale;
  237. }
  238. } else {
  239. this.margins[3] = this._height - this.margins[3];
  240. }
  241. };
  242. this._newPath = function (x, y) {
  243. this._x = x;
  244. this._y = y;
  245. return '<path d="m' + x + ' ' + y + ' ';
  246. };
  247. this._closePath = function () {
  248. return 'z" ';
  249. };
  250. this.text = function (x, y, fontSize, width, alignment, string) {
  251. this._x = x;
  252. this._y = y;
  253. this._checkMinMax();
  254. this._x = x + width;
  255. this._y = y - fontSize;
  256. this._checkMinMax();
  257. // writing-mode:lr';
  258. switch (alignment) {
  259. case 'left':
  260. case 'start':
  261. var align = 'start';
  262. break;
  263. case 'middle':
  264. case 'center':
  265. var align = 'middle';
  266. break;
  267. case 'right':
  268. case 'end':
  269. var align = 'end';
  270. break;
  271. }
  272. var yy = y;
  273. var tspans = string.split('\n');
  274. var text = '<text style="font-size:' + fontSize + 'px;fill:#000000;font-family:sans-serif;text-anchor:' + align + '">';
  275. for (var i = 0; i < tspans.length; i++) {
  276. text += '<tspan x="' + x + '" y="' + yy + '">' + tspans[i] + '</tspan>';
  277. yy += fontSize;
  278. }
  279. text += '</text>';
  280. return text;
  281. };
  282. this._lineTo = function (x, y) {
  283. this._checkMinMax();
  284. if (this._x === x && this._y === y) {
  285. return '';
  286. } else {
  287. this._x = x;
  288. this._y = y;
  289. this._checkMinMax();
  290. return 'L ' + x + ' ' + y + ' ';
  291. }
  292. };
  293. this._rLineTo = function (dx, dy) {
  294. if (dx === 0 && dy === 0) {
  295. return '';
  296. } else {
  297. return this._lineTo(this._x + dx, this._y + dy);
  298. }
  299. };
  300. this._arcTo = function (x, y, r, a, l, s) {
  301. this._checkMinMax();
  302. if (r === 0) {
  303. return this._lineTo(x, y);
  304. } else {
  305. this._x = x;
  306. this._y = y;
  307. this._checkMinMax();
  308. return 'A ' + r + ' ' + r + ' ' + a + ' ' + l + ' ' + s + ' ' + x + ' ' + y + ' ';
  309. }
  310. };
  311. this._rarcTo = function (signX, signY, a, l, s) {
  312. if (this._radius === 0) {
  313. return '';
  314. } else {
  315. var x = this._x + signX * this._radius;
  316. var y = this._y + signY * this._radius;
  317. return this._arcTo(x, y, this._radius, a, l, s);
  318. }
  319. };
  320. this._corner = function (signX, signY, a, l, s, start, end, skip) {
  321. var svg_str = '';
  322. if (this._radius > 0) {
  323. var r2 = this._radius / 2.0;
  324. if (start) {
  325. if (signX * signY === 1) {
  326. svg_str += this._rLineTo(signX * r2, 0);
  327. } else if (!skip) {
  328. svg_str += this._rLineTo(0, signY * r2);
  329. }
  330. }
  331. var x = this._x + signX * r2;
  332. var y = this._y + signY * r2;
  333. svg_str += this._arcTo(x, y, r2, a, l, s);
  334. if (end) {
  335. if (signX * signY === 1) {
  336. svg_str += this._rLineTo(0, signY * r2);
  337. } else if (!skip) {
  338. svg_str += this._rLineTo(signX * r2, 0);
  339. }
  340. }
  341. }
  342. return svg_str;
  343. };
  344. this._iCorner = function (signX, signY, a, l, s, start, end) {
  345. var r2 = this._strokeWidth + this._radius / 2.0;
  346. if (start) {
  347. if (signX * signY === -1) {
  348. var svg_str = this._rLineTo(signX * (r2 - this._strokeWidth), 0);
  349. } else {
  350. var svg_str = this._rLineTo(0, signY * (r2 - this._strokeWidth));
  351. }
  352. } else {
  353. var svg_str = '';
  354. }
  355. var x = this._x + signX * r2;
  356. var y = this._y + signY * r2;
  357. svg_str += this._arcTo(x, y, r2, a, l, s);
  358. if (end) {
  359. if (signX * signY === -1) {
  360. svg_str += this._rLineTo(0, signY * (r2 - this._strokeWidth));
  361. } else {
  362. svg_str += this._rLineTo(signX * (r2 - this._strokeWidth), 0);
  363. }
  364. }
  365. return svg_str;
  366. };
  367. this._doInnie = function () {
  368. this.docks.push([(this._x + this._strokeWidth) * this._scale,
  369. (this._y + this._innieY2) * this._scale]);
  370. if (this.margins[2] === 0) {
  371. this.margins[1] = (this._y - this._innieY1) * this._scale;
  372. this.margins[2] = (this._x - this._innieX1 - this._innieX2 - this._strokeWidth * 2) * this._scale;
  373. }
  374. this.margins[3] = (this._y + this._innieY2 + this._innieY1) * this._scale;
  375. return this._rLineTo(-this._innieX1, 0) + this._rLineTo(0, -this._innieY1) + this._rLineTo(-this._innieX2, 0) + this._rLineTo(0, this._innieY2 + 2 * this._innieY1) + this._rLineTo(this._innieX2, 0) + this._rLineTo(0, -this._innieY1) + this._rLineTo(this._innieX1, 0);
  376. };
  377. this._doOutie = function () {
  378. if (!this._outie) {
  379. return this._rLineTo(0, -this._innieY2);
  380. }
  381. // Outie needs to be the first dock element.
  382. this.docks.unshift([(this._x * this._scale), (this._y * this._scale)]);
  383. return this._rLineTo(0, -this._strokeWidth) + this._rLineTo(-this._innieX1 - 2 * this._strokeWidth, 0) + this._rLineTo(0, this._innieY1) + this._rLineTo(-this._innieX2 + 2 * this._strokeWidth, 0) + this._rLineTo(0, -this._innieY2 - 2 * this._innieY1 + 2 * this._strokeWidth) + this._rLineTo(this._innieX2 - 2 * this._strokeWidth, 0) + this._rLineTo(0, this._innieY1) + this._rLineTo(this._innieX1 + 2 * this._strokeWidth, 0) + this._rLineTo(0, -this._strokeWidth);
  384. };
  385. this._doSlot = function () {
  386. if (this._slot) {
  387. var x = this._x + this._slotX / 2.0;
  388. this.docks.push([(x * this._scale), (this._y * this._scale)]);
  389. return this._rLineTo(0, this._slotY) + this._rLineTo(this._slotX, 0) + this._rLineTo(0, -this._slotY);
  390. } else if (this._cap) {
  391. var x = this._x + this._slotX / 2.0;
  392. this.docks.push([(x * this._scale), (this._y * this._scale)]);
  393. return this._rLineTo(this._slotX / 2.0, -this._slotY * 3.0) + this._rLineTo(this._slotX / 2.0, this._slotY * 3.0);
  394. } else {
  395. return this._rLineTo(this._slotX, 0);
  396. }
  397. };
  398. this._doTail = function () {
  399. if (this._outie) {
  400. return this._rLineTo(-this._slotX, 0);
  401. } else if (this._tail) {
  402. var x = this._x + this._slotX / 2.0;
  403. this.docks.push([(x * this._scale),
  404. (this._y * this._scale)]);
  405. return this._rLineTo(-this._slotX / 2.0, this._slotY * 3.0) + this._rLineTo(-this._slotX / 2.0, -this._slotY * 3.0);
  406. } else {
  407. return this._rLineTo(-this._slotX, 0);
  408. }
  409. };
  410. this._doTab = function () {
  411. if (this._outie) {
  412. return this._rLineTo(-this._slotX, 0);
  413. }
  414. var x = this._x - this._slotX / 2.0;
  415. this.docks.push([x * this._scale, (this._y + this._strokeWidth) * this._scale]);
  416. return this._rLineTo(-this._strokeWidth, 0) + this._rLineTo(0, this._slotY) + this._rLineTo(-this._slotX + 2 * this._strokeWidth, 0) + this._rLineTo(0, -this._slotY) + this._rLineTo(-this._strokeWidth, 0);
  417. };
  418. this._doPorch = function (flag) {
  419. if (flag) {
  420. return this._rLineTo(0, this._porchY + this._innieY1) + this._rLineTo(this._porchX - this._radius, 0) + this._corner(1, 1, 90, 0, 1, true, true, false);
  421. } else {
  422. return this._rLineTo(0, this._porchY - this._padding) + this._rLineTo(this._porchX - this._radius, 0) + this._corner(1, 1, 90, 0, 1, true, true, false);
  423. }
  424. };
  425. this._startBoolean = function (xoffset, yoffset) {
  426. var svg = this._newPath(xoffset, yoffset); // - this._radius);
  427. this._radius -= this._strokeWidth;
  428. this.docks.push([this._x * this._scale, this._y * this._scale]);
  429. svg += this._rarcTo(1, -1, 90, 0, 1);
  430. this._radius += this._strokeWidth;
  431. svg += this._rLineTo(this._strokeWidth, 0);
  432. svg += this._rLineTo(0, -this._expandY);
  433. return svg;
  434. };
  435. this._doBoolean = function () {
  436. this.docks.push([(this._x - this._radius + this._strokeWidth) * this._scale, (this._y + this._radius) * this._scale]);
  437. this.margins[2] = (this._x - this._radius - this._strokeWidth) * this._scale;
  438. var svg = this._rarcTo(-1, 1, 90, 0, 0) + this._rarcTo(1, 1, 90, 0, 0);
  439. return svg;
  440. };
  441. this._endBoolean = function (notnot) {
  442. if (!notnot) {
  443. var svg = this._rLineTo(-this._radius * 1.5, 0);
  444. } else {
  445. var svg = '';
  446. }
  447. svg += this._rLineTo(0, -this._strokeWidth);
  448. svg += this._rLineTo(-this._strokeWidth, 0);
  449. this._radius -= this._strokeWidth;
  450. svg += this._rarcTo(-1, -1, 90, 0, 1);
  451. this._radius += this._strokeWidth;
  452. svg += this._closePath();
  453. this._calculateWH(true);
  454. svg += this._style();
  455. return svg;
  456. };
  457. this._header = function (center) {
  458. // FIXME: Why are our calculations off by 2 x strokeWidth?
  459. var width = this._width + 2 * this._strokeWidth;
  460. return '<svg xmlns="http://www.w3.org/2000/svg" width="' + width * 1.1 + '" height="' + this._height * 1.3 + '">' + this._transform(center) + '<filter id="dropshadow" height="130%"> \
  461. <feGaussianBlur in="SourceAlpha" stdDeviation="3"/> \
  462. <feOffset dx="2" dy="2" result="offsetblur"/> \
  463. <feComponentTransfer xmlns="http://www.w3.org/2000/svg"> \
  464. <feFuncA type="linear" slope="0.2"/> \
  465. </feComponentTransfer> \
  466. <feMerge> \
  467. <feMergeNode/> \
  468. <feMergeNode in="SourceGraphic"/> \
  469. </feMerge> \
  470. </filter>';
  471. };
  472. this._transform = function (center) {
  473. if (this._orientation !== 0) {
  474. var w = this._width / 2.0;
  475. var h = this._height / 2.0;
  476. var orientation = '<g transform = "rotate(' + this._orientation + ' ' + w + ' ' + h + ')">';
  477. } else {
  478. var orientation = '';
  479. }
  480. if (center) {
  481. var x = -this._minX;
  482. var y = -this._minY;
  483. return '<g transform="translate(' + x + ', ' + y + ')">';
  484. } else {
  485. return '<g transform="scale(' + this._scale + ', ' + this._scale + ')">' + orientation;
  486. }
  487. };
  488. this._footer = function () {
  489. if (this._orientation !== 0) {
  490. return '</g></g></svg>';
  491. } else {
  492. return '</g></svg>';
  493. }
  494. };
  495. this._style = function () {
  496. return 'style="fill:' + this._fill + ';fill-opacity:1;stroke:' + this._stroke + ';stroke-width:' + this._strokeWidth + ';stroke-linecap:round;stroke-opacity:1;filter:url(#dropshadow);" />';
  497. };
  498. /*
  499. The block construction methods typically start on the upper-left side
  500. of a block and proceed clockwise around the block, first constructing
  501. a corner (1, -1), a slot or hat on along the top, a corner (1, 1),
  502. right side connectors ("innies"), possibly a "porch" to suggest an
  503. order of arguments, another corner (-1, 1), a tab or tail, and the
  504. fourth corner (-1, -1), and finally, a left-side connector ("outie").
  505. In addition:
  506. * Minimum and maximum values are calculated for the SVG bounding box;
  507. * Docking coordinates are calculated for each innies, outie, tab, and slot.
  508. */
  509. this.basicBlock = function() {
  510. // The most common block type: used for 0, 1, 2, or 3
  511. // argument commands (forward, setxy, plus, sqrt, etc.)
  512. this._resetMinMax();
  513. var obj = this._calculateXY();
  514. var x = obj[0];
  515. var y = obj[1];
  516. this.margins[2] = 0;
  517. this.margins[3] = 0;
  518. var svg = this._newPath(x, y);
  519. svg += this._corner(1, -1 , 90, 0, 1, true, true, false);
  520. svg += this._doSlot();
  521. svg += this._rLineTo(this._expandX, 0);
  522. xx = this._x;
  523. svg += this._corner(1, 1 , 90, 0, 1, true, true, false);
  524. if (this._innies.length === 0) {
  525. // To maintain standard block height
  526. svg += this._rLineTo(0, this._padding);
  527. } else {
  528. for (var i = 0; i < this._innies.length; i++) {
  529. if (this._innies[i]) {
  530. svg += this._doInnie();
  531. }
  532. if (i === 0) {
  533. svg += this._rLineTo(0, this._expandY);
  534. } else if (i === 1 && this._expandY2 > 0) {
  535. svg += this._rLineTo(0, this._expandY2);
  536. }
  537. if (i === 0 && this._porch) {
  538. svg += this._doPorch(false);
  539. } else if (this._innies.length - 1 > i) {
  540. svg += this._rLineTo(0, 2 * this._innieY2 + this._inniesSpacer);
  541. }
  542. }
  543. }
  544. svg += this._corner(-1, 1 , 90, 0, 1, true, true, false);
  545. svg += this._lineTo(xx, this._y);
  546. svg += this._rLineTo(-this._expandX, 0);
  547. if (this._tab) {
  548. svg += this._doTab();
  549. } else {
  550. svg += this._doTail();
  551. }
  552. svg += this._corner(-1, -1 , 90, 0, 1, true, true, false);
  553. svg += this._rLineTo(0, -this._expandY);
  554. if (this._innies.indexOf(true) !== -1) {
  555. svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0);
  556. svg += this._doOutie();
  557. }
  558. this._calculateWH(true);
  559. svg += this._closePath();
  560. svg += this._style();
  561. // Add a block label
  562. var tx = this._width - this._scale * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth;
  563. var ty = this._height / 2 + this._fontSize / (5 / this._scale);
  564. // If we have an odd number of innie slots, we need to avoid a
  565. // collision between the block label and the slot label.
  566. var nInnies = this._innies.length;
  567. if (nInnies > 2 && Math.round(nInnies / 2) * 2 !== nInnies) {
  568. ty -= 2 * this._fontSize;
  569. }
  570. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label');
  571. // Add a label for each innies
  572. if (this._slot || this._outie) {
  573. var di = 1; // Skip the first dock since it is a slot.
  574. } else {
  575. var di = 0;
  576. }
  577. var count = 1;
  578. for (var i = 0; i < this._innies.length; i++) {
  579. if (this._innies[i]) {
  580. ty = this.docks[di][1] - (this._fontSize / (8 / this._scale));
  581. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_' + count);
  582. count += 1;
  583. di += 1;
  584. }
  585. }
  586. svg += this._footer();
  587. return this._header(false) + svg;
  588. };
  589. this.basicBox = function () {
  590. // Basic argument style used for numbers, text, media, parameters
  591. this._resetMinMax();
  592. this.setOutie(true);
  593. var x = this._strokeWidth / 2.0 + this._innieX1 + this._innieX2;
  594. this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale;
  595. this.margins[1] = (this._strokeWidth + 0.5) * this._scale;
  596. this.margins[2] = 0;
  597. this.margins[3] = 0;
  598. var svg = this._newPath(x, this._strokeWidth / 2.0);
  599. svg += this._rLineTo(this._expandX, 0);
  600. svg += this._rLineTo(0, 2 * this._radius + this._innieY2 + this._expandY);
  601. svg += this._rLineTo(-this._expandX, 0);
  602. svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0);
  603. svg += this._doOutie();
  604. svg += this._closePath();
  605. this._calculateWH(true);
  606. svg += this._style();
  607. // Add a block label
  608. var tx = 2 * (this._innieX1 + this._innieX2) + 4 * this._strokeWidth;
  609. var ty = this._height / 2 + this._fontSize / 2;
  610. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label');
  611. svg += this._footer();
  612. return this._header(false) + svg;
  613. };
  614. this.booleanAndOr = function () {
  615. // Booleans are in a class of their own
  616. this._resetMinMax();
  617. var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 5.5 + this._strokeWidth / 2.0 + this._innieY2 + this._inniesSpacer + this._expandY);
  618. svg += this._rLineTo(0, -this._radius * 3.5 - this._innieY2 - this._inniesSpacer - this._strokeWidth);
  619. svg += this._rarcTo(1, -1, 90, 0, 1);
  620. svg += this._rLineTo(this._radius / 2.0 + this._expandX, 0);
  621. var xx = this._x;
  622. svg += this._rLineTo(0, this._radius / 2.0);
  623. svg += this._doBoolean();
  624. svg += this._rLineTo(0, this._radius * 1.5 + this._innieY2 + this._inniesSpacer);
  625. svg += this._rLineTo(0, this._expandY);
  626. svg += this._doBoolean();
  627. svg += this._rLineTo(0, this._radius / 2.0);
  628. svg += this._lineTo(xx, this._y);
  629. svg += this._rLineTo(-this._expandX, 0);
  630. svg += this._endBoolean(false);
  631. this.margins[0] = (this._radius + this._strokeWidth + 0.5) * this._scale;
  632. this.margins[1] = this._strokeWidth * this._scale;
  633. this.margins[2] = this._strokeWidth * this._scale;
  634. this.margins[3] = this._strokeWidth * this._scale;
  635. // Add a block label
  636. var tx = this._width - this._scale * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth;
  637. var ty = this._height / 2 + this._fontSize / 2;
  638. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label');
  639. svg += this._footer();
  640. return this._header(false) + svg;
  641. };
  642. this.booleanNot = function (notnot) {
  643. // Booleans are in a class of their own: not and not not
  644. this._resetMinMax();
  645. if (this._innies[0]) {
  646. var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 1.25 + this._strokeWidth / 2.0);
  647. } else if (!notnot) {
  648. var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 2.0 + this._strokeWidth / 2.0);
  649. } else {
  650. var svg = this._startBoolean(this._strokeWidth / 2.0, this._radius * 1.25 + this._strokeWidth / 2.0);
  651. }
  652. svg += this._rLineTo(0, -this._strokeWidth);
  653. if (this._innies[0]) {
  654. svg += this._rLineTo(0, -this._radius / 4.0);
  655. } else if (!notnot) {
  656. svg += this._rarcTo(1, -1, 90, 0, 1);
  657. } else {
  658. svg += this._rLineTo(0, -this._radius / 4.0);
  659. }
  660. svg += this._rLineTo(this._radius / 2.0 + this._expandX, 0);
  661. var xx = this._x;
  662. if (this._innies[0]) {
  663. svg += this._rLineTo(0, this._radius);
  664. svg += this._doInnie();
  665. svg += this._rLineTo(0, this._radius);
  666. } else if (!notnot) {
  667. svg += this._rLineTo(0, this._radius / 2.0);
  668. svg += this._doBoolean();
  669. svg += this._rLineTo(0, this._radius / 2.0);
  670. } else {
  671. svg += this._rLineTo(0, this._radius * 2.25);
  672. }
  673. svg += this._lineTo(xx, this._y);
  674. // FIXME: Is this in the correct place?
  675. if (this._expandY2 > 0) {
  676. svg += this._rLineTo(0, this._expandY2);
  677. }
  678. if (this._innies[0]) {
  679. svg += this._rLineTo(-this._radius / 2.0 - this._expandX, 0);
  680. svg += this._rLineTo(0, -this._radius / 4.0);
  681. } else if (!notnot) {
  682. svg += this._rLineTo(-this._expandX, 0);
  683. } else {
  684. svg += this._rLineTo(-this._radius / 2.0 - this._expandX, 0);
  685. }
  686. // FIXME: Is this in the correct place?
  687. if (this._expandY2 > 0) {
  688. svg += this._rLineTo(0, -this._expandY2);
  689. }
  690. svg += this._endBoolean(notnot);
  691. if (notnot) {
  692. this.margins[0] = (this._radius + this._strokeWidth + 0.5) * this._scale;
  693. this.margins[2] = (this._radius + this._strokeWidth + 0.5) * this._scale;
  694. } else {
  695. this.margins[0] = (this._strokeWidth + 0.5) * this._scale;
  696. this.margins[2] = (this._strokeWidth + 0.5) * this._scale;
  697. }
  698. this.margins[1] = this._strokeWidth * this._scale;
  699. this.margins[3] = this._strokeWidth * this._scale;
  700. // Add a block label
  701. var tx = this._width - 2 * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth;
  702. var ty = this._height / 2 + this._fontSize / 2;
  703. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label');
  704. svg += this._footer();
  705. return this._header(false) + svg;
  706. };
  707. this.booleanCompare = function () {
  708. // Booleans are in a class of their own (greater than, less than, etc)
  709. this._resetMinMax();
  710. var yoffset = this._radius * 2 + 2 * this._innieY2 + this._inniesSpacer + this._strokeWidth / 2.0 + this._expandY;
  711. var xoffset = this._strokeWidth / 2.0;
  712. var yoff = this._radius * 2;
  713. var svg = '<g transform="matrix(1,0,0,1,0,-' + yoff + ')"> ';
  714. svg += this._newPath(xoffset, yoffset + this._radius);
  715. this.docks.push([this._x * this._scale, (this._y - 2 * this._radius) * this._scale]);
  716. this._radius -= this._strokeWidth;
  717. svg += this._rarcTo(1, -1, 90, 0, 1);
  718. this._radius += this._strokeWidth;
  719. svg += this._rLineTo(this._strokeWidth, 0);
  720. svg += this._rLineTo(0, -this._expandY);
  721. yoffset = -2 * this._innieY2 - this._inniesSpacer - this._strokeWidth;
  722. svg += this._rLineTo(0, yoffset + this._radius);
  723. svg += this._rarcTo(1, -1, 90, 0, 1);
  724. svg += this._rLineTo(this._radius / 2.0 + this._expandX, 0);
  725. svg += this._rLineTo(0, this._radius);
  726. var xx = this._x;
  727. svg += this._doInnie();
  728. this.docks[1][1] -= this._radius * 2 * this._scale;
  729. svg += this._rLineTo(0, this._expandY);
  730. if (this._porch) {
  731. svg += this._doPorch(false);
  732. } else {
  733. svg += this._rLineTo(0, 2 * this._innieY2 + this._inniesSpacer);
  734. }
  735. svg += this._doInnie();
  736. this.docks[2][1] -= this._radius * 2 * this._scale;
  737. svg += this._rLineTo(0, this._radius);
  738. svg += this._lineTo(xx, this._y);
  739. svg += this._rLineTo(-this._expandX, 0);
  740. svg += this._rLineTo(-this._radius * 1.5, 0);
  741. svg += this._rLineTo(0, -this._radius);
  742. svg += this._rLineTo(0, -this._strokeWidth);
  743. svg += this._rLineTo(-this._strokeWidth, 0);
  744. this._radius -= this._strokeWidth;
  745. svg += this._rarcTo(-1, -1, 90, 0, 1);
  746. this._radius += this._strokeWidth;
  747. svg += this._closePath();
  748. this._calculateWH(true);
  749. svg += this._style();
  750. svg += '</g>';
  751. this.margins[0] = (this._radius + this._strokeWidth) * this._scale;
  752. this.margins[1] = this._strokeWidth * this._scale;
  753. this.margins[2] = this._strokeWidth * this._scale;
  754. // Add a block label
  755. var tx = this._width - 2 * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth;
  756. var ty = this._height / 2 + this._fontSize / 2; // + this._radius * this._scale;
  757. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'right', 'block_label');
  758. svg += this._footer();
  759. return this._header(false) + svg;
  760. };
  761. this.basicClamp = function () {
  762. // Special block for collapsible stacks; includes an 'arm'
  763. // that extends down the left side of a stack and a bottom jaw
  764. // to clamp the blocks. (Used for start, action, repeat, etc.)
  765. var save_cap = this._cap;
  766. var save_slot = this._slot;
  767. this._resetMinMax();
  768. if (this._outie) {
  769. var x = this._strokeWidth / 2.0 + this._innieX1 + this._innieX2;
  770. } else {
  771. var x = this._strokeWidth / 2.0;
  772. }
  773. if (this._cap) {
  774. var y = this._strokeWidth / 2.0 + this._radius + this._slotY * 3.0;
  775. } else {
  776. var y = this._strokeWidth / 2.0 + this._radius;
  777. }
  778. this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale;
  779. this.margins[1] = (this._strokeWidth + 0.5) * this._scale;
  780. this.margins[2] = 0;
  781. this.margins[3] = 0;
  782. var svg = this._newPath(x, y);
  783. svg += this._corner(1, -1 , 90, 0, 1, true, true, false);
  784. svg += this._doSlot();
  785. if (this._cap) {
  786. this._slot = true;
  787. this._cap = false;
  788. }
  789. svg += this._rLineTo(this._radius + this._strokeWidth, 0);
  790. var xx = this._x;
  791. svg += this._rLineTo(this._expandX, 0);
  792. svg += this._corner(1, 1 , 90, 0, 1, true, true, false);
  793. if (this._innies[0]) {
  794. // svg += this._doInnie();
  795. for (var i = 0; i < this._innies.length; i++) {
  796. if (this._innies[i]) {
  797. svg += this._doInnie();
  798. }
  799. if (i === 0) {
  800. svg += this._rLineTo(0, this._expandY);
  801. } else if (i === 1 && this._expandY2 > 0) {
  802. svg += this._rLineTo(0, this._expandY2);
  803. }
  804. if (i === 0 && this._porch) {
  805. svg += this._doPorch(false);
  806. } else if (this._innies.length - 1 > i) {
  807. svg += this._rLineTo(0, 2 * this._innieY2 + this._inniesSpacer);
  808. }
  809. }
  810. } else if (this._bool) {
  811. svg += this._rLineTo(0, 2 * this._padding + this._strokeWidth);
  812. svg += this._doBoolean();
  813. this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale;
  814. } else {
  815. svg += this._rLineTo(0, this._padding);
  816. this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale;
  817. }
  818. for (var clamp = 0; clamp < this._clampCount; clamp++) {
  819. if (clamp > 0) {
  820. svg += this._rLineTo(0, 3 * this._padding);
  821. }
  822. svg += this._corner(-1, 1, 90, 0, 1, true, true, false);
  823. svg += this._lineTo(xx, this._y);
  824. var saveOutie = this._outie;
  825. this._outie = false;
  826. svg += this._doTab();
  827. this._outie = saveOutie;
  828. svg += this._iCorner(-1, 1, 90, 0, 0, true, true);
  829. svg += this._rLineTo(0, this._padding);
  830. if (this._clampSlots[clamp] > 1) {
  831. var dy = this._slotSize * (this._clampSlots[clamp] - 1);
  832. svg += this._rLineTo(0, dy);
  833. }
  834. svg += this._rLineTo(0, this._expandY2);
  835. svg += this._iCorner(1, 1, 90, 0, 0, true, true);
  836. var saveSlot = this._slot;
  837. this._slot = true;
  838. svg += this._doSlot();
  839. this._slot = saveSlot;
  840. this.docks.pop(); // We don't need this dock.
  841. svg += this._rLineTo(this._radius, 0);
  842. }
  843. svg += this._rLineTo(0, this._innieY1 * 2);
  844. // Add a bit of padding to make multiple of standard block height.
  845. svg += this._rLineTo(0, this._innieY1 + 3 * this._strokeWidth);
  846. svg += this._corner(-1, 1, 90, 0, 1, true, true, false);
  847. if (this._clampCount === 0) {
  848. svg += this._lineTo(xx, this._y);
  849. }
  850. svg += this._rLineTo(-this._radius - this._strokeWidth, 0);
  851. if (this._tail) {
  852. svg += this._doTail();
  853. } else {
  854. svg += this._doTab();
  855. }
  856. this._cap = save_cap;
  857. this._slot = save_slot;
  858. svg += this._corner(-1, -1, 90, 0, 1, true, true, false);
  859. if (this._outie) {
  860. svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0);
  861. svg += this._doOutie();
  862. }
  863. svg += this._closePath();
  864. this._calculateWH(true);
  865. svg += this._style();
  866. // Add a block label
  867. if (this._outie) {
  868. var tx = 10 * this._strokeWidth + this._innieX1 + this._innieX2;
  869. } else {
  870. var tx = 8 * this._strokeWidth;
  871. }
  872. if (this._cap) {
  873. var ty = (this._strokeWidth / 2.0 + this._radius + this._slotY) * this._scale;
  874. } else if (this._innies.length > 1) {
  875. var ty = (this._strokeWidth / 2.0 + this._radius) * this._scale / 2;
  876. ty += this._fontSize;
  877. } else {
  878. var ty = (this._strokeWidth / 2.0 + this._radius) * this._scale / 2;
  879. }
  880. ty += (this._fontSize + 1) * this._scale;
  881. if (this._bool) {
  882. ty += this._fontSize / 2;
  883. }
  884. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label');
  885. // Booleans get an extra label.
  886. if (this._bool) {
  887. var count = 1;
  888. var tx = this._width - this._radius;
  889. for (var clamp = 0; clamp < this._clampCount; clamp++) {
  890. ty = this.docks[clamp + 2][1] - this._fontSize + 3 * this._strokeWidth;
  891. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_' + count);
  892. count += 1;
  893. }
  894. }
  895. // Add a label for each innies
  896. if (this._slot || this._outie) {
  897. var di = 1; // Skip the first dock since it is a slot.
  898. } else {
  899. var di = 0;
  900. }
  901. var count = 1;
  902. var tx = this._width - this._scale * (this._innieX1 + this._innieX2) - 4 * this._strokeWidth;
  903. for (var i = 0; i < this._innies.length; i++) {
  904. if (this._innies[i]) {
  905. ty = this.docks[di][1] - (this._fontSize / (8 / this._scale));
  906. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_' + count);
  907. count += 1;
  908. di += 1;
  909. }
  910. }
  911. svg += this._footer();
  912. return this._header(false) + svg;
  913. };
  914. this.argClamp = function () {
  915. // A clamp that contains innies rather than flow blocks
  916. this._resetMinMax();
  917. if (this._outie) {
  918. var x = this._strokeWidth / 2.0 + this._innieX1 + this._innieX2;
  919. } else {
  920. var x = this._strokeWidth / 2.0;
  921. }
  922. var y = this._strokeWidth / 2.0 + this._radius;
  923. this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale;
  924. this.margins[1] = (this._strokeWidth + 0.5) * this._scale;
  925. this.margins[2] = 0;
  926. this.margins[3] = 0;
  927. var svg = this._newPath(x, y);
  928. svg += this._corner(1, -1 , 90, 0, 1, true, true, false);
  929. svg += this._doSlot();
  930. svg += this._rLineTo(this._radius + this._strokeWidth, 0);
  931. var xx = this._x;
  932. svg += this._rLineTo(this._expandX, 0);
  933. svg += this._corner(1, 1 , 90, 0, 1, true, true, false);
  934. if (this._innies[0]) {
  935. svg += this._doInnie();
  936. } else {
  937. svg += this._rLineTo(0, this._padding);
  938. this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale;
  939. }
  940. svg += this._corner(-1, 1, 90, 0, 1, true, true, false);
  941. svg += this._lineTo(xx, this._y);
  942. svg += this._iCorner(-1, 1, 90, 0, 0, true, true);
  943. var j = 0;
  944. svg += this._doInnie();
  945. var dy = this._slotSize * (this._clampSlots[0][j] - 1);
  946. if (dy > 0) {
  947. svg += this._rLineTo(0, dy);
  948. }
  949. j += 1;
  950. var ddy = (this._slotSize - this._innieY2);
  951. for (var i = 1; i < this._clampSlots[0].length; i++) {
  952. svg += this._rLineTo(0, ddy);
  953. svg += this._doInnie();
  954. var dy = this._slotSize * (this._clampSlots[0][j] - 1);
  955. if (dy > 0) {
  956. svg += this._rLineTo(0, dy);
  957. }
  958. j += 1;
  959. }
  960. svg += this._rLineTo(0, this._expandY2);
  961. svg += this._iCorner(1, 1, 90, 0, 0, true, true);
  962. svg += this._rLineTo(this._radius, 0);
  963. svg += this._rLineTo(0, this._innieY1 * 2);
  964. // Add a bit of padding to make multiple of standard block height.
  965. svg += this._rLineTo(0, this._innieY1 + 3 * this._strokeWidth);
  966. svg += this._corner(-1, 1, 90, 0, 1, true, true, false);
  967. svg += this._lineTo(xx, this._y);
  968. svg += this._rLineTo(-this._radius - this._strokeWidth, 0);
  969. if (this._tail) {
  970. svg += this._doTail();
  971. } else {
  972. svg += this._doTab();
  973. }
  974. svg += this._corner(-1, -1, 90, 0, 1, true, true, false);
  975. if (this._outie) {
  976. svg += this._lineTo(x, this._radius + this._innieY2 + this._strokeWidth / 2.0);
  977. svg += this._doOutie();
  978. }
  979. svg += this._closePath();
  980. this._calculateWH(true);
  981. svg += this._style();
  982. // Add a block label
  983. if (this._outie) {
  984. var tx = 10 * this._strokeWidth + this._innieX1 + this._innieX2;
  985. } else {
  986. var tx = 8 * this._strokeWidth;
  987. }
  988. if (this._cap) {
  989. var ty = (this._strokeWidth / 2.0 + this._radius + this._slotY) * this._scale;
  990. } else {
  991. var ty = (this._strokeWidth / 2.0 + this._radius) * this._scale / 2;
  992. }
  993. ty += (this._fontSize + 1) * this._scale;
  994. if (this._bool) {
  995. ty += this._fontSize / 2;
  996. }
  997. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label');
  998. svg += this._footer();
  999. return this._header(false) + svg;
  1000. };
  1001. this.untilClamp = function () {
  1002. // Until block is like clamp but docks are flipped
  1003. this._resetMinMax();
  1004. var x = this._strokeWidth / 2.0;
  1005. var y = this._strokeWidth / 2.0 + this._radius;
  1006. this.margins[0] = (x + this._strokeWidth + 0.5) * this._scale;
  1007. this.margins[1] = (this._strokeWidth + 0.5) * this._scale;
  1008. this.margins[2] = 0;
  1009. this.margins[3] = 0;
  1010. var svg = this._newPath(x, y);
  1011. svg += this._corner(1, -1, 90, 0, 1, true, true, false);
  1012. svg += this._doSlot();
  1013. svg += this._rLineTo(this._radius + this._strokeWidth, 0);
  1014. svg += this._rLineTo(this._expandX, 0);
  1015. var xx = this._x;
  1016. svg += this._corner(1, 1, 90, 0, 1, true, true, true);
  1017. svg += this._rLineTo(0, 2 * this._innieY1);
  1018. svg += this._corner(-1, 1, 90, 0, 1, true, true, true);
  1019. svg += this._lineTo(xx, this._y);
  1020. svg += this._rLineTo(-this._expandX, 0);
  1021. svg += this._doTab();
  1022. svg += this._iCorner(-1, 1, 90, 0, 0, true, true);
  1023. svg += this._rLineTo(0, this._expandY);
  1024. svg += this._iCorner(1, 1, 90, 0, 0, true, true);
  1025. svg += this._doSlot();
  1026. this.docks.pop(); // We don't need this dock.
  1027. svg += this._rLineTo(this._radius, 0);
  1028. if (this._innies[0]) {
  1029. svg += this._doInnie();
  1030. } else {
  1031. this.margins[2] = (this._x - this._strokeWidth + 0.5) * this._scale;
  1032. }
  1033. svg += this._rLineTo(0, this._radius + this._expandY2);
  1034. if (this._bool) {
  1035. svg += this._doBoolean();
  1036. }
  1037. svg += this._corner(-1, 1, 90, 0, 1, true, true, false);
  1038. svg += this._rLineTo(-this._radius - this._strokeWidth, 0);
  1039. svg += this._doTab();
  1040. svg += this._corner(-1, -1, 90, 0, 1, true, true, false);
  1041. svg += this._closePath();
  1042. this._calculateWH(true);
  1043. svg += this._style();
  1044. // Add a block label
  1045. var tx = 4 * this._strokeWidth;
  1046. var ty = this.docks[2][1];
  1047. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize, this._width, 'left', 'block_label');
  1048. if (this._bool) {
  1049. // Booleans get an extra label.
  1050. var tx = this._width - this._radius;
  1051. ty = this.docks[1][1] - this._fontSize;
  1052. svg += this.text(tx / this._scale, ty / this._scale, this._fontSize / 1.5, this._width, 'right', 'arg_label_1');
  1053. }
  1054. if (this._bool) {
  1055. // Swap bool and tab args so that the docking behaves like the
  1056. // while block.
  1057. var tx = this.docks[1][0];
  1058. var ty = this.docks[1][1];
  1059. this.docks[1][0] = this.docks[2][0];
  1060. this.docks[1][1] = this.docks[2][1];
  1061. this.docks[2][0] = tx;
  1062. this.docks[2][1] = ty;
  1063. }
  1064. svg += this._footer();
  1065. return this._header(false) + svg;
  1066. };
  1067. this.statusBlock = function (graphic) {
  1068. // Generate a status block
  1069. this._resetMinMax();
  1070. var obj = this._calculateXY();
  1071. var x = obj[0];
  1072. var y = obj[1];
  1073. this.margins[2] = 0;
  1074. this.margins[3] = 0;
  1075. var svg = this._newPath(x, y);
  1076. svg += this._corner(1, -1, 90, 0, 1, true, true, false);
  1077. svg += this._rLineTo(this._expandX, 0);
  1078. var xx = this._x;
  1079. svg += this._corner(1, 1, 90, 0, 1, true, true, false);
  1080. svg += this._rLineTo(0, this._expandY);
  1081. svg += this._corner(-1, 1, 90, 0, 1, true, true, false);
  1082. svg += this._lineTo(xx, this._y);
  1083. svg += this._rLineTo(-this._expandX, 0);
  1084. svg += this._corner(-1, -1, 90, 0, 1, true, true, false);
  1085. svg += this._rLineTo(0, -this._expandY);
  1086. this._calculateWH(true);
  1087. svg += this._closePath();
  1088. svg += this._style();
  1089. svg += this._footer();
  1090. return this._header(false) + svg;
  1091. };
  1092. };