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.

26123 lines
758 KiB

  1. /* build: `node build.js modules=ALL exclude=json,gestures minifier=uglifyjs` */
  2. /*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
  3. var fabric = fabric || { version: "1.6.7" };
  4. if (typeof exports !== 'undefined') {
  5. exports.fabric = fabric;
  6. }
  7. if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  8. fabric.document = document;
  9. fabric.window = window;
  10. // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)
  11. window.fabric = fabric;
  12. }
  13. else {
  14. // assume we're running under node.js when document/window are not present
  15. fabric.document = require("jsdom")
  16. .jsdom("<!DOCTYPE html><html><head></head><body></body></html>");
  17. if (fabric.document.createWindow) {
  18. fabric.window = fabric.document.createWindow();
  19. } else {
  20. fabric.window = fabric.document.parentWindow;
  21. }
  22. }
  23. /**
  24. * True when in environment that supports touch events
  25. * @type boolean
  26. */
  27. fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
  28. /**
  29. * True when in environment that's probably Node.js
  30. * @type boolean
  31. */
  32. fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
  33. typeof window === 'undefined';
  34. /* _FROM_SVG_START_ */
  35. /**
  36. * Attributes parsed from all SVG elements
  37. * @type array
  38. */
  39. fabric.SHARED_ATTRIBUTES = [
  40. "display",
  41. "transform",
  42. "fill", "fill-opacity", "fill-rule",
  43. "opacity",
  44. "stroke", "stroke-dasharray", "stroke-linecap",
  45. "stroke-linejoin", "stroke-miterlimit",
  46. "stroke-opacity", "stroke-width",
  47. "id"
  48. ];
  49. /* _FROM_SVG_END_ */
  50. /**
  51. * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
  52. */
  53. fabric.DPI = 96;
  54. fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
  55. fabric.fontPaths = { };
  56. /**
  57. * Cache Object for widths of chars in text rendering.
  58. */
  59. fabric.charWidthsCache = { };
  60. /**
  61. * Device Pixel Ratio
  62. * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html
  63. */
  64. fabric.devicePixelRatio = fabric.window.devicePixelRatio ||
  65. fabric.window.webkitDevicePixelRatio ||
  66. fabric.window.mozDevicePixelRatio ||
  67. 1;
  68. (function() {
  69. /**
  70. * @private
  71. * @param {String} eventName
  72. * @param {Function} handler
  73. */
  74. function _removeEventListener(eventName, handler) {
  75. if (!this.__eventListeners[eventName]) {
  76. return;
  77. }
  78. var eventListener = this.__eventListeners[eventName];
  79. if (handler) {
  80. eventListener[eventListener.indexOf(handler)] = false;
  81. }
  82. else {
  83. fabric.util.array.fill(eventListener, false);
  84. }
  85. }
  86. /**
  87. * Observes specified event
  88. * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
  89. * @memberOf fabric.Observable
  90. * @alias on
  91. * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
  92. * @param {Function} handler Function that receives a notification when an event of the specified type occurs
  93. * @return {Self} thisArg
  94. * @chainable
  95. */
  96. function observe(eventName, handler) {
  97. if (!this.__eventListeners) {
  98. this.__eventListeners = { };
  99. }
  100. // one object with key/value pairs was passed
  101. if (arguments.length === 1) {
  102. for (var prop in eventName) {
  103. this.on(prop, eventName[prop]);
  104. }
  105. }
  106. else {
  107. if (!this.__eventListeners[eventName]) {
  108. this.__eventListeners[eventName] = [];
  109. }
  110. this.__eventListeners[eventName].push(handler);
  111. }
  112. return this;
  113. }
  114. /**
  115. * Stops event observing for a particular event handler. Calling this method
  116. * without arguments removes all handlers for all events
  117. * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
  118. * @memberOf fabric.Observable
  119. * @alias off
  120. * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
  121. * @param {Function} handler Function to be deleted from EventListeners
  122. * @return {Self} thisArg
  123. * @chainable
  124. */
  125. function stopObserving(eventName, handler) {
  126. if (!this.__eventListeners) {
  127. return;
  128. }
  129. // remove all key/value pairs (event name -> event handler)
  130. if (arguments.length === 0) {
  131. for (eventName in this.__eventListeners) {
  132. _removeEventListener.call(this, eventName);
  133. }
  134. }
  135. // one object with key/value pairs was passed
  136. else if (arguments.length === 1 && typeof arguments[0] === 'object') {
  137. for (var prop in eventName) {
  138. _removeEventListener.call(this, prop, eventName[prop]);
  139. }
  140. }
  141. else {
  142. _removeEventListener.call(this, eventName, handler);
  143. }
  144. return this;
  145. }
  146. /**
  147. * Fires event with an optional options object
  148. * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
  149. * @memberOf fabric.Observable
  150. * @alias trigger
  151. * @param {String} eventName Event name to fire
  152. * @param {Object} [options] Options object
  153. * @return {Self} thisArg
  154. * @chainable
  155. */
  156. function fire(eventName, options) {
  157. if (!this.__eventListeners) {
  158. return;
  159. }
  160. var listenersForEvent = this.__eventListeners[eventName];
  161. if (!listenersForEvent) {
  162. return;
  163. }
  164. for (var i = 0, len = listenersForEvent.length; i < len; i++) {
  165. listenersForEvent[i] && listenersForEvent[i].call(this, options || { });
  166. }
  167. this.__eventListeners[eventName] = listenersForEvent.filter(function(value) {
  168. return value !== false;
  169. });
  170. return this;
  171. }
  172. /**
  173. * @namespace fabric.Observable
  174. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events}
  175. * @see {@link http://fabricjs.com/events|Events demo}
  176. */
  177. fabric.Observable = {
  178. observe: observe,
  179. stopObserving: stopObserving,
  180. fire: fire,
  181. on: observe,
  182. off: stopObserving,
  183. trigger: fire
  184. };
  185. })();
  186. /**
  187. * @namespace fabric.Collection
  188. */
  189. fabric.Collection = {
  190. _objects: [],
  191. /**
  192. * Adds objects to collection, Canvas or Group, then renders canvas
  193. * (if `renderOnAddRemove` is not `false`).
  194. * in case of Group no changes to bounding box are made.
  195. * Objects should be instances of (or inherit from) fabric.Object
  196. * @param {...fabric.Object} object Zero or more fabric instances
  197. * @return {Self} thisArg
  198. * @chainable
  199. */
  200. add: function () {
  201. this._objects.push.apply(this._objects, arguments);
  202. if (this._onObjectAdded) {
  203. for (var i = 0, length = arguments.length; i < length; i++) {
  204. this._onObjectAdded(arguments[i]);
  205. }
  206. }
  207. this.renderOnAddRemove && this.renderAll();
  208. return this;
  209. },
  210. /**
  211. * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
  212. * An object should be an instance of (or inherit from) fabric.Object
  213. * @param {Object} object Object to insert
  214. * @param {Number} index Index to insert object at
  215. * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
  216. * @return {Self} thisArg
  217. * @chainable
  218. */
  219. insertAt: function (object, index, nonSplicing) {
  220. var objects = this.getObjects();
  221. if (nonSplicing) {
  222. objects[index] = object;
  223. }
  224. else {
  225. objects.splice(index, 0, object);
  226. }
  227. this._onObjectAdded && this._onObjectAdded(object);
  228. this.renderOnAddRemove && this.renderAll();
  229. return this;
  230. },
  231. /**
  232. * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
  233. * @param {...fabric.Object} object Zero or more fabric instances
  234. * @return {Self} thisArg
  235. * @chainable
  236. */
  237. remove: function() {
  238. var objects = this.getObjects(),
  239. index, somethingRemoved = false;
  240. for (var i = 0, length = arguments.length; i < length; i++) {
  241. index = objects.indexOf(arguments[i]);
  242. // only call onObjectRemoved if an object was actually removed
  243. if (index !== -1) {
  244. somethingRemoved = true;
  245. objects.splice(index, 1);
  246. this._onObjectRemoved && this._onObjectRemoved(arguments[i]);
  247. }
  248. }
  249. this.renderOnAddRemove && somethingRemoved && this.renderAll();
  250. return this;
  251. },
  252. /**
  253. * Executes given function for each object in this group
  254. * @param {Function} callback
  255. * Callback invoked with current object as first argument,
  256. * index - as second and an array of all objects - as third.
  257. * Callback is invoked in a context of Global Object (e.g. `window`)
  258. * when no `context` argument is given
  259. *
  260. * @param {Object} context Context (aka thisObject)
  261. * @return {Self} thisArg
  262. * @chainable
  263. */
  264. forEachObject: function(callback, context) {
  265. var objects = this.getObjects();
  266. for (var i = 0, len = objects.length; i < len; i++) {
  267. callback.call(context, objects[i], i, objects);
  268. }
  269. return this;
  270. },
  271. /**
  272. * Returns an array of children objects of this instance
  273. * Type parameter introduced in 1.3.10
  274. * @param {String} [type] When specified, only objects of this type are returned
  275. * @return {Array}
  276. */
  277. getObjects: function(type) {
  278. if (typeof type === 'undefined') {
  279. return this._objects;
  280. }
  281. return this._objects.filter(function(o) {
  282. return o.type === type;
  283. });
  284. },
  285. /**
  286. * Returns object at specified index
  287. * @param {Number} index
  288. * @return {Self} thisArg
  289. */
  290. item: function (index) {
  291. return this.getObjects()[index];
  292. },
  293. /**
  294. * Returns true if collection contains no objects
  295. * @return {Boolean} true if collection is empty
  296. */
  297. isEmpty: function () {
  298. return this.getObjects().length === 0;
  299. },
  300. /**
  301. * Returns a size of a collection (i.e: length of an array containing its objects)
  302. * @return {Number} Collection size
  303. */
  304. size: function() {
  305. return this.getObjects().length;
  306. },
  307. /**
  308. * Returns true if collection contains an object
  309. * @param {Object} object Object to check against
  310. * @return {Boolean} `true` if collection contains an object
  311. */
  312. contains: function(object) {
  313. return this.getObjects().indexOf(object) > -1;
  314. },
  315. /**
  316. * Returns number representation of a collection complexity
  317. * @return {Number} complexity
  318. */
  319. complexity: function () {
  320. return this.getObjects().reduce(function (memo, current) {
  321. memo += current.complexity ? current.complexity() : 0;
  322. return memo;
  323. }, 0);
  324. }
  325. };
  326. (function(global) {
  327. var sqrt = Math.sqrt,
  328. atan2 = Math.atan2,
  329. pow = Math.pow,
  330. abs = Math.abs,
  331. PiBy180 = Math.PI / 180;
  332. /**
  333. * @namespace fabric.util
  334. */
  335. fabric.util = {
  336. /**
  337. * Removes value from an array.
  338. * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
  339. * @static
  340. * @memberOf fabric.util
  341. * @param {Array} array
  342. * @param {*} value
  343. * @return {Array} original array
  344. */
  345. removeFromArray: function(array, value) {
  346. var idx = array.indexOf(value);
  347. if (idx !== -1) {
  348. array.splice(idx, 1);
  349. }
  350. return array;
  351. },
  352. /**
  353. * Returns random number between 2 specified ones.
  354. * @static
  355. * @memberOf fabric.util
  356. * @param {Number} min lower limit
  357. * @param {Number} max upper limit
  358. * @return {Number} random value (between min and max)
  359. */
  360. getRandomInt: function(min, max) {
  361. return Math.floor(Math.random() * (max - min + 1)) + min;
  362. },
  363. /**
  364. * Transforms degrees to radians.
  365. * @static
  366. * @memberOf fabric.util
  367. * @param {Number} degrees value in degrees
  368. * @return {Number} value in radians
  369. */
  370. degreesToRadians: function(degrees) {
  371. return degrees * PiBy180;
  372. },
  373. /**
  374. * Transforms radians to degrees.
  375. * @static
  376. * @memberOf fabric.util
  377. * @param {Number} radians value in radians
  378. * @return {Number} value in degrees
  379. */
  380. radiansToDegrees: function(radians) {
  381. return radians / PiBy180;
  382. },
  383. /**
  384. * Rotates `point` around `origin` with `radians`
  385. * @static
  386. * @memberOf fabric.util
  387. * @param {fabric.Point} point The point to rotate
  388. * @param {fabric.Point} origin The origin of the rotation
  389. * @param {Number} radians The radians of the angle for the rotation
  390. * @return {fabric.Point} The new rotated point
  391. */
  392. rotatePoint: function(point, origin, radians) {
  393. point.subtractEquals(origin);
  394. var v = fabric.util.rotateVector(point, radians);
  395. return new fabric.Point(v.x, v.y).addEquals(origin);
  396. },
  397. /**
  398. * Rotates `vector` with `radians`
  399. * @static
  400. * @memberOf fabric.util
  401. * @param {Object} vector The vector to rotate (x and y)
  402. * @param {Number} radians The radians of the angle for the rotation
  403. * @return {Object} The new rotated point
  404. */
  405. rotateVector: function(vector, radians) {
  406. var sin = Math.sin(radians),
  407. cos = Math.cos(radians),
  408. rx = vector.x * cos - vector.y * sin,
  409. ry = vector.x * sin + vector.y * cos;
  410. return {
  411. x: rx,
  412. y: ry
  413. };
  414. },
  415. /**
  416. * Apply transform t to point p
  417. * @static
  418. * @memberOf fabric.util
  419. * @param {fabric.Point} p The point to transform
  420. * @param {Array} t The transform
  421. * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
  422. * @return {fabric.Point} The transformed point
  423. */
  424. transformPoint: function(p, t, ignoreOffset) {
  425. if (ignoreOffset) {
  426. return new fabric.Point(
  427. t[0] * p.x + t[2] * p.y,
  428. t[1] * p.x + t[3] * p.y
  429. );
  430. }
  431. return new fabric.Point(
  432. t[0] * p.x + t[2] * p.y + t[4],
  433. t[1] * p.x + t[3] * p.y + t[5]
  434. );
  435. },
  436. /**
  437. * Returns coordinates of points's bounding rectangle (left, top, width, height)
  438. * @param {Array} points 4 points array
  439. * @return {Object} Object with left, top, width, height properties
  440. */
  441. makeBoundingBoxFromPoints: function(points) {
  442. var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x],
  443. minX = fabric.util.array.min(xPoints),
  444. maxX = fabric.util.array.max(xPoints),
  445. width = Math.abs(minX - maxX),
  446. yPoints = [points[0].y, points[1].y, points[2].y, points[3].y],
  447. minY = fabric.util.array.min(yPoints),
  448. maxY = fabric.util.array.max(yPoints),
  449. height = Math.abs(minY - maxY);
  450. return {
  451. left: minX,
  452. top: minY,
  453. width: width,
  454. height: height
  455. };
  456. },
  457. /**
  458. * Invert transformation t
  459. * @static
  460. * @memberOf fabric.util
  461. * @param {Array} t The transform
  462. * @return {Array} The inverted transform
  463. */
  464. invertTransform: function(t) {
  465. var a = 1 / (t[0] * t[3] - t[1] * t[2]),
  466. r = [a * t[3], -a * t[1], -a * t[2], a * t[0]],
  467. o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true);
  468. r[4] = -o.x;
  469. r[5] = -o.y;
  470. return r;
  471. },
  472. /**
  473. * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
  474. * @static
  475. * @memberOf fabric.util
  476. * @param {Number|String} number number to operate on
  477. * @param {Number} fractionDigits number of fraction digits to "leave"
  478. * @return {Number}
  479. */
  480. toFixed: function(number, fractionDigits) {
  481. return parseFloat(Number(number).toFixed(fractionDigits));
  482. },
  483. /**
  484. * Converts from attribute value to pixel value if applicable.
  485. * Returns converted pixels or original value not converted.
  486. * @param {Number|String} value number to operate on
  487. * @param {Number} fontSize
  488. * @return {Number|String}
  489. */
  490. parseUnit: function(value, fontSize) {
  491. var unit = /\D{0,2}$/.exec(value),
  492. number = parseFloat(value);
  493. if (!fontSize) {
  494. fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
  495. }
  496. switch (unit[0]) {
  497. case 'mm':
  498. return number * fabric.DPI / 25.4;
  499. case 'cm':
  500. return number * fabric.DPI / 2.54;
  501. case 'in':
  502. return number * fabric.DPI;
  503. case 'pt':
  504. return number * fabric.DPI / 72; // or * 4 / 3
  505. case 'pc':
  506. return number * fabric.DPI / 72 * 12; // or * 16
  507. case 'em':
  508. return number * fontSize;
  509. default:
  510. return number;
  511. }
  512. },
  513. /**
  514. * Function which always returns `false`.
  515. * @static
  516. * @memberOf fabric.util
  517. * @return {Boolean}
  518. */
  519. falseFunction: function() {
  520. return false;
  521. },
  522. /**
  523. * Returns klass "Class" object of given namespace
  524. * @memberOf fabric.util
  525. * @param {String} type Type of object (eg. 'circle')
  526. * @param {String} namespace Namespace to get klass "Class" object from
  527. * @return {Object} klass "Class"
  528. */
  529. getKlass: function(type, namespace) {
  530. // capitalize first letter only
  531. type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
  532. return fabric.util.resolveNamespace(namespace)[type];
  533. },
  534. /**
  535. * Returns object of given namespace
  536. * @memberOf fabric.util
  537. * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
  538. * @return {Object} Object for given namespace (default fabric)
  539. */
  540. resolveNamespace: function(namespace) {
  541. if (!namespace) {
  542. return fabric;
  543. }
  544. var parts = namespace.split('.'),
  545. len = parts.length, i,
  546. obj = global || fabric.window;
  547. for (i = 0; i < len; ++i) {
  548. obj = obj[parts[i]];
  549. }
  550. return obj;
  551. },
  552. /**
  553. * Loads image element from given url and passes it to a callback
  554. * @memberOf fabric.util
  555. * @param {String} url URL representing an image
  556. * @param {Function} callback Callback; invoked with loaded image
  557. * @param {*} [context] Context to invoke callback in
  558. * @param {Object} [crossOrigin] crossOrigin value to set image element to
  559. */
  560. loadImage: function(url, callback, context, crossOrigin) {
  561. if (!url) {
  562. callback && callback.call(context, url);
  563. return;
  564. }
  565. var img = fabric.util.createImage();
  566. /** @ignore */
  567. img.onload = function () {
  568. callback && callback.call(context, img);
  569. img = img.onload = img.onerror = null;
  570. };
  571. /** @ignore */
  572. img.onerror = function() {
  573. fabric.log('Error loading ' + img.src);
  574. callback && callback.call(context, null, true);
  575. img = img.onload = img.onerror = null;
  576. };
  577. // data-urls appear to be buggy with crossOrigin
  578. // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
  579. // see https://code.google.com/p/chromium/issues/detail?id=315152
  580. // https://bugzilla.mozilla.org/show_bug.cgi?id=935069
  581. if (url.indexOf('data') !== 0 && crossOrigin) {
  582. img.crossOrigin = crossOrigin;
  583. }
  584. img.src = url;
  585. },
  586. /**
  587. * Creates corresponding fabric instances from their object representations
  588. * @static
  589. * @memberOf fabric.util
  590. * @param {Array} objects Objects to enliven
  591. * @param {Function} callback Callback to invoke when all objects are created
  592. * @param {String} namespace Namespace to get klass "Class" object from
  593. * @param {Function} reviver Method for further parsing of object elements,
  594. * called after each fabric object created.
  595. */
  596. enlivenObjects: function(objects, callback, namespace, reviver) {
  597. objects = objects || [];
  598. function onLoaded() {
  599. if (++numLoadedObjects === numTotalObjects) {
  600. callback && callback(enlivenedObjects);
  601. }
  602. }
  603. var enlivenedObjects = [],
  604. numLoadedObjects = 0,
  605. numTotalObjects = objects.length;
  606. if (!numTotalObjects) {
  607. callback && callback(enlivenedObjects);
  608. return;
  609. }
  610. objects.forEach(function (o, index) {
  611. // if sparse array
  612. if (!o || !o.type) {
  613. onLoaded();
  614. return;
  615. }
  616. var klass = fabric.util.getKlass(o.type, namespace);
  617. if (klass.async) {
  618. klass.fromObject(o, function (obj, error) {
  619. if (!error) {
  620. enlivenedObjects[index] = obj;
  621. reviver && reviver(o, enlivenedObjects[index]);
  622. }
  623. onLoaded();
  624. });
  625. }
  626. else {
  627. enlivenedObjects[index] = klass.fromObject(o);
  628. reviver && reviver(o, enlivenedObjects[index]);
  629. onLoaded();
  630. }
  631. });
  632. },
  633. /**
  634. * Groups SVG elements (usually those retrieved from SVG document)
  635. * @static
  636. * @memberOf fabric.util
  637. * @param {Array} elements SVG elements to group
  638. * @param {Object} [options] Options object
  639. * @param {String} path Value to set sourcePath to
  640. * @return {fabric.Object|fabric.PathGroup}
  641. */
  642. groupSVGElements: function(elements, options, path) {
  643. var object;
  644. object = new fabric.PathGroup(elements, options);
  645. if (typeof path !== 'undefined') {
  646. object.setSourcePath(path);
  647. }
  648. return object;
  649. },
  650. /**
  651. * Populates an object with properties of another object
  652. * @static
  653. * @memberOf fabric.util
  654. * @param {Object} source Source object
  655. * @param {Object} destination Destination object
  656. * @return {Array} properties Propertie names to include
  657. */
  658. populateWithProperties: function(source, destination, properties) {
  659. if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
  660. for (var i = 0, len = properties.length; i < len; i++) {
  661. if (properties[i] in source) {
  662. destination[properties[i]] = source[properties[i]];
  663. }
  664. }
  665. }
  666. },
  667. /**
  668. * Draws a dashed line between two points
  669. *
  670. * This method is used to draw dashed line around selection area.
  671. * See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
  672. *
  673. * @param {CanvasRenderingContext2D} ctx context
  674. * @param {Number} x start x coordinate
  675. * @param {Number} y start y coordinate
  676. * @param {Number} x2 end x coordinate
  677. * @param {Number} y2 end y coordinate
  678. * @param {Array} da dash array pattern
  679. */
  680. drawDashedLine: function(ctx, x, y, x2, y2, da) {
  681. var dx = x2 - x,
  682. dy = y2 - y,
  683. len = sqrt(dx * dx + dy * dy),
  684. rot = atan2(dy, dx),
  685. dc = da.length,
  686. di = 0,
  687. draw = true;
  688. ctx.save();
  689. ctx.translate(x, y);
  690. ctx.moveTo(0, 0);
  691. ctx.rotate(rot);
  692. x = 0;
  693. while (len > x) {
  694. x += da[di++ % dc];
  695. if (x > len) {
  696. x = len;
  697. }
  698. ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
  699. draw = !draw;
  700. }
  701. ctx.restore();
  702. },
  703. /**
  704. * Creates canvas element and initializes it via excanvas if necessary
  705. * @static
  706. * @memberOf fabric.util
  707. * @param {CanvasElement} [canvasEl] optional canvas element to initialize;
  708. * when not given, element is created implicitly
  709. * @return {CanvasElement} initialized canvas element
  710. */
  711. createCanvasElement: function(canvasEl) {
  712. canvasEl || (canvasEl = fabric.document.createElement('canvas'));
  713. /* eslint-disable camelcase */
  714. if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') {
  715. G_vmlCanvasManager.initElement(canvasEl);
  716. }
  717. /* eslint-enable camelcase */
  718. return canvasEl;
  719. },
  720. /**
  721. * Creates image element (works on client and node)
  722. * @static
  723. * @memberOf fabric.util
  724. * @return {HTMLImageElement} HTML image element
  725. */
  726. createImage: function() {
  727. return fabric.isLikelyNode
  728. ? new (require('canvas').Image)()
  729. : fabric.document.createElement('img');
  730. },
  731. /**
  732. * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array
  733. * @static
  734. * @memberOf fabric.util
  735. * @param {Object} klass "Class" to create accessors for
  736. */
  737. createAccessors: function(klass) {
  738. var proto = klass.prototype, i, propName,
  739. capitalizedPropName, setterName, getterName;
  740. for (i = proto.stateProperties.length; i--; ) {
  741. propName = proto.stateProperties[i];
  742. capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
  743. setterName = 'set' + capitalizedPropName;
  744. getterName = 'get' + capitalizedPropName;
  745. // using `new Function` for better introspection
  746. if (!proto[getterName]) {
  747. proto[getterName] = (function(property) {
  748. return new Function('return this.get("' + property + '")');
  749. })(propName);
  750. }
  751. if (!proto[setterName]) {
  752. proto[setterName] = (function(property) {
  753. return new Function('value', 'return this.set("' + property + '", value)');
  754. })(propName);
  755. }
  756. }
  757. },
  758. /**
  759. * @static
  760. * @memberOf fabric.util
  761. * @param {fabric.Object} receiver Object implementing `clipTo` method
  762. * @param {CanvasRenderingContext2D} ctx Context to clip
  763. */
  764. clipContext: function(receiver, ctx) {
  765. ctx.save();
  766. ctx.beginPath();
  767. receiver.clipTo(ctx);
  768. ctx.clip();
  769. },
  770. /**
  771. * Multiply matrix A by matrix B to nest transformations
  772. * @static
  773. * @memberOf fabric.util
  774. * @param {Array} a First transformMatrix
  775. * @param {Array} b Second transformMatrix
  776. * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices
  777. * @return {Array} The product of the two transform matrices
  778. */
  779. multiplyTransformMatrices: function(a, b, is2x2) {
  780. // Matrix multiply a * b
  781. return [
  782. a[0] * b[0] + a[2] * b[1],
  783. a[1] * b[0] + a[3] * b[1],
  784. a[0] * b[2] + a[2] * b[3],
  785. a[1] * b[2] + a[3] * b[3],
  786. is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
  787. is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
  788. ];
  789. },
  790. /**
  791. * Decomposes standard 2x2 matrix into transform componentes
  792. * @static
  793. * @memberOf fabric.util
  794. * @param {Array} a transformMatrix
  795. * @return {Object} Components of transform
  796. */
  797. qrDecompose: function(a) {
  798. var angle = atan2(a[1], a[0]),
  799. denom = pow(a[0], 2) + pow(a[1], 2),
  800. scaleX = sqrt(denom),
  801. scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX,
  802. skewX = atan2(a[0] * a[2] + a[1] * a [3], denom);
  803. return {
  804. angle: angle / PiBy180,
  805. scaleX: scaleX,
  806. scaleY: scaleY,
  807. skewX: skewX / PiBy180,
  808. skewY: 0,
  809. translateX: a[4],
  810. translateY: a[5]
  811. };
  812. },
  813. customTransformMatrix: function(scaleX, scaleY, skewX) {
  814. var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1],
  815. scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)];
  816. return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true);
  817. },
  818. resetObjectTransform: function (target) {
  819. target.scaleX = 1;
  820. target.scaleY = 1;
  821. target.skewX = 0;
  822. target.skewY = 0;
  823. target.flipX = false;
  824. target.flipY = false;
  825. target.setAngle(0);
  826. },
  827. /**
  828. * Returns string representation of function body
  829. * @param {Function} fn Function to get body of
  830. * @return {String} Function body
  831. */
  832. getFunctionBody: function(fn) {
  833. return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
  834. },
  835. /**
  836. * Returns true if context has transparent pixel
  837. * at specified location (taking tolerance into account)
  838. * @param {CanvasRenderingContext2D} ctx context
  839. * @param {Number} x x coordinate
  840. * @param {Number} y y coordinate
  841. * @param {Number} tolerance Tolerance
  842. */
  843. isTransparent: function(ctx, x, y, tolerance) {
  844. // If tolerance is > 0 adjust start coords to take into account.
  845. // If moves off Canvas fix to 0
  846. if (tolerance > 0) {
  847. if (x > tolerance) {
  848. x -= tolerance;
  849. }
  850. else {
  851. x = 0;
  852. }
  853. if (y > tolerance) {
  854. y -= tolerance;
  855. }
  856. else {
  857. y = 0;
  858. }
  859. }
  860. var _isTransparent = true, i, temp,
  861. imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1),
  862. l = imageData.data.length;
  863. // Split image data - for tolerance > 1, pixelDataSize = 4;
  864. for (i = 3; i < l; i += 4) {
  865. temp = imageData.data[i];
  866. _isTransparent = temp <= 0;
  867. if (_isTransparent === false) {
  868. break; // Stop if colour found
  869. }
  870. }
  871. imageData = null;
  872. return _isTransparent;
  873. },
  874. /**
  875. * Parse preserveAspectRatio attribute from element
  876. * @param {string} attribute to be parsed
  877. * @return {Object} an object containing align and meetOrSlice attribute
  878. */
  879. parsePreserveAspectRatioAttribute: function(attribute) {
  880. var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid',
  881. aspectRatioAttrs = attribute.split(' '), align;
  882. if (aspectRatioAttrs && aspectRatioAttrs.length) {
  883. meetOrSlice = aspectRatioAttrs.pop();
  884. if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
  885. align = meetOrSlice;
  886. meetOrSlice = 'meet';
  887. }
  888. else if (aspectRatioAttrs.length) {
  889. align = aspectRatioAttrs.pop();
  890. }
  891. }
  892. //divide align in alignX and alignY
  893. alignX = align !== 'none' ? align.slice(1, 4) : 'none';
  894. alignY = align !== 'none' ? align.slice(5, 8) : 'none';
  895. return {
  896. meetOrSlice: meetOrSlice,
  897. alignX: alignX,
  898. alignY: alignY
  899. };
  900. },
  901. /**
  902. * Clear char widths cache for a font family.
  903. * @memberOf fabric.util
  904. * @param {String} [fontFamily] font family to clear
  905. */
  906. clearFabricFontCache: function(fontFamily) {
  907. if (!fontFamily) {
  908. fabric.charWidthsCache = { };
  909. }
  910. else if (fabric.charWidthsCache[fontFamily]) {
  911. delete fabric.charWidthsCache[fontFamily];
  912. }
  913. }
  914. };
  915. })(typeof exports !== 'undefined' ? exports : this);
  916. (function() {
  917. var arcToSegmentsCache = { },
  918. segmentToBezierCache = { },
  919. boundsOfCurveCache = { },
  920. _join = Array.prototype.join;
  921. /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
  922. * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
  923. * http://mozilla.org/MPL/2.0/
  924. */
  925. function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
  926. var argsString = _join.call(arguments);
  927. if (arcToSegmentsCache[argsString]) {
  928. return arcToSegmentsCache[argsString];
  929. }
  930. var PI = Math.PI, th = rotateX * PI / 180,
  931. sinTh = Math.sin(th),
  932. cosTh = Math.cos(th),
  933. fromX = 0, fromY = 0;
  934. rx = Math.abs(rx);
  935. ry = Math.abs(ry);
  936. var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
  937. py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
  938. rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
  939. pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
  940. root = 0;
  941. if (pl < 0) {
  942. var s = Math.sqrt(1 - pl / (rx2 * ry2));
  943. rx *= s;
  944. ry *= s;
  945. }
  946. else {
  947. root = (large === sweep ? -1.0 : 1.0) *
  948. Math.sqrt( pl / (rx2 * py2 + ry2 * px2));
  949. }
  950. var cx = root * rx * py / ry,
  951. cy = -root * ry * px / rx,
  952. cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
  953. cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
  954. mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
  955. dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
  956. if (sweep === 0 && dtheta > 0) {
  957. dtheta -= 2 * PI;
  958. }
  959. else if (sweep === 1 && dtheta < 0) {
  960. dtheta += 2 * PI;
  961. }
  962. // Convert into cubic bezier segments <= 90deg
  963. var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
  964. result = [], mDelta = dtheta / segments,
  965. mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
  966. th3 = mTheta + mDelta;
  967. for (var i = 0; i < segments; i++) {
  968. result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
  969. fromX = result[i][4];
  970. fromY = result[i][5];
  971. mTheta = th3;
  972. th3 += mDelta;
  973. }
  974. arcToSegmentsCache[argsString] = result;
  975. return result;
  976. }
  977. function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
  978. var argsString2 = _join.call(arguments);
  979. if (segmentToBezierCache[argsString2]) {
  980. return segmentToBezierCache[argsString2];
  981. }
  982. var costh2 = Math.cos(th2),
  983. sinth2 = Math.sin(th2),
  984. costh3 = Math.cos(th3),
  985. sinth3 = Math.sin(th3),
  986. toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
  987. toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
  988. cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2),
  989. cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2),
  990. cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
  991. cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
  992. segmentToBezierCache[argsString2] = [
  993. cp1X, cp1Y,
  994. cp2X, cp2Y,
  995. toX, toY
  996. ];
  997. return segmentToBezierCache[argsString2];
  998. }
  999. /*
  1000. * Private
  1001. */
  1002. function calcVectorAngle(ux, uy, vx, vy) {
  1003. var ta = Math.atan2(uy, ux),
  1004. tb = Math.atan2(vy, vx);
  1005. if (tb >= ta) {
  1006. return tb - ta;
  1007. }
  1008. else {
  1009. return 2 * Math.PI - (ta - tb);
  1010. }
  1011. }
  1012. /**
  1013. * Draws arc
  1014. * @param {CanvasRenderingContext2D} ctx
  1015. * @param {Number} fx
  1016. * @param {Number} fy
  1017. * @param {Array} coords
  1018. */
  1019. fabric.util.drawArc = function(ctx, fx, fy, coords) {
  1020. var rx = coords[0],
  1021. ry = coords[1],
  1022. rot = coords[2],
  1023. large = coords[3],
  1024. sweep = coords[4],
  1025. tx = coords[5],
  1026. ty = coords[6],
  1027. segs = [[], [], [], []],
  1028. segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
  1029. for (var i = 0, len = segsNorm.length; i < len; i++) {
  1030. segs[i][0] = segsNorm[i][0] + fx;
  1031. segs[i][1] = segsNorm[i][1] + fy;
  1032. segs[i][2] = segsNorm[i][2] + fx;
  1033. segs[i][3] = segsNorm[i][3] + fy;
  1034. segs[i][4] = segsNorm[i][4] + fx;
  1035. segs[i][5] = segsNorm[i][5] + fy;
  1036. ctx.bezierCurveTo.apply(ctx, segs[i]);
  1037. }
  1038. };
  1039. /**
  1040. * Calculate bounding box of a elliptic-arc
  1041. * @param {Number} fx start point of arc
  1042. * @param {Number} fy
  1043. * @param {Number} rx horizontal radius
  1044. * @param {Number} ry vertical radius
  1045. * @param {Number} rot angle of horizontal axe
  1046. * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
  1047. * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
  1048. * @param {Number} tx end point of arc
  1049. * @param {Number} ty
  1050. */
  1051. fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
  1052. var fromX = 0, fromY = 0, bound, bounds = [],
  1053. segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
  1054. for (var i = 0, len = segs.length; i < len; i++) {
  1055. bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
  1056. bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
  1057. bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
  1058. fromX = segs[i][4];
  1059. fromY = segs[i][5];
  1060. }
  1061. return bounds;
  1062. };
  1063. /**
  1064. * Calculate bounding box of a beziercurve
  1065. * @param {Number} x0 starting point
  1066. * @param {Number} y0
  1067. * @param {Number} x1 first control point
  1068. * @param {Number} y1
  1069. * @param {Number} x2 secondo control point
  1070. * @param {Number} y2
  1071. * @param {Number} x3 end of beizer
  1072. * @param {Number} y3
  1073. */
  1074. // taken from http://jsbin.com/ivomiq/56/edit no credits available for that.
  1075. function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
  1076. var argsString = _join.call(arguments);
  1077. if (boundsOfCurveCache[argsString]) {
  1078. return boundsOfCurveCache[argsString];
  1079. }
  1080. var sqrt = Math.sqrt,
  1081. min = Math.min, max = Math.max,
  1082. abs = Math.abs, tvalues = [],
  1083. bounds = [[], []],
  1084. a, b, c, t, t1, t2, b2ac, sqrtb2ac;
  1085. b = 6 * x0 - 12 * x1 + 6 * x2;
  1086. a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
  1087. c = 3 * x1 - 3 * x0;
  1088. for (var i = 0; i < 2; ++i) {
  1089. if (i > 0) {
  1090. b = 6 * y0 - 12 * y1 + 6 * y2;
  1091. a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
  1092. c = 3 * y1 - 3 * y0;
  1093. }
  1094. if (abs(a) < 1e-12) {
  1095. if (abs(b) < 1e-12) {
  1096. continue;
  1097. }
  1098. t = -c / b;
  1099. if (0 < t && t < 1) {
  1100. tvalues.push(t);
  1101. }
  1102. continue;
  1103. }
  1104. b2ac = b * b - 4 * c * a;
  1105. if (b2ac < 0) {
  1106. continue;
  1107. }
  1108. sqrtb2ac = sqrt(b2ac);
  1109. t1 = (-b + sqrtb2ac) / (2 * a);
  1110. if (0 < t1 && t1 < 1) {
  1111. tvalues.push(t1);
  1112. }
  1113. t2 = (-b - sqrtb2ac) / (2 * a);
  1114. if (0 < t2 && t2 < 1) {
  1115. tvalues.push(t2);
  1116. }
  1117. }
  1118. var x, y, j = tvalues.length, jlen = j, mt;
  1119. while (j--) {
  1120. t = tvalues[j];
  1121. mt = 1 - t;
  1122. x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
  1123. bounds[0][j] = x;
  1124. y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
  1125. bounds[1][j] = y;
  1126. }
  1127. bounds[0][jlen] = x0;
  1128. bounds[1][jlen] = y0;
  1129. bounds[0][jlen + 1] = x3;
  1130. bounds[1][jlen + 1] = y3;
  1131. var result = [
  1132. {
  1133. x: min.apply(null, bounds[0]),
  1134. y: min.apply(null, bounds[1])
  1135. },
  1136. {
  1137. x: max.apply(null, bounds[0]),
  1138. y: max.apply(null, bounds[1])
  1139. }
  1140. ];
  1141. boundsOfCurveCache[argsString] = result;
  1142. return result;
  1143. }
  1144. fabric.util.getBoundsOfCurve = getBoundsOfCurve;
  1145. })();
  1146. (function() {
  1147. var slice = Array.prototype.slice;
  1148. /* _ES5_COMPAT_START_ */
  1149. if (!Array.prototype.indexOf) {
  1150. /**
  1151. * Finds index of an element in an array
  1152. * @param {*} searchElement
  1153. * @return {Number}
  1154. */
  1155. Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
  1156. if (this === void 0 || this === null) {
  1157. throw new TypeError();
  1158. }
  1159. var t = Object(this), len = t.length >>> 0;
  1160. if (len === 0) {
  1161. return -1;
  1162. }
  1163. var n = 0;
  1164. if (arguments.length > 0) {
  1165. n = Number(arguments[1]);
  1166. if (n !== n) { // shortcut for verifying if it's NaN
  1167. n = 0;
  1168. }
  1169. else if (n !== 0 && n !== Number.POSITIVE_INFINITY && n !== Number.NEGATIVE_INFINITY) {
  1170. n = (n > 0 || -1) * Math.floor(Math.abs(n));
  1171. }
  1172. }
  1173. if (n >= len) {
  1174. return -1;
  1175. }
  1176. var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
  1177. for (; k < len; k++) {
  1178. if (k in t && t[k] === searchElement) {
  1179. return k;
  1180. }
  1181. }
  1182. return -1;
  1183. };
  1184. }
  1185. if (!Array.prototype.forEach) {
  1186. /**
  1187. * Iterates an array, invoking callback for each element
  1188. * @param {Function} fn Callback to invoke for each element
  1189. * @param {Object} [context] Context to invoke callback in
  1190. * @return {Array}
  1191. */
  1192. Array.prototype.forEach = function(fn, context) {
  1193. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1194. if (i in this) {
  1195. fn.call(context, this[i], i, this);
  1196. }
  1197. }
  1198. };
  1199. }
  1200. if (!Array.prototype.map) {
  1201. /**
  1202. * Returns a result of iterating over an array, invoking callback for each element
  1203. * @param {Function} fn Callback to invoke for each element
  1204. * @param {Object} [context] Context to invoke callback in
  1205. * @return {Array}
  1206. */
  1207. Array.prototype.map = function(fn, context) {
  1208. var result = [];
  1209. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1210. if (i in this) {
  1211. result[i] = fn.call(context, this[i], i, this);
  1212. }
  1213. }
  1214. return result;
  1215. };
  1216. }
  1217. if (!Array.prototype.every) {
  1218. /**
  1219. * Returns true if a callback returns truthy value for all elements in an array
  1220. * @param {Function} fn Callback to invoke for each element
  1221. * @param {Object} [context] Context to invoke callback in
  1222. * @return {Boolean}
  1223. */
  1224. Array.prototype.every = function(fn, context) {
  1225. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1226. if (i in this && !fn.call(context, this[i], i, this)) {
  1227. return false;
  1228. }
  1229. }
  1230. return true;
  1231. };
  1232. }
  1233. if (!Array.prototype.some) {
  1234. /**
  1235. * Returns true if a callback returns truthy value for at least one element in an array
  1236. * @param {Function} fn Callback to invoke for each element
  1237. * @param {Object} [context] Context to invoke callback in
  1238. * @return {Boolean}
  1239. */
  1240. Array.prototype.some = function(fn, context) {
  1241. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1242. if (i in this && fn.call(context, this[i], i, this)) {
  1243. return true;
  1244. }
  1245. }
  1246. return false;
  1247. };
  1248. }
  1249. if (!Array.prototype.filter) {
  1250. /**
  1251. * Returns the result of iterating over elements in an array
  1252. * @param {Function} fn Callback to invoke for each element
  1253. * @param {Object} [context] Context to invoke callback in
  1254. * @return {Array}
  1255. */
  1256. Array.prototype.filter = function(fn, context) {
  1257. var result = [], val;
  1258. for (var i = 0, len = this.length >>> 0; i < len; i++) {
  1259. if (i in this) {
  1260. val = this[i]; // in case fn mutates this
  1261. if (fn.call(context, val, i, this)) {
  1262. result.push(val);
  1263. }
  1264. }
  1265. }
  1266. return result;
  1267. };
  1268. }
  1269. if (!Array.prototype.reduce) {
  1270. /**
  1271. * Returns "folded" (reduced) result of iterating over elements in an array
  1272. * @param {Function} fn Callback to invoke for each element
  1273. * @return {*}
  1274. */
  1275. Array.prototype.reduce = function(fn /*, initial*/) {
  1276. var len = this.length >>> 0,
  1277. i = 0,
  1278. rv;
  1279. if (arguments.length > 1) {
  1280. rv = arguments[1];
  1281. }
  1282. else {
  1283. do {
  1284. if (i in this) {
  1285. rv = this[i++];
  1286. break;
  1287. }
  1288. // if array contains no values, no initial value to return
  1289. if (++i >= len) {
  1290. throw new TypeError();
  1291. }
  1292. }
  1293. while (true);
  1294. }
  1295. for (; i < len; i++) {
  1296. if (i in this) {
  1297. rv = fn.call(null, rv, this[i], i, this);
  1298. }
  1299. }
  1300. return rv;
  1301. };
  1302. }
  1303. /* _ES5_COMPAT_END_ */
  1304. /**
  1305. * Invokes method on all items in a given array
  1306. * @memberOf fabric.util.array
  1307. * @param {Array} array Array to iterate over
  1308. * @param {String} method Name of a method to invoke
  1309. * @return {Array}
  1310. */
  1311. function invoke(array, method) {
  1312. var args = slice.call(arguments, 2), result = [];
  1313. for (var i = 0, len = array.length; i < len; i++) {
  1314. result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
  1315. }
  1316. return result;
  1317. }
  1318. /**
  1319. * Finds maximum value in array (not necessarily "first" one)
  1320. * @memberOf fabric.util.array
  1321. * @param {Array} array Array to iterate over
  1322. * @param {String} byProperty
  1323. * @return {*}
  1324. */
  1325. function max(array, byProperty) {
  1326. return find(array, byProperty, function(value1, value2) {
  1327. return value1 >= value2;
  1328. });
  1329. }
  1330. /**
  1331. * Finds minimum value in array (not necessarily "first" one)
  1332. * @memberOf fabric.util.array
  1333. * @param {Array} array Array to iterate over
  1334. * @param {String} byProperty
  1335. * @return {*}
  1336. */
  1337. function min(array, byProperty) {
  1338. return find(array, byProperty, function(value1, value2) {
  1339. return value1 < value2;
  1340. });
  1341. }
  1342. /**
  1343. * @private
  1344. */
  1345. function fill(array, value) {
  1346. var k = array.length;
  1347. while (k--) {
  1348. array[k] = value;
  1349. }
  1350. return array;
  1351. }
  1352. /**
  1353. * @private
  1354. */
  1355. function find(array, byProperty, condition) {
  1356. if (!array || array.length === 0) {
  1357. return;
  1358. }
  1359. var i = array.length - 1,
  1360. result = byProperty ? array[i][byProperty] : array[i];
  1361. if (byProperty) {
  1362. while (i--) {
  1363. if (condition(array[i][byProperty], result)) {
  1364. result = array[i][byProperty];
  1365. }
  1366. }
  1367. }
  1368. else {
  1369. while (i--) {
  1370. if (condition(array[i], result)) {
  1371. result = array[i];
  1372. }
  1373. }
  1374. }
  1375. return result;
  1376. }
  1377. /**
  1378. * @namespace fabric.util.array
  1379. */
  1380. fabric.util.array = {
  1381. fill: fill,
  1382. invoke: invoke,
  1383. min: min,
  1384. max: max
  1385. };
  1386. })();
  1387. (function() {
  1388. /**
  1389. * Copies all enumerable properties of one object to another
  1390. * @memberOf fabric.util.object
  1391. * @param {Object} destination Where to copy to
  1392. * @param {Object} source Where to copy from
  1393. * @return {Object}
  1394. */
  1395. function extend(destination, source, deep) {
  1396. // JScript DontEnum bug is not taken care of
  1397. // the deep clone is for internal use, is not meant to avoid
  1398. // javascript traps or cloning html element or self referenced objects.
  1399. if (deep) {
  1400. if (!fabric.isLikelyNode && source instanceof Element) {
  1401. // avoid cloning deep images, canvases,
  1402. destination = source;
  1403. }
  1404. else if (source instanceof Array) {
  1405. destination = source.map(function(v) {
  1406. return clone(v, deep)
  1407. })
  1408. }
  1409. else if (source instanceof Object) {
  1410. for (var property in source) {
  1411. destination[property] = clone(source[property], deep)
  1412. }
  1413. }
  1414. else {
  1415. // this sounds odd for an extend but is ok for recursive use
  1416. destination = source;
  1417. }
  1418. }
  1419. else {
  1420. for (var property in source) {
  1421. destination[property] = source[property];
  1422. }
  1423. }
  1424. return destination;
  1425. }
  1426. /**
  1427. * Creates an empty object and copies all enumerable properties of another object to it
  1428. * @memberOf fabric.util.object
  1429. * @param {Object} object Object to clone
  1430. * @return {Object}
  1431. */
  1432. function clone(object, deep) {
  1433. return extend({ }, object, deep);
  1434. }
  1435. /** @namespace fabric.util.object */
  1436. fabric.util.object = {
  1437. extend: extend,
  1438. clone: clone
  1439. };
  1440. })();
  1441. (function() {
  1442. /* _ES5_COMPAT_START_ */
  1443. if (!String.prototype.trim) {
  1444. /**
  1445. * Trims a string (removing whitespace from the beginning and the end)
  1446. * @function external:String#trim
  1447. * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
  1448. */
  1449. String.prototype.trim = function () {
  1450. // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
  1451. return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
  1452. };
  1453. }
  1454. /* _ES5_COMPAT_END_ */
  1455. /**
  1456. * Camelizes a string
  1457. * @memberOf fabric.util.string
  1458. * @param {String} string String to camelize
  1459. * @return {String} Camelized version of a string
  1460. */
  1461. function camelize(string) {
  1462. return string.replace(/-+(.)?/g, function(match, character) {
  1463. return character ? character.toUpperCase() : '';
  1464. });
  1465. }
  1466. /**
  1467. * Capitalizes a string
  1468. * @memberOf fabric.util.string
  1469. * @param {String} string String to capitalize
  1470. * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
  1471. * and other letters stay untouched, if false first letter is capitalized
  1472. * and other letters are converted to lowercase.
  1473. * @return {String} Capitalized version of a string
  1474. */
  1475. function capitalize(string, firstLetterOnly) {
  1476. return string.charAt(0).toUpperCase() +
  1477. (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
  1478. }
  1479. /**
  1480. * Escapes XML in a string
  1481. * @memberOf fabric.util.string
  1482. * @param {String} string String to escape
  1483. * @return {String} Escaped version of a string
  1484. */
  1485. function escapeXml(string) {
  1486. return string.replace(/&/g, '&amp;')
  1487. .replace(/"/g, '&quot;')
  1488. .replace(/'/g, '&apos;')
  1489. .replace(/</g, '&lt;')
  1490. .replace(/>/g, '&gt;');
  1491. }
  1492. /**
  1493. * String utilities
  1494. * @namespace fabric.util.string
  1495. */
  1496. fabric.util.string = {
  1497. camelize: camelize,
  1498. capitalize: capitalize,
  1499. escapeXml: escapeXml
  1500. };
  1501. })();
  1502. /* _ES5_COMPAT_START_ */
  1503. (function() {
  1504. var slice = Array.prototype.slice,
  1505. apply = Function.prototype.apply,
  1506. Dummy = function() { };
  1507. if (!Function.prototype.bind) {
  1508. /**
  1509. * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
  1510. * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a>
  1511. * @param {Object} thisArg Object to bind function to
  1512. * @param {Any[]} Values to pass to a bound function
  1513. * @return {Function}
  1514. */
  1515. Function.prototype.bind = function(thisArg) {
  1516. var _this = this, args = slice.call(arguments, 1), bound;
  1517. if (args.length) {
  1518. bound = function() {
  1519. return apply.call(_this, this instanceof Dummy ? this : thisArg, args.concat(slice.call(arguments)));
  1520. };
  1521. }
  1522. else {
  1523. /** @ignore */
  1524. bound = function() {
  1525. return apply.call(_this, this instanceof Dummy ? this : thisArg, arguments);
  1526. };
  1527. }
  1528. Dummy.prototype = this.prototype;
  1529. bound.prototype = new Dummy();
  1530. return bound;
  1531. };
  1532. }
  1533. })();
  1534. /* _ES5_COMPAT_END_ */
  1535. (function() {
  1536. var slice = Array.prototype.slice, emptyFunction = function() { },
  1537. IS_DONTENUM_BUGGY = (function() {
  1538. for (var p in { toString: 1 }) {
  1539. if (p === 'toString') {
  1540. return false;
  1541. }
  1542. }
  1543. return true;
  1544. })(),
  1545. /** @ignore */
  1546. addMethods = function(klass, source, parent) {
  1547. for (var property in source) {
  1548. if (property in klass.prototype &&
  1549. typeof klass.prototype[property] === 'function' &&
  1550. (source[property] + '').indexOf('callSuper') > -1) {
  1551. klass.prototype[property] = (function(property) {
  1552. return function() {
  1553. var superclass = this.constructor.superclass;
  1554. this.constructor.superclass = parent;
  1555. var returnValue = source[property].apply(this, arguments);
  1556. this.constructor.superclass = superclass;
  1557. if (property !== 'initialize') {
  1558. return returnValue;
  1559. }
  1560. };
  1561. })(property);
  1562. }
  1563. else {
  1564. klass.prototype[property] = source[property];
  1565. }
  1566. if (IS_DONTENUM_BUGGY) {
  1567. if (source.toString !== Object.prototype.toString) {
  1568. klass.prototype.toString = source.toString;
  1569. }
  1570. if (source.valueOf !== Object.prototype.valueOf) {
  1571. klass.prototype.valueOf = source.valueOf;
  1572. }
  1573. }
  1574. }
  1575. };
  1576. function Subclass() { }
  1577. function callSuper(methodName) {
  1578. var fn = this.constructor.superclass.prototype[methodName];
  1579. return (arguments.length > 1)
  1580. ? fn.apply(this, slice.call(arguments, 1))
  1581. : fn.call(this);
  1582. }
  1583. /**
  1584. * Helper for creation of "classes".
  1585. * @memberOf fabric.util
  1586. * @param {Function} [parent] optional "Class" to inherit from
  1587. * @param {Object} [properties] Properties shared by all instances of this class
  1588. * (be careful modifying objects defined here as this would affect all instances)
  1589. */
  1590. function createClass() {
  1591. var parent = null,
  1592. properties = slice.call(arguments, 0);
  1593. if (typeof properties[0] === 'function') {
  1594. parent = properties.shift();
  1595. }
  1596. function klass() {
  1597. this.initialize.apply(this, arguments);
  1598. }
  1599. klass.superclass = parent;
  1600. klass.subclasses = [];
  1601. if (parent) {
  1602. Subclass.prototype = parent.prototype;
  1603. klass.prototype = new Subclass();
  1604. parent.subclasses.push(klass);
  1605. }
  1606. for (var i = 0, length = properties.length; i < length; i++) {
  1607. addMethods(klass, properties[i], parent);
  1608. }
  1609. if (!klass.prototype.initialize) {
  1610. klass.prototype.initialize = emptyFunction;
  1611. }
  1612. klass.prototype.constructor = klass;
  1613. klass.prototype.callSuper = callSuper;
  1614. return klass;
  1615. }
  1616. fabric.util.createClass = createClass;
  1617. })();
  1618. (function () {
  1619. var unknown = 'unknown';
  1620. /* EVENT HANDLING */
  1621. function areHostMethods(object) {
  1622. var methodNames = Array.prototype.slice.call(arguments, 1),
  1623. t, i, len = methodNames.length;
  1624. for (i = 0; i < len; i++) {
  1625. t = typeof object[methodNames[i]];
  1626. if (!(/^(?:function|object|unknown)$/).test(t)) {
  1627. return false;
  1628. }
  1629. }
  1630. return true;
  1631. }
  1632. /** @ignore */
  1633. var getElement,
  1634. setElement,
  1635. getUniqueId = (function () {
  1636. var uid = 0;
  1637. return function (element) {
  1638. return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
  1639. };
  1640. })();
  1641. (function () {
  1642. var elements = { };
  1643. /** @ignore */
  1644. getElement = function (uid) {
  1645. return elements[uid];
  1646. };
  1647. /** @ignore */
  1648. setElement = function (uid, element) {
  1649. elements[uid] = element;
  1650. };
  1651. })();
  1652. function createListener(uid, handler) {
  1653. return {
  1654. handler: handler,
  1655. wrappedHandler: createWrappedHandler(uid, handler)
  1656. };
  1657. }
  1658. function createWrappedHandler(uid, handler) {
  1659. return function (e) {
  1660. handler.call(getElement(uid), e || fabric.window.event);
  1661. };
  1662. }
  1663. function createDispatcher(uid, eventName) {
  1664. return function (e) {
  1665. if (handlers[uid] && handlers[uid][eventName]) {
  1666. var handlersForEvent = handlers[uid][eventName];
  1667. for (var i = 0, len = handlersForEvent.length; i < len; i++) {
  1668. handlersForEvent[i].call(this, e || fabric.window.event);
  1669. }
  1670. }
  1671. };
  1672. }
  1673. var shouldUseAddListenerRemoveListener = (
  1674. areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
  1675. areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
  1676. shouldUseAttachEventDetachEvent = (
  1677. areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
  1678. areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
  1679. // IE branch
  1680. listeners = { },
  1681. // DOM L0 branch
  1682. handlers = { },
  1683. addListener, removeListener;
  1684. if (shouldUseAddListenerRemoveListener) {
  1685. /** @ignore */
  1686. addListener = function (element, eventName, handler) {
  1687. element.addEventListener(eventName, handler, false);
  1688. };
  1689. /** @ignore */
  1690. removeListener = function (element, eventName, handler) {
  1691. element.removeEventListener(eventName, handler, false);
  1692. };
  1693. }
  1694. else if (shouldUseAttachEventDetachEvent) {
  1695. /** @ignore */
  1696. addListener = function (element, eventName, handler) {
  1697. var uid = getUniqueId(element);
  1698. setElement(uid, element);
  1699. if (!listeners[uid]) {
  1700. listeners[uid] = { };
  1701. }
  1702. if (!listeners[uid][eventName]) {
  1703. listeners[uid][eventName] = [];
  1704. }
  1705. var listener = createListener(uid, handler);
  1706. listeners[uid][eventName].push(listener);
  1707. element.attachEvent('on' + eventName, listener.wrappedHandler);
  1708. };
  1709. /** @ignore */
  1710. removeListener = function (element, eventName, handler) {
  1711. var uid = getUniqueId(element), listener;
  1712. if (listeners[uid] && listeners[uid][eventName]) {
  1713. for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
  1714. listener = listeners[uid][eventName][i];
  1715. if (listener && listener.handler === handler) {
  1716. element.detachEvent('on' + eventName, listener.wrappedHandler);
  1717. listeners[uid][eventName][i] = null;
  1718. }
  1719. }
  1720. }
  1721. };
  1722. }
  1723. else {
  1724. /** @ignore */
  1725. addListener = function (element, eventName, handler) {
  1726. var uid = getUniqueId(element);
  1727. if (!handlers[uid]) {
  1728. handlers[uid] = { };
  1729. }
  1730. if (!handlers[uid][eventName]) {
  1731. handlers[uid][eventName] = [];
  1732. var existingHandler = element['on' + eventName];
  1733. if (existingHandler) {
  1734. handlers[uid][eventName].push(existingHandler);
  1735. }
  1736. element['on' + eventName] = createDispatcher(uid, eventName);
  1737. }
  1738. handlers[uid][eventName].push(handler);
  1739. };
  1740. /** @ignore */
  1741. removeListener = function (element, eventName, handler) {
  1742. var uid = getUniqueId(element);
  1743. if (handlers[uid] && handlers[uid][eventName]) {
  1744. var handlersForEvent = handlers[uid][eventName];
  1745. for (var i = 0, len = handlersForEvent.length; i < len; i++) {
  1746. if (handlersForEvent[i] === handler) {
  1747. handlersForEvent.splice(i, 1);
  1748. }
  1749. }
  1750. }
  1751. };
  1752. }
  1753. /**
  1754. * Adds an event listener to an element
  1755. * @function
  1756. * @memberOf fabric.util
  1757. * @param {HTMLElement} element
  1758. * @param {String} eventName
  1759. * @param {Function} handler
  1760. */
  1761. fabric.util.addListener = addListener;
  1762. /**
  1763. * Removes an event listener from an element
  1764. * @function
  1765. * @memberOf fabric.util
  1766. * @param {HTMLElement} element
  1767. * @param {String} eventName
  1768. * @param {Function} handler
  1769. */
  1770. fabric.util.removeListener = removeListener;
  1771. /**
  1772. * Cross-browser wrapper for getting event's coordinates
  1773. * @memberOf fabric.util
  1774. * @param {Event} event Event object
  1775. */
  1776. function getPointer(event) {
  1777. event || (event = fabric.window.event);
  1778. var element = event.target ||
  1779. (typeof event.srcElement !== unknown ? event.srcElement : null),
  1780. scroll = fabric.util.getScrollLeftTop(element);
  1781. return {
  1782. x: pointerX(event) + scroll.left,
  1783. y: pointerY(event) + scroll.top
  1784. };
  1785. }
  1786. var pointerX = function(event) {
  1787. // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
  1788. // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
  1789. // need to investigate later
  1790. return (typeof event.clientX !== unknown ? event.clientX : 0);
  1791. },
  1792. pointerY = function(event) {
  1793. return (typeof event.clientY !== unknown ? event.clientY : 0);
  1794. };
  1795. function _getPointer(event, pageProp, clientProp) {
  1796. var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
  1797. return (event[touchProp] && event[touchProp][0]
  1798. ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp]))
  1799. || event[clientProp]
  1800. : event[clientProp]);
  1801. }
  1802. if (fabric.isTouchSupported) {
  1803. pointerX = function(event) {
  1804. return _getPointer(event, 'pageX', 'clientX');
  1805. };
  1806. pointerY = function(event) {
  1807. return _getPointer(event, 'pageY', 'clientY');
  1808. };
  1809. }
  1810. fabric.util.getPointer = getPointer;
  1811. fabric.util.object.extend(fabric.util, fabric.Observable);
  1812. })();
  1813. (function () {
  1814. /**
  1815. * Cross-browser wrapper for setting element's style
  1816. * @memberOf fabric.util
  1817. * @param {HTMLElement} element
  1818. * @param {Object} styles
  1819. * @return {HTMLElement} Element that was passed as a first argument
  1820. */
  1821. function setStyle(element, styles) {
  1822. var elementStyle = element.style;
  1823. if (!elementStyle) {
  1824. return element;
  1825. }
  1826. if (typeof styles === 'string') {
  1827. element.style.cssText += ';' + styles;
  1828. return styles.indexOf('opacity') > -1
  1829. ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
  1830. : element;
  1831. }
  1832. for (var property in styles) {
  1833. if (property === 'opacity') {
  1834. setOpacity(element, styles[property]);
  1835. }
  1836. else {
  1837. var normalizedProperty = (property === 'float' || property === 'cssFloat')
  1838. ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat')
  1839. : property;
  1840. elementStyle[normalizedProperty] = styles[property];
  1841. }
  1842. }
  1843. return element;
  1844. }
  1845. var parseEl = fabric.document.createElement('div'),
  1846. supportsOpacity = typeof parseEl.style.opacity === 'string',
  1847. supportsFilters = typeof parseEl.style.filter === 'string',
  1848. reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
  1849. /** @ignore */
  1850. setOpacity = function (element) { return element; };
  1851. if (supportsOpacity) {
  1852. /** @ignore */
  1853. setOpacity = function(element, value) {
  1854. element.style.opacity = value;
  1855. return element;
  1856. };
  1857. }
  1858. else if (supportsFilters) {
  1859. /** @ignore */
  1860. setOpacity = function(element, value) {
  1861. var es = element.style;
  1862. if (element.currentStyle && !element.currentStyle.hasLayout) {
  1863. es.zoom = 1;
  1864. }
  1865. if (reOpacity.test(es.filter)) {
  1866. value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
  1867. es.filter = es.filter.replace(reOpacity, value);
  1868. }
  1869. else {
  1870. es.filter += ' alpha(opacity=' + (value * 100) + ')';
  1871. }
  1872. return element;
  1873. };
  1874. }
  1875. fabric.util.setStyle = setStyle;
  1876. })();
  1877. (function() {
  1878. var _slice = Array.prototype.slice;
  1879. /**
  1880. * Takes id and returns an element with that id (if one exists in a document)
  1881. * @memberOf fabric.util
  1882. * @param {String|HTMLElement} id
  1883. * @return {HTMLElement|null}
  1884. */
  1885. function getById(id) {
  1886. return typeof id === 'string' ? fabric.document.getElementById(id) : id;
  1887. }
  1888. var sliceCanConvertNodelists,
  1889. /**
  1890. * Converts an array-like object (e.g. arguments or NodeList) to an array
  1891. * @memberOf fabric.util
  1892. * @param {Object} arrayLike
  1893. * @return {Array}
  1894. */
  1895. toArray = function(arrayLike) {
  1896. return _slice.call(arrayLike, 0);
  1897. };
  1898. try {
  1899. sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
  1900. }
  1901. catch (err) { }
  1902. if (!sliceCanConvertNodelists) {
  1903. toArray = function(arrayLike) {
  1904. var arr = new Array(arrayLike.length), i = arrayLike.length;
  1905. while (i--) {
  1906. arr[i] = arrayLike[i];
  1907. }
  1908. return arr;
  1909. };
  1910. }
  1911. /**
  1912. * Creates specified element with specified attributes
  1913. * @memberOf fabric.util
  1914. * @param {String} tagName Type of an element to create
  1915. * @param {Object} [attributes] Attributes to set on an element
  1916. * @return {HTMLElement} Newly created element
  1917. */
  1918. function makeElement(tagName, attributes) {
  1919. var el = fabric.document.createElement(tagName);
  1920. for (var prop in attributes) {
  1921. if (prop === 'class') {
  1922. el.className = attributes[prop];
  1923. }
  1924. else if (prop === 'for') {
  1925. el.htmlFor = attributes[prop];
  1926. }
  1927. else {
  1928. el.setAttribute(prop, attributes[prop]);
  1929. }
  1930. }
  1931. return el;
  1932. }
  1933. /**
  1934. * Adds class to an element
  1935. * @memberOf fabric.util
  1936. * @param {HTMLElement} element Element to add class to
  1937. * @param {String} className Class to add to an element
  1938. */
  1939. function addClass(element, className) {
  1940. if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
  1941. element.className += (element.className ? ' ' : '') + className;
  1942. }
  1943. }
  1944. /**
  1945. * Wraps element with another element
  1946. * @memberOf fabric.util
  1947. * @param {HTMLElement} element Element to wrap
  1948. * @param {HTMLElement|String} wrapper Element to wrap with
  1949. * @param {Object} [attributes] Attributes to set on a wrapper
  1950. * @return {HTMLElement} wrapper
  1951. */
  1952. function wrapElement(element, wrapper, attributes) {
  1953. if (typeof wrapper === 'string') {
  1954. wrapper = makeElement(wrapper, attributes);
  1955. }
  1956. if (element.parentNode) {
  1957. element.parentNode.replaceChild(wrapper, element);
  1958. }
  1959. wrapper.appendChild(element);
  1960. return wrapper;
  1961. }
  1962. /**
  1963. * Returns element scroll offsets
  1964. * @memberOf fabric.util
  1965. * @param {HTMLElement} element Element to operate on
  1966. * @return {Object} Object with left/top values
  1967. */
  1968. function getScrollLeftTop(element) {
  1969. var left = 0,
  1970. top = 0,
  1971. docElement = fabric.document.documentElement,
  1972. body = fabric.document.body || {
  1973. scrollLeft: 0, scrollTop: 0
  1974. };
  1975. // While loop checks (and then sets element to) .parentNode OR .host
  1976. // to account for ShadowDOM. We still want to traverse up out of ShadowDOM,
  1977. // but the .parentNode of a root ShadowDOM node will always be null, instead
  1978. // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938
  1979. while (element && (element.parentNode || element.host)) {
  1980. // Set element to element parent, or 'host' in case of ShadowDOM
  1981. element = element.parentNode || element.host;
  1982. if (element === fabric.document) {
  1983. left = body.scrollLeft || docElement.scrollLeft || 0;
  1984. top = body.scrollTop || docElement.scrollTop || 0;
  1985. }
  1986. else {
  1987. left += element.scrollLeft || 0;
  1988. top += element.scrollTop || 0;
  1989. }
  1990. if (element.nodeType === 1 &&
  1991. fabric.util.getElementStyle(element, 'position') === 'fixed') {
  1992. break;
  1993. }
  1994. }
  1995. return { left: left, top: top };
  1996. }
  1997. /**
  1998. * Returns offset for a given element
  1999. * @function
  2000. * @memberOf fabric.util
  2001. * @param {HTMLElement} element Element to get offset for
  2002. * @return {Object} Object with "left" and "top" properties
  2003. */
  2004. function getElementOffset(element) {
  2005. var docElem,
  2006. doc = element && element.ownerDocument,
  2007. box = { left: 0, top: 0 },
  2008. offset = { left: 0, top: 0 },
  2009. scrollLeftTop,
  2010. offsetAttributes = {
  2011. borderLeftWidth: 'left',
  2012. borderTopWidth: 'top',
  2013. paddingLeft: 'left',
  2014. paddingTop: 'top'
  2015. };
  2016. if (!doc) {
  2017. return offset;
  2018. }
  2019. for (var attr in offsetAttributes) {
  2020. offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
  2021. }
  2022. docElem = doc.documentElement;
  2023. if ( typeof element.getBoundingClientRect !== 'undefined' ) {
  2024. box = element.getBoundingClientRect();
  2025. }
  2026. scrollLeftTop = getScrollLeftTop(element);
  2027. return {
  2028. left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
  2029. top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
  2030. };
  2031. }
  2032. /**
  2033. * Returns style attribute value of a given element
  2034. * @memberOf fabric.util
  2035. * @param {HTMLElement} element Element to get style attribute for
  2036. * @param {String} attr Style attribute to get for element
  2037. * @return {String} Style attribute value of the given element.
  2038. */
  2039. var getElementStyle;
  2040. if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
  2041. getElementStyle = function(element, attr) {
  2042. var style = fabric.document.defaultView.getComputedStyle(element, null);
  2043. return style ? style[attr] : undefined;
  2044. };
  2045. }
  2046. else {
  2047. getElementStyle = function(element, attr) {
  2048. var value = element.style[attr];
  2049. if (!value && element.currentStyle) {
  2050. value = element.currentStyle[attr];
  2051. }
  2052. return value;
  2053. };
  2054. }
  2055. (function () {
  2056. var style = fabric.document.documentElement.style,
  2057. selectProp = 'userSelect' in style
  2058. ? 'userSelect'
  2059. : 'MozUserSelect' in style
  2060. ? 'MozUserSelect'
  2061. : 'WebkitUserSelect' in style
  2062. ? 'WebkitUserSelect'
  2063. : 'KhtmlUserSelect' in style
  2064. ? 'KhtmlUserSelect'
  2065. : '';
  2066. /**
  2067. * Makes element unselectable
  2068. * @memberOf fabric.util
  2069. * @param {HTMLElement} element Element to make unselectable
  2070. * @return {HTMLElement} Element that was passed in
  2071. */
  2072. function makeElementUnselectable(element) {
  2073. if (typeof element.onselectstart !== 'undefined') {
  2074. element.onselectstart = fabric.util.falseFunction;
  2075. }
  2076. if (selectProp) {
  2077. element.style[selectProp] = 'none';
  2078. }
  2079. else if (typeof element.unselectable === 'string') {
  2080. element.unselectable = 'on';
  2081. }
  2082. return element;
  2083. }
  2084. /**
  2085. * Makes element selectable
  2086. * @memberOf fabric.util
  2087. * @param {HTMLElement} element Element to make selectable
  2088. * @return {HTMLElement} Element that was passed in
  2089. */
  2090. function makeElementSelectable(element) {
  2091. if (typeof element.onselectstart !== 'undefined') {
  2092. element.onselectstart = null;
  2093. }
  2094. if (selectProp) {
  2095. element.style[selectProp] = '';
  2096. }
  2097. else if (typeof element.unselectable === 'string') {
  2098. element.unselectable = '';
  2099. }
  2100. return element;
  2101. }
  2102. fabric.util.makeElementUnselectable = makeElementUnselectable;
  2103. fabric.util.makeElementSelectable = makeElementSelectable;
  2104. })();
  2105. (function() {
  2106. /**
  2107. * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
  2108. * @memberOf fabric.util
  2109. * @param {String} url URL of a script to load
  2110. * @param {Function} callback Callback to execute when script is finished loading
  2111. */
  2112. function getScript(url, callback) {
  2113. var headEl = fabric.document.getElementsByTagName('head')[0],
  2114. scriptEl = fabric.document.createElement('script'),
  2115. loading = true;
  2116. /** @ignore */
  2117. scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
  2118. if (loading) {
  2119. if (typeof this.readyState === 'string' &&
  2120. this.readyState !== 'loaded' &&
  2121. this.readyState !== 'complete') {
  2122. return;
  2123. }
  2124. loading = false;
  2125. callback(e || fabric.window.event);
  2126. scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
  2127. }
  2128. };
  2129. scriptEl.src = url;
  2130. headEl.appendChild(scriptEl);
  2131. // causes issue in Opera
  2132. // headEl.removeChild(scriptEl);
  2133. }
  2134. fabric.util.getScript = getScript;
  2135. })();
  2136. fabric.util.getById = getById;
  2137. fabric.util.toArray = toArray;
  2138. fabric.util.makeElement = makeElement;
  2139. fabric.util.addClass = addClass;
  2140. fabric.util.wrapElement = wrapElement;
  2141. fabric.util.getScrollLeftTop = getScrollLeftTop;
  2142. fabric.util.getElementOffset = getElementOffset;
  2143. fabric.util.getElementStyle = getElementStyle;
  2144. })();
  2145. (function() {
  2146. function addParamToUrl(url, param) {
  2147. return url + (/\?/.test(url) ? '&' : '?') + param;
  2148. }
  2149. var makeXHR = (function() {
  2150. var factories = [
  2151. function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
  2152. function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
  2153. function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); },
  2154. function() { return new XMLHttpRequest(); }
  2155. ];
  2156. for (var i = factories.length; i--; ) {
  2157. try {
  2158. var req = factories[i]();
  2159. if (req) {
  2160. return factories[i];
  2161. }
  2162. }
  2163. catch (err) { }
  2164. }
  2165. })();
  2166. function emptyFn() { }
  2167. /**
  2168. * Cross-browser abstraction for sending XMLHttpRequest
  2169. * @memberOf fabric.util
  2170. * @param {String} url URL to send XMLHttpRequest to
  2171. * @param {Object} [options] Options object
  2172. * @param {String} [options.method="GET"]
  2173. * @param {String} [options.parameters] parameters to append to url in GET or in body
  2174. * @param {String} [options.body] body to send with POST or PUT request
  2175. * @param {Function} options.onComplete Callback to invoke when request is completed
  2176. * @return {XMLHttpRequest} request
  2177. */
  2178. function request(url, options) {
  2179. options || (options = { });
  2180. var method = options.method ? options.method.toUpperCase() : 'GET',
  2181. onComplete = options.onComplete || function() { },
  2182. xhr = makeXHR(),
  2183. body = options.body || options.parameters;
  2184. /** @ignore */
  2185. xhr.onreadystatechange = function() {
  2186. if (xhr.readyState === 4) {
  2187. onComplete(xhr);
  2188. xhr.onreadystatechange = emptyFn;
  2189. }
  2190. };
  2191. if (method === 'GET') {
  2192. body = null;
  2193. if (typeof options.parameters === 'string') {
  2194. url = addParamToUrl(url, options.parameters);
  2195. }
  2196. }
  2197. xhr.open(method, url, true);
  2198. if (method === 'POST' || method === 'PUT') {
  2199. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  2200. }
  2201. xhr.send(body);
  2202. return xhr;
  2203. }
  2204. fabric.util.request = request;
  2205. })();
  2206. /**
  2207. * Wrapper around `console.log` (when available)
  2208. * @param {*} [values] Values to log
  2209. */
  2210. fabric.log = function() { };
  2211. /**
  2212. * Wrapper around `console.warn` (when available)
  2213. * @param {*} [values] Values to log as a warning
  2214. */
  2215. fabric.warn = function() { };
  2216. /* eslint-disable */
  2217. if (typeof console !== 'undefined') {
  2218. ['log', 'warn'].forEach(function(methodName) {
  2219. if (typeof console[methodName] !== 'undefined' &&
  2220. typeof console[methodName].apply === 'function') {
  2221. fabric[methodName] = function() {
  2222. return console[methodName].apply(console, arguments);
  2223. };
  2224. }
  2225. });
  2226. }
  2227. /* eslint-enable */
  2228. (function() {
  2229. /**
  2230. * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
  2231. * @memberOf fabric.util
  2232. * @param {Object} [options] Animation options
  2233. * @param {Function} [options.onChange] Callback; invoked on every value change
  2234. * @param {Function} [options.onComplete] Callback; invoked when value change is completed
  2235. * @param {Number} [options.startValue=0] Starting value
  2236. * @param {Number} [options.endValue=100] Ending value
  2237. * @param {Number} [options.byValue=100] Value to modify the property by
  2238. * @param {Function} [options.easing] Easing function
  2239. * @param {Number} [options.duration=500] Duration of change (in ms)
  2240. */
  2241. function animate(options) {
  2242. requestAnimFrame(function(timestamp) {
  2243. options || (options = { });
  2244. var start = timestamp || +new Date(),
  2245. duration = options.duration || 500,
  2246. finish = start + duration, time,
  2247. onChange = options.onChange || function() { },
  2248. abort = options.abort || function() { return false; },
  2249. easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;},
  2250. startValue = 'startValue' in options ? options.startValue : 0,
  2251. endValue = 'endValue' in options ? options.endValue : 100,
  2252. byValue = options.byValue || endValue - startValue;
  2253. options.onStart && options.onStart();
  2254. (function tick(ticktime) {
  2255. time = ticktime || +new Date();
  2256. var currentTime = time > finish ? duration : (time - start);
  2257. if (abort()) {
  2258. options.onComplete && options.onComplete();
  2259. return;
  2260. }
  2261. onChange(easing(currentTime, startValue, byValue, duration));
  2262. if (time > finish) {
  2263. options.onComplete && options.onComplete();
  2264. return;
  2265. }
  2266. requestAnimFrame(tick);
  2267. })(start);
  2268. });
  2269. }
  2270. var _requestAnimFrame = fabric.window.requestAnimationFrame ||
  2271. fabric.window.webkitRequestAnimationFrame ||
  2272. fabric.window.mozRequestAnimationFrame ||
  2273. fabric.window.oRequestAnimationFrame ||
  2274. fabric.window.msRequestAnimationFrame ||
  2275. function(callback) {
  2276. fabric.window.setTimeout(callback, 1000 / 60);
  2277. };
  2278. /**
  2279. * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
  2280. * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
  2281. * @memberOf fabric.util
  2282. * @param {Function} callback Callback to invoke
  2283. * @param {DOMElement} element optional Element to associate with animation
  2284. */
  2285. function requestAnimFrame() {
  2286. return _requestAnimFrame.apply(fabric.window, arguments);
  2287. }
  2288. fabric.util.animate = animate;
  2289. fabric.util.requestAnimFrame = requestAnimFrame;
  2290. })();
  2291. (function() {
  2292. function normalize(a, c, p, s) {
  2293. if (a < Math.abs(c)) {
  2294. a = c;
  2295. s = p / 4;
  2296. }
  2297. else {
  2298. //handle the 0/0 case:
  2299. if (c === 0 && a === 0) {
  2300. s = p / (2 * Math.PI) * Math.asin(1);
  2301. }
  2302. else {
  2303. s = p / (2 * Math.PI) * Math.asin(c / a);
  2304. }
  2305. }
  2306. return { a: a, c: c, p: p, s: s };
  2307. }
  2308. function elastic(opts, t, d) {
  2309. return opts.a *
  2310. Math.pow(2, 10 * (t -= 1)) *
  2311. Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
  2312. }
  2313. /**
  2314. * Cubic easing out
  2315. * @memberOf fabric.util.ease
  2316. */
  2317. function easeOutCubic(t, b, c, d) {
  2318. return c * ((t = t / d - 1) * t * t + 1) + b;
  2319. }
  2320. /**
  2321. * Cubic easing in and out
  2322. * @memberOf fabric.util.ease
  2323. */
  2324. function easeInOutCubic(t, b, c, d) {
  2325. t /= d / 2;
  2326. if (t < 1) {
  2327. return c / 2 * t * t * t + b;
  2328. }
  2329. return c / 2 * ((t -= 2) * t * t + 2) + b;
  2330. }
  2331. /**
  2332. * Quartic easing in
  2333. * @memberOf fabric.util.ease
  2334. */
  2335. function easeInQuart(t, b, c, d) {
  2336. return c * (t /= d) * t * t * t + b;
  2337. }
  2338. /**
  2339. * Quartic easing out
  2340. * @memberOf fabric.util.ease
  2341. */
  2342. function easeOutQuart(t, b, c, d) {
  2343. return -c * ((t = t / d - 1) * t * t * t - 1) + b;
  2344. }
  2345. /**
  2346. * Quartic easing in and out
  2347. * @memberOf fabric.util.ease
  2348. */
  2349. function easeInOutQuart(t, b, c, d) {
  2350. t /= d / 2;
  2351. if (t < 1) {
  2352. return c / 2 * t * t * t * t + b;
  2353. }
  2354. return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
  2355. }
  2356. /**
  2357. * Quintic easing in
  2358. * @memberOf fabric.util.ease
  2359. */
  2360. function easeInQuint(t, b, c, d) {
  2361. return c * (t /= d) * t * t * t * t + b;
  2362. }
  2363. /**
  2364. * Quintic easing out
  2365. * @memberOf fabric.util.ease
  2366. */
  2367. function easeOutQuint(t, b, c, d) {
  2368. return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
  2369. }
  2370. /**
  2371. * Quintic easing in and out
  2372. * @memberOf fabric.util.ease
  2373. */
  2374. function easeInOutQuint(t, b, c, d) {
  2375. t /= d / 2;
  2376. if (t < 1) {
  2377. return c / 2 * t * t * t * t * t + b;
  2378. }
  2379. return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
  2380. }
  2381. /**
  2382. * Sinusoidal easing in
  2383. * @memberOf fabric.util.ease
  2384. */
  2385. function easeInSine(t, b, c, d) {
  2386. return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
  2387. }
  2388. /**
  2389. * Sinusoidal easing out
  2390. * @memberOf fabric.util.ease
  2391. */
  2392. function easeOutSine(t, b, c, d) {
  2393. return c * Math.sin(t / d * (Math.PI / 2)) + b;
  2394. }
  2395. /**
  2396. * Sinusoidal easing in and out
  2397. * @memberOf fabric.util.ease
  2398. */
  2399. function easeInOutSine(t, b, c, d) {
  2400. return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
  2401. }
  2402. /**
  2403. * Exponential easing in
  2404. * @memberOf fabric.util.ease
  2405. */
  2406. function easeInExpo(t, b, c, d) {
  2407. return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
  2408. }
  2409. /**
  2410. * Exponential easing out
  2411. * @memberOf fabric.util.ease
  2412. */
  2413. function easeOutExpo(t, b, c, d) {
  2414. return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
  2415. }
  2416. /**
  2417. * Exponential easing in and out
  2418. * @memberOf fabric.util.ease
  2419. */
  2420. function easeInOutExpo(t, b, c, d) {
  2421. if (t === 0) {
  2422. return b;
  2423. }
  2424. if (t === d) {
  2425. return b + c;
  2426. }
  2427. t /= d / 2;
  2428. if (t < 1) {
  2429. return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
  2430. }
  2431. return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
  2432. }
  2433. /**
  2434. * Circular easing in
  2435. * @memberOf fabric.util.ease
  2436. */
  2437. function easeInCirc(t, b, c, d) {
  2438. return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
  2439. }
  2440. /**
  2441. * Circular easing out
  2442. * @memberOf fabric.util.ease
  2443. */
  2444. function easeOutCirc(t, b, c, d) {
  2445. return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
  2446. }
  2447. /**
  2448. * Circular easing in and out
  2449. * @memberOf fabric.util.ease
  2450. */
  2451. function easeInOutCirc(t, b, c, d) {
  2452. t /= d / 2;
  2453. if (t < 1) {
  2454. return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
  2455. }
  2456. return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
  2457. }
  2458. /**
  2459. * Elastic easing in
  2460. * @memberOf fabric.util.ease
  2461. */
  2462. function easeInElastic(t, b, c, d) {
  2463. var s = 1.70158, p = 0, a = c;
  2464. if (t === 0) {
  2465. return b;
  2466. }
  2467. t /= d;
  2468. if (t === 1) {
  2469. return b + c;
  2470. }
  2471. if (!p) {
  2472. p = d * 0.3;
  2473. }
  2474. var opts = normalize(a, c, p, s);
  2475. return -elastic(opts, t, d) + b;
  2476. }
  2477. /**
  2478. * Elastic easing out
  2479. * @memberOf fabric.util.ease
  2480. */
  2481. function easeOutElastic(t, b, c, d) {
  2482. var s = 1.70158, p = 0, a = c;
  2483. if (t === 0) {
  2484. return b;
  2485. }
  2486. t /= d;
  2487. if (t === 1) {
  2488. return b + c;
  2489. }
  2490. if (!p) {
  2491. p = d * 0.3;
  2492. }
  2493. var opts = normalize(a, c, p, s);
  2494. return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;
  2495. }
  2496. /**
  2497. * Elastic easing in and out
  2498. * @memberOf fabric.util.ease
  2499. */
  2500. function easeInOutElastic(t, b, c, d) {
  2501. var s = 1.70158, p = 0, a = c;
  2502. if (t === 0) {
  2503. return b;
  2504. }
  2505. t /= d / 2;
  2506. if (t === 2) {
  2507. return b + c;
  2508. }
  2509. if (!p) {
  2510. p = d * (0.3 * 1.5);
  2511. }
  2512. var opts = normalize(a, c, p, s);
  2513. if (t < 1) {
  2514. return -0.5 * elastic(opts, t, d) + b;
  2515. }
  2516. return opts.a * Math.pow(2, -10 * (t -= 1)) *
  2517. Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;
  2518. }
  2519. /**
  2520. * Backwards easing in
  2521. * @memberOf fabric.util.ease
  2522. */
  2523. function easeInBack(t, b, c, d, s) {
  2524. if (s === undefined) {
  2525. s = 1.70158;
  2526. }
  2527. return c * (t /= d) * t * ((s + 1) * t - s) + b;
  2528. }
  2529. /**
  2530. * Backwards easing out
  2531. * @memberOf fabric.util.ease
  2532. */
  2533. function easeOutBack(t, b, c, d, s) {
  2534. if (s === undefined) {
  2535. s = 1.70158;
  2536. }
  2537. return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
  2538. }
  2539. /**
  2540. * Backwards easing in and out
  2541. * @memberOf fabric.util.ease
  2542. */
  2543. function easeInOutBack(t, b, c, d, s) {
  2544. if (s === undefined) {
  2545. s = 1.70158;
  2546. }
  2547. t /= d / 2;
  2548. if (t < 1) {
  2549. return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
  2550. }
  2551. return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
  2552. }
  2553. /**
  2554. * Bouncing easing in
  2555. * @memberOf fabric.util.ease
  2556. */
  2557. function easeInBounce(t, b, c, d) {
  2558. return c - easeOutBounce (d - t, 0, c, d) + b;
  2559. }
  2560. /**
  2561. * Bouncing easing out
  2562. * @memberOf fabric.util.ease
  2563. */
  2564. function easeOutBounce(t, b, c, d) {
  2565. if ((t /= d) < (1 / 2.75)) {
  2566. return c * (7.5625 * t * t) + b;
  2567. }
  2568. else if (t < (2 / 2.75)) {
  2569. return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
  2570. }
  2571. else if (t < (2.5 / 2.75)) {
  2572. return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
  2573. }
  2574. else {
  2575. return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
  2576. }
  2577. }
  2578. /**
  2579. * Bouncing easing in and out
  2580. * @memberOf fabric.util.ease
  2581. */
  2582. function easeInOutBounce(t, b, c, d) {
  2583. if (t < d / 2) {
  2584. return easeInBounce (t * 2, 0, c, d) * 0.5 + b;
  2585. }
  2586. return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
  2587. }
  2588. /**
  2589. * Easing functions
  2590. * See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>
  2591. * @namespace fabric.util.ease
  2592. */
  2593. fabric.util.ease = {
  2594. /**
  2595. * Quadratic easing in
  2596. * @memberOf fabric.util.ease
  2597. */
  2598. easeInQuad: function(t, b, c, d) {
  2599. return c * (t /= d) * t + b;
  2600. },
  2601. /**
  2602. * Quadratic easing out
  2603. * @memberOf fabric.util.ease
  2604. */
  2605. easeOutQuad: function(t, b, c, d) {
  2606. return -c * (t /= d) * (t - 2) + b;
  2607. },
  2608. /**
  2609. * Quadratic easing in and out
  2610. * @memberOf fabric.util.ease
  2611. */
  2612. easeInOutQuad: function(t, b, c, d) {
  2613. t /= (d / 2);
  2614. if (t < 1) {
  2615. return c / 2 * t * t + b;
  2616. }
  2617. return -c / 2 * ((--t) * (t - 2) - 1) + b;
  2618. },
  2619. /**
  2620. * Cubic easing in
  2621. * @memberOf fabric.util.ease
  2622. */
  2623. easeInCubic: function(t, b, c, d) {
  2624. return c * (t /= d) * t * t + b;
  2625. },
  2626. easeOutCubic: easeOutCubic,
  2627. easeInOutCubic: easeInOutCubic,
  2628. easeInQuart: easeInQuart,
  2629. easeOutQuart: easeOutQuart,
  2630. easeInOutQuart: easeInOutQuart,
  2631. easeInQuint: easeInQuint,
  2632. easeOutQuint: easeOutQuint,
  2633. easeInOutQuint: easeInOutQuint,
  2634. easeInSine: easeInSine,
  2635. easeOutSine: easeOutSine,
  2636. easeInOutSine: easeInOutSine,
  2637. easeInExpo: easeInExpo,
  2638. easeOutExpo: easeOutExpo,
  2639. easeInOutExpo: easeInOutExpo,
  2640. easeInCirc: easeInCirc,
  2641. easeOutCirc: easeOutCirc,
  2642. easeInOutCirc: easeInOutCirc,
  2643. easeInElastic: easeInElastic,
  2644. easeOutElastic: easeOutElastic,
  2645. easeInOutElastic: easeInOutElastic,
  2646. easeInBack: easeInBack,
  2647. easeOutBack: easeOutBack,
  2648. easeInOutBack: easeInOutBack,
  2649. easeInBounce: easeInBounce,
  2650. easeOutBounce: easeOutBounce,
  2651. easeInOutBounce: easeInOutBounce
  2652. };
  2653. })();
  2654. (function(global) {
  2655. 'use strict';
  2656. /**
  2657. * @name fabric
  2658. * @namespace
  2659. */
  2660. var fabric = global.fabric || (global.fabric = { }),
  2661. extend = fabric.util.object.extend,
  2662. capitalize = fabric.util.string.capitalize,
  2663. clone = fabric.util.object.clone,
  2664. toFixed = fabric.util.toFixed,
  2665. parseUnit = fabric.util.parseUnit,
  2666. multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
  2667. reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/i,
  2668. reViewBoxTagNames = /^(symbol|image|marker|pattern|view|svg)$/i,
  2669. reNotAllowedAncestors = /^(?:pattern|defs|symbol|metadata)$/i,
  2670. reAllowedParents = /^(symbol|g|a|svg)$/i,
  2671. attributesMap = {
  2672. cx: 'left',
  2673. x: 'left',
  2674. r: 'radius',
  2675. cy: 'top',
  2676. y: 'top',
  2677. display: 'visible',
  2678. visibility: 'visible',
  2679. transform: 'transformMatrix',
  2680. 'fill-opacity': 'fillOpacity',
  2681. 'fill-rule': 'fillRule',
  2682. 'font-family': 'fontFamily',
  2683. 'font-size': 'fontSize',
  2684. 'font-style': 'fontStyle',
  2685. 'font-weight': 'fontWeight',
  2686. 'stroke-dasharray': 'strokeDashArray',
  2687. 'stroke-linecap': 'strokeLineCap',
  2688. 'stroke-linejoin': 'strokeLineJoin',
  2689. 'stroke-miterlimit': 'strokeMiterLimit',
  2690. 'stroke-opacity': 'strokeOpacity',
  2691. 'stroke-width': 'strokeWidth',
  2692. 'text-decoration': 'textDecoration',
  2693. 'text-anchor': 'originX'
  2694. },
  2695. colorAttributes = {
  2696. stroke: 'strokeOpacity',
  2697. fill: 'fillOpacity'
  2698. };
  2699. fabric.cssRules = { };
  2700. fabric.gradientDefs = { };
  2701. function normalizeAttr(attr) {
  2702. // transform attribute names
  2703. if (attr in attributesMap) {
  2704. return attributesMap[attr];
  2705. }
  2706. return attr;
  2707. }
  2708. function normalizeValue(attr, value, parentAttributes, fontSize) {
  2709. var isArray = Object.prototype.toString.call(value) === '[object Array]',
  2710. parsed;
  2711. if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
  2712. value = '';
  2713. }
  2714. else if (attr === 'strokeDashArray') {
  2715. value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
  2716. return parseFloat(n);
  2717. });
  2718. }
  2719. else if (attr === 'transformMatrix') {
  2720. if (parentAttributes && parentAttributes.transformMatrix) {
  2721. value = multiplyTransformMatrices(
  2722. parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
  2723. }
  2724. else {
  2725. value = fabric.parseTransformAttribute(value);
  2726. }
  2727. }
  2728. else if (attr === 'visible') {
  2729. value = (value === 'none' || value === 'hidden') ? false : true;
  2730. // display=none on parent element always takes precedence over child element
  2731. if (parentAttributes && parentAttributes.visible === false) {
  2732. value = false;
  2733. }
  2734. }
  2735. else if (attr === 'originX' /* text-anchor */) {
  2736. value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
  2737. }
  2738. else {
  2739. parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
  2740. }
  2741. return (!isArray && isNaN(parsed) ? value : parsed);
  2742. }
  2743. /**
  2744. * @private
  2745. * @param {Object} attributes Array of attributes to parse
  2746. */
  2747. function _setStrokeFillOpacity(attributes) {
  2748. for (var attr in colorAttributes) {
  2749. if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') {
  2750. continue;
  2751. }
  2752. if (typeof attributes[attr] === 'undefined') {
  2753. if (!fabric.Object.prototype[attr]) {
  2754. continue;
  2755. }
  2756. attributes[attr] = fabric.Object.prototype[attr];
  2757. }
  2758. if (attributes[attr].indexOf('url(') === 0) {
  2759. continue;
  2760. }
  2761. var color = new fabric.Color(attributes[attr]);
  2762. attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
  2763. }
  2764. return attributes;
  2765. }
  2766. /**
  2767. * @private
  2768. */
  2769. function _getMultipleNodes(doc, nodeNames) {
  2770. var nodeName, nodeArray = [], nodeList;
  2771. for (var i = 0; i < nodeNames.length; i++) {
  2772. nodeName = nodeNames[i];
  2773. nodeList = doc.getElementsByTagName(nodeName);
  2774. nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
  2775. }
  2776. return nodeArray;
  2777. }
  2778. /**
  2779. * Parses "transform" attribute, returning an array of values
  2780. * @static
  2781. * @function
  2782. * @memberOf fabric
  2783. * @param {String} attributeValue String containing attribute value
  2784. * @return {Array} Array of 6 elements representing transformation matrix
  2785. */
  2786. fabric.parseTransformAttribute = (function() {
  2787. function rotateMatrix(matrix, args) {
  2788. var angle = args[0],
  2789. x = (args.length === 3) ? args[1] : 0,
  2790. y = (args.length === 3) ? args[2] : 0;
  2791. matrix[0] = Math.cos(angle);
  2792. matrix[1] = Math.sin(angle);
  2793. matrix[2] = -Math.sin(angle);
  2794. matrix[3] = Math.cos(angle);
  2795. matrix[4] = x - (matrix[0] * x + matrix[2] * y);
  2796. matrix[5] = y - (matrix[1] * x + matrix[3] * y);
  2797. }
  2798. function scaleMatrix(matrix, args) {
  2799. var multiplierX = args[0],
  2800. multiplierY = (args.length === 2) ? args[1] : args[0];
  2801. matrix[0] = multiplierX;
  2802. matrix[3] = multiplierY;
  2803. }
  2804. function skewXMatrix(matrix, args) {
  2805. matrix[2] = Math.tan(fabric.util.degreesToRadians(args[0]));
  2806. }
  2807. function skewYMatrix(matrix, args) {
  2808. matrix[1] = Math.tan(fabric.util.degreesToRadians(args[0]));
  2809. }
  2810. function translateMatrix(matrix, args) {
  2811. matrix[4] = args[0];
  2812. if (args.length === 2) {
  2813. matrix[5] = args[1];
  2814. }
  2815. }
  2816. // identity matrix
  2817. var iMatrix = [
  2818. 1, // a
  2819. 0, // b
  2820. 0, // c
  2821. 1, // d
  2822. 0, // e
  2823. 0 // f
  2824. ],
  2825. // == begin transform regexp
  2826. number = fabric.reNum,
  2827. commaWsp = '(?:\\s+,?\\s*|,\\s*)',
  2828. skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
  2829. skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
  2830. rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
  2831. commaWsp + '(' + number + ')' +
  2832. commaWsp + '(' + number + '))?\\s*\\))',
  2833. scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
  2834. commaWsp + '(' + number + '))?\\s*\\))',
  2835. translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
  2836. commaWsp + '(' + number + '))?\\s*\\))',
  2837. matrix = '(?:(matrix)\\s*\\(\\s*' +
  2838. '(' + number + ')' + commaWsp +
  2839. '(' + number + ')' + commaWsp +
  2840. '(' + number + ')' + commaWsp +
  2841. '(' + number + ')' + commaWsp +
  2842. '(' + number + ')' + commaWsp +
  2843. '(' + number + ')' +
  2844. '\\s*\\))',
  2845. transform = '(?:' +
  2846. matrix + '|' +
  2847. translate + '|' +
  2848. scale + '|' +
  2849. rotate + '|' +
  2850. skewX + '|' +
  2851. skewY +
  2852. ')',
  2853. transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')',
  2854. transformList = '^\\s*(?:' + transforms + '?)\\s*$',
  2855. // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
  2856. reTransformList = new RegExp(transformList),
  2857. // == end transform regexp
  2858. reTransform = new RegExp(transform, 'g');
  2859. return function(attributeValue) {
  2860. // start with identity matrix
  2861. var matrix = iMatrix.concat(),
  2862. matrices = [];
  2863. // return if no argument was given or
  2864. // an argument does not match transform attribute regexp
  2865. if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
  2866. return matrix;
  2867. }
  2868. attributeValue.replace(reTransform, function(match) {
  2869. var m = new RegExp(transform).exec(match).filter(function (match) {
  2870. // match !== '' && match != null
  2871. return (!!match);
  2872. }),
  2873. operation = m[1],
  2874. args = m.slice(2).map(parseFloat);
  2875. switch (operation) {
  2876. case 'translate':
  2877. translateMatrix(matrix, args);
  2878. break;
  2879. case 'rotate':
  2880. args[0] = fabric.util.degreesToRadians(args[0]);
  2881. rotateMatrix(matrix, args);
  2882. break;
  2883. case 'scale':
  2884. scaleMatrix(matrix, args);
  2885. break;
  2886. case 'skewX':
  2887. skewXMatrix(matrix, args);
  2888. break;
  2889. case 'skewY':
  2890. skewYMatrix(matrix, args);
  2891. break;
  2892. case 'matrix':
  2893. matrix = args;
  2894. break;
  2895. }
  2896. // snapshot current matrix into matrices array
  2897. matrices.push(matrix.concat());
  2898. // reset
  2899. matrix = iMatrix.concat();
  2900. });
  2901. var combinedMatrix = matrices[0];
  2902. while (matrices.length > 1) {
  2903. matrices.shift();
  2904. combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
  2905. }
  2906. return combinedMatrix;
  2907. };
  2908. })();
  2909. /**
  2910. * @private
  2911. */
  2912. function parseStyleString(style, oStyle) {
  2913. var attr, value;
  2914. style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
  2915. var pair = chunk.split(':');
  2916. attr = normalizeAttr(pair[0].trim().toLowerCase());
  2917. value = normalizeValue(attr, pair[1].trim());
  2918. oStyle[attr] = value;
  2919. });
  2920. }
  2921. /**
  2922. * @private
  2923. */
  2924. function parseStyleObject(style, oStyle) {
  2925. var attr, value;
  2926. for (var prop in style) {
  2927. if (typeof style[prop] === 'undefined') {
  2928. continue;
  2929. }
  2930. attr = normalizeAttr(prop.toLowerCase());
  2931. value = normalizeValue(attr, style[prop]);
  2932. oStyle[attr] = value;
  2933. }
  2934. }
  2935. /**
  2936. * @private
  2937. */
  2938. function getGlobalStylesForElement(element, svgUid) {
  2939. var styles = { };
  2940. for (var rule in fabric.cssRules[svgUid]) {
  2941. if (elementMatchesRule(element, rule.split(' '))) {
  2942. for (var property in fabric.cssRules[svgUid][rule]) {
  2943. styles[property] = fabric.cssRules[svgUid][rule][property];
  2944. }
  2945. }
  2946. }
  2947. return styles;
  2948. }
  2949. /**
  2950. * @private
  2951. */
  2952. function elementMatchesRule(element, selectors) {
  2953. var firstMatching, parentMatching = true;
  2954. //start from rightmost selector.
  2955. firstMatching = selectorMatches(element, selectors.pop());
  2956. if (firstMatching && selectors.length) {
  2957. parentMatching = doesSomeParentMatch(element, selectors);
  2958. }
  2959. return firstMatching && parentMatching && (selectors.length === 0);
  2960. }
  2961. function doesSomeParentMatch(element, selectors) {
  2962. var selector, parentMatching = true;
  2963. while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
  2964. if (parentMatching) {
  2965. selector = selectors.pop();
  2966. }
  2967. element = element.parentNode;
  2968. parentMatching = selectorMatches(element, selector);
  2969. }
  2970. return selectors.length === 0;
  2971. }
  2972. /**
  2973. * @private
  2974. */
  2975. function selectorMatches(element, selector) {
  2976. var nodeName = element.nodeName,
  2977. classNames = element.getAttribute('class'),
  2978. id = element.getAttribute('id'), matcher;
  2979. // i check if a selector matches slicing away part from it.
  2980. // if i get empty string i should match
  2981. matcher = new RegExp('^' + nodeName, 'i');
  2982. selector = selector.replace(matcher, '');
  2983. if (id && selector.length) {
  2984. matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
  2985. selector = selector.replace(matcher, '');
  2986. }
  2987. if (classNames && selector.length) {
  2988. classNames = classNames.split(' ');
  2989. for (var i = classNames.length; i--;) {
  2990. matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
  2991. selector = selector.replace(matcher, '');
  2992. }
  2993. }
  2994. return selector.length === 0;
  2995. }
  2996. /**
  2997. * @private
  2998. * to support IE8 missing getElementById on SVGdocument
  2999. */
  3000. function elementById(doc, id) {
  3001. var el;
  3002. doc.getElementById && (el = doc.getElementById(id));
  3003. if (el) {
  3004. return el;
  3005. }
  3006. var node, i, nodelist = doc.getElementsByTagName('*');
  3007. for (i = 0; i < nodelist.length; i++) {
  3008. node = nodelist[i];
  3009. if (id === node.getAttribute('id')) {
  3010. return node;
  3011. }
  3012. }
  3013. }
  3014. /**
  3015. * @private
  3016. */
  3017. function parseUseDirectives(doc) {
  3018. var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
  3019. while (nodelist.length && i < nodelist.length) {
  3020. var el = nodelist[i],
  3021. xlink = el.getAttribute('xlink:href').substr(1),
  3022. x = el.getAttribute('x') || 0,
  3023. y = el.getAttribute('y') || 0,
  3024. el2 = elementById(doc, xlink).cloneNode(true),
  3025. currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
  3026. parentNode, oldLength = nodelist.length, attr, j, attrs, l;
  3027. applyViewboxTransform(el2);
  3028. if (/^svg$/i.test(el2.nodeName)) {
  3029. var el3 = el2.ownerDocument.createElement('g');
  3030. for (j = 0, attrs = el2.attributes, l = attrs.length; j < l; j++) {
  3031. attr = attrs.item(j);
  3032. el3.setAttribute(attr.nodeName, attr.nodeValue);
  3033. }
  3034. // el2.firstChild != null
  3035. while (el2.firstChild) {
  3036. el3.appendChild(el2.firstChild);
  3037. }
  3038. el2 = el3;
  3039. }
  3040. for (j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) {
  3041. attr = attrs.item(j);
  3042. if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
  3043. continue;
  3044. }
  3045. if (attr.nodeName === 'transform') {
  3046. currentTrans = attr.nodeValue + ' ' + currentTrans;
  3047. }
  3048. else {
  3049. el2.setAttribute(attr.nodeName, attr.nodeValue);
  3050. }
  3051. }
  3052. el2.setAttribute('transform', currentTrans);
  3053. el2.setAttribute('instantiated_by_use', '1');
  3054. el2.removeAttribute('id');
  3055. parentNode = el.parentNode;
  3056. parentNode.replaceChild(el2, el);
  3057. // some browsers do not shorten nodelist after replaceChild (IE8)
  3058. if (nodelist.length === oldLength) {
  3059. i++;
  3060. }
  3061. }
  3062. }
  3063. // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
  3064. // matches, e.g.: +14.56e-12, etc.
  3065. var reViewBoxAttrValue = new RegExp(
  3066. '^' +
  3067. '\\s*(' + fabric.reNum + '+)\\s*,?' +
  3068. '\\s*(' + fabric.reNum + '+)\\s*,?' +
  3069. '\\s*(' + fabric.reNum + '+)\\s*,?' +
  3070. '\\s*(' + fabric.reNum + '+)\\s*' +
  3071. '$'
  3072. );
  3073. /**
  3074. * Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
  3075. */
  3076. function applyViewboxTransform(element) {
  3077. var viewBoxAttr = element.getAttribute('viewBox'),
  3078. scaleX = 1,
  3079. scaleY = 1,
  3080. minX = 0,
  3081. minY = 0,
  3082. viewBoxWidth, viewBoxHeight, matrix, el,
  3083. widthAttr = element.getAttribute('width'),
  3084. heightAttr = element.getAttribute('height'),
  3085. x = element.getAttribute('x') || 0,
  3086. y = element.getAttribute('y') || 0,
  3087. preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
  3088. missingViewBox = (!viewBoxAttr || !reViewBoxTagNames.test(element.nodeName)
  3089. || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
  3090. missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
  3091. toBeParsed = missingViewBox && missingDimAttr,
  3092. parsedDim = { }, translateMatrix = '';
  3093. parsedDim.width = 0;
  3094. parsedDim.height = 0;
  3095. parsedDim.toBeParsed = toBeParsed;
  3096. if (toBeParsed) {
  3097. return parsedDim;
  3098. }
  3099. if (missingViewBox) {
  3100. parsedDim.width = parseUnit(widthAttr);
  3101. parsedDim.height = parseUnit(heightAttr);
  3102. return parsedDim;
  3103. }
  3104. minX = -parseFloat(viewBoxAttr[1]);
  3105. minY = -parseFloat(viewBoxAttr[2]);
  3106. viewBoxWidth = parseFloat(viewBoxAttr[3]);
  3107. viewBoxHeight = parseFloat(viewBoxAttr[4]);
  3108. if (!missingDimAttr) {
  3109. parsedDim.width = parseUnit(widthAttr);
  3110. parsedDim.height = parseUnit(heightAttr);
  3111. scaleX = parsedDim.width / viewBoxWidth;
  3112. scaleY = parsedDim.height / viewBoxHeight;
  3113. }
  3114. else {
  3115. parsedDim.width = viewBoxWidth;
  3116. parsedDim.height = viewBoxHeight;
  3117. }
  3118. // default is to preserve aspect ratio
  3119. preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
  3120. if (preserveAspectRatio.alignX !== 'none') {
  3121. //translate all container for the effect of Mid, Min, Max
  3122. scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
  3123. }
  3124. if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
  3125. return parsedDim;
  3126. }
  3127. if (x || y) {
  3128. translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
  3129. }
  3130. matrix = translateMatrix + ' matrix(' + scaleX +
  3131. ' 0' +
  3132. ' 0 ' +
  3133. scaleY + ' ' +
  3134. (minX * scaleX) + ' ' +
  3135. (minY * scaleY) + ') ';
  3136. if (element.nodeName === 'svg') {
  3137. el = element.ownerDocument.createElement('g');
  3138. // element.firstChild != null
  3139. while (element.firstChild) {
  3140. el.appendChild(element.firstChild);
  3141. }
  3142. element.appendChild(el);
  3143. }
  3144. else {
  3145. el = element;
  3146. matrix = el.getAttribute('transform') + matrix;
  3147. }
  3148. el.setAttribute('transform', matrix);
  3149. return parsedDim;
  3150. }
  3151. /**
  3152. * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
  3153. * @static
  3154. * @function
  3155. * @memberOf fabric
  3156. * @param {SVGDocument} doc SVG document to parse
  3157. * @param {Function} callback Callback to call when parsing is finished;
  3158. * It's being passed an array of elements (parsed from a document).
  3159. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3160. */
  3161. fabric.parseSVGDocument = (function() {
  3162. function hasAncestorWithNodeName(element, nodeName) {
  3163. while (element && (element = element.parentNode)) {
  3164. if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', ''))
  3165. && !element.getAttribute('instantiated_by_use')) {
  3166. return true;
  3167. }
  3168. }
  3169. return false;
  3170. }
  3171. return function(doc, callback, reviver) {
  3172. if (!doc) {
  3173. return;
  3174. }
  3175. parseUseDirectives(doc);
  3176. var startTime = new Date(),
  3177. svgUid = fabric.Object.__uid++,
  3178. options = applyViewboxTransform(doc),
  3179. descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
  3180. options.svgUid = svgUid;
  3181. if (descendants.length === 0 && fabric.isLikelyNode) {
  3182. // we're likely in node, where "o3-xml" library fails to gEBTN("*")
  3183. // https://github.com/ajaxorg/node-o3-xml/issues/21
  3184. descendants = doc.selectNodes('//*[name(.)!="svg"]');
  3185. var arr = [];
  3186. for (var i = 0, len = descendants.length; i < len; i++) {
  3187. arr[i] = descendants[i];
  3188. }
  3189. descendants = arr;
  3190. }
  3191. var elements = descendants.filter(function(el) {
  3192. applyViewboxTransform(el);
  3193. return reAllowedSVGTagNames.test(el.nodeName.replace('svg:', '')) &&
  3194. !hasAncestorWithNodeName(el, reNotAllowedAncestors); // http://www.w3.org/TR/SVG/struct.html#DefsElement
  3195. });
  3196. if (!elements || (elements && !elements.length)) {
  3197. callback && callback([], {});
  3198. return;
  3199. }
  3200. fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
  3201. fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
  3202. // Precedence of rules: style > class > attribute
  3203. fabric.parseElements(elements, function(instances) {
  3204. fabric.documentParsingTime = new Date() - startTime;
  3205. if (callback) {
  3206. callback(instances, options);
  3207. }
  3208. }, clone(options), reviver);
  3209. };
  3210. })();
  3211. /**
  3212. * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
  3213. * @namespace
  3214. */
  3215. var svgCache = {
  3216. /**
  3217. * @param {String} name
  3218. * @param {Function} callback
  3219. */
  3220. has: function (name, callback) {
  3221. callback(false);
  3222. },
  3223. get: function () {
  3224. /* NOOP */
  3225. },
  3226. set: function () {
  3227. /* NOOP */
  3228. }
  3229. };
  3230. /**
  3231. * @private
  3232. */
  3233. function _enlivenCachedObject(cachedObject) {
  3234. var objects = cachedObject.objects,
  3235. options = cachedObject.options;
  3236. objects = objects.map(function (o) {
  3237. return fabric[capitalize(o.type)].fromObject(o);
  3238. });
  3239. return ({ objects: objects, options: options });
  3240. }
  3241. /**
  3242. * @private
  3243. */
  3244. function _createSVGPattern(markup, canvas, property) {
  3245. if (canvas[property] && canvas[property].toSVG) {
  3246. markup.push(
  3247. '\t<pattern x="0" y="0" id="', property, 'Pattern" ',
  3248. 'width="', canvas[property].source.width,
  3249. '" height="', canvas[property].source.height,
  3250. '" patternUnits="userSpaceOnUse">\n',
  3251. '\t\t<image x="0" y="0" ',
  3252. 'width="', canvas[property].source.width,
  3253. '" height="', canvas[property].source.height,
  3254. '" xlink:href="', canvas[property].source.src,
  3255. '"></image>\n\t</pattern>\n'
  3256. );
  3257. }
  3258. }
  3259. var reFontDeclaration = new RegExp(
  3260. '(normal|italic)?\\s*(normal|small-caps)?\\s*' +
  3261. '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
  3262. fabric.reNum +
  3263. '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');
  3264. extend(fabric, {
  3265. /**
  3266. * Parses a short font declaration, building adding its properties to a style object
  3267. * @static
  3268. * @function
  3269. * @memberOf fabric
  3270. * @param {String} value font declaration
  3271. * @param {Object} oStyle definition
  3272. */
  3273. parseFontDeclaration: function(value, oStyle) {
  3274. var match = value.match(reFontDeclaration);
  3275. if (!match) {
  3276. return;
  3277. }
  3278. var fontStyle = match[1],
  3279. // font variant is not used
  3280. // fontVariant = match[2],
  3281. fontWeight = match[3],
  3282. fontSize = match[4],
  3283. lineHeight = match[5],
  3284. fontFamily = match[6];
  3285. if (fontStyle) {
  3286. oStyle.fontStyle = fontStyle;
  3287. }
  3288. if (fontWeight) {
  3289. oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
  3290. }
  3291. if (fontSize) {
  3292. oStyle.fontSize = parseUnit(fontSize);
  3293. }
  3294. if (fontFamily) {
  3295. oStyle.fontFamily = fontFamily;
  3296. }
  3297. if (lineHeight) {
  3298. oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
  3299. }
  3300. },
  3301. /**
  3302. * Parses an SVG document, returning all of the gradient declarations found in it
  3303. * @static
  3304. * @function
  3305. * @memberOf fabric
  3306. * @param {SVGDocument} doc SVG document to parse
  3307. * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
  3308. */
  3309. getGradientDefs: function(doc) {
  3310. var tagArray = [
  3311. 'linearGradient',
  3312. 'radialGradient',
  3313. 'svg:linearGradient',
  3314. 'svg:radialGradient'],
  3315. elList = _getMultipleNodes(doc, tagArray),
  3316. el, j = 0, id, xlink,
  3317. gradientDefs = { }, idsToXlinkMap = { };
  3318. j = elList.length;
  3319. while (j--) {
  3320. el = elList[j];
  3321. xlink = el.getAttribute('xlink:href');
  3322. id = el.getAttribute('id');
  3323. if (xlink) {
  3324. idsToXlinkMap[id] = xlink.substr(1);
  3325. }
  3326. gradientDefs[id] = el;
  3327. }
  3328. for (id in idsToXlinkMap) {
  3329. var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
  3330. el = gradientDefs[id];
  3331. while (el2.firstChild) {
  3332. el.appendChild(el2.firstChild);
  3333. }
  3334. }
  3335. return gradientDefs;
  3336. },
  3337. /**
  3338. * Returns an object of attributes' name/value, given element and an array of attribute names;
  3339. * Parses parent "g" nodes recursively upwards.
  3340. * @static
  3341. * @memberOf fabric
  3342. * @param {DOMElement} element Element to parse
  3343. * @param {Array} attributes Array of attributes to parse
  3344. * @return {Object} object containing parsed attributes' names/values
  3345. */
  3346. parseAttributes: function(element, attributes, svgUid) {
  3347. if (!element) {
  3348. return;
  3349. }
  3350. var value,
  3351. parentAttributes = { },
  3352. fontSize;
  3353. if (typeof svgUid === 'undefined') {
  3354. svgUid = element.getAttribute('svgUid');
  3355. }
  3356. // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
  3357. if (element.parentNode && reAllowedParents.test(element.parentNode.nodeName)) {
  3358. parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
  3359. }
  3360. fontSize = (parentAttributes && parentAttributes.fontSize ) ||
  3361. element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
  3362. var ownAttributes = attributes.reduce(function(memo, attr) {
  3363. value = element.getAttribute(attr);
  3364. if (value) {
  3365. attr = normalizeAttr(attr);
  3366. value = normalizeValue(attr, value, parentAttributes, fontSize);
  3367. memo[attr] = value;
  3368. }
  3369. return memo;
  3370. }, { });
  3371. // add values parsed from style, which take precedence over attributes
  3372. // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
  3373. ownAttributes = extend(ownAttributes,
  3374. extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
  3375. if (ownAttributes.font) {
  3376. fabric.parseFontDeclaration(ownAttributes.font, ownAttributes);
  3377. }
  3378. return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes));
  3379. },
  3380. /**
  3381. * Transforms an array of svg elements to corresponding fabric.* instances
  3382. * @static
  3383. * @memberOf fabric
  3384. * @param {Array} elements Array of elements to parse
  3385. * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
  3386. * @param {Object} [options] Options object
  3387. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3388. */
  3389. parseElements: function(elements, callback, options, reviver) {
  3390. new fabric.ElementsParser(elements, callback, options, reviver).parse();
  3391. },
  3392. /**
  3393. * Parses "style" attribute, retuning an object with values
  3394. * @static
  3395. * @memberOf fabric
  3396. * @param {SVGElement} element Element to parse
  3397. * @return {Object} Objects with values parsed from style attribute of an element
  3398. */
  3399. parseStyleAttribute: function(element) {
  3400. var oStyle = { },
  3401. style = element.getAttribute('style');
  3402. if (!style) {
  3403. return oStyle;
  3404. }
  3405. if (typeof style === 'string') {
  3406. parseStyleString(style, oStyle);
  3407. }
  3408. else {
  3409. parseStyleObject(style, oStyle);
  3410. }
  3411. return oStyle;
  3412. },
  3413. /**
  3414. * Parses "points" attribute, returning an array of values
  3415. * @static
  3416. * @memberOf fabric
  3417. * @param {String} points points attribute string
  3418. * @return {Array} array of points
  3419. */
  3420. parsePointsAttribute: function(points) {
  3421. // points attribute is required and must not be empty
  3422. if (!points) {
  3423. return null;
  3424. }
  3425. // replace commas with whitespace and remove bookending whitespace
  3426. points = points.replace(/,/g, ' ').trim();
  3427. points = points.split(/\s+/);
  3428. var parsedPoints = [], i, len;
  3429. i = 0;
  3430. len = points.length;
  3431. for (; i < len; i += 2) {
  3432. parsedPoints.push({
  3433. x: parseFloat(points[i]),
  3434. y: parseFloat(points[i + 1])
  3435. });
  3436. }
  3437. // odd number of points is an error
  3438. // if (parsedPoints.length % 2 !== 0) {
  3439. // return null;
  3440. // }
  3441. return parsedPoints;
  3442. },
  3443. /**
  3444. * Returns CSS rules for a given SVG document
  3445. * @static
  3446. * @function
  3447. * @memberOf fabric
  3448. * @param {SVGDocument} doc SVG document to parse
  3449. * @return {Object} CSS rules of this document
  3450. */
  3451. getCSSRules: function(doc) {
  3452. var styles = doc.getElementsByTagName('style'),
  3453. allRules = { }, rules;
  3454. // very crude parsing of style contents
  3455. for (var i = 0, len = styles.length; i < len; i++) {
  3456. // IE9 doesn't support textContent, but provides text instead.
  3457. var styleContents = styles[i].textContent || styles[i].text;
  3458. // remove comments
  3459. styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
  3460. if (styleContents.trim() === '') {
  3461. continue;
  3462. }
  3463. rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
  3464. rules = rules.map(function(rule) { return rule.trim(); });
  3465. rules.forEach(function(rule) {
  3466. var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
  3467. ruleObj = { }, declaration = match[2].trim(),
  3468. propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
  3469. for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
  3470. var pair = propertyValuePairs[i].split(/\s*:\s*/),
  3471. property = normalizeAttr(pair[0]),
  3472. value = normalizeValue(property, pair[1], pair[0]);
  3473. ruleObj[property] = value;
  3474. }
  3475. rule = match[1];
  3476. rule.split(',').forEach(function(_rule) {
  3477. _rule = _rule.replace(/^svg/i, '').trim();
  3478. if (_rule === '') {
  3479. return;
  3480. }
  3481. if (allRules[_rule]) {
  3482. fabric.util.object.extend(allRules[_rule], ruleObj);
  3483. }
  3484. else {
  3485. allRules[_rule] = fabric.util.object.clone(ruleObj);
  3486. }
  3487. });
  3488. });
  3489. }
  3490. return allRules;
  3491. },
  3492. /**
  3493. * Takes url corresponding to an SVG document, and parses it into a set of fabric objects.
  3494. * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
  3495. * @memberOf fabric
  3496. * @param {String} url
  3497. * @param {Function} callback
  3498. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3499. */
  3500. loadSVGFromURL: function(url, callback, reviver) {
  3501. url = url.replace(/^\n\s*/, '').trim();
  3502. svgCache.has(url, function (hasUrl) {
  3503. if (hasUrl) {
  3504. svgCache.get(url, function (value) {
  3505. var enlivedRecord = _enlivenCachedObject(value);
  3506. callback(enlivedRecord.objects, enlivedRecord.options);
  3507. });
  3508. }
  3509. else {
  3510. new fabric.util.request(url, {
  3511. method: 'get',
  3512. onComplete: onComplete
  3513. });
  3514. }
  3515. });
  3516. function onComplete(r) {
  3517. var xml = r.responseXML;
  3518. if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
  3519. xml = new ActiveXObject('Microsoft.XMLDOM');
  3520. xml.async = 'false';
  3521. //IE chokes on DOCTYPE
  3522. xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
  3523. }
  3524. if (!xml || !xml.documentElement) {
  3525. callback && callback(null);
  3526. }
  3527. fabric.parseSVGDocument(xml.documentElement, function (results, options) {
  3528. svgCache.set(url, {
  3529. objects: fabric.util.array.invoke(results, 'toObject'),
  3530. options: options
  3531. });
  3532. callback && callback(results, options);
  3533. }, reviver);
  3534. }
  3535. },
  3536. /**
  3537. * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
  3538. * @memberOf fabric
  3539. * @param {String} string
  3540. * @param {Function} callback
  3541. * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
  3542. */
  3543. loadSVGFromString: function(string, callback, reviver) {
  3544. string = string.trim();
  3545. var doc;
  3546. if (typeof DOMParser !== 'undefined') {
  3547. var parser = new DOMParser();
  3548. if (parser && parser.parseFromString) {
  3549. doc = parser.parseFromString(string, 'text/xml');
  3550. }
  3551. }
  3552. else if (fabric.window.ActiveXObject) {
  3553. doc = new ActiveXObject('Microsoft.XMLDOM');
  3554. doc.async = 'false';
  3555. // IE chokes on DOCTYPE
  3556. doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
  3557. }
  3558. fabric.parseSVGDocument(doc.documentElement, function (results, options) {
  3559. callback(results, options);
  3560. }, reviver);
  3561. },
  3562. /**
  3563. * Creates markup containing SVG font faces,
  3564. * font URLs for font faces must be collected by developers
  3565. * and are not extracted from the DOM by fabricjs
  3566. * @param {Array} objects Array of fabric objects
  3567. * @return {String}
  3568. */
  3569. createSVGFontFacesMarkup: function(objects) {
  3570. var markup = '', fontList = { }, obj, fontFamily,
  3571. style, row, rowIndex, _char, charIndex,
  3572. fontPaths = fabric.fontPaths;
  3573. for (var i = 0, len = objects.length; i < len; i++) {
  3574. obj = objects[i];
  3575. fontFamily = obj.fontFamily;
  3576. if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) {
  3577. continue;
  3578. }
  3579. fontList[fontFamily] = true;
  3580. if (!obj.styles) {
  3581. continue;
  3582. }
  3583. style = obj.styles;
  3584. for (rowIndex in style) {
  3585. row = style[rowIndex];
  3586. for (charIndex in row) {
  3587. _char = row[charIndex];
  3588. fontFamily = _char.fontFamily;
  3589. if (!fontList[fontFamily] && fontPaths[fontFamily]) {
  3590. fontList[fontFamily] = true;
  3591. }
  3592. }
  3593. }
  3594. }
  3595. for (var j in fontList) {
  3596. markup += [
  3597. '\t\t@font-face {\n',
  3598. '\t\t\tfont-family: \'', j, '\';\n',
  3599. '\t\t\tsrc: url(\'', fontPaths[j], '\');\n',
  3600. '\t\t}\n'
  3601. ].join('');
  3602. }
  3603. if (markup) {
  3604. markup = [
  3605. '\t<style type="text/css">',
  3606. '<![CDATA[\n',
  3607. markup,
  3608. ']]>',
  3609. '</style>\n'
  3610. ].join('');
  3611. }
  3612. return markup;
  3613. },
  3614. /**
  3615. * Creates markup containing SVG referenced elements like patterns, gradients etc.
  3616. * @param {fabric.Canvas} canvas instance of fabric.Canvas
  3617. * @return {String}
  3618. */
  3619. createSVGRefElementsMarkup: function(canvas) {
  3620. var markup = [];
  3621. _createSVGPattern(markup, canvas, 'backgroundColor');
  3622. _createSVGPattern(markup, canvas, 'overlayColor');
  3623. return markup.join('');
  3624. }
  3625. });
  3626. })(typeof exports !== 'undefined' ? exports : this);
  3627. fabric.ElementsParser = function(elements, callback, options, reviver) {
  3628. this.elements = elements;
  3629. this.callback = callback;
  3630. this.options = options;
  3631. this.reviver = reviver;
  3632. this.svgUid = (options && options.svgUid) || 0;
  3633. };
  3634. fabric.ElementsParser.prototype.parse = function() {
  3635. this.instances = new Array(this.elements.length);
  3636. this.numElements = this.elements.length;
  3637. this.createObjects();
  3638. };
  3639. fabric.ElementsParser.prototype.createObjects = function() {
  3640. for (var i = 0, len = this.elements.length; i < len; i++) {
  3641. this.elements[i].setAttribute('svgUid', this.svgUid);
  3642. (function(_obj, i) {
  3643. setTimeout(function() {
  3644. _obj.createObject(_obj.elements[i], i);
  3645. }, 0);
  3646. })(this, i);
  3647. }
  3648. };
  3649. fabric.ElementsParser.prototype.createObject = function(el, index) {
  3650. var klass = fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))];
  3651. if (klass && klass.fromElement) {
  3652. try {
  3653. this._createObject(klass, el, index);
  3654. }
  3655. catch (err) {
  3656. fabric.log(err);
  3657. }
  3658. }
  3659. else {
  3660. this.checkIfDone();
  3661. }
  3662. };
  3663. fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
  3664. if (klass.async) {
  3665. klass.fromElement(el, this.createCallback(index, el), this.options);
  3666. }
  3667. else {
  3668. var obj = klass.fromElement(el, this.options);
  3669. this.resolveGradient(obj, 'fill');
  3670. this.resolveGradient(obj, 'stroke');
  3671. this.reviver && this.reviver(el, obj);
  3672. this.instances[index] = obj;
  3673. this.checkIfDone();
  3674. }
  3675. };
  3676. fabric.ElementsParser.prototype.createCallback = function(index, el) {
  3677. var _this = this;
  3678. return function(obj) {
  3679. _this.resolveGradient(obj, 'fill');
  3680. _this.resolveGradient(obj, 'stroke');
  3681. _this.reviver && _this.reviver(el, obj);
  3682. _this.instances[index] = obj;
  3683. _this.checkIfDone();
  3684. };
  3685. };
  3686. fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
  3687. var instanceFillValue = obj.get(property);
  3688. if (!(/^url\(/).test(instanceFillValue)) {
  3689. return;
  3690. }
  3691. var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
  3692. if (fabric.gradientDefs[this.svgUid][gradientId]) {
  3693. obj.set(property,
  3694. fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
  3695. }
  3696. };
  3697. fabric.ElementsParser.prototype.checkIfDone = function() {
  3698. if (--this.numElements === 0) {
  3699. this.instances = this.instances.filter(function(el) {
  3700. // eslint-disable-next-line no-eq-null, eqeqeq
  3701. return el != null;
  3702. });
  3703. this.callback(this.instances);
  3704. }
  3705. };
  3706. (function(global) {
  3707. 'use strict';
  3708. /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
  3709. var fabric = global.fabric || (global.fabric = { });
  3710. if (fabric.Point) {
  3711. fabric.warn('fabric.Point is already defined');
  3712. return;
  3713. }
  3714. fabric.Point = Point;
  3715. /**
  3716. * Point class
  3717. * @class fabric.Point
  3718. * @memberOf fabric
  3719. * @constructor
  3720. * @param {Number} x
  3721. * @param {Number} y
  3722. * @return {fabric.Point} thisArg
  3723. */
  3724. function Point(x, y) {
  3725. this.x = x;
  3726. this.y = y;
  3727. }
  3728. Point.prototype = /** @lends fabric.Point.prototype */ {
  3729. type: 'point',
  3730. constructor: Point,
  3731. /**
  3732. * Adds another point to this one and returns another one
  3733. * @param {fabric.Point} that
  3734. * @return {fabric.Point} new Point instance with added values
  3735. */
  3736. add: function (that) {
  3737. return new Point(this.x + that.x, this.y + that.y);
  3738. },
  3739. /**
  3740. * Adds another point to this one
  3741. * @param {fabric.Point} that
  3742. * @return {fabric.Point} thisArg
  3743. * @chainable
  3744. */
  3745. addEquals: function (that) {
  3746. this.x += that.x;
  3747. this.y += that.y;
  3748. return this;
  3749. },
  3750. /**
  3751. * Adds value to this point and returns a new one
  3752. * @param {Number} scalar
  3753. * @return {fabric.Point} new Point with added value
  3754. */
  3755. scalarAdd: function (scalar) {
  3756. return new Point(this.x + scalar, this.y + scalar);
  3757. },
  3758. /**
  3759. * Adds value to this point
  3760. * @param {Number} scalar
  3761. * @return {fabric.Point} thisArg
  3762. * @chainable
  3763. */
  3764. scalarAddEquals: function (scalar) {
  3765. this.x += scalar;
  3766. this.y += scalar;
  3767. return this;
  3768. },
  3769. /**
  3770. * Subtracts another point from this point and returns a new one
  3771. * @param {fabric.Point} that
  3772. * @return {fabric.Point} new Point object with subtracted values
  3773. */
  3774. subtract: function (that) {
  3775. return new Point(this.x - that.x, this.y - that.y);
  3776. },
  3777. /**
  3778. * Subtracts another point from this point
  3779. * @param {fabric.Point} that
  3780. * @return {fabric.Point} thisArg
  3781. * @chainable
  3782. */
  3783. subtractEquals: function (that) {
  3784. this.x -= that.x;
  3785. this.y -= that.y;
  3786. return this;
  3787. },
  3788. /**
  3789. * Subtracts value from this point and returns a new one
  3790. * @param {Number} scalar
  3791. * @return {fabric.Point}
  3792. */
  3793. scalarSubtract: function (scalar) {
  3794. return new Point(this.x - scalar, this.y - scalar);
  3795. },
  3796. /**
  3797. * Subtracts value from this point
  3798. * @param {Number} scalar
  3799. * @return {fabric.Point} thisArg
  3800. * @chainable
  3801. */
  3802. scalarSubtractEquals: function (scalar) {
  3803. this.x -= scalar;
  3804. this.y -= scalar;
  3805. return this;
  3806. },
  3807. /**
  3808. * Miltiplies this point by a value and returns a new one
  3809. * TODO: rename in scalarMultiply in 2.0
  3810. * @param {Number} scalar
  3811. * @return {fabric.Point}
  3812. */
  3813. multiply: function (scalar) {
  3814. return new Point(this.x * scalar, this.y * scalar);
  3815. },
  3816. /**
  3817. * Miltiplies this point by a value
  3818. * TODO: rename in scalarMultiplyEquals in 2.0
  3819. * @param {Number} scalar
  3820. * @return {fabric.Point} thisArg
  3821. * @chainable
  3822. */
  3823. multiplyEquals: function (scalar) {
  3824. this.x *= scalar;
  3825. this.y *= scalar;
  3826. return this;
  3827. },
  3828. /**
  3829. * Divides this point by a value and returns a new one
  3830. * TODO: rename in scalarDivide in 2.0
  3831. * @param {Number} scalar
  3832. * @return {fabric.Point}
  3833. */
  3834. divide: function (scalar) {
  3835. return new Point(this.x / scalar, this.y / scalar);
  3836. },
  3837. /**
  3838. * Divides this point by a value
  3839. * TODO: rename in scalarDivideEquals in 2.0
  3840. * @param {Number} scalar
  3841. * @return {fabric.Point} thisArg
  3842. * @chainable
  3843. */
  3844. divideEquals: function (scalar) {
  3845. this.x /= scalar;
  3846. this.y /= scalar;
  3847. return this;
  3848. },
  3849. /**
  3850. * Returns true if this point is equal to another one
  3851. * @param {fabric.Point} that
  3852. * @return {Boolean}
  3853. */
  3854. eq: function (that) {
  3855. return (this.x === that.x && this.y === that.y);
  3856. },
  3857. /**
  3858. * Returns true if this point is less than another one
  3859. * @param {fabric.Point} that
  3860. * @return {Boolean}
  3861. */
  3862. lt: function (that) {
  3863. return (this.x < that.x && this.y < that.y);
  3864. },
  3865. /**
  3866. * Returns true if this point is less than or equal to another one
  3867. * @param {fabric.Point} that
  3868. * @return {Boolean}
  3869. */
  3870. lte: function (that) {
  3871. return (this.x <= that.x && this.y <= that.y);
  3872. },
  3873. /**
  3874. * Returns true if this point is greater another one
  3875. * @param {fabric.Point} that
  3876. * @return {Boolean}
  3877. */
  3878. gt: function (that) {
  3879. return (this.x > that.x && this.y > that.y);
  3880. },
  3881. /**
  3882. * Returns true if this point is greater than or equal to another one
  3883. * @param {fabric.Point} that
  3884. * @return {Boolean}
  3885. */
  3886. gte: function (that) {
  3887. return (this.x >= that.x && this.y >= that.y);
  3888. },
  3889. /**
  3890. * Returns new point which is the result of linear interpolation with this one and another one
  3891. * @param {fabric.Point} that
  3892. * @param {Number} t , position of interpolation, between 0 and 1 default 0.5
  3893. * @return {fabric.Point}
  3894. */
  3895. lerp: function (that, t) {
  3896. if (typeof t === 'undefined') {
  3897. t = 0.5;
  3898. }
  3899. t = Math.max(Math.min(1, t), 0);
  3900. return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
  3901. },
  3902. /**
  3903. * Returns distance from this point and another one
  3904. * @param {fabric.Point} that
  3905. * @return {Number}
  3906. */
  3907. distanceFrom: function (that) {
  3908. var dx = this.x - that.x,
  3909. dy = this.y - that.y;
  3910. return Math.sqrt(dx * dx + dy * dy);
  3911. },
  3912. /**
  3913. * Returns the point between this point and another one
  3914. * @param {fabric.Point} that
  3915. * @return {fabric.Point}
  3916. */
  3917. midPointFrom: function (that) {
  3918. return this.lerp(that);
  3919. },
  3920. /**
  3921. * Returns a new point which is the min of this and another one
  3922. * @param {fabric.Point} that
  3923. * @return {fabric.Point}
  3924. */
  3925. min: function (that) {
  3926. return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
  3927. },
  3928. /**
  3929. * Returns a new point which is the max of this and another one
  3930. * @param {fabric.Point} that
  3931. * @return {fabric.Point}
  3932. */
  3933. max: function (that) {
  3934. return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
  3935. },
  3936. /**
  3937. * Returns string representation of this point
  3938. * @return {String}
  3939. */
  3940. toString: function () {
  3941. return this.x + ',' + this.y;
  3942. },
  3943. /**
  3944. * Sets x/y of this point
  3945. * @param {Number} x
  3946. * @param {Number} y
  3947. * @chainable
  3948. */
  3949. setXY: function (x, y) {
  3950. this.x = x;
  3951. this.y = y;
  3952. return this;
  3953. },
  3954. /**
  3955. * Sets x of this point
  3956. * @param {Number} x
  3957. * @chainable
  3958. */
  3959. setX: function (x) {
  3960. this.x = x;
  3961. return this;
  3962. },
  3963. /**
  3964. * Sets y of this point
  3965. * @param {Number} y
  3966. * @chainable
  3967. */
  3968. setY: function (y) {
  3969. this.y = y;
  3970. return this;
  3971. },
  3972. /**
  3973. * Sets x/y of this point from another point
  3974. * @param {fabric.Point} that
  3975. * @chainable
  3976. */
  3977. setFromPoint: function (that) {
  3978. this.x = that.x;
  3979. this.y = that.y;
  3980. return this;
  3981. },
  3982. /**
  3983. * Swaps x/y of this point and another point
  3984. * @param {fabric.Point} that
  3985. */
  3986. swap: function (that) {
  3987. var x = this.x,
  3988. y = this.y;
  3989. this.x = that.x;
  3990. this.y = that.y;
  3991. that.x = x;
  3992. that.y = y;
  3993. },
  3994. /**
  3995. * return a cloned instance of the point
  3996. * @return {fabric.Point}
  3997. */
  3998. clone: function () {
  3999. return new Point(this.x, this.y);
  4000. }
  4001. };
  4002. })(typeof exports !== 'undefined' ? exports : this);
  4003. (function(global) {
  4004. 'use strict';
  4005. /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
  4006. var fabric = global.fabric || (global.fabric = { });
  4007. if (fabric.Intersection) {
  4008. fabric.warn('fabric.Intersection is already defined');
  4009. return;
  4010. }
  4011. /**
  4012. * Intersection class
  4013. * @class fabric.Intersection
  4014. * @memberOf fabric
  4015. * @constructor
  4016. */
  4017. function Intersection(status) {
  4018. this.status = status;
  4019. this.points = [];
  4020. }
  4021. fabric.Intersection = Intersection;
  4022. fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
  4023. constructor: Intersection,
  4024. /**
  4025. * Appends a point to intersection
  4026. * @param {fabric.Point} point
  4027. * @return {fabric.Intersection} thisArg
  4028. * @chainable
  4029. */
  4030. appendPoint: function (point) {
  4031. this.points.push(point);
  4032. return this;
  4033. },
  4034. /**
  4035. * Appends points to intersection
  4036. * @param {Array} points
  4037. * @return {fabric.Intersection} thisArg
  4038. * @chainable
  4039. */
  4040. appendPoints: function (points) {
  4041. this.points = this.points.concat(points);
  4042. return this;
  4043. }
  4044. };
  4045. /**
  4046. * Checks if one line intersects another
  4047. * TODO: rename in intersectSegmentSegment
  4048. * @static
  4049. * @param {fabric.Point} a1
  4050. * @param {fabric.Point} a2
  4051. * @param {fabric.Point} b1
  4052. * @param {fabric.Point} b2
  4053. * @return {fabric.Intersection}
  4054. */
  4055. fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
  4056. var result,
  4057. uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
  4058. ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
  4059. uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
  4060. if (uB !== 0) {
  4061. var ua = uaT / uB,
  4062. ub = ubT / uB;
  4063. if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
  4064. result = new Intersection('Intersection');
  4065. result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
  4066. }
  4067. else {
  4068. result = new Intersection();
  4069. }
  4070. }
  4071. else {
  4072. if (uaT === 0 || ubT === 0) {
  4073. result = new Intersection('Coincident');
  4074. }
  4075. else {
  4076. result = new Intersection('Parallel');
  4077. }
  4078. }
  4079. return result;
  4080. };
  4081. /**
  4082. * Checks if line intersects polygon
  4083. * TODO: rename in intersectSegmentPolygon
  4084. * fix detection of coincident
  4085. * @static
  4086. * @param {fabric.Point} a1
  4087. * @param {fabric.Point} a2
  4088. * @param {Array} points
  4089. * @return {fabric.Intersection}
  4090. */
  4091. fabric.Intersection.intersectLinePolygon = function(a1, a2, points) {
  4092. var result = new Intersection(),
  4093. length = points.length,
  4094. b1, b2, inter;
  4095. for (var i = 0; i < length; i++) {
  4096. b1 = points[i];
  4097. b2 = points[(i + 1) % length];
  4098. inter = Intersection.intersectLineLine(a1, a2, b1, b2);
  4099. result.appendPoints(inter.points);
  4100. }
  4101. if (result.points.length > 0) {
  4102. result.status = 'Intersection';
  4103. }
  4104. return result;
  4105. };
  4106. /**
  4107. * Checks if polygon intersects another polygon
  4108. * @static
  4109. * @param {Array} points1
  4110. * @param {Array} points2
  4111. * @return {fabric.Intersection}
  4112. */
  4113. fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
  4114. var result = new Intersection(),
  4115. length = points1.length;
  4116. for (var i = 0; i < length; i++) {
  4117. var a1 = points1[i],
  4118. a2 = points1[(i + 1) % length],
  4119. inter = Intersection.intersectLinePolygon(a1, a2, points2);
  4120. result.appendPoints(inter.points);
  4121. }
  4122. if (result.points.length > 0) {
  4123. result.status = 'Intersection';
  4124. }
  4125. return result;
  4126. };
  4127. /**
  4128. * Checks if polygon intersects rectangle
  4129. * @static
  4130. * @param {Array} points
  4131. * @param {fabric.Point} r1
  4132. * @param {fabric.Point} r2
  4133. * @return {fabric.Intersection}
  4134. */
  4135. fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
  4136. var min = r1.min(r2),
  4137. max = r1.max(r2),
  4138. topRight = new fabric.Point(max.x, min.y),
  4139. bottomLeft = new fabric.Point(min.x, max.y),
  4140. inter1 = Intersection.intersectLinePolygon(min, topRight, points),
  4141. inter2 = Intersection.intersectLinePolygon(topRight, max, points),
  4142. inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
  4143. inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
  4144. result = new Intersection();
  4145. result.appendPoints(inter1.points);
  4146. result.appendPoints(inter2.points);
  4147. result.appendPoints(inter3.points);
  4148. result.appendPoints(inter4.points);
  4149. if (result.points.length > 0) {
  4150. result.status = 'Intersection';
  4151. }
  4152. return result;
  4153. };
  4154. })(typeof exports !== 'undefined' ? exports : this);
  4155. (function(global) {
  4156. 'use strict';
  4157. var fabric = global.fabric || (global.fabric = { });
  4158. if (fabric.Color) {
  4159. fabric.warn('fabric.Color is already defined.');
  4160. return;
  4161. }
  4162. /**
  4163. * Color class
  4164. * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
  4165. * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
  4166. *
  4167. * @class fabric.Color
  4168. * @param {String} color optional in hex or rgb(a) or hsl format or from known color list
  4169. * @return {fabric.Color} thisArg
  4170. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
  4171. */
  4172. function Color(color) {
  4173. if (!color) {
  4174. this.setSource([0, 0, 0, 1]);
  4175. }
  4176. else {
  4177. this._tryParsingColor(color);
  4178. }
  4179. }
  4180. fabric.Color = Color;
  4181. fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
  4182. /**
  4183. * @private
  4184. * @param {String|Array} color Color value to parse
  4185. */
  4186. _tryParsingColor: function(color) {
  4187. var source;
  4188. if (color in Color.colorNameMap) {
  4189. color = Color.colorNameMap[color];
  4190. }
  4191. if (color === 'transparent') {
  4192. source = [255, 255, 255, 0];
  4193. }
  4194. if (!source) {
  4195. source = Color.sourceFromHex(color);
  4196. }
  4197. if (!source) {
  4198. source = Color.sourceFromRgb(color);
  4199. }
  4200. if (!source) {
  4201. source = Color.sourceFromHsl(color);
  4202. }
  4203. if (!source) {
  4204. //if color is not recognize let's make black as canvas does
  4205. source = [0, 0, 0, 1];
  4206. }
  4207. if (source) {
  4208. this.setSource(source);
  4209. }
  4210. },
  4211. /**
  4212. * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
  4213. * @private
  4214. * @param {Number} r Red color value
  4215. * @param {Number} g Green color value
  4216. * @param {Number} b Blue color value
  4217. * @return {Array} Hsl color
  4218. */
  4219. _rgbToHsl: function(r, g, b) {
  4220. r /= 255; g /= 255; b /= 255;
  4221. var h, s, l,
  4222. max = fabric.util.array.max([r, g, b]),
  4223. min = fabric.util.array.min([r, g, b]);
  4224. l = (max + min) / 2;
  4225. if (max === min) {
  4226. h = s = 0; // achromatic
  4227. }
  4228. else {
  4229. var d = max - min;
  4230. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  4231. switch (max) {
  4232. case r:
  4233. h = (g - b) / d + (g < b ? 6 : 0);
  4234. break;
  4235. case g:
  4236. h = (b - r) / d + 2;
  4237. break;
  4238. case b:
  4239. h = (r - g) / d + 4;
  4240. break;
  4241. }
  4242. h /= 6;
  4243. }
  4244. return [
  4245. Math.round(h * 360),
  4246. Math.round(s * 100),
  4247. Math.round(l * 100)
  4248. ];
  4249. },
  4250. /**
  4251. * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
  4252. * @return {Array}
  4253. */
  4254. getSource: function() {
  4255. return this._source;
  4256. },
  4257. /**
  4258. * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
  4259. * @param {Array} source
  4260. */
  4261. setSource: function(source) {
  4262. this._source = source;
  4263. },
  4264. /**
  4265. * Returns color represenation in RGB format
  4266. * @return {String} ex: rgb(0-255,0-255,0-255)
  4267. */
  4268. toRgb: function() {
  4269. var source = this.getSource();
  4270. return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
  4271. },
  4272. /**
  4273. * Returns color represenation in RGBA format
  4274. * @return {String} ex: rgba(0-255,0-255,0-255,0-1)
  4275. */
  4276. toRgba: function() {
  4277. var source = this.getSource();
  4278. return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
  4279. },
  4280. /**
  4281. * Returns color represenation in HSL format
  4282. * @return {String} ex: hsl(0-360,0%-100%,0%-100%)
  4283. */
  4284. toHsl: function() {
  4285. var source = this.getSource(),
  4286. hsl = this._rgbToHsl(source[0], source[1], source[2]);
  4287. return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';
  4288. },
  4289. /**
  4290. * Returns color represenation in HSLA format
  4291. * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
  4292. */
  4293. toHsla: function() {
  4294. var source = this.getSource(),
  4295. hsl = this._rgbToHsl(source[0], source[1], source[2]);
  4296. return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';
  4297. },
  4298. /**
  4299. * Returns color represenation in HEX format
  4300. * @return {String} ex: FF5555
  4301. */
  4302. toHex: function() {
  4303. var source = this.getSource(), r, g, b;
  4304. r = source[0].toString(16);
  4305. r = (r.length === 1) ? ('0' + r) : r;
  4306. g = source[1].toString(16);
  4307. g = (g.length === 1) ? ('0' + g) : g;
  4308. b = source[2].toString(16);
  4309. b = (b.length === 1) ? ('0' + b) : b;
  4310. return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
  4311. },
  4312. /**
  4313. * Gets value of alpha channel for this color
  4314. * @return {Number} 0-1
  4315. */
  4316. getAlpha: function() {
  4317. return this.getSource()[3];
  4318. },
  4319. /**
  4320. * Sets value of alpha channel for this color
  4321. * @param {Number} alpha Alpha value 0-1
  4322. * @return {fabric.Color} thisArg
  4323. */
  4324. setAlpha: function(alpha) {
  4325. var source = this.getSource();
  4326. source[3] = alpha;
  4327. this.setSource(source);
  4328. return this;
  4329. },
  4330. /**
  4331. * Transforms color to its grayscale representation
  4332. * @return {fabric.Color} thisArg
  4333. */
  4334. toGrayscale: function() {
  4335. var source = this.getSource(),
  4336. average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
  4337. currentAlpha = source[3];
  4338. this.setSource([average, average, average, currentAlpha]);
  4339. return this;
  4340. },
  4341. /**
  4342. * Transforms color to its black and white representation
  4343. * @param {Number} threshold
  4344. * @return {fabric.Color} thisArg
  4345. */
  4346. toBlackWhite: function(threshold) {
  4347. var source = this.getSource(),
  4348. average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
  4349. currentAlpha = source[3];
  4350. threshold = threshold || 127;
  4351. average = (Number(average) < Number(threshold)) ? 0 : 255;
  4352. this.setSource([average, average, average, currentAlpha]);
  4353. return this;
  4354. },
  4355. /**
  4356. * Overlays color with another color
  4357. * @param {String|fabric.Color} otherColor
  4358. * @return {fabric.Color} thisArg
  4359. */
  4360. overlayWith: function(otherColor) {
  4361. if (!(otherColor instanceof Color)) {
  4362. otherColor = new Color(otherColor);
  4363. }
  4364. var result = [],
  4365. alpha = this.getAlpha(),
  4366. otherAlpha = 0.5,
  4367. source = this.getSource(),
  4368. otherSource = otherColor.getSource();
  4369. for (var i = 0; i < 3; i++) {
  4370. result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
  4371. }
  4372. result[3] = alpha;
  4373. this.setSource(result);
  4374. return this;
  4375. }
  4376. };
  4377. /**
  4378. * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))
  4379. * @static
  4380. * @field
  4381. * @memberOf fabric.Color
  4382. */
  4383. // eslint-disable-next-line max-len
  4384. fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
  4385. /**
  4386. * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))
  4387. * @static
  4388. * @field
  4389. * @memberOf fabric.Color
  4390. */
  4391. fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
  4392. /**
  4393. * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff)
  4394. * @static
  4395. * @field
  4396. * @memberOf fabric.Color
  4397. */
  4398. fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i;
  4399. /**
  4400. * Map of the 17 basic color names with HEX code
  4401. * @static
  4402. * @field
  4403. * @memberOf fabric.Color
  4404. * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units
  4405. */
  4406. fabric.Color.colorNameMap = {
  4407. aqua: '#00FFFF',
  4408. black: '#000000',
  4409. blue: '#0000FF',
  4410. fuchsia: '#FF00FF',
  4411. gray: '#808080',
  4412. grey: '#808080',
  4413. green: '#008000',
  4414. lime: '#00FF00',
  4415. maroon: '#800000',
  4416. navy: '#000080',
  4417. olive: '#808000',
  4418. orange: '#FFA500',
  4419. purple: '#800080',
  4420. red: '#FF0000',
  4421. silver: '#C0C0C0',
  4422. teal: '#008080',
  4423. white: '#FFFFFF',
  4424. yellow: '#FFFF00'
  4425. };
  4426. /**
  4427. * @private
  4428. * @param {Number} p
  4429. * @param {Number} q
  4430. * @param {Number} t
  4431. * @return {Number}
  4432. */
  4433. function hue2rgb(p, q, t) {
  4434. if (t < 0) {
  4435. t += 1;
  4436. }
  4437. if (t > 1) {
  4438. t -= 1;
  4439. }
  4440. if (t < 1 / 6) {
  4441. return p + (q - p) * 6 * t;
  4442. }
  4443. if (t < 1 / 2) {
  4444. return q;
  4445. }
  4446. if (t < 2 / 3) {
  4447. return p + (q - p) * (2 / 3 - t) * 6;
  4448. }
  4449. return p;
  4450. }
  4451. /**
  4452. * Returns new color object, when given a color in RGB format
  4453. * @memberOf fabric.Color
  4454. * @param {String} color Color value ex: rgb(0-255,0-255,0-255)
  4455. * @return {fabric.Color}
  4456. */
  4457. fabric.Color.fromRgb = function(color) {
  4458. return Color.fromSource(Color.sourceFromRgb(color));
  4459. };
  4460. /**
  4461. * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
  4462. * @memberOf fabric.Color
  4463. * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
  4464. * @return {Array} source
  4465. */
  4466. fabric.Color.sourceFromRgb = function(color) {
  4467. var match = color.match(Color.reRGBa);
  4468. if (match) {
  4469. var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1),
  4470. g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1),
  4471. b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
  4472. return [
  4473. parseInt(r, 10),
  4474. parseInt(g, 10),
  4475. parseInt(b, 10),
  4476. match[4] ? parseFloat(match[4]) : 1
  4477. ];
  4478. }
  4479. };
  4480. /**
  4481. * Returns new color object, when given a color in RGBA format
  4482. * @static
  4483. * @function
  4484. * @memberOf fabric.Color
  4485. * @param {String} color
  4486. * @return {fabric.Color}
  4487. */
  4488. fabric.Color.fromRgba = Color.fromRgb;
  4489. /**
  4490. * Returns new color object, when given a color in HSL format
  4491. * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
  4492. * @memberOf fabric.Color
  4493. * @return {fabric.Color}
  4494. */
  4495. fabric.Color.fromHsl = function(color) {
  4496. return Color.fromSource(Color.sourceFromHsl(color));
  4497. };
  4498. /**
  4499. * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
  4500. * Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
  4501. * @memberOf fabric.Color
  4502. * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
  4503. * @return {Array} source
  4504. * @see http://http://www.w3.org/TR/css3-color/#hsl-color
  4505. */
  4506. fabric.Color.sourceFromHsl = function(color) {
  4507. var match = color.match(Color.reHSLa);
  4508. if (!match) {
  4509. return;
  4510. }
  4511. var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
  4512. s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
  4513. l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
  4514. r, g, b;
  4515. if (s === 0) {
  4516. r = g = b = l;
  4517. }
  4518. else {
  4519. var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,
  4520. p = l * 2 - q;
  4521. r = hue2rgb(p, q, h + 1 / 3);
  4522. g = hue2rgb(p, q, h);
  4523. b = hue2rgb(p, q, h - 1 / 3);
  4524. }
  4525. return [
  4526. Math.round(r * 255),
  4527. Math.round(g * 255),
  4528. Math.round(b * 255),
  4529. match[4] ? parseFloat(match[4]) : 1
  4530. ];
  4531. };
  4532. /**
  4533. * Returns new color object, when given a color in HSLA format
  4534. * @static
  4535. * @function
  4536. * @memberOf fabric.Color
  4537. * @param {String} color
  4538. * @return {fabric.Color}
  4539. */
  4540. fabric.Color.fromHsla = Color.fromHsl;
  4541. /**
  4542. * Returns new color object, when given a color in HEX format
  4543. * @static
  4544. * @memberOf fabric.Color
  4545. * @param {String} color Color value ex: FF5555
  4546. * @return {fabric.Color}
  4547. */
  4548. fabric.Color.fromHex = function(color) {
  4549. return Color.fromSource(Color.sourceFromHex(color));
  4550. };
  4551. /**
  4552. * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format
  4553. * @static
  4554. * @memberOf fabric.Color
  4555. * @param {String} color ex: FF5555 or FF5544CC (RGBa)
  4556. * @return {Array} source
  4557. */
  4558. fabric.Color.sourceFromHex = function(color) {
  4559. if (color.match(Color.reHex)) {
  4560. var value = color.slice(color.indexOf('#') + 1),
  4561. isShortNotation = (value.length === 3 || value.length === 4),
  4562. isRGBa = (value.length === 8 || value.length === 4),
  4563. r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2),
  4564. g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4),
  4565. b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6),
  4566. a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF';
  4567. return [
  4568. parseInt(r, 16),
  4569. parseInt(g, 16),
  4570. parseInt(b, 16),
  4571. parseFloat((parseInt(a, 16) / 255).toFixed(2))
  4572. ];
  4573. }
  4574. };
  4575. /**
  4576. * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
  4577. * @static
  4578. * @memberOf fabric.Color
  4579. * @param {Array} source
  4580. * @return {fabric.Color}
  4581. */
  4582. fabric.Color.fromSource = function(source) {
  4583. var oColor = new Color();
  4584. oColor.setSource(source);
  4585. return oColor;
  4586. };
  4587. })(typeof exports !== 'undefined' ? exports : this);
  4588. (function() {
  4589. /* _FROM_SVG_START_ */
  4590. function getColorStop(el) {
  4591. var style = el.getAttribute('style'),
  4592. offset = el.getAttribute('offset') || 0,
  4593. color, colorAlpha, opacity;
  4594. // convert percents to absolute values
  4595. offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
  4596. offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
  4597. if (style) {
  4598. var keyValuePairs = style.split(/\s*;\s*/);
  4599. if (keyValuePairs[keyValuePairs.length - 1] === '') {
  4600. keyValuePairs.pop();
  4601. }
  4602. for (var i = keyValuePairs.length; i--; ) {
  4603. var split = keyValuePairs[i].split(/\s*:\s*/),
  4604. key = split[0].trim(),
  4605. value = split[1].trim();
  4606. if (key === 'stop-color') {
  4607. color = value;
  4608. }
  4609. else if (key === 'stop-opacity') {
  4610. opacity = value;
  4611. }
  4612. }
  4613. }
  4614. if (!color) {
  4615. color = el.getAttribute('stop-color') || 'rgb(0,0,0)';
  4616. }
  4617. if (!opacity) {
  4618. opacity = el.getAttribute('stop-opacity');
  4619. }
  4620. color = new fabric.Color(color);
  4621. colorAlpha = color.getAlpha();
  4622. opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
  4623. opacity *= colorAlpha;
  4624. return {
  4625. offset: offset,
  4626. color: color.toRgb(),
  4627. opacity: opacity
  4628. };
  4629. }
  4630. function getLinearCoords(el) {
  4631. return {
  4632. x1: el.getAttribute('x1') || 0,
  4633. y1: el.getAttribute('y1') || 0,
  4634. x2: el.getAttribute('x2') || '100%',
  4635. y2: el.getAttribute('y2') || 0
  4636. };
  4637. }
  4638. function getRadialCoords(el) {
  4639. return {
  4640. x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
  4641. y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
  4642. r1: 0,
  4643. x2: el.getAttribute('cx') || '50%',
  4644. y2: el.getAttribute('cy') || '50%',
  4645. r2: el.getAttribute('r') || '50%'
  4646. };
  4647. }
  4648. /* _FROM_SVG_END_ */
  4649. /**
  4650. * Gradient class
  4651. * @class fabric.Gradient
  4652. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients}
  4653. * @see {@link fabric.Gradient#initialize} for constructor definition
  4654. */
  4655. fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
  4656. /**
  4657. * Horizontal offset for aligning gradients coming from SVG when outside pathgroups
  4658. * @type Number
  4659. * @default 0
  4660. */
  4661. offsetX: 0,
  4662. /**
  4663. * Vertical offset for aligning gradients coming from SVG when outside pathgroups
  4664. * @type Number
  4665. * @default 0
  4666. */
  4667. offsetY: 0,
  4668. /**
  4669. * Constructor
  4670. * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
  4671. * @return {fabric.Gradient} thisArg
  4672. */
  4673. initialize: function(options) {
  4674. options || (options = { });
  4675. var coords = { };
  4676. this.id = fabric.Object.__uid++;
  4677. this.type = options.type || 'linear';
  4678. coords = {
  4679. x1: options.coords.x1 || 0,
  4680. y1: options.coords.y1 || 0,
  4681. x2: options.coords.x2 || 0,
  4682. y2: options.coords.y2 || 0
  4683. };
  4684. if (this.type === 'radial') {
  4685. coords.r1 = options.coords.r1 || 0;
  4686. coords.r2 = options.coords.r2 || 0;
  4687. }
  4688. this.coords = coords;
  4689. this.colorStops = options.colorStops.slice();
  4690. if (options.gradientTransform) {
  4691. this.gradientTransform = options.gradientTransform;
  4692. }
  4693. this.offsetX = options.offsetX || this.offsetX;
  4694. this.offsetY = options.offsetY || this.offsetY;
  4695. },
  4696. /**
  4697. * Adds another colorStop
  4698. * @param {Object} colorStop Object with offset and color
  4699. * @return {fabric.Gradient} thisArg
  4700. */
  4701. addColorStop: function(colorStop) {
  4702. for (var position in colorStop) {
  4703. var color = new fabric.Color(colorStop[position]);
  4704. this.colorStops.push({
  4705. offset: position,
  4706. color: color.toRgb(),
  4707. opacity: color.getAlpha()
  4708. });
  4709. }
  4710. return this;
  4711. },
  4712. /**
  4713. * Returns object representation of a gradient
  4714. * @return {Object}
  4715. */
  4716. toObject: function() {
  4717. return {
  4718. type: this.type,
  4719. coords: this.coords,
  4720. colorStops: this.colorStops,
  4721. offsetX: this.offsetX,
  4722. offsetY: this.offsetY,
  4723. gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
  4724. };
  4725. },
  4726. /* _TO_SVG_START_ */
  4727. /**
  4728. * Returns SVG representation of an gradient
  4729. * @param {Object} object Object to create a gradient for
  4730. * @return {String} SVG representation of an gradient (linear/radial)
  4731. */
  4732. toSVG: function(object) {
  4733. var coords = fabric.util.object.clone(this.coords),
  4734. markup, commonAttributes;
  4735. // colorStops must be sorted ascending
  4736. this.colorStops.sort(function(a, b) {
  4737. return a.offset - b.offset;
  4738. });
  4739. if (!(object.group && object.group.type === 'path-group')) {
  4740. for (var prop in coords) {
  4741. if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
  4742. coords[prop] += this.offsetX - object.width / 2;
  4743. }
  4744. else if (prop === 'y1' || prop === 'y2') {
  4745. coords[prop] += this.offsetY - object.height / 2;
  4746. }
  4747. }
  4748. }
  4749. commonAttributes = 'id="SVGID_' + this.id +
  4750. '" gradientUnits="userSpaceOnUse"';
  4751. if (this.gradientTransform) {
  4752. commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
  4753. }
  4754. if (this.type === 'linear') {
  4755. markup = [
  4756. '<linearGradient ',
  4757. commonAttributes,
  4758. ' x1="', coords.x1,
  4759. '" y1="', coords.y1,
  4760. '" x2="', coords.x2,
  4761. '" y2="', coords.y2,
  4762. '">\n'
  4763. ];
  4764. }
  4765. else if (this.type === 'radial') {
  4766. markup = [
  4767. '<radialGradient ',
  4768. commonAttributes,
  4769. ' cx="', coords.x2,
  4770. '" cy="', coords.y2,
  4771. '" r="', coords.r2,
  4772. '" fx="', coords.x1,
  4773. '" fy="', coords.y1,
  4774. '">\n'
  4775. ];
  4776. }
  4777. for (var i = 0; i < this.colorStops.length; i++) {
  4778. markup.push(
  4779. '<stop ',
  4780. 'offset="', (this.colorStops[i].offset * 100) + '%',
  4781. '" style="stop-color:', this.colorStops[i].color,
  4782. (this.colorStops[i].opacity !== null ? ';stop-opacity: ' + this.colorStops[i].opacity : ';'),
  4783. '"/>\n'
  4784. );
  4785. }
  4786. markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n'));
  4787. return markup.join('');
  4788. },
  4789. /* _TO_SVG_END_ */
  4790. /**
  4791. * Returns an instance of CanvasGradient
  4792. * @param {CanvasRenderingContext2D} ctx Context to render on
  4793. * @param {Object} object
  4794. * @return {CanvasGradient}
  4795. */
  4796. toLive: function(ctx, object) {
  4797. var gradient, prop, coords = fabric.util.object.clone(this.coords);
  4798. if (!this.type) {
  4799. return;
  4800. }
  4801. if (object.group && object.group.type === 'path-group') {
  4802. for (prop in coords) {
  4803. if (prop === 'x1' || prop === 'x2') {
  4804. coords[prop] += -this.offsetX + object.width / 2;
  4805. }
  4806. else if (prop === 'y1' || prop === 'y2') {
  4807. coords[prop] += -this.offsetY + object.height / 2;
  4808. }
  4809. }
  4810. }
  4811. if (this.type === 'linear') {
  4812. gradient = ctx.createLinearGradient(
  4813. coords.x1, coords.y1, coords.x2, coords.y2);
  4814. }
  4815. else if (this.type === 'radial') {
  4816. gradient = ctx.createRadialGradient(
  4817. coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2);
  4818. }
  4819. for (var i = 0, len = this.colorStops.length; i < len; i++) {
  4820. var color = this.colorStops[i].color,
  4821. opacity = this.colorStops[i].opacity,
  4822. offset = this.colorStops[i].offset;
  4823. if (typeof opacity !== 'undefined') {
  4824. color = new fabric.Color(color).setAlpha(opacity).toRgba();
  4825. }
  4826. gradient.addColorStop(parseFloat(offset), color);
  4827. }
  4828. return gradient;
  4829. }
  4830. });
  4831. fabric.util.object.extend(fabric.Gradient, {
  4832. /* _FROM_SVG_START_ */
  4833. /**
  4834. * Returns {@link fabric.Gradient} instance from an SVG element
  4835. * @static
  4836. * @memberOf fabric.Gradient
  4837. * @param {SVGGradientElement} el SVG gradient element
  4838. * @param {fabric.Object} instance
  4839. * @return {fabric.Gradient} Gradient instance
  4840. * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
  4841. * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
  4842. */
  4843. fromElement: function(el, instance) {
  4844. /**
  4845. * @example:
  4846. *
  4847. * <linearGradient id="linearGrad1">
  4848. * <stop offset="0%" stop-color="white"/>
  4849. * <stop offset="100%" stop-color="black"/>
  4850. * </linearGradient>
  4851. *
  4852. * OR
  4853. *
  4854. * <linearGradient id="linearGrad2">
  4855. * <stop offset="0" style="stop-color:rgb(255,255,255)"/>
  4856. * <stop offset="1" style="stop-color:rgb(0,0,0)"/>
  4857. * </linearGradient>
  4858. *
  4859. * OR
  4860. *
  4861. * <radialGradient id="radialGrad1">
  4862. * <stop offset="0%" stop-color="white" stop-opacity="1" />
  4863. * <stop offset="50%" stop-color="black" stop-opacity="0.5" />
  4864. * <stop offset="100%" stop-color="white" stop-opacity="1" />
  4865. * </radialGradient>
  4866. *
  4867. * OR
  4868. *
  4869. * <radialGradient id="radialGrad2">
  4870. * <stop offset="0" stop-color="rgb(255,255,255)" />
  4871. * <stop offset="0.5" stop-color="rgb(0,0,0)" />
  4872. * <stop offset="1" stop-color="rgb(255,255,255)" />
  4873. * </radialGradient>
  4874. *
  4875. */
  4876. var colorStopEls = el.getElementsByTagName('stop'),
  4877. type,
  4878. gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
  4879. gradientTransform = el.getAttribute('gradientTransform'),
  4880. colorStops = [],
  4881. coords, ellipseMatrix;
  4882. if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') {
  4883. type = 'linear';
  4884. }
  4885. else {
  4886. type = 'radial';
  4887. }
  4888. if (type === 'linear') {
  4889. coords = getLinearCoords(el);
  4890. }
  4891. else if (type === 'radial') {
  4892. coords = getRadialCoords(el);
  4893. }
  4894. for (var i = colorStopEls.length; i--; ) {
  4895. colorStops.push(getColorStop(colorStopEls[i]));
  4896. }
  4897. ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
  4898. var gradient = new fabric.Gradient({
  4899. type: type,
  4900. coords: coords,
  4901. colorStops: colorStops,
  4902. offsetX: -instance.left,
  4903. offsetY: -instance.top
  4904. });
  4905. if (gradientTransform || ellipseMatrix !== '') {
  4906. gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
  4907. }
  4908. return gradient;
  4909. },
  4910. /* _FROM_SVG_END_ */
  4911. /**
  4912. * Returns {@link fabric.Gradient} instance from its object representation
  4913. * @static
  4914. * @memberOf fabric.Gradient
  4915. * @param {Object} obj
  4916. * @param {Object} [options] Options object
  4917. */
  4918. forObject: function(obj, options) {
  4919. options || (options = { });
  4920. _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
  4921. return new fabric.Gradient(options);
  4922. }
  4923. });
  4924. /**
  4925. * @private
  4926. */
  4927. function _convertPercentUnitsToValues(object, options, gradientUnits) {
  4928. var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
  4929. for (var prop in options) {
  4930. if (options[prop] === 'Infinity') {
  4931. options[prop] = 1;
  4932. }
  4933. else if (options[prop] === '-Infinity') {
  4934. options[prop] = 0;
  4935. }
  4936. propValue = parseFloat(options[prop], 10);
  4937. if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
  4938. multFactor = 0.01;
  4939. }
  4940. else {
  4941. multFactor = 1;
  4942. }
  4943. if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
  4944. multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
  4945. addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
  4946. }
  4947. else if (prop === 'y1' || prop === 'y2') {
  4948. multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
  4949. addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
  4950. }
  4951. options[prop] = propValue * multFactor + addFactor;
  4952. }
  4953. if (object.type === 'ellipse' &&
  4954. options.r2 !== null &&
  4955. gradientUnits === 'objectBoundingBox' &&
  4956. object.rx !== object.ry) {
  4957. var scaleFactor = object.ry / object.rx;
  4958. ellipseMatrix = ' scale(1, ' + scaleFactor + ')';
  4959. if (options.y1) {
  4960. options.y1 /= scaleFactor;
  4961. }
  4962. if (options.y2) {
  4963. options.y2 /= scaleFactor;
  4964. }
  4965. }
  4966. return ellipseMatrix;
  4967. }
  4968. })();
  4969. /**
  4970. * Pattern class
  4971. * @class fabric.Pattern
  4972. * @see {@link http://fabricjs.com/patterns|Pattern demo}
  4973. * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo}
  4974. * @see {@link fabric.Pattern#initialize} for constructor definition
  4975. */
  4976. fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ {
  4977. /**
  4978. * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
  4979. * @type String
  4980. * @default
  4981. */
  4982. repeat: 'repeat',
  4983. /**
  4984. * Pattern horizontal offset from object's left/top corner
  4985. * @type Number
  4986. * @default
  4987. */
  4988. offsetX: 0,
  4989. /**
  4990. * Pattern vertical offset from object's left/top corner
  4991. * @type Number
  4992. * @default
  4993. */
  4994. offsetY: 0,
  4995. /**
  4996. * Constructor
  4997. * @param {Object} [options] Options object
  4998. * @return {fabric.Pattern} thisArg
  4999. */
  5000. initialize: function(options) {
  5001. options || (options = { });
  5002. this.id = fabric.Object.__uid++;
  5003. if (options.source) {
  5004. if (typeof options.source === 'string') {
  5005. // function string
  5006. if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') {
  5007. this.source = new Function(fabric.util.getFunctionBody(options.source));
  5008. }
  5009. else {
  5010. // img src string
  5011. var _this = this;
  5012. this.source = fabric.util.createImage();
  5013. fabric.util.loadImage(options.source, function(img) {
  5014. _this.source = img;
  5015. });
  5016. }
  5017. }
  5018. else {
  5019. // img element
  5020. this.source = options.source;
  5021. }
  5022. }
  5023. if (options.repeat) {
  5024. this.repeat = options.repeat;
  5025. }
  5026. if (options.offsetX) {
  5027. this.offsetX = options.offsetX;
  5028. }
  5029. if (options.offsetY) {
  5030. this.offsetY = options.offsetY;
  5031. }
  5032. },
  5033. /**
  5034. * Returns object representation of a pattern
  5035. * @return {Object} Object representation of a pattern instance
  5036. */
  5037. toObject: function() {
  5038. var source;
  5039. // callback
  5040. if (typeof this.source === 'function') {
  5041. source = String(this.source);
  5042. }
  5043. // <img> element
  5044. else if (typeof this.source.src === 'string') {
  5045. source = this.source.src;
  5046. }
  5047. // <canvas> element
  5048. else if (typeof this.source === 'object' && this.source.toDataURL) {
  5049. source = this.source.toDataURL();
  5050. }
  5051. return {
  5052. source: source,
  5053. repeat: this.repeat,
  5054. offsetX: this.offsetX,
  5055. offsetY: this.offsetY
  5056. };
  5057. },
  5058. /* _TO_SVG_START_ */
  5059. /**
  5060. * Returns SVG representation of a pattern
  5061. * @param {fabric.Object} object
  5062. * @return {String} SVG representation of a pattern
  5063. */
  5064. toSVG: function(object) {
  5065. var patternSource = typeof this.source === 'function' ? this.source() : this.source,
  5066. patternWidth = patternSource.width / object.getWidth(),
  5067. patternHeight = patternSource.height / object.getHeight(),
  5068. patternOffsetX = this.offsetX / object.getWidth(),
  5069. patternOffsetY = this.offsetY / object.getHeight(),
  5070. patternImgSrc = '';
  5071. if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') {
  5072. patternHeight = 1;
  5073. }
  5074. if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') {
  5075. patternWidth = 1;
  5076. }
  5077. if (patternSource.src) {
  5078. patternImgSrc = patternSource.src;
  5079. }
  5080. else if (patternSource.toDataURL) {
  5081. patternImgSrc = patternSource.toDataURL();
  5082. }
  5083. return '<pattern id="SVGID_' + this.id +
  5084. '" x="' + patternOffsetX +
  5085. '" y="' + patternOffsetY +
  5086. '" width="' + patternWidth +
  5087. '" height="' + patternHeight + '">\n' +
  5088. '<image x="0" y="0"' +
  5089. ' width="' + patternSource.width +
  5090. '" height="' + patternSource.height +
  5091. '" xlink:href="' + patternImgSrc +
  5092. '"></image>\n' +
  5093. '</pattern>\n';
  5094. },
  5095. /* _TO_SVG_END_ */
  5096. /**
  5097. * Returns an instance of CanvasPattern
  5098. * @param {CanvasRenderingContext2D} ctx Context to create pattern
  5099. * @return {CanvasPattern}
  5100. */
  5101. toLive: function(ctx) {
  5102. var source = typeof this.source === 'function'
  5103. ? this.source()
  5104. : this.source;
  5105. // if the image failed to load, return, and allow rest to continue loading
  5106. if (!source) {
  5107. return '';
  5108. }
  5109. // if an image
  5110. if (typeof source.src !== 'undefined') {
  5111. if (!source.complete) {
  5112. return '';
  5113. }
  5114. if (source.naturalWidth === 0 || source.naturalHeight === 0) {
  5115. return '';
  5116. }
  5117. }
  5118. return ctx.createPattern(source, this.repeat);
  5119. }
  5120. });
  5121. (function(global) {
  5122. 'use strict';
  5123. var fabric = global.fabric || (global.fabric = { }),
  5124. toFixed = fabric.util.toFixed;
  5125. if (fabric.Shadow) {
  5126. fabric.warn('fabric.Shadow is already defined.');
  5127. return;
  5128. }
  5129. /**
  5130. * Shadow class
  5131. * @class fabric.Shadow
  5132. * @see {@link http://fabricjs.com/shadows|Shadow demo}
  5133. * @see {@link fabric.Shadow#initialize} for constructor definition
  5134. */
  5135. fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ {
  5136. /**
  5137. * Shadow color
  5138. * @type String
  5139. * @default
  5140. */
  5141. color: 'rgb(0,0,0)',
  5142. /**
  5143. * Shadow blur
  5144. * @type Number
  5145. */
  5146. blur: 0,
  5147. /**
  5148. * Shadow horizontal offset
  5149. * @type Number
  5150. * @default
  5151. */
  5152. offsetX: 0,
  5153. /**
  5154. * Shadow vertical offset
  5155. * @type Number
  5156. * @default
  5157. */
  5158. offsetY: 0,
  5159. /**
  5160. * Whether the shadow should affect stroke operations
  5161. * @type Boolean
  5162. * @default
  5163. */
  5164. affectStroke: false,
  5165. /**
  5166. * Indicates whether toObject should include default values
  5167. * @type Boolean
  5168. * @default
  5169. */
  5170. includeDefaultValues: true,
  5171. /**
  5172. * Constructor
  5173. * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)")
  5174. * @return {fabric.Shadow} thisArg
  5175. */
  5176. initialize: function(options) {
  5177. if (typeof options === 'string') {
  5178. options = this._parseShadow(options);
  5179. }
  5180. for (var prop in options) {
  5181. this[prop] = options[prop];
  5182. }
  5183. this.id = fabric.Object.__uid++;
  5184. },
  5185. /**
  5186. * @private
  5187. * @param {String} shadow Shadow value to parse
  5188. * @return {Object} Shadow object with color, offsetX, offsetY and blur
  5189. */
  5190. _parseShadow: function(shadow) {
  5191. var shadowStr = shadow.trim(),
  5192. offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [],
  5193. color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)';
  5194. return {
  5195. color: color.trim(),
  5196. offsetX: parseInt(offsetsAndBlur[1], 10) || 0,
  5197. offsetY: parseInt(offsetsAndBlur[2], 10) || 0,
  5198. blur: parseInt(offsetsAndBlur[3], 10) || 0
  5199. };
  5200. },
  5201. /**
  5202. * Returns a string representation of an instance
  5203. * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow
  5204. * @return {String} Returns CSS3 text-shadow declaration
  5205. */
  5206. toString: function() {
  5207. return [this.offsetX, this.offsetY, this.blur, this.color].join('px ');
  5208. },
  5209. /* _TO_SVG_START_ */
  5210. /**
  5211. * Returns SVG representation of a shadow
  5212. * @param {fabric.Object} object
  5213. * @return {String} SVG representation of a shadow
  5214. */
  5215. toSVG: function(object) {
  5216. var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  5217. offset = fabric.util.rotateVector(
  5218. { x: this.offsetX, y: this.offsetY },
  5219. fabric.util.degreesToRadians(-object.angle)),
  5220. BLUR_BOX = 20;
  5221. if (object.width && object.height) {
  5222. //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion
  5223. // we add some extra space to filter box to contain the blur ( 20 )
  5224. fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
  5225. fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX;
  5226. }
  5227. if (object.flipX) {
  5228. offset.x *= -1;
  5229. }
  5230. if (object.flipY) {
  5231. offset.y *= -1;
  5232. }
  5233. return (
  5234. '<filter id="SVGID_' + this.id + '" y="-' + fBoxY + '%" height="' + (100 + 2 * fBoxY) + '%" ' +
  5235. 'x="-' + fBoxX + '%" width="' + (100 + 2 * fBoxX) + '%" ' + '>\n' +
  5236. '\t<feGaussianBlur in="SourceAlpha" stdDeviation="' +
  5237. toFixed(this.blur ? this.blur / 2 : 0, NUM_FRACTION_DIGITS) + '"></feGaussianBlur>\n' +
  5238. '\t<feOffset dx="' + toFixed(offset.x, NUM_FRACTION_DIGITS) +
  5239. '" dy="' + toFixed(offset.y, NUM_FRACTION_DIGITS) + '" result="oBlur" ></feOffset>\n' +
  5240. '\t<feFlood flood-color="' + this.color + '"/>\n' +
  5241. '\t<feComposite in2="oBlur" operator="in" />\n' +
  5242. '\t<feMerge>\n' +
  5243. '\t\t<feMergeNode></feMergeNode>\n' +
  5244. '\t\t<feMergeNode in="SourceGraphic"></feMergeNode>\n' +
  5245. '\t</feMerge>\n' +
  5246. '</filter>\n');
  5247. },
  5248. /* _TO_SVG_END_ */
  5249. /**
  5250. * Returns object representation of a shadow
  5251. * @return {Object} Object representation of a shadow instance
  5252. */
  5253. toObject: function() {
  5254. if (this.includeDefaultValues) {
  5255. return {
  5256. color: this.color,
  5257. blur: this.blur,
  5258. offsetX: this.offsetX,
  5259. offsetY: this.offsetY,
  5260. affectStroke: this.affectStroke
  5261. };
  5262. }
  5263. var obj = { }, proto = fabric.Shadow.prototype;
  5264. ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke'].forEach(function(prop) {
  5265. if (this[prop] !== proto[prop]) {
  5266. obj[prop] = this[prop];
  5267. }
  5268. }, this);
  5269. return obj;
  5270. }
  5271. });
  5272. /**
  5273. * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
  5274. * @static
  5275. * @field
  5276. * @memberOf fabric.Shadow
  5277. */
  5278. // eslint-disable-next-line max-len
  5279. fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/;
  5280. })(typeof exports !== 'undefined' ? exports : this);
  5281. (function () {
  5282. 'use strict';
  5283. if (fabric.StaticCanvas) {
  5284. fabric.warn('fabric.StaticCanvas is already defined.');
  5285. return;
  5286. }
  5287. // aliases for faster resolution
  5288. var extend = fabric.util.object.extend,
  5289. getElementOffset = fabric.util.getElementOffset,
  5290. removeFromArray = fabric.util.removeFromArray,
  5291. toFixed = fabric.util.toFixed,
  5292. CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
  5293. /**
  5294. * Static canvas class
  5295. * @class fabric.StaticCanvas
  5296. * @mixes fabric.Collection
  5297. * @mixes fabric.Observable
  5298. * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo}
  5299. * @see {@link fabric.StaticCanvas#initialize} for constructor definition
  5300. * @fires before:render
  5301. * @fires after:render
  5302. * @fires canvas:cleared
  5303. * @fires object:added
  5304. * @fires object:removed
  5305. */
  5306. fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ {
  5307. /**
  5308. * Constructor
  5309. * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
  5310. * @param {Object} [options] Options object
  5311. * @return {Object} thisArg
  5312. */
  5313. initialize: function(el, options) {
  5314. options || (options = { });
  5315. this._initStatic(el, options);
  5316. },
  5317. /**
  5318. * Background color of canvas instance.
  5319. * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}.
  5320. * @type {(String|fabric.Pattern)}
  5321. * @default
  5322. */
  5323. backgroundColor: '',
  5324. /**
  5325. * Background image of canvas instance.
  5326. * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}.
  5327. * <b>Backwards incompatibility note:</b> The "backgroundImageOpacity"
  5328. * and "backgroundImageStretch" properties are deprecated since 1.3.9.
  5329. * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}.
  5330. * @type fabric.Image
  5331. * @default
  5332. */
  5333. backgroundImage: null,
  5334. /**
  5335. * Overlay color of canvas instance.
  5336. * Should be set via {@link fabric.StaticCanvas#setOverlayColor}
  5337. * @since 1.3.9
  5338. * @type {(String|fabric.Pattern)}
  5339. * @default
  5340. */
  5341. overlayColor: '',
  5342. /**
  5343. * Overlay image of canvas instance.
  5344. * Should be set via {@link fabric.StaticCanvas#setOverlayImage}.
  5345. * <b>Backwards incompatibility note:</b> The "overlayImageLeft"
  5346. * and "overlayImageTop" properties are deprecated since 1.3.9.
  5347. * Use {@link fabric.Image#left} and {@link fabric.Image#top}.
  5348. * @type fabric.Image
  5349. * @default
  5350. */
  5351. overlayImage: null,
  5352. /**
  5353. * Indicates whether toObject/toDatalessObject should include default values
  5354. * @type Boolean
  5355. * @default
  5356. */
  5357. includeDefaultValues: true,
  5358. /**
  5359. * Indicates whether objects' state should be saved
  5360. * @type Boolean
  5361. * @default
  5362. */
  5363. stateful: true,
  5364. /**
  5365. * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas.
  5366. * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once
  5367. * (followed by a manual rendering after addition/deletion)
  5368. * @type Boolean
  5369. * @default
  5370. */
  5371. renderOnAddRemove: true,
  5372. /**
  5373. * Function that determines clipping of entire canvas area
  5374. * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ}
  5375. * @type Function
  5376. * @default
  5377. */
  5378. clipTo: null,
  5379. /**
  5380. * Indicates whether object controls (borders/controls) are rendered above overlay image
  5381. * @type Boolean
  5382. * @default
  5383. */
  5384. controlsAboveOverlay: false,
  5385. /**
  5386. * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas
  5387. * @type Boolean
  5388. * @default
  5389. */
  5390. allowTouchScrolling: false,
  5391. /**
  5392. * Indicates whether this canvas will use image smoothing, this is on by default in browsers
  5393. * @type Boolean
  5394. * @default
  5395. */
  5396. imageSmoothingEnabled: true,
  5397. /**
  5398. * The transformation (in the format of Canvas transform) which focuses the viewport
  5399. * @type Array
  5400. * @default
  5401. */
  5402. viewportTransform: [1, 0, 0, 1, 0, 0],
  5403. /**
  5404. * if set to false background image is not affected by viewport transform
  5405. * @since 1.6.3
  5406. * @type Boolean
  5407. * @default
  5408. */
  5409. backgroundVpt: true,
  5410. /**
  5411. * if set to false overlya image is not affected by viewport transform
  5412. * @since 1.6.3
  5413. * @type Boolean
  5414. * @default
  5415. */
  5416. overlayVpt: true,
  5417. /**
  5418. * Callback; invoked right before object is about to be scaled/rotated
  5419. */
  5420. onBeforeScaleRotate: function () {
  5421. /* NOOP */
  5422. },
  5423. /**
  5424. * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens
  5425. */
  5426. enableRetinaScaling: true,
  5427. /**
  5428. * @private
  5429. * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
  5430. * @param {Object} [options] Options object
  5431. */
  5432. _initStatic: function(el, options) {
  5433. var cb = fabric.StaticCanvas.prototype.renderAll.bind(this);
  5434. this._objects = [];
  5435. this._createLowerCanvas(el);
  5436. this._initOptions(options);
  5437. this._setImageSmoothing();
  5438. // only initialize retina scaling once
  5439. if (!this.interactive) {
  5440. this._initRetinaScaling();
  5441. }
  5442. if (options.overlayImage) {
  5443. this.setOverlayImage(options.overlayImage, cb);
  5444. }
  5445. if (options.backgroundImage) {
  5446. this.setBackgroundImage(options.backgroundImage, cb);
  5447. }
  5448. if (options.backgroundColor) {
  5449. this.setBackgroundColor(options.backgroundColor, cb);
  5450. }
  5451. if (options.overlayColor) {
  5452. this.setOverlayColor(options.overlayColor, cb);
  5453. }
  5454. this.calcOffset();
  5455. },
  5456. /**
  5457. * @private
  5458. */
  5459. _isRetinaScaling: function() {
  5460. return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling);
  5461. },
  5462. /**
  5463. * @private
  5464. * @return {Number} retinaScaling if applied, otherwise 1;
  5465. */
  5466. getRetinaScaling: function() {
  5467. return this._isRetinaScaling() ? fabric.devicePixelRatio : 1;
  5468. },
  5469. /**
  5470. * @private
  5471. */
  5472. _initRetinaScaling: function() {
  5473. if (!this._isRetinaScaling()) {
  5474. return;
  5475. }
  5476. this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
  5477. this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);
  5478. this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
  5479. },
  5480. /**
  5481. * Calculates canvas element offset relative to the document
  5482. * This method is also attached as "resize" event handler of window
  5483. * @return {fabric.Canvas} instance
  5484. * @chainable
  5485. */
  5486. calcOffset: function () {
  5487. this._offset = getElementOffset(this.lowerCanvasEl);
  5488. return this;
  5489. },
  5490. /**
  5491. * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas
  5492. * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to
  5493. * @param {Function} callback callback to invoke when image is loaded and set as an overlay
  5494. * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}.
  5495. * @return {fabric.Canvas} thisArg
  5496. * @chainable
  5497. * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo}
  5498. * @example <caption>Normal overlayImage with left/top = 0</caption>
  5499. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5500. * // Needed to position overlayImage at 0/0
  5501. * originX: 'left',
  5502. * originY: 'top'
  5503. * });
  5504. * @example <caption>overlayImage with different properties</caption>
  5505. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5506. * opacity: 0.5,
  5507. * angle: 45,
  5508. * left: 400,
  5509. * top: 400,
  5510. * originX: 'left',
  5511. * originY: 'top'
  5512. * });
  5513. * @example <caption>Stretched overlayImage #1 - width/height correspond to canvas width/height</caption>
  5514. * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) {
  5515. * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
  5516. * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas));
  5517. * });
  5518. * @example <caption>Stretched overlayImage #2 - width/height correspond to canvas width/height</caption>
  5519. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5520. * width: canvas.width,
  5521. * height: canvas.height,
  5522. * // Needed to position overlayImage at 0/0
  5523. * originX: 'left',
  5524. * originY: 'top'
  5525. * });
  5526. * @example <caption>overlayImage loaded from cross-origin</caption>
  5527. * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
  5528. * opacity: 0.5,
  5529. * angle: 45,
  5530. * left: 400,
  5531. * top: 400,
  5532. * originX: 'left',
  5533. * originY: 'top',
  5534. * crossOrigin: 'anonymous'
  5535. * });
  5536. */
  5537. setOverlayImage: function (image, callback, options) {
  5538. return this.__setBgOverlayImage('overlayImage', image, callback, options);
  5539. },
  5540. /**
  5541. * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas
  5542. * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to
  5543. * @param {Function} callback Callback to invoke when image is loaded and set as background
  5544. * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}.
  5545. * @return {fabric.Canvas} thisArg
  5546. * @chainable
  5547. * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo}
  5548. * @example <caption>Normal backgroundImage with left/top = 0</caption>
  5549. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5550. * // Needed to position backgroundImage at 0/0
  5551. * originX: 'left',
  5552. * originY: 'top'
  5553. * });
  5554. * @example <caption>backgroundImage with different properties</caption>
  5555. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5556. * opacity: 0.5,
  5557. * angle: 45,
  5558. * left: 400,
  5559. * top: 400,
  5560. * originX: 'left',
  5561. * originY: 'top'
  5562. * });
  5563. * @example <caption>Stretched backgroundImage #1 - width/height correspond to canvas width/height</caption>
  5564. * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) {
  5565. * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'});
  5566. * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
  5567. * });
  5568. * @example <caption>Stretched backgroundImage #2 - width/height correspond to canvas width/height</caption>
  5569. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5570. * width: canvas.width,
  5571. * height: canvas.height,
  5572. * // Needed to position backgroundImage at 0/0
  5573. * originX: 'left',
  5574. * originY: 'top'
  5575. * });
  5576. * @example <caption>backgroundImage loaded from cross-origin</caption>
  5577. * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), {
  5578. * opacity: 0.5,
  5579. * angle: 45,
  5580. * left: 400,
  5581. * top: 400,
  5582. * originX: 'left',
  5583. * originY: 'top',
  5584. * crossOrigin: 'anonymous'
  5585. * });
  5586. */
  5587. setBackgroundImage: function (image, callback, options) {
  5588. return this.__setBgOverlayImage('backgroundImage', image, callback, options);
  5589. },
  5590. /**
  5591. * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas
  5592. * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to
  5593. * @param {Function} callback Callback to invoke when background color is set
  5594. * @return {fabric.Canvas} thisArg
  5595. * @chainable
  5596. * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo}
  5597. * @example <caption>Normal overlayColor - color value</caption>
  5598. * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
  5599. * @example <caption>fabric.Pattern used as overlayColor</caption>
  5600. * canvas.setOverlayColor({
  5601. * source: 'http://fabricjs.com/assets/escheresque_ste.png'
  5602. * }, canvas.renderAll.bind(canvas));
  5603. * @example <caption>fabric.Pattern used as overlayColor with repeat and offset</caption>
  5604. * canvas.setOverlayColor({
  5605. * source: 'http://fabricjs.com/assets/escheresque_ste.png',
  5606. * repeat: 'repeat',
  5607. * offsetX: 200,
  5608. * offsetY: 100
  5609. * }, canvas.renderAll.bind(canvas));
  5610. */
  5611. setOverlayColor: function(overlayColor, callback) {
  5612. return this.__setBgOverlayColor('overlayColor', overlayColor, callback);
  5613. },
  5614. /**
  5615. * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas
  5616. * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to
  5617. * @param {Function} callback Callback to invoke when background color is set
  5618. * @return {fabric.Canvas} thisArg
  5619. * @chainable
  5620. * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo}
  5621. * @example <caption>Normal backgroundColor - color value</caption>
  5622. * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas));
  5623. * @example <caption>fabric.Pattern used as backgroundColor</caption>
  5624. * canvas.setBackgroundColor({
  5625. * source: 'http://fabricjs.com/assets/escheresque_ste.png'
  5626. * }, canvas.renderAll.bind(canvas));
  5627. * @example <caption>fabric.Pattern used as backgroundColor with repeat and offset</caption>
  5628. * canvas.setBackgroundColor({
  5629. * source: 'http://fabricjs.com/assets/escheresque_ste.png',
  5630. * repeat: 'repeat',
  5631. * offsetX: 200,
  5632. * offsetY: 100
  5633. * }, canvas.renderAll.bind(canvas));
  5634. */
  5635. setBackgroundColor: function(backgroundColor, callback) {
  5636. return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback);
  5637. },
  5638. /**
  5639. * @private
  5640. * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard}
  5641. */
  5642. _setImageSmoothing: function() {
  5643. var ctx = this.getContext();
  5644. ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled
  5645. || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled;
  5646. ctx.imageSmoothingEnabled = this.imageSmoothingEnabled;
  5647. },
  5648. /**
  5649. * @private
  5650. * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage}
  5651. * or {@link fabric.StaticCanvas#overlayImage|overlayImage})
  5652. * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to
  5653. * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay
  5654. * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}.
  5655. */
  5656. __setBgOverlayImage: function(property, image, callback, options) {
  5657. if (typeof image === 'string') {
  5658. fabric.util.loadImage(image, function(img) {
  5659. img && (this[property] = new fabric.Image(img, options));
  5660. callback && callback(img);
  5661. }, this, options && options.crossOrigin);
  5662. }
  5663. else {
  5664. options && image.setOptions(options);
  5665. this[property] = image;
  5666. callback && callback(image);
  5667. }
  5668. return this;
  5669. },
  5670. /**
  5671. * @private
  5672. * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor}
  5673. * or {@link fabric.StaticCanvas#overlayColor|overlayColor})
  5674. * @param {(Object|String|null)} color Object with pattern information, color value or null
  5675. * @param {Function} [callback] Callback is invoked when color is set
  5676. */
  5677. __setBgOverlayColor: function(property, color, callback) {
  5678. if (color && color.source) {
  5679. var _this = this;
  5680. fabric.util.loadImage(color.source, function(img) {
  5681. _this[property] = new fabric.Pattern({
  5682. source: img,
  5683. repeat: color.repeat,
  5684. offsetX: color.offsetX,
  5685. offsetY: color.offsetY
  5686. });
  5687. callback && callback();
  5688. });
  5689. }
  5690. else {
  5691. this[property] = color;
  5692. callback && callback();
  5693. }
  5694. return this;
  5695. },
  5696. /**
  5697. * @private
  5698. */
  5699. _createCanvasElement: function(canvasEl) {
  5700. var element = fabric.util.createCanvasElement(canvasEl)
  5701. if (!element.style) {
  5702. element.style = { };
  5703. }
  5704. if (!element) {
  5705. throw CANVAS_INIT_ERROR;
  5706. }
  5707. if (typeof element.getContext === 'undefined') {
  5708. throw CANVAS_INIT_ERROR;
  5709. }
  5710. return element;
  5711. },
  5712. /**
  5713. * @private
  5714. * @param {Object} [options] Options object
  5715. */
  5716. _initOptions: function (options) {
  5717. for (var prop in options) {
  5718. this[prop] = options[prop];
  5719. }
  5720. this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0;
  5721. this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0;
  5722. if (!this.lowerCanvasEl.style) {
  5723. return;
  5724. }
  5725. this.lowerCanvasEl.width = this.width;
  5726. this.lowerCanvasEl.height = this.height;
  5727. this.lowerCanvasEl.style.width = this.width + 'px';
  5728. this.lowerCanvasEl.style.height = this.height + 'px';
  5729. this.viewportTransform = this.viewportTransform.slice();
  5730. },
  5731. /**
  5732. * Creates a bottom canvas
  5733. * @private
  5734. * @param {HTMLElement} [canvasEl]
  5735. */
  5736. _createLowerCanvas: function (canvasEl) {
  5737. this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(canvasEl);
  5738. fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
  5739. if (this.interactive) {
  5740. this._applyCanvasStyle(this.lowerCanvasEl);
  5741. }
  5742. this.contextContainer = this.lowerCanvasEl.getContext('2d');
  5743. },
  5744. /**
  5745. * Returns canvas width (in px)
  5746. * @return {Number}
  5747. */
  5748. getWidth: function () {
  5749. return this.width;
  5750. },
  5751. /**
  5752. * Returns canvas height (in px)
  5753. * @return {Number}
  5754. */
  5755. getHeight: function () {
  5756. return this.height;
  5757. },
  5758. /**
  5759. * Sets width of this canvas instance
  5760. * @param {Number|String} value Value to set width to
  5761. * @param {Object} [options] Options object
  5762. * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
  5763. * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
  5764. * @return {fabric.Canvas} instance
  5765. * @chainable true
  5766. */
  5767. setWidth: function (value, options) {
  5768. return this.setDimensions({ width: value }, options);
  5769. },
  5770. /**
  5771. * Sets height of this canvas instance
  5772. * @param {Number|String} value Value to set height to
  5773. * @param {Object} [options] Options object
  5774. * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
  5775. * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
  5776. * @return {fabric.Canvas} instance
  5777. * @chainable true
  5778. */
  5779. setHeight: function (value, options) {
  5780. return this.setDimensions({ height: value }, options);
  5781. },
  5782. /**
  5783. * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em)
  5784. * @param {Object} dimensions Object with width/height properties
  5785. * @param {Number|String} [dimensions.width] Width of canvas element
  5786. * @param {Number|String} [dimensions.height] Height of canvas element
  5787. * @param {Object} [options] Options object
  5788. * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions
  5789. * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions
  5790. * @return {fabric.Canvas} thisArg
  5791. * @chainable
  5792. */
  5793. setDimensions: function (dimensions, options) {
  5794. var cssValue;
  5795. options = options || {};
  5796. for (var prop in dimensions) {
  5797. cssValue = dimensions[prop];
  5798. if (!options.cssOnly) {
  5799. this._setBackstoreDimension(prop, dimensions[prop]);
  5800. cssValue += 'px';
  5801. }
  5802. if (!options.backstoreOnly) {
  5803. this._setCssDimension(prop, cssValue);
  5804. }
  5805. }
  5806. this._initRetinaScaling();
  5807. this._setImageSmoothing();
  5808. this.calcOffset();
  5809. if (!options.cssOnly) {
  5810. this.renderAll();
  5811. }
  5812. return this;
  5813. },
  5814. /**
  5815. * Helper for setting width/height
  5816. * @private
  5817. * @param {String} prop property (width|height)
  5818. * @param {Number} value value to set property to
  5819. * @return {fabric.Canvas} instance
  5820. * @chainable true
  5821. */
  5822. _setBackstoreDimension: function (prop, value) {
  5823. this.lowerCanvasEl[prop] = value;
  5824. if (this.upperCanvasEl) {
  5825. this.upperCanvasEl[prop] = value;
  5826. }
  5827. if (this.cacheCanvasEl) {
  5828. this.cacheCanvasEl[prop] = value;
  5829. }
  5830. this[prop] = value;
  5831. return this;
  5832. },
  5833. /**
  5834. * Helper for setting css width/height
  5835. * @private
  5836. * @param {String} prop property (width|height)
  5837. * @param {String} value value to set property to
  5838. * @return {fabric.Canvas} instance
  5839. * @chainable true
  5840. */
  5841. _setCssDimension: function (prop, value) {
  5842. this.lowerCanvasEl.style[prop] = value;
  5843. if (this.upperCanvasEl) {
  5844. this.upperCanvasEl.style[prop] = value;
  5845. }
  5846. if (this.wrapperEl) {
  5847. this.wrapperEl.style[prop] = value;
  5848. }
  5849. return this;
  5850. },
  5851. /**
  5852. * Returns canvas zoom level
  5853. * @return {Number}
  5854. */
  5855. getZoom: function () {
  5856. return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]);
  5857. },
  5858. /**
  5859. * Sets viewport transform of this canvas instance
  5860. * @param {Array} vpt the transform in the form of context.transform
  5861. * @return {fabric.Canvas} instance
  5862. * @chainable true
  5863. */
  5864. setViewportTransform: function (vpt) {
  5865. var activeGroup = this._activeGroup, object;
  5866. this.viewportTransform = vpt;
  5867. for (var i = 0, len = this._objects.length; i < len; i++) {
  5868. object = this._objects[i];
  5869. object.group || object.setCoords();
  5870. }
  5871. if (activeGroup) {
  5872. activeGroup.setCoords();
  5873. }
  5874. this.renderAll();
  5875. return this;
  5876. },
  5877. /**
  5878. * Sets zoom level of this canvas instance, zoom centered around point
  5879. * @param {fabric.Point} point to zoom with respect to
  5880. * @param {Number} value to set zoom to, less than 1 zooms out
  5881. * @return {fabric.Canvas} instance
  5882. * @chainable true
  5883. */
  5884. zoomToPoint: function (point, value) {
  5885. // TODO: just change the scale, preserve other transformations
  5886. var before = point, vpt = this.viewportTransform.slice(0);
  5887. point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform));
  5888. vpt[0] = value;
  5889. vpt[3] = value;
  5890. var after = fabric.util.transformPoint(point, vpt);
  5891. vpt[4] += before.x - after.x;
  5892. vpt[5] += before.y - after.y;
  5893. return this.setViewportTransform(vpt);
  5894. },
  5895. /**
  5896. * Sets zoom level of this canvas instance
  5897. * @param {Number} value to set zoom to, less than 1 zooms out
  5898. * @return {fabric.Canvas} instance
  5899. * @chainable true
  5900. */
  5901. setZoom: function (value) {
  5902. this.zoomToPoint(new fabric.Point(0, 0), value);
  5903. return this;
  5904. },
  5905. /**
  5906. * Pan viewport so as to place point at top left corner of canvas
  5907. * @param {fabric.Point} point to move to
  5908. * @return {fabric.Canvas} instance
  5909. * @chainable true
  5910. */
  5911. absolutePan: function (point) {
  5912. var vpt = this.viewportTransform.slice(0);
  5913. vpt[4] = -point.x;
  5914. vpt[5] = -point.y;
  5915. return this.setViewportTransform(vpt);
  5916. },
  5917. /**
  5918. * Pans viewpoint relatively
  5919. * @param {fabric.Point} point (position vector) to move by
  5920. * @return {fabric.Canvas} instance
  5921. * @chainable true
  5922. */
  5923. relativePan: function (point) {
  5924. return this.absolutePan(new fabric.Point(
  5925. -point.x - this.viewportTransform[4],
  5926. -point.y - this.viewportTransform[5]
  5927. ));
  5928. },
  5929. /**
  5930. * Returns &lt;canvas> element corresponding to this instance
  5931. * @return {HTMLCanvasElement}
  5932. */
  5933. getElement: function () {
  5934. return this.lowerCanvasEl;
  5935. },
  5936. /**
  5937. * @private
  5938. * @param {fabric.Object} obj Object that was added
  5939. */
  5940. _onObjectAdded: function(obj) {
  5941. this.stateful && obj.setupState();
  5942. obj._set('canvas', this);
  5943. obj.setCoords();
  5944. this.fire('object:added', { target: obj });
  5945. obj.fire('added');
  5946. },
  5947. /**
  5948. * @private
  5949. * @param {fabric.Object} obj Object that was removed
  5950. */
  5951. _onObjectRemoved: function(obj) {
  5952. this.fire('object:removed', { target: obj });
  5953. obj.fire('removed');
  5954. delete obj.canvas;
  5955. },
  5956. /**
  5957. * Clears specified context of canvas element
  5958. * @param {CanvasRenderingContext2D} ctx Context to clear
  5959. * @return {fabric.Canvas} thisArg
  5960. * @chainable
  5961. */
  5962. clearContext: function(ctx) {
  5963. ctx.clearRect(0, 0, this.width, this.height);
  5964. return this;
  5965. },
  5966. /**
  5967. * Returns context of canvas where objects are drawn
  5968. * @return {CanvasRenderingContext2D}
  5969. */
  5970. getContext: function () {
  5971. return this.contextContainer;
  5972. },
  5973. /**
  5974. * Clears all contexts (background, main, top) of an instance
  5975. * @return {fabric.Canvas} thisArg
  5976. * @chainable
  5977. */
  5978. clear: function () {
  5979. this._objects.length = 0;
  5980. this.backgroundImage = null;
  5981. this.overlayImage = null;
  5982. this.backgroundColor = '';
  5983. this.overlayColor = ''
  5984. if (this._hasITextHandlers) {
  5985. this.off('selection:cleared', this._canvasITextSelectionClearedHanlder);
  5986. this.off('object:selected', this._canvasITextSelectionClearedHanlder);
  5987. this.off('mouse:up', this._mouseUpITextHandler);
  5988. this._iTextInstances = null;
  5989. this._hasITextHandlers = false;
  5990. }
  5991. this.clearContext(this.contextContainer);
  5992. this.fire('canvas:cleared');
  5993. this.renderAll();
  5994. return this;
  5995. },
  5996. /**
  5997. * Renders both the canvas.
  5998. * @return {fabric.Canvas} instance
  5999. * @chainable
  6000. */
  6001. renderAll: function () {
  6002. var canvasToDrawOn = this.contextContainer;
  6003. this.renderCanvas(canvasToDrawOn, this._objects);
  6004. return this;
  6005. },
  6006. /**
  6007. * Renders background, objects, overlay and controls.
  6008. * @param {CanvasRenderingContext2D} ctx
  6009. * @param {Array} objects to render
  6010. * @return {fabric.Canvas} instance
  6011. * @chainable
  6012. */
  6013. renderCanvas: function(ctx, objects) {
  6014. this.clearContext(ctx);
  6015. this.fire('before:render');
  6016. if (this.clipTo) {
  6017. fabric.util.clipContext(this, ctx);
  6018. }
  6019. this._renderBackground(ctx);
  6020. ctx.save();
  6021. //apply viewport transform once for all rendering process
  6022. ctx.transform.apply(ctx, this.viewportTransform);
  6023. this._renderObjects(ctx, objects);
  6024. ctx.restore();
  6025. if (!this.controlsAboveOverlay && this.interactive) {
  6026. this.drawControls(ctx);
  6027. }
  6028. if (this.clipTo) {
  6029. ctx.restore();
  6030. }
  6031. this._renderOverlay(ctx);
  6032. if (this.controlsAboveOverlay && this.interactive) {
  6033. this.drawControls(ctx);
  6034. }
  6035. this.fire('after:render');
  6036. },
  6037. /**
  6038. * @private
  6039. * @param {CanvasRenderingContext2D} ctx Context to render on
  6040. * @param {Array} objects to render
  6041. */
  6042. _renderObjects: function(ctx, objects) {
  6043. for (var i = 0, length = objects.length; i < length; ++i) {
  6044. objects[i] && objects[i].render(ctx);
  6045. }
  6046. },
  6047. /**
  6048. * @private
  6049. * @param {CanvasRenderingContext2D} ctx Context to render on
  6050. * @param {string} property 'background' or 'overlay'
  6051. */
  6052. _renderBackgroundOrOverlay: function(ctx, property) {
  6053. var object = this[property + 'Color'];
  6054. if (object) {
  6055. ctx.fillStyle = object.toLive
  6056. ? object.toLive(ctx)
  6057. : object;
  6058. ctx.fillRect(
  6059. object.offsetX || 0,
  6060. object.offsetY || 0,
  6061. this.width,
  6062. this.height);
  6063. }
  6064. object = this[property + 'Image'];
  6065. if (object) {
  6066. if (this[property + 'Vpt']) {
  6067. ctx.save();
  6068. ctx.transform.apply(ctx, this.viewportTransform);
  6069. }
  6070. object.render(ctx);
  6071. this[property + 'Vpt'] && ctx.restore();
  6072. }
  6073. },
  6074. /**
  6075. * @private
  6076. * @param {CanvasRenderingContext2D} ctx Context to render on
  6077. */
  6078. _renderBackground: function(ctx) {
  6079. this._renderBackgroundOrOverlay(ctx, 'background');
  6080. },
  6081. /**
  6082. * @private
  6083. * @param {CanvasRenderingContext2D} ctx Context to render on
  6084. */
  6085. _renderOverlay: function(ctx) {
  6086. this._renderBackgroundOrOverlay(ctx, 'overlay');
  6087. },
  6088. /**
  6089. * Returns coordinates of a center of canvas.
  6090. * Returned value is an object with top and left properties
  6091. * @return {Object} object with "top" and "left" number values
  6092. */
  6093. getCenter: function () {
  6094. return {
  6095. top: this.getHeight() / 2,
  6096. left: this.getWidth() / 2
  6097. };
  6098. },
  6099. /**
  6100. * Centers object horizontally in the canvas
  6101. * You might need to call `setCoords` on an object after centering, to update controls area.
  6102. * @param {fabric.Object} object Object to center horizontally
  6103. * @return {fabric.Canvas} thisArg
  6104. */
  6105. centerObjectH: function (object) {
  6106. return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y));
  6107. },
  6108. /**
  6109. * Centers object vertically in the canvas
  6110. * You might need to call `setCoords` on an object after centering, to update controls area.
  6111. * @param {fabric.Object} object Object to center vertically
  6112. * @return {fabric.Canvas} thisArg
  6113. * @chainable
  6114. */
  6115. centerObjectV: function (object) {
  6116. return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top));
  6117. },
  6118. /**
  6119. * Centers object vertically and horizontally in the canvas
  6120. * You might need to call `setCoords` on an object after centering, to update controls area.
  6121. * @param {fabric.Object} object Object to center vertically and horizontally
  6122. * @return {fabric.Canvas} thisArg
  6123. * @chainable
  6124. */
  6125. centerObject: function(object) {
  6126. var center = this.getCenter();
  6127. return this._centerObject(object, new fabric.Point(center.left, center.top));
  6128. },
  6129. /**
  6130. * Centers object vertically and horizontally in the viewport
  6131. * You might need to call `setCoords` on an object after centering, to update controls area.
  6132. * @param {fabric.Object} object Object to center vertically and horizontally
  6133. * @return {fabric.Canvas} thisArg
  6134. * @chainable
  6135. */
  6136. viewportCenterObject: function(object) {
  6137. var vpCenter = this.getVpCenter();
  6138. return this._centerObject(object, vpCenter);
  6139. },
  6140. /**
  6141. * Centers object horizontally in the viewport, object.top is unchanged
  6142. * You might need to call `setCoords` on an object after centering, to update controls area.
  6143. * @param {fabric.Object} object Object to center vertically and horizontally
  6144. * @return {fabric.Canvas} thisArg
  6145. * @chainable
  6146. */
  6147. viewportCenterObjectH: function(object) {
  6148. var vpCenter = this.getVpCenter();
  6149. this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y));
  6150. return this;
  6151. },
  6152. /**
  6153. * Centers object Vertically in the viewport, object.top is unchanged
  6154. * You might need to call `setCoords` on an object after centering, to update controls area.
  6155. * @param {fabric.Object} object Object to center vertically and horizontally
  6156. * @return {fabric.Canvas} thisArg
  6157. * @chainable
  6158. */
  6159. viewportCenterObjectV: function(object) {
  6160. var vpCenter = this.getVpCenter();
  6161. return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y));
  6162. },
  6163. /**
  6164. * Calculate the point in canvas that correspond to the center of actual viewport.
  6165. * @return {fabric.Point} vpCenter, viewport center
  6166. * @chainable
  6167. */
  6168. getVpCenter: function() {
  6169. var center = this.getCenter(),
  6170. iVpt = fabric.util.invertTransform(this.viewportTransform);
  6171. return fabric.util.transformPoint({ x: center.left, y: center.top }, iVpt);
  6172. },
  6173. /**
  6174. * @private
  6175. * @param {fabric.Object} object Object to center
  6176. * @param {fabric.Point} center Center point
  6177. * @return {fabric.Canvas} thisArg
  6178. * @chainable
  6179. */
  6180. _centerObject: function(object, center) {
  6181. object.setPositionByOrigin(center, 'center', 'center');
  6182. this.renderAll();
  6183. return this;
  6184. },
  6185. /**
  6186. * Returs dataless JSON representation of canvas
  6187. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6188. * @return {String} json string
  6189. */
  6190. toDatalessJSON: function (propertiesToInclude) {
  6191. return this.toDatalessObject(propertiesToInclude);
  6192. },
  6193. /**
  6194. * Returns object representation of canvas
  6195. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6196. * @return {Object} object representation of an instance
  6197. */
  6198. toObject: function (propertiesToInclude) {
  6199. return this._toObjectMethod('toObject', propertiesToInclude);
  6200. },
  6201. /**
  6202. * Returns dataless object representation of canvas
  6203. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6204. * @return {Object} object representation of an instance
  6205. */
  6206. toDatalessObject: function (propertiesToInclude) {
  6207. return this._toObjectMethod('toDatalessObject', propertiesToInclude);
  6208. },
  6209. /**
  6210. * @private
  6211. */
  6212. _toObjectMethod: function (methodName, propertiesToInclude) {
  6213. var data = {
  6214. objects: this._toObjects(methodName, propertiesToInclude)
  6215. };
  6216. extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
  6217. fabric.util.populateWithProperties(this, data, propertiesToInclude);
  6218. return data;
  6219. },
  6220. /**
  6221. * @private
  6222. */
  6223. _toObjects: function(methodName, propertiesToInclude) {
  6224. return this.getObjects().filter(function(object) {
  6225. return !object.excludeFromExport;
  6226. }).map(function(instance) {
  6227. return this._toObject(instance, methodName, propertiesToInclude);
  6228. }, this);
  6229. },
  6230. /**
  6231. * @private
  6232. */
  6233. _toObject: function(instance, methodName, propertiesToInclude) {
  6234. var originalValue;
  6235. if (!this.includeDefaultValues) {
  6236. originalValue = instance.includeDefaultValues;
  6237. instance.includeDefaultValues = false;
  6238. }
  6239. var object = instance[methodName](propertiesToInclude);
  6240. if (!this.includeDefaultValues) {
  6241. instance.includeDefaultValues = originalValue;
  6242. }
  6243. return object;
  6244. },
  6245. /**
  6246. * @private
  6247. */
  6248. __serializeBgOverlay: function(methodName, propertiesToInclude) {
  6249. var data = {
  6250. background: (this.backgroundColor && this.backgroundColor.toObject)
  6251. ? this.backgroundColor.toObject(propertiesToInclude)
  6252. : this.backgroundColor
  6253. };
  6254. if (this.overlayColor) {
  6255. data.overlay = this.overlayColor.toObject
  6256. ? this.overlayColor.toObject(propertiesToInclude)
  6257. : this.overlayColor;
  6258. }
  6259. if (this.backgroundImage) {
  6260. data.backgroundImage = this._toObject(this.backgroundImage, methodName, propertiesToInclude);
  6261. }
  6262. if (this.overlayImage) {
  6263. data.overlayImage = this._toObject(this.overlayImage, methodName, propertiesToInclude);
  6264. }
  6265. return data;
  6266. },
  6267. /* _TO_SVG_START_ */
  6268. /**
  6269. * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true,
  6270. * a zoomed canvas will then produce zoomed SVG output.
  6271. * @type Boolean
  6272. * @default
  6273. */
  6274. svgViewportTransformation: true,
  6275. /**
  6276. * Returns SVG representation of canvas
  6277. * @function
  6278. * @param {Object} [options] Options object for SVG output
  6279. * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included
  6280. * @param {Object} [options.viewBox] SVG viewbox object
  6281. * @param {Number} [options.viewBox.x] x-cooridnate of viewbox
  6282. * @param {Number} [options.viewBox.y] y-coordinate of viewbox
  6283. * @param {Number} [options.viewBox.width] Width of viewbox
  6284. * @param {Number} [options.viewBox.height] Height of viewbox
  6285. * @param {String} [options.encoding=UTF-8] Encoding of SVG output
  6286. * @param {String} [options.width] desired width of svg with or without units
  6287. * @param {String} [options.height] desired height of svg with or without units
  6288. * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation.
  6289. * @return {String} SVG string
  6290. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization}
  6291. * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo}
  6292. * @example <caption>Normal SVG output</caption>
  6293. * var svg = canvas.toSVG();
  6294. * @example <caption>SVG output without preamble (without &lt;?xml ../>)</caption>
  6295. * var svg = canvas.toSVG({suppressPreamble: true});
  6296. * @example <caption>SVG output with viewBox attribute</caption>
  6297. * var svg = canvas.toSVG({
  6298. * viewBox: {
  6299. * x: 100,
  6300. * y: 100,
  6301. * width: 200,
  6302. * height: 300
  6303. * }
  6304. * });
  6305. * @example <caption>SVG output with different encoding (default: UTF-8)</caption>
  6306. * var svg = canvas.toSVG({encoding: 'ISO-8859-1'});
  6307. * @example <caption>Modify SVG output with reviver function</caption>
  6308. * var svg = canvas.toSVG(null, function(svg) {
  6309. * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', '');
  6310. * });
  6311. */
  6312. toSVG: function(options, reviver) {
  6313. options || (options = { });
  6314. var markup = [];
  6315. this._setSVGPreamble(markup, options);
  6316. this._setSVGHeader(markup, options);
  6317. this._setSVGBgOverlayColor(markup, 'backgroundColor');
  6318. this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver);
  6319. this._setSVGObjects(markup, reviver);
  6320. this._setSVGBgOverlayColor(markup, 'overlayColor');
  6321. this._setSVGBgOverlayImage(markup, 'overlayImage', reviver);
  6322. markup.push('</svg>');
  6323. return markup.join('');
  6324. },
  6325. /**
  6326. * @private
  6327. */
  6328. _setSVGPreamble: function(markup, options) {
  6329. if (options.suppressPreamble) {
  6330. return;
  6331. }
  6332. markup.push(
  6333. '<?xml version="1.0" encoding="', (options.encoding || 'UTF-8'), '" standalone="no" ?>\n',
  6334. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ',
  6335. '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
  6336. );
  6337. },
  6338. /**
  6339. * @private
  6340. */
  6341. _setSVGHeader: function(markup, options) {
  6342. var width = options.width || this.width,
  6343. height = options.height || this.height,
  6344. vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ',
  6345. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
  6346. if (options.viewBox) {
  6347. viewBox = 'viewBox="' +
  6348. options.viewBox.x + ' ' +
  6349. options.viewBox.y + ' ' +
  6350. options.viewBox.width + ' ' +
  6351. options.viewBox.height + '" ';
  6352. }
  6353. else {
  6354. if (this.svgViewportTransformation) {
  6355. vpt = this.viewportTransform;
  6356. viewBox = 'viewBox="' +
  6357. toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' +
  6358. toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' +
  6359. toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' +
  6360. toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" ';
  6361. }
  6362. }
  6363. markup.push(
  6364. '<svg ',
  6365. 'xmlns="http://www.w3.org/2000/svg" ',
  6366. 'xmlns:xlink="http://www.w3.org/1999/xlink" ',
  6367. 'version="1.1" ',
  6368. 'width="', width, '" ',
  6369. 'height="', height, '" ',
  6370. (this.backgroundColor && !this.backgroundColor.toLive
  6371. ? 'style="background-color: ' + this.backgroundColor + '" '
  6372. : null),
  6373. viewBox,
  6374. 'xml:space="preserve">\n',
  6375. '<desc>Created with Fabric.js ', fabric.version, '</desc>\n',
  6376. '<defs>',
  6377. fabric.createSVGFontFacesMarkup(this.getObjects()),
  6378. fabric.createSVGRefElementsMarkup(this),
  6379. '</defs>\n'
  6380. );
  6381. },
  6382. /**
  6383. * @private
  6384. */
  6385. _setSVGObjects: function(markup, reviver) {
  6386. var instance;
  6387. for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
  6388. instance = objects[i];
  6389. if (instance.excludeFromExport) {
  6390. continue;
  6391. }
  6392. this._setSVGObject(markup, instance, reviver);
  6393. }
  6394. },
  6395. /**
  6396. * push single object svg representation in the markup
  6397. * @private
  6398. */
  6399. _setSVGObject: function(markup, instance, reviver) {
  6400. markup.push(instance.toSVG(reviver));
  6401. },
  6402. /**
  6403. * @private
  6404. */
  6405. _setSVGBgOverlayImage: function(markup, property, reviver) {
  6406. if (this[property] && this[property].toSVG) {
  6407. markup.push(this[property].toSVG(reviver));
  6408. }
  6409. },
  6410. /**
  6411. * @private
  6412. */
  6413. _setSVGBgOverlayColor: function(markup, property) {
  6414. if (this[property] && this[property].source) {
  6415. markup.push(
  6416. '<rect x="', this[property].offsetX, '" y="', this[property].offsetY, '" ',
  6417. 'width="',
  6418. (this[property].repeat === 'repeat-y' || this[property].repeat === 'no-repeat'
  6419. ? this[property].source.width
  6420. : this.width),
  6421. '" height="',
  6422. (this[property].repeat === 'repeat-x' || this[property].repeat === 'no-repeat'
  6423. ? this[property].source.height
  6424. : this.height),
  6425. '" fill="url(#' + property + 'Pattern)"',
  6426. '></rect>\n'
  6427. );
  6428. }
  6429. else if (this[property] && property === 'overlayColor') {
  6430. markup.push(
  6431. '<rect x="0" y="0" ',
  6432. 'width="', this.width,
  6433. '" height="', this.height,
  6434. '" fill="', this[property], '"',
  6435. '></rect>\n'
  6436. );
  6437. }
  6438. },
  6439. /* _TO_SVG_END_ */
  6440. /**
  6441. * Moves an object or the objects of a multiple selection
  6442. * to the bottom of the stack of drawn objects
  6443. * @param {fabric.Object} object Object to send to back
  6444. * @return {fabric.Canvas} thisArg
  6445. * @chainable
  6446. */
  6447. sendToBack: function (object) {
  6448. if (!object) {
  6449. return this;
  6450. }
  6451. var activeGroup = this._activeGroup,
  6452. i, obj, objs;
  6453. if (object === activeGroup) {
  6454. objs = activeGroup._objects;
  6455. for (i = objs.length; i--;) {
  6456. obj = objs[i];
  6457. removeFromArray(this._objects, obj);
  6458. this._objects.unshift(obj);
  6459. }
  6460. }
  6461. else {
  6462. removeFromArray(this._objects, object);
  6463. this._objects.unshift(object);
  6464. }
  6465. return this.renderAll && this.renderAll();
  6466. },
  6467. /**
  6468. * Moves an object or the objects of a multiple selection
  6469. * to the top of the stack of drawn objects
  6470. * @param {fabric.Object} object Object to send
  6471. * @return {fabric.Canvas} thisArg
  6472. * @chainable
  6473. */
  6474. bringToFront: function (object) {
  6475. if (!object) {
  6476. return this;
  6477. }
  6478. var activeGroup = this._activeGroup,
  6479. i, obj, objs;
  6480. if (object === activeGroup) {
  6481. objs = activeGroup._objects;
  6482. for (i = 0; i < objs.length; i++) {
  6483. obj = objs[i];
  6484. removeFromArray(this._objects, obj);
  6485. this._objects.push(obj);
  6486. }
  6487. }
  6488. else {
  6489. removeFromArray(this._objects, object);
  6490. this._objects.push(object);
  6491. }
  6492. return this.renderAll && this.renderAll();
  6493. },
  6494. /**
  6495. * Moves an object or a selection down in stack of drawn objects
  6496. * @param {fabric.Object} object Object to send
  6497. * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
  6498. * @return {fabric.Canvas} thisArg
  6499. * @chainable
  6500. */
  6501. sendBackwards: function (object, intersecting) {
  6502. if (!object) {
  6503. return this;
  6504. }
  6505. var activeGroup = this._activeGroup,
  6506. i, obj, idx, newIdx, objs;
  6507. if (object === activeGroup) {
  6508. objs = activeGroup._objects;
  6509. for (i = 0; i < objs.length; i++) {
  6510. obj = objs[i];
  6511. idx = this._objects.indexOf(obj);
  6512. if (idx !== 0) {
  6513. newIdx = idx - 1;
  6514. removeFromArray(this._objects, obj);
  6515. this._objects.splice(newIdx, 0, obj);
  6516. }
  6517. }
  6518. }
  6519. else {
  6520. idx = this._objects.indexOf(object);
  6521. if (idx !== 0) {
  6522. // if object is not on the bottom of stack
  6523. newIdx = this._findNewLowerIndex(object, idx, intersecting);
  6524. removeFromArray(this._objects, object);
  6525. this._objects.splice(newIdx, 0, object);
  6526. }
  6527. }
  6528. this.renderAll && this.renderAll();
  6529. return this;
  6530. },
  6531. /**
  6532. * @private
  6533. */
  6534. _findNewLowerIndex: function(object, idx, intersecting) {
  6535. var newIdx;
  6536. if (intersecting) {
  6537. newIdx = idx;
  6538. // traverse down the stack looking for the nearest intersecting object
  6539. for (var i = idx - 1; i >= 0; --i) {
  6540. var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
  6541. object.isContainedWithinObject(this._objects[i]) ||
  6542. this._objects[i].isContainedWithinObject(object);
  6543. if (isIntersecting) {
  6544. newIdx = i;
  6545. break;
  6546. }
  6547. }
  6548. }
  6549. else {
  6550. newIdx = idx - 1;
  6551. }
  6552. return newIdx;
  6553. },
  6554. /**
  6555. * Moves an object or a selection up in stack of drawn objects
  6556. * @param {fabric.Object} object Object to send
  6557. * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
  6558. * @return {fabric.Canvas} thisArg
  6559. * @chainable
  6560. */
  6561. bringForward: function (object, intersecting) {
  6562. if (!object) {
  6563. return this;
  6564. }
  6565. var activeGroup = this._activeGroup,
  6566. i, obj, idx, newIdx, objs;
  6567. if (object === activeGroup) {
  6568. objs = activeGroup._objects;
  6569. for (i = objs.length; i--;) {
  6570. obj = objs[i];
  6571. idx = this._objects.indexOf(obj);
  6572. if (idx !== this._objects.length - 1) {
  6573. newIdx = idx + 1;
  6574. removeFromArray(this._objects, obj);
  6575. this._objects.splice(newIdx, 0, obj);
  6576. }
  6577. }
  6578. }
  6579. else {
  6580. idx = this._objects.indexOf(object);
  6581. if (idx !== this._objects.length - 1) {
  6582. // if object is not on top of stack (last item in an array)
  6583. newIdx = this._findNewUpperIndex(object, idx, intersecting);
  6584. removeFromArray(this._objects, object);
  6585. this._objects.splice(newIdx, 0, object);
  6586. }
  6587. }
  6588. this.renderAll && this.renderAll();
  6589. return this;
  6590. },
  6591. /**
  6592. * @private
  6593. */
  6594. _findNewUpperIndex: function(object, idx, intersecting) {
  6595. var newIdx;
  6596. if (intersecting) {
  6597. newIdx = idx;
  6598. // traverse up the stack looking for the nearest intersecting object
  6599. for (var i = idx + 1; i < this._objects.length; ++i) {
  6600. var isIntersecting = object.intersectsWithObject(this._objects[i]) ||
  6601. object.isContainedWithinObject(this._objects[i]) ||
  6602. this._objects[i].isContainedWithinObject(object);
  6603. if (isIntersecting) {
  6604. newIdx = i;
  6605. break;
  6606. }
  6607. }
  6608. }
  6609. else {
  6610. newIdx = idx + 1;
  6611. }
  6612. return newIdx;
  6613. },
  6614. /**
  6615. * Moves an object to specified level in stack of drawn objects
  6616. * @param {fabric.Object} object Object to send
  6617. * @param {Number} index Position to move to
  6618. * @return {fabric.Canvas} thisArg
  6619. * @chainable
  6620. */
  6621. moveTo: function (object, index) {
  6622. removeFromArray(this._objects, object);
  6623. this._objects.splice(index, 0, object);
  6624. return this.renderAll && this.renderAll();
  6625. },
  6626. /**
  6627. * Clears a canvas element and removes all event listeners
  6628. * @return {fabric.Canvas} thisArg
  6629. * @chainable
  6630. */
  6631. dispose: function () {
  6632. this.clear();
  6633. return this;
  6634. },
  6635. /**
  6636. * Returns a string representation of an instance
  6637. * @return {String} string representation of an instance
  6638. */
  6639. toString: function () {
  6640. return '#<fabric.Canvas (' + this.complexity() + '): ' +
  6641. '{ objects: ' + this.getObjects().length + ' }>';
  6642. }
  6643. });
  6644. extend(fabric.StaticCanvas.prototype, fabric.Observable);
  6645. extend(fabric.StaticCanvas.prototype, fabric.Collection);
  6646. extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter);
  6647. extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ {
  6648. /**
  6649. * @static
  6650. * @type String
  6651. * @default
  6652. */
  6653. EMPTY_JSON: '{"objects": [], "background": "white"}',
  6654. /**
  6655. * Provides a way to check support of some of the canvas methods
  6656. * (either those of HTMLCanvasElement itself, or rendering context)
  6657. *
  6658. * @param {String} methodName Method to check support for;
  6659. * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash"
  6660. * @return {Boolean | null} `true` if method is supported (or at least exists),
  6661. * `null` if canvas element or context can not be initialized
  6662. */
  6663. supports: function (methodName) {
  6664. var el = fabric.util.createCanvasElement();
  6665. if (!el || !el.getContext) {
  6666. return null;
  6667. }
  6668. var ctx = el.getContext('2d');
  6669. if (!ctx) {
  6670. return null;
  6671. }
  6672. switch (methodName) {
  6673. case 'getImageData':
  6674. return typeof ctx.getImageData !== 'undefined';
  6675. case 'setLineDash':
  6676. return typeof ctx.setLineDash !== 'undefined';
  6677. case 'toDataURL':
  6678. return typeof el.toDataURL !== 'undefined';
  6679. case 'toDataURLWithQuality':
  6680. try {
  6681. el.toDataURL('image/jpeg', 0);
  6682. return true;
  6683. }
  6684. catch (e) { }
  6685. return false;
  6686. default:
  6687. return null;
  6688. }
  6689. }
  6690. });
  6691. /**
  6692. * Returns JSON representation of canvas
  6693. * @function
  6694. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  6695. * @return {String} JSON string
  6696. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization}
  6697. * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo}
  6698. * @example <caption>JSON without additional properties</caption>
  6699. * var json = canvas.toJSON();
  6700. * @example <caption>JSON with additional properties included</caption>
  6701. * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']);
  6702. * @example <caption>JSON without default values</caption>
  6703. * canvas.includeDefaultValues = false;
  6704. * var json = canvas.toJSON();
  6705. */
  6706. fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
  6707. })();
  6708. /**
  6709. * BaseBrush class
  6710. * @class fabric.BaseBrush
  6711. * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo}
  6712. */
  6713. fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ {
  6714. /**
  6715. * Color of a brush
  6716. * @type String
  6717. * @default
  6718. */
  6719. color: 'rgb(0, 0, 0)',
  6720. /**
  6721. * Width of a brush
  6722. * @type Number
  6723. * @default
  6724. */
  6725. width: 1,
  6726. /**
  6727. * Shadow object representing shadow of this shape.
  6728. * <b>Backwards incompatibility note:</b> This property replaces "shadowColor" (String), "shadowOffsetX" (Number),
  6729. * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12
  6730. * @type fabric.Shadow
  6731. * @default
  6732. */
  6733. shadow: null,
  6734. /**
  6735. * Line endings style of a brush (one of "butt", "round", "square")
  6736. * @type String
  6737. * @default
  6738. */
  6739. strokeLineCap: 'round',
  6740. /**
  6741. * Corner style of a brush (one of "bevil", "round", "miter")
  6742. * @type String
  6743. * @default
  6744. */
  6745. strokeLineJoin: 'round',
  6746. /**
  6747. * Stroke Dash Array.
  6748. * @type Array
  6749. * @default
  6750. */
  6751. strokeDashArray: null,
  6752. /**
  6753. * Sets shadow of an object
  6754. * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
  6755. * @return {fabric.Object} thisArg
  6756. * @chainable
  6757. */
  6758. setShadow: function(options) {
  6759. this.shadow = new fabric.Shadow(options);
  6760. return this;
  6761. },
  6762. /**
  6763. * Sets brush styles
  6764. * @private
  6765. */
  6766. _setBrushStyles: function() {
  6767. var ctx = this.canvas.contextTop;
  6768. ctx.strokeStyle = this.color;
  6769. ctx.lineWidth = this.width;
  6770. ctx.lineCap = this.strokeLineCap;
  6771. ctx.lineJoin = this.strokeLineJoin;
  6772. if (this.strokeDashArray && fabric.StaticCanvas.supports('setLineDash')) {
  6773. ctx.setLineDash(this.strokeDashArray);
  6774. }
  6775. },
  6776. /**
  6777. * Sets brush shadow styles
  6778. * @private
  6779. */
  6780. _setShadow: function() {
  6781. if (!this.shadow) {
  6782. return;
  6783. }
  6784. var ctx = this.canvas.contextTop;
  6785. ctx.shadowColor = this.shadow.color;
  6786. ctx.shadowBlur = this.shadow.blur;
  6787. ctx.shadowOffsetX = this.shadow.offsetX;
  6788. ctx.shadowOffsetY = this.shadow.offsetY;
  6789. },
  6790. /**
  6791. * Removes brush shadow styles
  6792. * @private
  6793. */
  6794. _resetShadow: function() {
  6795. var ctx = this.canvas.contextTop;
  6796. ctx.shadowColor = '';
  6797. ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
  6798. }
  6799. });
  6800. (function() {
  6801. /**
  6802. * PencilBrush class
  6803. * @class fabric.PencilBrush
  6804. * @extends fabric.BaseBrush
  6805. */
  6806. fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ {
  6807. /**
  6808. * Constructor
  6809. * @param {fabric.Canvas} canvas
  6810. * @return {fabric.PencilBrush} Instance of a pencil brush
  6811. */
  6812. initialize: function(canvas) {
  6813. this.canvas = canvas;
  6814. this._points = [];
  6815. },
  6816. /**
  6817. * Inovoked on mouse down
  6818. * @param {Object} pointer
  6819. */
  6820. onMouseDown: function(pointer) {
  6821. this._prepareForDrawing(pointer);
  6822. // capture coordinates immediately
  6823. // this allows to draw dots (when movement never occurs)
  6824. this._captureDrawingPath(pointer);
  6825. this._render();
  6826. },
  6827. /**
  6828. * Inovoked on mouse move
  6829. * @param {Object} pointer
  6830. */
  6831. onMouseMove: function(pointer) {
  6832. this._captureDrawingPath(pointer);
  6833. // redraw curve
  6834. // clear top canvas
  6835. this.canvas.clearContext(this.canvas.contextTop);
  6836. this._render();
  6837. },
  6838. /**
  6839. * Invoked on mouse up
  6840. */
  6841. onMouseUp: function() {
  6842. this._finalizeAndAddPath();
  6843. },
  6844. /**
  6845. * @private
  6846. * @param {Object} pointer Actual mouse position related to the canvas.
  6847. */
  6848. _prepareForDrawing: function(pointer) {
  6849. var p = new fabric.Point(pointer.x, pointer.y);
  6850. this._reset();
  6851. this._addPoint(p);
  6852. this.canvas.contextTop.moveTo(p.x, p.y);
  6853. },
  6854. /**
  6855. * @private
  6856. * @param {fabric.Point} point Point to be added to points array
  6857. */
  6858. _addPoint: function(point) {
  6859. this._points.push(point);
  6860. },
  6861. /**
  6862. * Clear points array and set contextTop canvas style.
  6863. * @private
  6864. */
  6865. _reset: function() {
  6866. this._points.length = 0;
  6867. this._setBrushStyles();
  6868. this._setShadow();
  6869. },
  6870. /**
  6871. * @private
  6872. * @param {Object} pointer Actual mouse position related to the canvas.
  6873. */
  6874. _captureDrawingPath: function(pointer) {
  6875. var pointerPoint = new fabric.Point(pointer.x, pointer.y);
  6876. this._addPoint(pointerPoint);
  6877. },
  6878. /**
  6879. * Draw a smooth path on the topCanvas using quadraticCurveTo
  6880. * @private
  6881. */
  6882. _render: function() {
  6883. var ctx = this.canvas.contextTop,
  6884. v = this.canvas.viewportTransform,
  6885. p1 = this._points[0],
  6886. p2 = this._points[1];
  6887. ctx.save();
  6888. ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
  6889. ctx.beginPath();
  6890. //if we only have 2 points in the path and they are the same
  6891. //it means that the user only clicked the canvas without moving the mouse
  6892. //then we should be drawing a dot. A path isn't drawn between two identical dots
  6893. //that's why we set them apart a bit
  6894. if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) {
  6895. p1.x -= 0.5;
  6896. p2.x += 0.5;
  6897. }
  6898. ctx.moveTo(p1.x, p1.y);
  6899. for (var i = 1, len = this._points.length; i < len; i++) {
  6900. // we pick the point between pi + 1 & pi + 2 as the
  6901. // end point and p1 as our control point.
  6902. var midPoint = p1.midPointFrom(p2);
  6903. ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
  6904. p1 = this._points[i];
  6905. p2 = this._points[i + 1];
  6906. }
  6907. // Draw last line as a straight line while
  6908. // we wait for the next point to be able to calculate
  6909. // the bezier control point
  6910. ctx.lineTo(p1.x, p1.y);
  6911. ctx.stroke();
  6912. ctx.restore();
  6913. },
  6914. /**
  6915. * Converts points to SVG path
  6916. * @param {Array} points Array of points
  6917. * @return {String} SVG path
  6918. */
  6919. convertPointsToSVGPath: function(points) {
  6920. var path = [],
  6921. p1 = new fabric.Point(points[0].x, points[0].y),
  6922. p2 = new fabric.Point(points[1].x, points[1].y);
  6923. path.push('M ', points[0].x, ' ', points[0].y, ' ');
  6924. for (var i = 1, len = points.length; i < len; i++) {
  6925. var midPoint = p1.midPointFrom(p2);
  6926. // p1 is our bezier control point
  6927. // midpoint is our endpoint
  6928. // start point is p(i-1) value.
  6929. path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' ');
  6930. p1 = new fabric.Point(points[i].x, points[i].y);
  6931. if ((i + 1) < points.length) {
  6932. p2 = new fabric.Point(points[i + 1].x, points[i + 1].y);
  6933. }
  6934. }
  6935. path.push('L ', p1.x, ' ', p1.y, ' ');
  6936. return path;
  6937. },
  6938. /**
  6939. * Creates fabric.Path object to add on canvas
  6940. * @param {String} pathData Path data
  6941. * @return {fabric.Path} Path to add on canvas
  6942. */
  6943. createPath: function(pathData) {
  6944. var path = new fabric.Path(pathData, {
  6945. fill: null,
  6946. stroke: this.color,
  6947. strokeWidth: this.width,
  6948. strokeLineCap: this.strokeLineCap,
  6949. strokeLineJoin: this.strokeLineJoin,
  6950. strokeDashArray: this.strokeDashArray,
  6951. originX: 'center',
  6952. originY: 'center'
  6953. });
  6954. if (this.shadow) {
  6955. this.shadow.affectStroke = true;
  6956. path.setShadow(this.shadow);
  6957. }
  6958. return path;
  6959. },
  6960. /**
  6961. * On mouseup after drawing the path on contextTop canvas
  6962. * we use the points captured to create an new fabric path object
  6963. * and add it to the fabric canvas.
  6964. */
  6965. _finalizeAndAddPath: function() {
  6966. var ctx = this.canvas.contextTop;
  6967. ctx.closePath();
  6968. var pathData = this.convertPointsToSVGPath(this._points).join('');
  6969. if (pathData === 'M 0 0 Q 0 0 0 0 L 0 0') {
  6970. // do not create 0 width/height paths, as they are
  6971. // rendered inconsistently across browsers
  6972. // Firefox 4, for example, renders a dot,
  6973. // whereas Chrome 10 renders nothing
  6974. this.canvas.renderAll();
  6975. return;
  6976. }
  6977. var path = this.createPath(pathData);
  6978. this.canvas.add(path);
  6979. path.setCoords();
  6980. this.canvas.clearContext(this.canvas.contextTop);
  6981. this._resetShadow();
  6982. this.canvas.renderAll();
  6983. // fire event 'path' created
  6984. this.canvas.fire('path:created', { path: path });
  6985. }
  6986. });
  6987. })();
  6988. /**
  6989. * CircleBrush class
  6990. * @class fabric.CircleBrush
  6991. */
  6992. fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ {
  6993. /**
  6994. * Width of a brush
  6995. * @type Number
  6996. * @default
  6997. */
  6998. width: 10,
  6999. /**
  7000. * Constructor
  7001. * @param {fabric.Canvas} canvas
  7002. * @return {fabric.CircleBrush} Instance of a circle brush
  7003. */
  7004. initialize: function(canvas) {
  7005. this.canvas = canvas;
  7006. this.points = [];
  7007. },
  7008. /**
  7009. * Invoked inside on mouse down and mouse move
  7010. * @param {Object} pointer
  7011. */
  7012. drawDot: function(pointer) {
  7013. var point = this.addPoint(pointer),
  7014. ctx = this.canvas.contextTop,
  7015. v = this.canvas.viewportTransform;
  7016. ctx.save();
  7017. ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
  7018. ctx.fillStyle = point.fill;
  7019. ctx.beginPath();
  7020. ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false);
  7021. ctx.closePath();
  7022. ctx.fill();
  7023. ctx.restore();
  7024. },
  7025. /**
  7026. * Invoked on mouse down
  7027. */
  7028. onMouseDown: function(pointer) {
  7029. this.points.length = 0;
  7030. this.canvas.clearContext(this.canvas.contextTop);
  7031. this._setShadow();
  7032. this.drawDot(pointer);
  7033. },
  7034. /**
  7035. * Invoked on mouse move
  7036. * @param {Object} pointer
  7037. */
  7038. onMouseMove: function(pointer) {
  7039. this.drawDot(pointer);
  7040. },
  7041. /**
  7042. * Invoked on mouse up
  7043. */
  7044. onMouseUp: function() {
  7045. var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
  7046. this.canvas.renderOnAddRemove = false;
  7047. var circles = [];
  7048. for (var i = 0, len = this.points.length; i < len; i++) {
  7049. var point = this.points[i],
  7050. circle = new fabric.Circle({
  7051. radius: point.radius,
  7052. left: point.x,
  7053. top: point.y,
  7054. originX: 'center',
  7055. originY: 'center',
  7056. fill: point.fill
  7057. });
  7058. this.shadow && circle.setShadow(this.shadow);
  7059. circles.push(circle);
  7060. }
  7061. var group = new fabric.Group(circles, { originX: 'center', originY: 'center' });
  7062. group.canvas = this.canvas;
  7063. this.canvas.add(group);
  7064. this.canvas.fire('path:created', { path: group });
  7065. this.canvas.clearContext(this.canvas.contextTop);
  7066. this._resetShadow();
  7067. this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
  7068. this.canvas.renderAll();
  7069. },
  7070. /**
  7071. * @param {Object} pointer
  7072. * @return {fabric.Point} Just added pointer point
  7073. */
  7074. addPoint: function(pointer) {
  7075. var pointerPoint = new fabric.Point(pointer.x, pointer.y),
  7076. circleRadius = fabric.util.getRandomInt(
  7077. Math.max(0, this.width - 20), this.width + 20) / 2,
  7078. circleColor = new fabric.Color(this.color)
  7079. .setAlpha(fabric.util.getRandomInt(0, 100) / 100)
  7080. .toRgba();
  7081. pointerPoint.radius = circleRadius;
  7082. pointerPoint.fill = circleColor;
  7083. this.points.push(pointerPoint);
  7084. return pointerPoint;
  7085. }
  7086. });
  7087. /**
  7088. * SprayBrush class
  7089. * @class fabric.SprayBrush
  7090. */
  7091. fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ {
  7092. /**
  7093. * Width of a spray
  7094. * @type Number
  7095. * @default
  7096. */
  7097. width: 10,
  7098. /**
  7099. * Density of a spray (number of dots per chunk)
  7100. * @type Number
  7101. * @default
  7102. */
  7103. density: 20,
  7104. /**
  7105. * Width of spray dots
  7106. * @type Number
  7107. * @default
  7108. */
  7109. dotWidth: 1,
  7110. /**
  7111. * Width variance of spray dots
  7112. * @type Number
  7113. * @default
  7114. */
  7115. dotWidthVariance: 1,
  7116. /**
  7117. * Whether opacity of a dot should be random
  7118. * @type Boolean
  7119. * @default
  7120. */
  7121. randomOpacity: false,
  7122. /**
  7123. * Whether overlapping dots (rectangles) should be removed (for performance reasons)
  7124. * @type Boolean
  7125. * @default
  7126. */
  7127. optimizeOverlapping: true,
  7128. /**
  7129. * Constructor
  7130. * @param {fabric.Canvas} canvas
  7131. * @return {fabric.SprayBrush} Instance of a spray brush
  7132. */
  7133. initialize: function(canvas) {
  7134. this.canvas = canvas;
  7135. this.sprayChunks = [];
  7136. },
  7137. /**
  7138. * Invoked on mouse down
  7139. * @param {Object} pointer
  7140. */
  7141. onMouseDown: function(pointer) {
  7142. this.sprayChunks.length = 0;
  7143. this.canvas.clearContext(this.canvas.contextTop);
  7144. this._setShadow();
  7145. this.addSprayChunk(pointer);
  7146. this.render();
  7147. },
  7148. /**
  7149. * Invoked on mouse move
  7150. * @param {Object} pointer
  7151. */
  7152. onMouseMove: function(pointer) {
  7153. this.addSprayChunk(pointer);
  7154. this.render();
  7155. },
  7156. /**
  7157. * Invoked on mouse up
  7158. */
  7159. onMouseUp: function() {
  7160. var originalRenderOnAddRemove = this.canvas.renderOnAddRemove;
  7161. this.canvas.renderOnAddRemove = false;
  7162. var rects = [];
  7163. for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) {
  7164. var sprayChunk = this.sprayChunks[i];
  7165. for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) {
  7166. var rect = new fabric.Rect({
  7167. width: sprayChunk[j].width,
  7168. height: sprayChunk[j].width,
  7169. left: sprayChunk[j].x + 1,
  7170. top: sprayChunk[j].y + 1,
  7171. originX: 'center',
  7172. originY: 'center',
  7173. fill: this.color
  7174. });
  7175. this.shadow && rect.setShadow(this.shadow);
  7176. rects.push(rect);
  7177. }
  7178. }
  7179. if (this.optimizeOverlapping) {
  7180. rects = this._getOptimizedRects(rects);
  7181. }
  7182. var group = new fabric.Group(rects, { originX: 'center', originY: 'center' });
  7183. group.canvas = this.canvas;
  7184. this.canvas.add(group);
  7185. this.canvas.fire('path:created', { path: group });
  7186. this.canvas.clearContext(this.canvas.contextTop);
  7187. this._resetShadow();
  7188. this.canvas.renderOnAddRemove = originalRenderOnAddRemove;
  7189. this.canvas.renderAll();
  7190. },
  7191. /**
  7192. * @private
  7193. * @param {Array} rects
  7194. */
  7195. _getOptimizedRects: function(rects) {
  7196. // avoid creating duplicate rects at the same coordinates
  7197. var uniqueRects = { }, key;
  7198. for (var i = 0, len = rects.length; i < len; i++) {
  7199. key = rects[i].left + '' + rects[i].top;
  7200. if (!uniqueRects[key]) {
  7201. uniqueRects[key] = rects[i];
  7202. }
  7203. }
  7204. var uniqueRectsArray = [];
  7205. for (key in uniqueRects) {
  7206. uniqueRectsArray.push(uniqueRects[key]);
  7207. }
  7208. return uniqueRectsArray;
  7209. },
  7210. /**
  7211. * Renders brush
  7212. */
  7213. render: function() {
  7214. var ctx = this.canvas.contextTop;
  7215. ctx.fillStyle = this.color;
  7216. var v = this.canvas.viewportTransform;
  7217. ctx.save();
  7218. ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
  7219. for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) {
  7220. var point = this.sprayChunkPoints[i];
  7221. if (typeof point.opacity !== 'undefined') {
  7222. ctx.globalAlpha = point.opacity;
  7223. }
  7224. ctx.fillRect(point.x, point.y, point.width, point.width);
  7225. }
  7226. ctx.restore();
  7227. },
  7228. /**
  7229. * @param {Object} pointer
  7230. */
  7231. addSprayChunk: function(pointer) {
  7232. this.sprayChunkPoints = [];
  7233. var x, y, width, radius = this.width / 2;
  7234. for (var i = 0; i < this.density; i++) {
  7235. x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius);
  7236. y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius);
  7237. if (this.dotWidthVariance) {
  7238. width = fabric.util.getRandomInt(
  7239. // bottom clamp width to 1
  7240. Math.max(1, this.dotWidth - this.dotWidthVariance),
  7241. this.dotWidth + this.dotWidthVariance);
  7242. }
  7243. else {
  7244. width = this.dotWidth;
  7245. }
  7246. var point = new fabric.Point(x, y);
  7247. point.width = width;
  7248. if (this.randomOpacity) {
  7249. point.opacity = fabric.util.getRandomInt(0, 100) / 100;
  7250. }
  7251. this.sprayChunkPoints.push(point);
  7252. }
  7253. this.sprayChunks.push(this.sprayChunkPoints);
  7254. }
  7255. });
  7256. /**
  7257. * PatternBrush class
  7258. * @class fabric.PatternBrush
  7259. * @extends fabric.BaseBrush
  7260. */
  7261. fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ {
  7262. getPatternSrc: function() {
  7263. var dotWidth = 20,
  7264. dotDistance = 5,
  7265. patternCanvas = fabric.document.createElement('canvas'),
  7266. patternCtx = patternCanvas.getContext('2d');
  7267. patternCanvas.width = patternCanvas.height = dotWidth + dotDistance;
  7268. patternCtx.fillStyle = this.color;
  7269. patternCtx.beginPath();
  7270. patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false);
  7271. patternCtx.closePath();
  7272. patternCtx.fill();
  7273. return patternCanvas;
  7274. },
  7275. getPatternSrcFunction: function() {
  7276. return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"');
  7277. },
  7278. /**
  7279. * Creates "pattern" instance property
  7280. */
  7281. getPattern: function() {
  7282. return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat');
  7283. },
  7284. /**
  7285. * Sets brush styles
  7286. */
  7287. _setBrushStyles: function() {
  7288. this.callSuper('_setBrushStyles');
  7289. this.canvas.contextTop.strokeStyle = this.getPattern();
  7290. },
  7291. /**
  7292. * Creates path
  7293. */
  7294. createPath: function(pathData) {
  7295. var path = this.callSuper('createPath', pathData),
  7296. topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2);
  7297. path.stroke = new fabric.Pattern({
  7298. source: this.source || this.getPatternSrcFunction(),
  7299. offsetX: -topLeft.x,
  7300. offsetY: -topLeft.y
  7301. });
  7302. return path;
  7303. }
  7304. });
  7305. (function() {
  7306. var getPointer = fabric.util.getPointer,
  7307. degreesToRadians = fabric.util.degreesToRadians,
  7308. radiansToDegrees = fabric.util.radiansToDegrees,
  7309. atan2 = Math.atan2,
  7310. abs = Math.abs,
  7311. supportLineDash = fabric.StaticCanvas.supports('setLineDash'),
  7312. STROKE_OFFSET = 0.5;
  7313. /**
  7314. * Canvas class
  7315. * @class fabric.Canvas
  7316. * @extends fabric.StaticCanvas
  7317. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas}
  7318. * @see {@link fabric.Canvas#initialize} for constructor definition
  7319. *
  7320. * @fires object:added
  7321. * @fires object:modified
  7322. * @fires object:rotating
  7323. * @fires object:scaling
  7324. * @fires object:moving
  7325. * @fires object:selected
  7326. *
  7327. * @fires before:selection:cleared
  7328. * @fires selection:cleared
  7329. * @fires selection:created
  7330. *
  7331. * @fires path:created
  7332. * @fires mouse:down
  7333. * @fires mouse:move
  7334. * @fires mouse:up
  7335. * @fires mouse:over
  7336. * @fires mouse:out
  7337. *
  7338. */
  7339. fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ {
  7340. /**
  7341. * Constructor
  7342. * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
  7343. * @param {Object} [options] Options object
  7344. * @return {Object} thisArg
  7345. */
  7346. initialize: function(el, options) {
  7347. options || (options = { });
  7348. this._initStatic(el, options);
  7349. this._initInteractive();
  7350. this._createCacheCanvas();
  7351. },
  7352. /**
  7353. * When true, objects can be transformed by one side (unproportionally)
  7354. * @type Boolean
  7355. * @default
  7356. */
  7357. uniScaleTransform: false,
  7358. /**
  7359. * Indicates which key enable unproportional scaling
  7360. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7361. * If `null` or 'none' or any other string that is not a modifier key
  7362. * feature is disabled feature disabled.
  7363. * @since 1.6.2
  7364. * @type String
  7365. * @default
  7366. */
  7367. uniScaleKey: 'shiftKey',
  7368. /**
  7369. * When true, objects use center point as the origin of scale transformation.
  7370. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  7371. * @since 1.3.4
  7372. * @type Boolean
  7373. * @default
  7374. */
  7375. centeredScaling: false,
  7376. /**
  7377. * When true, objects use center point as the origin of rotate transformation.
  7378. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  7379. * @since 1.3.4
  7380. * @type Boolean
  7381. * @default
  7382. */
  7383. centeredRotation: false,
  7384. /**
  7385. * Indicates which key enable centered Transfrom
  7386. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7387. * If `null` or 'none' or any other string that is not a modifier key
  7388. * feature is disabled feature disabled.
  7389. * @since 1.6.2
  7390. * @type String
  7391. * @default
  7392. */
  7393. centeredKey: 'altKey',
  7394. /**
  7395. * Indicates which key enable alternate action on corner
  7396. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7397. * If `null` or 'none' or any other string that is not a modifier key
  7398. * feature is disabled feature disabled.
  7399. * @since 1.6.2
  7400. * @type String
  7401. * @default
  7402. */
  7403. altActionKey: 'shiftKey',
  7404. /**
  7405. * Indicates that canvas is interactive. This property should not be changed.
  7406. * @type Boolean
  7407. * @default
  7408. */
  7409. interactive: true,
  7410. /**
  7411. * Indicates whether group selection should be enabled
  7412. * @type Boolean
  7413. * @default
  7414. */
  7415. selection: true,
  7416. /**
  7417. * Indicates which key enable multiple click selection
  7418. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7419. * If `null` or 'none' or any other string that is not a modifier key
  7420. * feature is disabled feature disabled.
  7421. * @since 1.6.2
  7422. * @type String
  7423. * @default
  7424. */
  7425. selectionKey: 'shiftKey',
  7426. /**
  7427. * Indicates which key enable alternative selection
  7428. * in case of target overlapping with active object
  7429. * values: 'altKey', 'shiftKey', 'ctrlKey'.
  7430. * If `null` or 'none' or any other string that is not a modifier key
  7431. * feature is disabled feature disabled.
  7432. * @since 1.6.5
  7433. * @type null|String
  7434. * @default
  7435. */
  7436. altSelectionKey: null,
  7437. /**
  7438. * Color of selection
  7439. * @type String
  7440. * @default
  7441. */
  7442. selectionColor: 'rgba(100, 100, 255, 0.3)', // blue
  7443. /**
  7444. * Default dash array pattern
  7445. * If not empty the selection border is dashed
  7446. * @type Array
  7447. */
  7448. selectionDashArray: [],
  7449. /**
  7450. * Color of the border of selection (usually slightly darker than color of selection itself)
  7451. * @type String
  7452. * @default
  7453. */
  7454. selectionBorderColor: 'rgba(255, 255, 255, 0.3)',
  7455. /**
  7456. * Width of a line used in object/group selection
  7457. * @type Number
  7458. * @default
  7459. */
  7460. selectionLineWidth: 1,
  7461. /**
  7462. * Default cursor value used when hovering over an object on canvas
  7463. * @type String
  7464. * @default
  7465. */
  7466. hoverCursor: 'move',
  7467. /**
  7468. * Default cursor value used when moving an object on canvas
  7469. * @type String
  7470. * @default
  7471. */
  7472. moveCursor: 'move',
  7473. /**
  7474. * Default cursor value used for the entire canvas
  7475. * @type String
  7476. * @default
  7477. */
  7478. defaultCursor: 'default',
  7479. /**
  7480. * Cursor value used during free drawing
  7481. * @type String
  7482. * @default
  7483. */
  7484. freeDrawingCursor: 'crosshair',
  7485. /**
  7486. * Cursor value used for rotation point
  7487. * @type String
  7488. * @default
  7489. */
  7490. rotationCursor: 'crosshair',
  7491. /**
  7492. * Default element class that's given to wrapper (div) element of canvas
  7493. * @type String
  7494. * @default
  7495. */
  7496. containerClass: 'canvas-container',
  7497. /**
  7498. * When true, object detection happens on per-pixel basis rather than on per-bounding-box
  7499. * @type Boolean
  7500. * @default
  7501. */
  7502. perPixelTargetFind: false,
  7503. /**
  7504. * Number of pixels around target pixel to tolerate (consider active) during object detection
  7505. * @type Number
  7506. * @default
  7507. */
  7508. targetFindTolerance: 0,
  7509. /**
  7510. * When true, target detection is skipped when hovering over canvas. This can be used to improve performance.
  7511. * @type Boolean
  7512. * @default
  7513. */
  7514. skipTargetFind: false,
  7515. /**
  7516. * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing.
  7517. * After mousedown, mousemove creates a shape,
  7518. * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas.
  7519. * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing}
  7520. * @type Boolean
  7521. * @default
  7522. */
  7523. isDrawingMode: false,
  7524. /**
  7525. * Indicates whether objects should remain in current stack position when selected.
  7526. * When false objects are brought to top and rendered as part of the selection group
  7527. * @type Boolean
  7528. * @default
  7529. */
  7530. preserveObjectStacking: false,
  7531. /**
  7532. * Indicates the angle that an object will lock to while rotating.
  7533. * @type Number
  7534. * @since 1.6.7
  7535. * @default
  7536. */
  7537. snapAngle: 0,
  7538. /**
  7539. * Indicates the distance from the snapAngle the rotation will lock to the snapAngle.
  7540. * When `null`, the snapThreshold will default to the snapAngle.
  7541. * @type null|Number
  7542. * @since 1.6.7
  7543. * @default
  7544. */
  7545. snapThreshold: null,
  7546. /**
  7547. * Indicates if the right click on canvas can output the context menu or not
  7548. * @type Boolean
  7549. * @since 1.6.5
  7550. * @default
  7551. */
  7552. stopContextMenu: false,
  7553. /**
  7554. * Indicates if the canvas can fire right click events
  7555. * @type Boolean
  7556. * @since 1.6.5
  7557. * @default
  7558. */
  7559. fireRightClick: false,
  7560. /**
  7561. * @private
  7562. */
  7563. _initInteractive: function() {
  7564. this._currentTransform = null;
  7565. this._groupSelector = null;
  7566. this._initWrapperElement();
  7567. this._createUpperCanvas();
  7568. this._initEventListeners();
  7569. this._initRetinaScaling();
  7570. this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this);
  7571. this.calcOffset();
  7572. },
  7573. /**
  7574. * Divides objects in two groups, one to render immediately
  7575. * and one to render as activeGroup.
  7576. * @return {Array} objects to render immediately and pushes the other in the activeGroup.
  7577. */
  7578. _chooseObjectsToRender: function() {
  7579. var activeGroup = this.getActiveGroup(),
  7580. activeObject = this.getActiveObject(),
  7581. object, objsToRender = [], activeGroupObjects = [];
  7582. if ((activeGroup || activeObject) && !this.preserveObjectStacking) {
  7583. for (var i = 0, length = this._objects.length; i < length; i++) {
  7584. object = this._objects[i];
  7585. if ((!activeGroup || !activeGroup.contains(object)) && object !== activeObject) {
  7586. objsToRender.push(object);
  7587. }
  7588. else {
  7589. activeGroupObjects.push(object);
  7590. }
  7591. }
  7592. if (activeGroup) {
  7593. activeGroup._set('_objects', activeGroupObjects);
  7594. objsToRender.push(activeGroup);
  7595. }
  7596. activeObject && objsToRender.push(activeObject);
  7597. }
  7598. else {
  7599. objsToRender = this._objects;
  7600. }
  7601. return objsToRender;
  7602. },
  7603. /**
  7604. * Renders both the top canvas and the secondary container canvas.
  7605. * @return {fabric.Canvas} instance
  7606. * @chainable
  7607. */
  7608. renderAll: function () {
  7609. if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) {
  7610. this.clearContext(this.contextTop);
  7611. this.contextTopDirty = false;
  7612. }
  7613. var canvasToDrawOn = this.contextContainer;
  7614. this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender());
  7615. return this;
  7616. },
  7617. /**
  7618. * Method to render only the top canvas.
  7619. * Also used to render the group selection box.
  7620. * @return {fabric.Canvas} thisArg
  7621. * @chainable
  7622. */
  7623. renderTop: function () {
  7624. var ctx = this.contextTop;
  7625. this.clearContext(ctx);
  7626. // we render the top context - last object
  7627. if (this.selection && this._groupSelector) {
  7628. this._drawSelection(ctx);
  7629. }
  7630. this.fire('after:render');
  7631. this.contextTopDirty = true;
  7632. return this;
  7633. },
  7634. /**
  7635. * Resets the current transform to its original values and chooses the type of resizing based on the event
  7636. * @private
  7637. */
  7638. _resetCurrentTransform: function() {
  7639. var t = this._currentTransform;
  7640. t.target.set({
  7641. scaleX: t.original.scaleX,
  7642. scaleY: t.original.scaleY,
  7643. skewX: t.original.skewX,
  7644. skewY: t.original.skewY,
  7645. left: t.original.left,
  7646. top: t.original.top
  7647. });
  7648. if (this._shouldCenterTransform(t.target)) {
  7649. if (t.action === 'rotate') {
  7650. this._setOriginToCenter(t.target);
  7651. }
  7652. else {
  7653. if (t.originX !== 'center') {
  7654. if (t.originX === 'right') {
  7655. t.mouseXSign = -1;
  7656. }
  7657. else {
  7658. t.mouseXSign = 1;
  7659. }
  7660. }
  7661. if (t.originY !== 'center') {
  7662. if (t.originY === 'bottom') {
  7663. t.mouseYSign = -1;
  7664. }
  7665. else {
  7666. t.mouseYSign = 1;
  7667. }
  7668. }
  7669. t.originX = 'center';
  7670. t.originY = 'center';
  7671. }
  7672. }
  7673. else {
  7674. t.originX = t.original.originX;
  7675. t.originY = t.original.originY;
  7676. }
  7677. },
  7678. /**
  7679. * Checks if point is contained within an area of given object
  7680. * @param {Event} e Event object
  7681. * @param {fabric.Object} target Object to test against
  7682. * @param {Object} [point] x,y object of point coordinates we want to check.
  7683. * @return {Boolean} true if point is contained within an area of given object
  7684. */
  7685. containsPoint: function (e, target, point) {
  7686. var ignoreZoom = true,
  7687. pointer = point || this.getPointer(e, ignoreZoom),
  7688. xy;
  7689. if (target.group && target.group === this.getActiveGroup()) {
  7690. xy = this._normalizePointer(target.group, pointer);
  7691. }
  7692. else {
  7693. xy = { x: pointer.x, y: pointer.y };
  7694. }
  7695. // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
  7696. // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
  7697. return (target.containsPoint(xy) || target._findTargetCorner(pointer));
  7698. },
  7699. /**
  7700. * @private
  7701. */
  7702. _normalizePointer: function (object, pointer) {
  7703. var m = object.calcTransformMatrix(),
  7704. invertedM = fabric.util.invertTransform(m),
  7705. vpt = this.viewportTransform,
  7706. vptPointer = this.restorePointerVpt(pointer),
  7707. p = fabric.util.transformPoint(vptPointer, invertedM);
  7708. return fabric.util.transformPoint(p, vpt);
  7709. },
  7710. /**
  7711. * Returns true if object is transparent at a certain location
  7712. * @param {fabric.Object} target Object to check
  7713. * @param {Number} x Left coordinate
  7714. * @param {Number} y Top coordinate
  7715. * @return {Boolean}
  7716. */
  7717. isTargetTransparent: function (target, x, y) {
  7718. var hasBorders = target.hasBorders,
  7719. transparentCorners = target.transparentCorners,
  7720. ctx = this.contextCache,
  7721. originalColor = target.selectionBackgroundColor;
  7722. target.hasBorders = target.transparentCorners = false;
  7723. target.selectionBackgroundColor = '';
  7724. ctx.save();
  7725. ctx.transform.apply(ctx, this.viewportTransform);
  7726. target.render(ctx);
  7727. ctx.restore();
  7728. target.active && target._renderControls(ctx);
  7729. target.hasBorders = hasBorders;
  7730. target.transparentCorners = transparentCorners;
  7731. target.selectionBackgroundColor = originalColor;
  7732. var isTransparent = fabric.util.isTransparent(
  7733. ctx, x, y, this.targetFindTolerance);
  7734. this.clearContext(ctx);
  7735. return isTransparent;
  7736. },
  7737. /**
  7738. * @private
  7739. * @param {Event} e Event object
  7740. * @param {fabric.Object} target
  7741. */
  7742. _shouldClearSelection: function (e, target) {
  7743. var activeGroup = this.getActiveGroup(),
  7744. activeObject = this.getActiveObject();
  7745. return (
  7746. !target
  7747. ||
  7748. (target &&
  7749. activeGroup &&
  7750. !activeGroup.contains(target) &&
  7751. activeGroup !== target &&
  7752. !e[this.selectionKey])
  7753. ||
  7754. (target && !target.evented)
  7755. ||
  7756. (target &&
  7757. !target.selectable &&
  7758. activeObject &&
  7759. activeObject !== target)
  7760. );
  7761. },
  7762. /**
  7763. * @private
  7764. * @param {fabric.Object} target
  7765. */
  7766. _shouldCenterTransform: function (target) {
  7767. if (!target) {
  7768. return;
  7769. }
  7770. var t = this._currentTransform,
  7771. centerTransform;
  7772. if (t.action === 'scale' || t.action === 'scaleX' || t.action === 'scaleY') {
  7773. centerTransform = this.centeredScaling || target.centeredScaling;
  7774. }
  7775. else if (t.action === 'rotate') {
  7776. centerTransform = this.centeredRotation || target.centeredRotation;
  7777. }
  7778. return centerTransform ? !t.altKey : t.altKey;
  7779. },
  7780. /**
  7781. * @private
  7782. */
  7783. _getOriginFromCorner: function(target, corner) {
  7784. var origin = {
  7785. x: target.originX,
  7786. y: target.originY
  7787. };
  7788. if (corner === 'ml' || corner === 'tl' || corner === 'bl') {
  7789. origin.x = 'right';
  7790. }
  7791. else if (corner === 'mr' || corner === 'tr' || corner === 'br') {
  7792. origin.x = 'left';
  7793. }
  7794. if (corner === 'tl' || corner === 'mt' || corner === 'tr') {
  7795. origin.y = 'bottom';
  7796. }
  7797. else if (corner === 'bl' || corner === 'mb' || corner === 'br') {
  7798. origin.y = 'top';
  7799. }
  7800. return origin;
  7801. },
  7802. /**
  7803. * @private
  7804. */
  7805. _getActionFromCorner: function(target, corner, e) {
  7806. if (!corner) {
  7807. return 'drag';
  7808. }
  7809. switch (corner) {
  7810. case 'mtr':
  7811. return 'rotate';
  7812. case 'ml':
  7813. case 'mr':
  7814. return e[this.altActionKey] ? 'skewY' : 'scaleX';
  7815. case 'mt':
  7816. case 'mb':
  7817. return e[this.altActionKey] ? 'skewX' : 'scaleY';
  7818. default:
  7819. return 'scale';
  7820. }
  7821. },
  7822. /**
  7823. * @private
  7824. * @param {Event} e Event object
  7825. * @param {fabric.Object} target
  7826. */
  7827. _setupCurrentTransform: function (e, target) {
  7828. if (!target) {
  7829. return;
  7830. }
  7831. var pointer = this.getPointer(e),
  7832. corner = target._findTargetCorner(this.getPointer(e, true)),
  7833. action = this._getActionFromCorner(target, corner, e),
  7834. origin = this._getOriginFromCorner(target, corner);
  7835. this._currentTransform = {
  7836. target: target,
  7837. action: action,
  7838. corner: corner,
  7839. scaleX: target.scaleX,
  7840. scaleY: target.scaleY,
  7841. skewX: target.skewX,
  7842. skewY: target.skewY,
  7843. offsetX: pointer.x - target.left,
  7844. offsetY: pointer.y - target.top,
  7845. originX: origin.x,
  7846. originY: origin.y,
  7847. ex: pointer.x,
  7848. ey: pointer.y,
  7849. lastX: pointer.x,
  7850. lastY: pointer.y,
  7851. left: target.left,
  7852. top: target.top,
  7853. theta: degreesToRadians(target.angle),
  7854. width: target.width * target.scaleX,
  7855. mouseXSign: 1,
  7856. mouseYSign: 1,
  7857. shiftKey: e.shiftKey,
  7858. altKey: e[this.centeredKey]
  7859. };
  7860. this._currentTransform.original = {
  7861. left: target.left,
  7862. top: target.top,
  7863. scaleX: target.scaleX,
  7864. scaleY: target.scaleY,
  7865. skewX: target.skewX,
  7866. skewY: target.skewY,
  7867. originX: origin.x,
  7868. originY: origin.y
  7869. };
  7870. this._resetCurrentTransform();
  7871. },
  7872. /**
  7873. * Translates object by "setting" its left/top
  7874. * @private
  7875. * @param {Number} x pointer's x coordinate
  7876. * @param {Number} y pointer's y coordinate
  7877. * @return {Boolean} true if the translation occurred
  7878. */
  7879. _translateObject: function (x, y) {
  7880. var transform = this._currentTransform,
  7881. target = transform.target,
  7882. newLeft = x - transform.offsetX,
  7883. newTop = y - transform.offsetY,
  7884. moveX = !target.get('lockMovementX') && target.left !== newLeft,
  7885. moveY = !target.get('lockMovementY') && target.top !== newTop;
  7886. moveX && target.set('left', newLeft);
  7887. moveY && target.set('top', newTop);
  7888. return moveX || moveY;
  7889. },
  7890. /**
  7891. * Check if we are increasing a positive skew or lower it,
  7892. * checking mouse direction and pressed corner.
  7893. * @private
  7894. */
  7895. _changeSkewTransformOrigin: function(mouseMove, t, by) {
  7896. var property = 'originX', origins = { 0: 'center' },
  7897. skew = t.target.skewX, originA = 'left', originB = 'right',
  7898. corner = t.corner === 'mt' || t.corner === 'ml' ? 1 : -1,
  7899. flipSign = 1;
  7900. mouseMove = mouseMove > 0 ? 1 : -1;
  7901. if (by === 'y') {
  7902. skew = t.target.skewY;
  7903. originA = 'top';
  7904. originB = 'bottom';
  7905. property = 'originY';
  7906. }
  7907. origins[-1] = originA;
  7908. origins[1] = originB;
  7909. t.target.flipX && (flipSign *= -1);
  7910. t.target.flipY && (flipSign *= -1);
  7911. if (skew === 0) {
  7912. t.skewSign = -corner * mouseMove * flipSign;
  7913. t[property] = origins[-mouseMove];
  7914. }
  7915. else {
  7916. skew = skew > 0 ? 1 : -1;
  7917. t.skewSign = skew;
  7918. t[property] = origins[skew * corner * flipSign];
  7919. }
  7920. },
  7921. /**
  7922. * Skew object by mouse events
  7923. * @private
  7924. * @param {Number} x pointer's x coordinate
  7925. * @param {Number} y pointer's y coordinate
  7926. * @param {String} by Either 'x' or 'y'
  7927. * @return {Boolean} true if the skewing occurred
  7928. */
  7929. _skewObject: function (x, y, by) {
  7930. var t = this._currentTransform,
  7931. target = t.target, skewed = false,
  7932. lockSkewingX = target.get('lockSkewingX'),
  7933. lockSkewingY = target.get('lockSkewingY');
  7934. if ((lockSkewingX && by === 'x') || (lockSkewingY && by === 'y')) {
  7935. return false;
  7936. }
  7937. // Get the constraint point
  7938. var center = target.getCenterPoint(),
  7939. actualMouseByCenter = target.toLocalPoint(new fabric.Point(x, y), 'center', 'center')[by],
  7940. lastMouseByCenter = target.toLocalPoint(new fabric.Point(t.lastX, t.lastY), 'center', 'center')[by],
  7941. actualMouseByOrigin, constraintPosition, dim = target._getTransformedDimensions();
  7942. this._changeSkewTransformOrigin(actualMouseByCenter - lastMouseByCenter, t, by);
  7943. actualMouseByOrigin = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY)[by];
  7944. constraintPosition = target.translateToOriginPoint(center, t.originX, t.originY);
  7945. // Actually skew the object
  7946. skewed = this._setObjectSkew(actualMouseByOrigin, t, by, dim);
  7947. t.lastX = x;
  7948. t.lastY = y;
  7949. // Make sure the constraints apply
  7950. target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
  7951. return skewed;
  7952. },
  7953. /**
  7954. * Set object skew
  7955. * @private
  7956. * @return {Boolean} true if the skewing occurred
  7957. */
  7958. _setObjectSkew: function(localMouse, transform, by, _dim) {
  7959. var target = transform.target, newValue, skewed = false,
  7960. skewSign = transform.skewSign, newDim, dimNoSkew,
  7961. otherBy, _otherBy, _by, newDimMouse, skewX, skewY;
  7962. if (by === 'x') {
  7963. otherBy = 'y';
  7964. _otherBy = 'Y';
  7965. _by = 'X';
  7966. skewX = 0;
  7967. skewY = target.skewY;
  7968. }
  7969. else {
  7970. otherBy = 'x';
  7971. _otherBy = 'X';
  7972. _by = 'Y';
  7973. skewX = target.skewX;
  7974. skewY = 0;
  7975. }
  7976. dimNoSkew = target._getTransformedDimensions(skewX, skewY);
  7977. newDimMouse = 2 * Math.abs(localMouse) - dimNoSkew[by];
  7978. if (newDimMouse <= 2) {
  7979. newValue = 0;
  7980. }
  7981. else {
  7982. newValue = skewSign * Math.atan((newDimMouse / target['scale' + _by]) /
  7983. (dimNoSkew[otherBy] / target['scale' + _otherBy]));
  7984. newValue = fabric.util.radiansToDegrees(newValue);
  7985. }
  7986. skewed = target['skew' + _by] !== newValue;
  7987. target.set('skew' + _by, newValue);
  7988. if (target['skew' + _otherBy] !== 0) {
  7989. newDim = target._getTransformedDimensions();
  7990. newValue = (_dim[otherBy] / newDim[otherBy]) * target['scale' + _otherBy];
  7991. target.set('scale' + _otherBy, newValue);
  7992. }
  7993. return skewed;
  7994. },
  7995. /**
  7996. * Scales object by invoking its scaleX/scaleY methods
  7997. * @private
  7998. * @param {Number} x pointer's x coordinate
  7999. * @param {Number} y pointer's y coordinate
  8000. * @param {String} by Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
  8001. * When not provided, an object is scaled by both dimensions equally
  8002. * @return {Boolean} true if the scaling occurred
  8003. */
  8004. _scaleObject: function (x, y, by) {
  8005. var t = this._currentTransform,
  8006. target = t.target,
  8007. lockScalingX = target.get('lockScalingX'),
  8008. lockScalingY = target.get('lockScalingY'),
  8009. lockScalingFlip = target.get('lockScalingFlip');
  8010. if (lockScalingX && lockScalingY) {
  8011. return false;
  8012. }
  8013. // Get the constraint point
  8014. var constraintPosition = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY),
  8015. localMouse = target.toLocalPoint(new fabric.Point(x, y), t.originX, t.originY),
  8016. dim = target._getTransformedDimensions(), scaled = false;
  8017. this._setLocalMouse(localMouse, t);
  8018. // Actually scale the object
  8019. scaled = this._setObjectScale(localMouse, t, lockScalingX, lockScalingY, by, lockScalingFlip, dim);
  8020. // Make sure the constraints apply
  8021. target.setPositionByOrigin(constraintPosition, t.originX, t.originY);
  8022. return scaled;
  8023. },
  8024. /**
  8025. * @private
  8026. * @return {Boolean} true if the scaling occurred
  8027. */
  8028. _setObjectScale: function(localMouse, transform, lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {
  8029. var target = transform.target, forbidScalingX = false, forbidScalingY = false, scaled = false,
  8030. changeX, changeY, scaleX, scaleY;
  8031. scaleX = localMouse.x * target.scaleX / _dim.x;
  8032. scaleY = localMouse.y * target.scaleY / _dim.y;
  8033. changeX = target.scaleX !== scaleX;
  8034. changeY = target.scaleY !== scaleY;
  8035. if (lockScalingFlip && scaleX <= 0 && scaleX < target.scaleX) {
  8036. forbidScalingX = true;
  8037. }
  8038. if (lockScalingFlip && scaleY <= 0 && scaleY < target.scaleY) {
  8039. forbidScalingY = true;
  8040. }
  8041. if (by === 'equally' && !lockScalingX && !lockScalingY) {
  8042. forbidScalingX || forbidScalingY || (scaled = this._scaleObjectEqually(localMouse, target, transform, _dim));
  8043. }
  8044. else if (!by) {
  8045. forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX));
  8046. forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY));
  8047. }
  8048. else if (by === 'x' && !target.get('lockUniScaling')) {
  8049. forbidScalingX || lockScalingX || (target.set('scaleX', scaleX) && (scaled = scaled || changeX));
  8050. }
  8051. else if (by === 'y' && !target.get('lockUniScaling')) {
  8052. forbidScalingY || lockScalingY || (target.set('scaleY', scaleY) && (scaled = scaled || changeY));
  8053. }
  8054. transform.newScaleX = scaleX;
  8055. transform.newScaleY = scaleY;
  8056. forbidScalingX || forbidScalingY || this._flipObject(transform, by);
  8057. return scaled;
  8058. },
  8059. /**
  8060. * @private
  8061. * @return {Boolean} true if the scaling occurred
  8062. */
  8063. _scaleObjectEqually: function(localMouse, target, transform, _dim) {
  8064. var dist = localMouse.y + localMouse.x,
  8065. lastDist = _dim.y * transform.original.scaleY / target.scaleY +
  8066. _dim.x * transform.original.scaleX / target.scaleX,
  8067. scaled;
  8068. // We use transform.scaleX/Y instead of target.scaleX/Y
  8069. // because the object may have a min scale and we'll loose the proportions
  8070. transform.newScaleX = transform.original.scaleX * dist / lastDist;
  8071. transform.newScaleY = transform.original.scaleY * dist / lastDist;
  8072. scaled = transform.newScaleX !== target.scaleX || transform.newScaleY !== target.scaleY;
  8073. target.set('scaleX', transform.newScaleX);
  8074. target.set('scaleY', transform.newScaleY);
  8075. return scaled;
  8076. },
  8077. /**
  8078. * @private
  8079. */
  8080. _flipObject: function(transform, by) {
  8081. if (transform.newScaleX < 0 && by !== 'y') {
  8082. if (transform.originX === 'left') {
  8083. transform.originX = 'right';
  8084. }
  8085. else if (transform.originX === 'right') {
  8086. transform.originX = 'left';
  8087. }
  8088. }
  8089. if (transform.newScaleY < 0 && by !== 'x') {
  8090. if (transform.originY === 'top') {
  8091. transform.originY = 'bottom';
  8092. }
  8093. else if (transform.originY === 'bottom') {
  8094. transform.originY = 'top';
  8095. }
  8096. }
  8097. },
  8098. /**
  8099. * @private
  8100. */
  8101. _setLocalMouse: function(localMouse, t) {
  8102. var target = t.target;
  8103. if (t.originX === 'right') {
  8104. localMouse.x *= -1;
  8105. }
  8106. else if (t.originX === 'center') {
  8107. localMouse.x *= t.mouseXSign * 2;
  8108. if (localMouse.x < 0) {
  8109. t.mouseXSign = -t.mouseXSign;
  8110. }
  8111. }
  8112. if (t.originY === 'bottom') {
  8113. localMouse.y *= -1;
  8114. }
  8115. else if (t.originY === 'center') {
  8116. localMouse.y *= t.mouseYSign * 2;
  8117. if (localMouse.y < 0) {
  8118. t.mouseYSign = -t.mouseYSign;
  8119. }
  8120. }
  8121. // adjust the mouse coordinates when dealing with padding
  8122. if (abs(localMouse.x) > target.padding) {
  8123. if (localMouse.x < 0) {
  8124. localMouse.x += target.padding;
  8125. }
  8126. else {
  8127. localMouse.x -= target.padding;
  8128. }
  8129. }
  8130. else { // mouse is within the padding, set to 0
  8131. localMouse.x = 0;
  8132. }
  8133. if (abs(localMouse.y) > target.padding) {
  8134. if (localMouse.y < 0) {
  8135. localMouse.y += target.padding;
  8136. }
  8137. else {
  8138. localMouse.y -= target.padding;
  8139. }
  8140. }
  8141. else {
  8142. localMouse.y = 0;
  8143. }
  8144. },
  8145. /**
  8146. * Rotates object by invoking its rotate method
  8147. * @private
  8148. * @param {Number} x pointer's x coordinate
  8149. * @param {Number} y pointer's y coordinate
  8150. * @return {Boolean} true if the rotation occurred
  8151. */
  8152. _rotateObject: function (x, y) {
  8153. var t = this._currentTransform;
  8154. if (t.target.get('lockRotation')) {
  8155. return false;
  8156. }
  8157. var lastAngle = atan2(t.ey - t.top, t.ex - t.left),
  8158. curAngle = atan2(y - t.top, x - t.left),
  8159. angle = radiansToDegrees(curAngle - lastAngle + t.theta),
  8160. hasRoated = true;
  8161. // normalize angle to positive value
  8162. if (angle < 0) {
  8163. angle = 360 + angle;
  8164. }
  8165. angle %= 360
  8166. if (t.target.snapAngle > 0) {
  8167. var snapAngle = t.target.snapAngle,
  8168. snapThreshold = t.target.snapThreshold || snapAngle,
  8169. rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle,
  8170. leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle;
  8171. if (Math.abs(angle - leftAngleLocked) < snapThreshold) {
  8172. angle = leftAngleLocked;
  8173. }
  8174. else if (Math.abs(angle - rightAngleLocked) < snapThreshold) {
  8175. angle = rightAngleLocked;
  8176. }
  8177. if (t.target.angle === angle) {
  8178. hasRoated = false
  8179. }
  8180. }
  8181. t.target.angle = angle;
  8182. return hasRoated;
  8183. },
  8184. /**
  8185. * Set the cursor type of the canvas element
  8186. * @param {String} value Cursor type of the canvas element.
  8187. * @see http://www.w3.org/TR/css3-ui/#cursor
  8188. */
  8189. setCursor: function (value) {
  8190. this.upperCanvasEl.style.cursor = value;
  8191. },
  8192. /**
  8193. * @param {fabric.Object} target to reset transform
  8194. * @private
  8195. */
  8196. _resetObjectTransform: function (target) {
  8197. target.scaleX = 1;
  8198. target.scaleY = 1;
  8199. target.skewX = 0;
  8200. target.skewY = 0;
  8201. target.setAngle(0);
  8202. },
  8203. /**
  8204. * @private
  8205. * @param {CanvasRenderingContext2D} ctx to draw the selection on
  8206. */
  8207. _drawSelection: function (ctx) {
  8208. var groupSelector = this._groupSelector,
  8209. left = groupSelector.left,
  8210. top = groupSelector.top,
  8211. aleft = abs(left),
  8212. atop = abs(top);
  8213. if (this.selectionColor) {
  8214. ctx.fillStyle = this.selectionColor;
  8215. ctx.fillRect(
  8216. groupSelector.ex - ((left > 0) ? 0 : -left),
  8217. groupSelector.ey - ((top > 0) ? 0 : -top),
  8218. aleft,
  8219. atop
  8220. );
  8221. }
  8222. if (!this.selectionLineWidth || !this.selectionBorderColor) {
  8223. return;
  8224. }
  8225. ctx.lineWidth = this.selectionLineWidth;
  8226. ctx.strokeStyle = this.selectionBorderColor;
  8227. // selection border
  8228. if (this.selectionDashArray.length > 1 && !supportLineDash) {
  8229. var px = groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
  8230. py = groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop);
  8231. ctx.beginPath();
  8232. fabric.util.drawDashedLine(ctx, px, py, px + aleft, py, this.selectionDashArray);
  8233. fabric.util.drawDashedLine(ctx, px, py + atop - 1, px + aleft, py + atop - 1, this.selectionDashArray);
  8234. fabric.util.drawDashedLine(ctx, px, py, px, py + atop, this.selectionDashArray);
  8235. fabric.util.drawDashedLine(ctx, px + aleft - 1, py, px + aleft - 1, py + atop, this.selectionDashArray);
  8236. ctx.closePath();
  8237. ctx.stroke();
  8238. }
  8239. else {
  8240. fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray);
  8241. ctx.strokeRect(
  8242. groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
  8243. groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
  8244. aleft,
  8245. atop
  8246. );
  8247. }
  8248. },
  8249. /**
  8250. * Method that determines what object we are clicking on
  8251. * @param {Event} e mouse event
  8252. * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through
  8253. */
  8254. findTarget: function (e, skipGroup) {
  8255. if (this.skipTargetFind) {
  8256. return;
  8257. }
  8258. var ignoreZoom = true,
  8259. pointer = this.getPointer(e, ignoreZoom),
  8260. activeGroup = this.getActiveGroup(),
  8261. activeObject = this.getActiveObject(),
  8262. activeTarget;
  8263. // first check current group (if one exists)
  8264. // active group does not check sub targets like normal groups.
  8265. // if active group just exits.
  8266. if (activeGroup && !skipGroup && this._checkTarget(pointer, activeGroup)) {
  8267. this._fireOverOutEvents(activeGroup, e);
  8268. return activeGroup;
  8269. }
  8270. // if we hit the corner of an activeObject, let's return that.
  8271. if (activeObject && activeObject._findTargetCorner(pointer)) {
  8272. this._fireOverOutEvents(activeObject, e);
  8273. return activeObject;
  8274. }
  8275. if (activeObject && this._checkTarget(pointer, activeObject)) {
  8276. if (!this.preserveObjectStacking) {
  8277. this._fireOverOutEvents(activeObject, e);
  8278. return activeObject;
  8279. }
  8280. else {
  8281. activeTarget = activeObject;
  8282. }
  8283. }
  8284. this.targets = [];
  8285. var target = this._searchPossibleTargets(this._objects, pointer);
  8286. if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) {
  8287. target = activeTarget;
  8288. }
  8289. this._fireOverOutEvents(target, e);
  8290. return target;
  8291. },
  8292. /**
  8293. * @private
  8294. */
  8295. _fireOverOutEvents: function(target, e) {
  8296. if (target) {
  8297. if (this._hoveredTarget !== target) {
  8298. if (this._hoveredTarget) {
  8299. this.fire('mouse:out', { target: this._hoveredTarget, e: e });
  8300. this._hoveredTarget.fire('mouseout');
  8301. }
  8302. this.fire('mouse:over', { target: target, e: e });
  8303. target.fire('mouseover');
  8304. this._hoveredTarget = target;
  8305. }
  8306. }
  8307. else if (this._hoveredTarget) {
  8308. this.fire('mouse:out', { target: this._hoveredTarget, e: e });
  8309. this._hoveredTarget.fire('mouseout');
  8310. this._hoveredTarget = null;
  8311. }
  8312. },
  8313. /**
  8314. * @private
  8315. */
  8316. _checkTarget: function(pointer, obj) {
  8317. if (obj &&
  8318. obj.visible &&
  8319. obj.evented &&
  8320. this.containsPoint(null, obj, pointer)){
  8321. if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) {
  8322. var isTransparent = this.isTargetTransparent(obj, pointer.x, pointer.y);
  8323. if (!isTransparent) {
  8324. return true;
  8325. }
  8326. }
  8327. else {
  8328. return true;
  8329. }
  8330. }
  8331. },
  8332. /**
  8333. * @private
  8334. */
  8335. _searchPossibleTargets: function(objects, pointer) {
  8336. // Cache all targets where their bounding box contains point.
  8337. var target, i = objects.length, normalizedPointer, subTarget;
  8338. // Do not check for currently grouped objects, since we check the parent group itself.
  8339. // untill we call this function specifically to search inside the activeGroup
  8340. while (i--) {
  8341. if (this._checkTarget(pointer, objects[i])) {
  8342. target = objects[i];
  8343. if (target.type === 'group' && target.subTargetCheck) {
  8344. normalizedPointer = this._normalizePointer(target, pointer);
  8345. subTarget = this._searchPossibleTargets(target._objects, normalizedPointer);
  8346. subTarget && this.targets.push(subTarget);
  8347. }
  8348. break;
  8349. }
  8350. }
  8351. return target;
  8352. },
  8353. /**
  8354. * Returns pointer coordinates without the effect of the viewport
  8355. * @param {Object} pointer with "x" and "y" number values
  8356. * @return {Object} object with "x" and "y" number values
  8357. */
  8358. restorePointerVpt: function(pointer) {
  8359. return fabric.util.transformPoint(
  8360. pointer,
  8361. fabric.util.invertTransform(this.viewportTransform)
  8362. );
  8363. },
  8364. /**
  8365. * Returns pointer coordinates relative to canvas.
  8366. * @param {Event} e
  8367. * @param {Boolean} ignoreZoom
  8368. * @return {Object} object with "x" and "y" number values
  8369. */
  8370. getPointer: function (e, ignoreZoom, upperCanvasEl) {
  8371. if (!upperCanvasEl) {
  8372. upperCanvasEl = this.upperCanvasEl;
  8373. }
  8374. var pointer = getPointer(e),
  8375. bounds = upperCanvasEl.getBoundingClientRect(),
  8376. boundsWidth = bounds.width || 0,
  8377. boundsHeight = bounds.height || 0,
  8378. cssScale;
  8379. if (!boundsWidth || !boundsHeight ) {
  8380. if ('top' in bounds && 'bottom' in bounds) {
  8381. boundsHeight = Math.abs( bounds.top - bounds.bottom );
  8382. }
  8383. if ('right' in bounds && 'left' in bounds) {
  8384. boundsWidth = Math.abs( bounds.right - bounds.left );
  8385. }
  8386. }
  8387. this.calcOffset();
  8388. pointer.x = pointer.x - this._offset.left;
  8389. pointer.y = pointer.y - this._offset.top;
  8390. if (!ignoreZoom) {
  8391. pointer = this.restorePointerVpt(pointer);
  8392. }
  8393. if (boundsWidth === 0 || boundsHeight === 0) {
  8394. // If bounds are not available (i.e. not visible), do not apply scale.
  8395. cssScale = { width: 1, height: 1 };
  8396. }
  8397. else {
  8398. cssScale = {
  8399. width: upperCanvasEl.width / boundsWidth,
  8400. height: upperCanvasEl.height / boundsHeight
  8401. };
  8402. }
  8403. return {
  8404. x: pointer.x * cssScale.width,
  8405. y: pointer.y * cssScale.height
  8406. };
  8407. },
  8408. /**
  8409. * @private
  8410. * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
  8411. */
  8412. _createUpperCanvas: function () {
  8413. var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, '');
  8414. this.upperCanvasEl = this._createCanvasElement();
  8415. fabric.util.addClass(this.upperCanvasEl, 'upper-canvas ' + lowerCanvasClass);
  8416. this.wrapperEl.appendChild(this.upperCanvasEl);
  8417. this._copyCanvasStyle(this.lowerCanvasEl, this.upperCanvasEl);
  8418. this._applyCanvasStyle(this.upperCanvasEl);
  8419. this.contextTop = this.upperCanvasEl.getContext('2d');
  8420. },
  8421. /**
  8422. * @private
  8423. */
  8424. _createCacheCanvas: function () {
  8425. this.cacheCanvasEl = this._createCanvasElement();
  8426. this.cacheCanvasEl.setAttribute('width', this.width);
  8427. this.cacheCanvasEl.setAttribute('height', this.height);
  8428. this.contextCache = this.cacheCanvasEl.getContext('2d');
  8429. },
  8430. /**
  8431. * @private
  8432. */
  8433. _initWrapperElement: function () {
  8434. this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
  8435. 'class': this.containerClass
  8436. });
  8437. fabric.util.setStyle(this.wrapperEl, {
  8438. width: this.getWidth() + 'px',
  8439. height: this.getHeight() + 'px',
  8440. position: 'relative'
  8441. });
  8442. fabric.util.makeElementUnselectable(this.wrapperEl);
  8443. },
  8444. /**
  8445. * @private
  8446. * @param {HTMLElement} element canvas element to apply styles on
  8447. */
  8448. _applyCanvasStyle: function (element) {
  8449. var width = this.getWidth() || element.width,
  8450. height = this.getHeight() || element.height;
  8451. fabric.util.setStyle(element, {
  8452. position: 'absolute',
  8453. width: width + 'px',
  8454. height: height + 'px',
  8455. left: 0,
  8456. top: 0
  8457. });
  8458. element.width = width;
  8459. element.height = height;
  8460. fabric.util.makeElementUnselectable(element);
  8461. },
  8462. /**
  8463. * Copys the the entire inline style from one element (fromEl) to another (toEl)
  8464. * @private
  8465. * @param {Element} fromEl Element style is copied from
  8466. * @param {Element} toEl Element copied style is applied to
  8467. */
  8468. _copyCanvasStyle: function (fromEl, toEl) {
  8469. toEl.style.cssText = fromEl.style.cssText;
  8470. },
  8471. /**
  8472. * Returns context of canvas where object selection is drawn
  8473. * @return {CanvasRenderingContext2D}
  8474. */
  8475. getSelectionContext: function() {
  8476. return this.contextTop;
  8477. },
  8478. /**
  8479. * Returns &lt;canvas> element on which object selection is drawn
  8480. * @return {HTMLCanvasElement}
  8481. */
  8482. getSelectionElement: function () {
  8483. return this.upperCanvasEl;
  8484. },
  8485. /**
  8486. * @private
  8487. * @param {Object} object
  8488. */
  8489. _setActiveObject: function(object) {
  8490. if (this._activeObject) {
  8491. this._activeObject.set('active', false);
  8492. }
  8493. this._activeObject = object;
  8494. object.set('active', true);
  8495. },
  8496. /**
  8497. * Sets given object as the only active object on canvas
  8498. * @param {fabric.Object} object Object to set as an active one
  8499. * @param {Event} [e] Event (passed along when firing "object:selected")
  8500. * @return {fabric.Canvas} thisArg
  8501. * @chainable
  8502. */
  8503. setActiveObject: function (object, e) {
  8504. this._setActiveObject(object);
  8505. this.renderAll();
  8506. this.fire('object:selected', { target: object, e: e });
  8507. object.fire('selected', { e: e });
  8508. return this;
  8509. },
  8510. /**
  8511. * Returns currently active object
  8512. * @return {fabric.Object} active object
  8513. */
  8514. getActiveObject: function () {
  8515. return this._activeObject;
  8516. },
  8517. /**
  8518. * @private
  8519. * @param {fabric.Object} obj Object that was removed
  8520. */
  8521. _onObjectRemoved: function(obj) {
  8522. // removing active object should fire "selection:cleared" events
  8523. if (this.getActiveObject() === obj) {
  8524. this.fire('before:selection:cleared', { target: obj });
  8525. this._discardActiveObject();
  8526. this.fire('selection:cleared', { target: obj });
  8527. obj.fire('deselected');
  8528. }
  8529. this.callSuper('_onObjectRemoved', obj);
  8530. },
  8531. /**
  8532. * @private
  8533. */
  8534. _discardActiveObject: function() {
  8535. if (this._activeObject) {
  8536. this._activeObject.set('active', false);
  8537. }
  8538. this._activeObject = null;
  8539. },
  8540. /**
  8541. * Discards currently active object and fire events
  8542. * @param {event} e
  8543. * @return {fabric.Canvas} thisArg
  8544. * @chainable
  8545. */
  8546. discardActiveObject: function (e) {
  8547. var activeObject = this._activeObject;
  8548. this.fire('before:selection:cleared', { target: activeObject, e: e });
  8549. this._discardActiveObject();
  8550. this.fire('selection:cleared', { e: e });
  8551. activeObject && activeObject.fire('deselected', { e: e });
  8552. return this;
  8553. },
  8554. /**
  8555. * @private
  8556. * @param {fabric.Group} group
  8557. */
  8558. _setActiveGroup: function(group) {
  8559. this._activeGroup = group;
  8560. if (group) {
  8561. group.set('active', true);
  8562. }
  8563. },
  8564. /**
  8565. * Sets active group to a specified one
  8566. * @param {fabric.Group} group Group to set as a current one
  8567. * @param {Event} e Event object
  8568. * @return {fabric.Canvas} thisArg
  8569. * @chainable
  8570. */
  8571. setActiveGroup: function (group, e) {
  8572. this._setActiveGroup(group);
  8573. if (group) {
  8574. this.fire('object:selected', { target: group, e: e });
  8575. group.fire('selected', { e: e });
  8576. }
  8577. return this;
  8578. },
  8579. /**
  8580. * Returns currently active group
  8581. * @return {fabric.Group} Current group
  8582. */
  8583. getActiveGroup: function () {
  8584. return this._activeGroup;
  8585. },
  8586. /**
  8587. * @private
  8588. */
  8589. _discardActiveGroup: function() {
  8590. var g = this.getActiveGroup();
  8591. if (g) {
  8592. g.destroy();
  8593. }
  8594. this.setActiveGroup(null);
  8595. },
  8596. /**
  8597. * Discards currently active group and fire events
  8598. * @return {fabric.Canvas} thisArg
  8599. * @chainable
  8600. */
  8601. discardActiveGroup: function (e) {
  8602. var g = this.getActiveGroup();
  8603. this.fire('before:selection:cleared', { e: e, target: g });
  8604. this._discardActiveGroup();
  8605. this.fire('selection:cleared', { e: e });
  8606. return this;
  8607. },
  8608. /**
  8609. * Deactivates all objects on canvas, removing any active group or object
  8610. * @return {fabric.Canvas} thisArg
  8611. * @chainable
  8612. */
  8613. deactivateAll: function () {
  8614. var allObjects = this.getObjects(),
  8615. i = 0,
  8616. len = allObjects.length;
  8617. for ( ; i < len; i++) {
  8618. allObjects[i].set('active', false);
  8619. }
  8620. this._discardActiveGroup();
  8621. this._discardActiveObject();
  8622. return this;
  8623. },
  8624. /**
  8625. * Deactivates all objects and dispatches appropriate events
  8626. * @return {fabric.Canvas} thisArg
  8627. * @chainable
  8628. */
  8629. deactivateAllWithDispatch: function (e) {
  8630. var activeGroup = this.getActiveGroup(),
  8631. activeObject = this.getActiveObject();
  8632. if (activeObject || activeGroup) {
  8633. this.fire('before:selection:cleared', { target: activeObject || activeGroup, e: e });
  8634. }
  8635. this.deactivateAll();
  8636. if (activeObject || activeGroup) {
  8637. this.fire('selection:cleared', { e: e, target: activeObject });
  8638. activeObject && activeObject.fire('deselected');
  8639. }
  8640. return this;
  8641. },
  8642. /**
  8643. * Clears a canvas element and removes all event listeners
  8644. * @return {fabric.Canvas} thisArg
  8645. * @chainable
  8646. */
  8647. dispose: function () {
  8648. this.callSuper('dispose');
  8649. var wrapper = this.wrapperEl;
  8650. this.removeListeners();
  8651. wrapper.removeChild(this.upperCanvasEl);
  8652. wrapper.removeChild(this.lowerCanvasEl);
  8653. delete this.upperCanvasEl;
  8654. if (wrapper.parentNode) {
  8655. wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl);
  8656. }
  8657. delete this.wrapperEl;
  8658. return this;
  8659. },
  8660. /**
  8661. * Clears all contexts (background, main, top) of an instance
  8662. * @return {fabric.Canvas} thisArg
  8663. * @chainable
  8664. */
  8665. clear: function () {
  8666. this.discardActiveGroup();
  8667. this.discardActiveObject();
  8668. this.clearContext(this.contextTop);
  8669. return this.callSuper('clear');
  8670. },
  8671. /**
  8672. * Draws objects' controls (borders/controls)
  8673. * @param {CanvasRenderingContext2D} ctx Context to render controls on
  8674. */
  8675. drawControls: function(ctx) {
  8676. var activeGroup = this.getActiveGroup();
  8677. if (activeGroup) {
  8678. activeGroup._renderControls(ctx);
  8679. }
  8680. else {
  8681. this._drawObjectsControls(ctx);
  8682. }
  8683. },
  8684. /**
  8685. * @private
  8686. */
  8687. _drawObjectsControls: function(ctx) {
  8688. for (var i = 0, len = this._objects.length; i < len; ++i) {
  8689. if (!this._objects[i] || !this._objects[i].active) {
  8690. continue;
  8691. }
  8692. this._objects[i]._renderControls(ctx);
  8693. }
  8694. },
  8695. /**
  8696. * @private
  8697. */
  8698. _toObject: function(instance, methodName, propertiesToInclude) {
  8699. //If the object is part of the current selection group, it should
  8700. //be transformed appropriately
  8701. //i.e. it should be serialised as it would appear if the selection group
  8702. //were to be destroyed.
  8703. var originalProperties = this._realizeGroupTransformOnObject(instance),
  8704. object = this.callSuper('_toObject', instance, methodName, propertiesToInclude);
  8705. //Undo the damage we did by changing all of its properties
  8706. this._unwindGroupTransformOnObject(instance, originalProperties);
  8707. return object;
  8708. },
  8709. /**
  8710. * Realises an object's group transformation on it
  8711. * @private
  8712. * @param {fabric.Object} [instance] the object to transform (gets mutated)
  8713. * @returns the original values of instance which were changed
  8714. */
  8715. _realizeGroupTransformOnObject: function(instance) {
  8716. var layoutProps = ['angle', 'flipX', 'flipY', 'height', 'left', 'scaleX', 'scaleY', 'top', 'width'];
  8717. if (instance.group && instance.group === this.getActiveGroup()) {
  8718. //Copy all the positionally relevant properties across now
  8719. var originalValues = {};
  8720. layoutProps.forEach(function(prop) {
  8721. originalValues[prop] = instance[prop];
  8722. });
  8723. this.getActiveGroup().realizeTransform(instance);
  8724. return originalValues;
  8725. }
  8726. else {
  8727. return null;
  8728. }
  8729. },
  8730. /**
  8731. * Restores the changed properties of instance
  8732. * @private
  8733. * @param {fabric.Object} [instance] the object to un-transform (gets mutated)
  8734. * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject
  8735. */
  8736. _unwindGroupTransformOnObject: function(instance, originalValues) {
  8737. if (originalValues) {
  8738. instance.set(originalValues);
  8739. }
  8740. },
  8741. /**
  8742. * @private
  8743. */
  8744. _setSVGObject: function(markup, instance, reviver) {
  8745. var originalProperties;
  8746. //If the object is in a selection group, simulate what would happen to that
  8747. //object when the group is deselected
  8748. originalProperties = this._realizeGroupTransformOnObject(instance);
  8749. this.callSuper('_setSVGObject', markup, instance, reviver);
  8750. this._unwindGroupTransformOnObject(instance, originalProperties);
  8751. },
  8752. });
  8753. // copying static properties manually to work around Opera's bug,
  8754. // where "prototype" property is enumerable and overrides existing prototype
  8755. for (var prop in fabric.StaticCanvas) {
  8756. if (prop !== 'prototype') {
  8757. fabric.Canvas[prop] = fabric.StaticCanvas[prop];
  8758. }
  8759. }
  8760. if (fabric.isTouchSupported) {
  8761. /** @ignore */
  8762. fabric.Canvas.prototype._setCursorFromEvent = function() { };
  8763. }
  8764. /**
  8765. * @ignore
  8766. * @class fabric.Element
  8767. * @alias fabric.Canvas
  8768. * @deprecated Use {@link fabric.Canvas} instead.
  8769. * @constructor
  8770. */
  8771. fabric.Element = fabric.Canvas;
  8772. })();
  8773. (function() {
  8774. var cursorOffset = {
  8775. mt: 0, // n
  8776. tr: 1, // ne
  8777. mr: 2, // e
  8778. br: 3, // se
  8779. mb: 4, // s
  8780. bl: 5, // sw
  8781. ml: 6, // w
  8782. tl: 7 // nw
  8783. },
  8784. addListener = fabric.util.addListener,
  8785. removeListener = fabric.util.removeListener;
  8786. fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
  8787. /**
  8788. * Map of cursor style values for each of the object controls
  8789. * @private
  8790. */
  8791. cursorMap: [
  8792. 'n-resize',
  8793. 'ne-resize',
  8794. 'e-resize',
  8795. 'se-resize',
  8796. 's-resize',
  8797. 'sw-resize',
  8798. 'w-resize',
  8799. 'nw-resize'
  8800. ],
  8801. /**
  8802. * Adds mouse listeners to canvas
  8803. * @private
  8804. */
  8805. _initEventListeners: function () {
  8806. this._bindEvents();
  8807. addListener(fabric.window, 'resize', this._onResize);
  8808. // mouse events
  8809. addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
  8810. addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  8811. addListener(this.upperCanvasEl, 'mouseout', this._onMouseOut);
  8812. addListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter);
  8813. addListener(this.upperCanvasEl, 'wheel', this._onMouseWheel);
  8814. addListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu);
  8815. // touch events
  8816. addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
  8817. addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
  8818. if (typeof eventjs !== 'undefined' && 'add' in eventjs) {
  8819. eventjs.add(this.upperCanvasEl, 'gesture', this._onGesture);
  8820. eventjs.add(this.upperCanvasEl, 'drag', this._onDrag);
  8821. eventjs.add(this.upperCanvasEl, 'orientation', this._onOrientationChange);
  8822. eventjs.add(this.upperCanvasEl, 'shake', this._onShake);
  8823. eventjs.add(this.upperCanvasEl, 'longpress', this._onLongPress);
  8824. }
  8825. },
  8826. /**
  8827. * @private
  8828. */
  8829. _bindEvents: function() {
  8830. this._onMouseDown = this._onMouseDown.bind(this);
  8831. this._onMouseMove = this._onMouseMove.bind(this);
  8832. this._onMouseUp = this._onMouseUp.bind(this);
  8833. this._onResize = this._onResize.bind(this);
  8834. this._onGesture = this._onGesture.bind(this);
  8835. this._onDrag = this._onDrag.bind(this);
  8836. this._onShake = this._onShake.bind(this);
  8837. this._onLongPress = this._onLongPress.bind(this);
  8838. this._onOrientationChange = this._onOrientationChange.bind(this);
  8839. this._onMouseWheel = this._onMouseWheel.bind(this);
  8840. this._onMouseOut = this._onMouseOut.bind(this);
  8841. this._onMouseEnter = this._onMouseEnter.bind(this);
  8842. this._onContextMenu = this._onContextMenu.bind(this);
  8843. },
  8844. /**
  8845. * Removes all event listeners
  8846. */
  8847. removeListeners: function() {
  8848. removeListener(fabric.window, 'resize', this._onResize);
  8849. removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
  8850. removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  8851. removeListener(this.upperCanvasEl, 'mouseout', this._onMouseOut);
  8852. removeListener(this.upperCanvasEl, 'mouseenter', this._onMouseEnter);
  8853. removeListener(this.upperCanvasEl, 'wheel', this._onMouseWheel);
  8854. removeListener(this.upperCanvasEl, 'contextmenu', this._onContextMenu);
  8855. removeListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
  8856. removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
  8857. if (typeof eventjs !== 'undefined' && 'remove' in eventjs) {
  8858. eventjs.remove(this.upperCanvasEl, 'gesture', this._onGesture);
  8859. eventjs.remove(this.upperCanvasEl, 'drag', this._onDrag);
  8860. eventjs.remove(this.upperCanvasEl, 'orientation', this._onOrientationChange);
  8861. eventjs.remove(this.upperCanvasEl, 'shake', this._onShake);
  8862. eventjs.remove(this.upperCanvasEl, 'longpress', this._onLongPress);
  8863. }
  8864. },
  8865. /**
  8866. * @private
  8867. * @param {Event} [e] Event object fired on Event.js gesture
  8868. * @param {Event} [self] Inner Event object
  8869. */
  8870. _onGesture: function(e, self) {
  8871. this.__onTransformGesture && this.__onTransformGesture(e, self);
  8872. },
  8873. /**
  8874. * @private
  8875. * @param {Event} [e] Event object fired on Event.js drag
  8876. * @param {Event} [self] Inner Event object
  8877. */
  8878. _onDrag: function(e, self) {
  8879. this.__onDrag && this.__onDrag(e, self);
  8880. },
  8881. /**
  8882. * @private
  8883. * @param {Event} [e] Event object fired on wheel event
  8884. */
  8885. _onMouseWheel: function(e) {
  8886. this.__onMouseWheel(e);
  8887. },
  8888. /**
  8889. * @private
  8890. * @param {Event} e Event object fired on mousedown
  8891. */
  8892. _onMouseOut: function(e) {
  8893. var target = this._hoveredTarget;
  8894. this.fire('mouse:out', { target: target, e: e });
  8895. this._hoveredTarget = null;
  8896. target && target.fire('mouseout', { e: e });
  8897. },
  8898. /**
  8899. * @private
  8900. * @param {Event} e Event object fired on mouseenter
  8901. */
  8902. _onMouseEnter: function(e) {
  8903. if (!this.findTarget(e)) {
  8904. this.fire('mouse:over', { target: null, e: e });
  8905. this._hoveredTarget = null;
  8906. }
  8907. },
  8908. /**
  8909. * @private
  8910. * @param {Event} [e] Event object fired on Event.js orientation change
  8911. * @param {Event} [self] Inner Event object
  8912. */
  8913. _onOrientationChange: function(e, self) {
  8914. this.__onOrientationChange && this.__onOrientationChange(e, self);
  8915. },
  8916. /**
  8917. * @private
  8918. * @param {Event} [e] Event object fired on Event.js shake
  8919. * @param {Event} [self] Inner Event object
  8920. */
  8921. _onShake: function(e, self) {
  8922. this.__onShake && this.__onShake(e, self);
  8923. },
  8924. /**
  8925. * @private
  8926. * @param {Event} [e] Event object fired on Event.js shake
  8927. * @param {Event} [self] Inner Event object
  8928. */
  8929. _onLongPress: function(e, self) {
  8930. this.__onLongPress && this.__onLongPress(e, self);
  8931. },
  8932. /**
  8933. * @private
  8934. * @param {Event} e Event object fired on mousedown
  8935. */
  8936. _onContextMenu: function (e) {
  8937. if (this.stopContextMenu) {
  8938. e.stopPropagation()
  8939. e.preventDefault();
  8940. }
  8941. return false;
  8942. },
  8943. /**
  8944. * @private
  8945. * @param {Event} e Event object fired on mousedown
  8946. */
  8947. _onMouseDown: function (e) {
  8948. this.__onMouseDown(e);
  8949. addListener(fabric.document, 'touchend', this._onMouseUp);
  8950. addListener(fabric.document, 'touchmove', this._onMouseMove);
  8951. removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  8952. removeListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
  8953. if (e.type === 'touchstart') {
  8954. // Unbind mousedown to prevent double triggers from touch devices
  8955. removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
  8956. }
  8957. else {
  8958. addListener(fabric.document, 'mouseup', this._onMouseUp);
  8959. addListener(fabric.document, 'mousemove', this._onMouseMove);
  8960. }
  8961. },
  8962. /**
  8963. * @private
  8964. * @param {Event} e Event object fired on mouseup
  8965. */
  8966. _onMouseUp: function (e) {
  8967. this.__onMouseUp(e);
  8968. removeListener(fabric.document, 'mouseup', this._onMouseUp);
  8969. removeListener(fabric.document, 'touchend', this._onMouseUp);
  8970. removeListener(fabric.document, 'mousemove', this._onMouseMove);
  8971. removeListener(fabric.document, 'touchmove', this._onMouseMove);
  8972. addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
  8973. addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
  8974. if (e.type === 'touchend') {
  8975. // Wait 400ms before rebinding mousedown to prevent double triggers
  8976. // from touch devices
  8977. var _this = this;
  8978. setTimeout(function() {
  8979. addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown);
  8980. }, 400);
  8981. }
  8982. },
  8983. /**
  8984. * @private
  8985. * @param {Event} e Event object fired on mousemove
  8986. */
  8987. _onMouseMove: function (e) {
  8988. !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
  8989. this.__onMouseMove(e);
  8990. },
  8991. /**
  8992. * @private
  8993. */
  8994. _onResize: function () {
  8995. this.calcOffset();
  8996. },
  8997. /**
  8998. * Decides whether the canvas should be redrawn in mouseup and mousedown events.
  8999. * @private
  9000. * @param {Object} target
  9001. * @param {Object} pointer
  9002. */
  9003. _shouldRender: function(target, pointer) {
  9004. var activeObject = this.getActiveGroup() || this.getActiveObject();
  9005. return !!(
  9006. (target && (
  9007. target.isMoving ||
  9008. target !== activeObject))
  9009. ||
  9010. (!target && !!activeObject)
  9011. ||
  9012. (!target && !activeObject && !this._groupSelector)
  9013. ||
  9014. (pointer &&
  9015. this._previousPointer &&
  9016. this.selection && (
  9017. pointer.x !== this._previousPointer.x ||
  9018. pointer.y !== this._previousPointer.y))
  9019. );
  9020. },
  9021. /**
  9022. * Method that defines the actions when mouse is released on canvas.
  9023. * The method resets the currentTransform parameters, store the image corner
  9024. * position in the image object and render the canvas on top.
  9025. * @private
  9026. * @param {Event} e Event object fired on mouseup
  9027. */
  9028. __onMouseUp: function (e) {
  9029. var target, searchTarget = true, transform = this._currentTransform,
  9030. groupSelector = this._groupSelector,
  9031. isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0));
  9032. if (this.isDrawingMode && this._isCurrentlyDrawing) {
  9033. this._onMouseUpInDrawingMode(e);
  9034. return;
  9035. }
  9036. if (transform) {
  9037. this._finalizeCurrentTransform();
  9038. searchTarget = !transform.actionPerformed;
  9039. }
  9040. target = searchTarget ? this.findTarget(e, true) : transform.target;
  9041. var shouldRender = this._shouldRender(target, this.getPointer(e));
  9042. if (target || !isClick) {
  9043. this._maybeGroupObjects(e);
  9044. }
  9045. else {
  9046. // those are done by default on mouse up
  9047. // by _maybeGroupObjects, we are skipping it in case of no target find
  9048. this._groupSelector = null;
  9049. this._currentTransform = null;
  9050. }
  9051. if (target) {
  9052. target.isMoving = false;
  9053. }
  9054. this._handleCursorAndEvent(e, target, 'up');
  9055. target && (target.__corner = 0);
  9056. shouldRender && this.renderAll();
  9057. },
  9058. /**
  9059. * set cursor for mouse up and handle mouseUp event
  9060. * @param {Event} e event from mouse
  9061. * @param {fabric.Object} target receiving event
  9062. * @param {String} eventType event to fire (up, down or move)
  9063. */
  9064. _handleCursorAndEvent: function(e, target, eventType) {
  9065. this._setCursorFromEvent(e, target);
  9066. this._handleEvent(e, eventType, target ? target : null);
  9067. },
  9068. /**
  9069. * Handle event firing for target and subtargets
  9070. * @param {Event} e event from mouse
  9071. * @param {String} eventType event to fire (up, down or move)
  9072. * @param {fabric.Object} targetObj receiving event
  9073. */
  9074. _handleEvent: function(e, eventType, targetObj) {
  9075. var target = typeof targetObj === undefined ? this.findTarget(e) : targetObj,
  9076. targets = this.targets || [],
  9077. options = { e: e, target: target, subTargets: targets };
  9078. this.fire('mouse:' + eventType, options);
  9079. target && target.fire('mouse' + eventType, options);
  9080. for (var i = 0; i < targets.length; i++) {
  9081. targets[i].fire('mouse' + eventType, options);
  9082. }
  9083. },
  9084. /**
  9085. * @private
  9086. */
  9087. _finalizeCurrentTransform: function() {
  9088. var transform = this._currentTransform,
  9089. target = transform.target;
  9090. if (target._scaling) {
  9091. target._scaling = false;
  9092. }
  9093. target.setCoords();
  9094. this._restoreOriginXY(target);
  9095. if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) {
  9096. this.fire('object:modified', { target: target });
  9097. target.fire('modified');
  9098. }
  9099. },
  9100. /**
  9101. * @private
  9102. * @param {Object} target Object to restore
  9103. */
  9104. _restoreOriginXY: function(target) {
  9105. if (this._previousOriginX && this._previousOriginY) {
  9106. var originPoint = target.translateToOriginPoint(
  9107. target.getCenterPoint(),
  9108. this._previousOriginX,
  9109. this._previousOriginY);
  9110. target.originX = this._previousOriginX;
  9111. target.originY = this._previousOriginY;
  9112. target.left = originPoint.x;
  9113. target.top = originPoint.y;
  9114. this._previousOriginX = null;
  9115. this._previousOriginY = null;
  9116. }
  9117. },
  9118. /**
  9119. * @private
  9120. * @param {Event} e Event object fired on mousedown
  9121. */
  9122. _onMouseDownInDrawingMode: function(e) {
  9123. this._isCurrentlyDrawing = true;
  9124. this.discardActiveObject(e).renderAll();
  9125. if (this.clipTo) {
  9126. fabric.util.clipContext(this, this.contextTop);
  9127. }
  9128. var pointer = this.getPointer(e);
  9129. this.freeDrawingBrush.onMouseDown(pointer);
  9130. this._handleEvent(e, 'down');
  9131. },
  9132. /**
  9133. * @private
  9134. * @param {Event} e Event object fired on mousemove
  9135. */
  9136. _onMouseMoveInDrawingMode: function(e) {
  9137. if (this._isCurrentlyDrawing) {
  9138. var pointer = this.getPointer(e);
  9139. this.freeDrawingBrush.onMouseMove(pointer);
  9140. }
  9141. this.setCursor(this.freeDrawingCursor);
  9142. this._handleEvent(e, 'move');
  9143. },
  9144. /**
  9145. * @private
  9146. * @param {Event} e Event object fired on mouseup
  9147. */
  9148. _onMouseUpInDrawingMode: function(e) {
  9149. this._isCurrentlyDrawing = false;
  9150. if (this.clipTo) {
  9151. this.contextTop.restore();
  9152. }
  9153. this.freeDrawingBrush.onMouseUp();
  9154. this._handleEvent(e, 'up');
  9155. },
  9156. /**
  9157. * Method that defines the actions when mouse is clic ked on canvas.
  9158. * The method inits the currentTransform parameters and renders all the
  9159. * canvas so the current image can be placed on the top canvas and the rest
  9160. * in on the container one.
  9161. * @private
  9162. * @param {Event} e Event object fired on mousedown
  9163. */
  9164. __onMouseDown: function (e) {
  9165. var target = this.findTarget(e),
  9166. pointer = this.getPointer(e, true);
  9167. // if right click just fire events
  9168. var isRightClick = 'which' in e ? e.which === 3 : e.button === 2;
  9169. if (isRightClick) {
  9170. if (this.fireRightClick) {
  9171. this._handleEvent(e, 'down', target ? target : null);
  9172. }
  9173. return;
  9174. }
  9175. if (this.isDrawingMode) {
  9176. this._onMouseDownInDrawingMode(e);
  9177. return;
  9178. }
  9179. // ignore if some object is being transformed at this moment
  9180. if (this._currentTransform) {
  9181. return;
  9182. }
  9183. // save pointer for check in __onMouseUp event
  9184. this._previousPointer = pointer;
  9185. var shouldRender = this._shouldRender(target, pointer),
  9186. shouldGroup = this._shouldGroup(e, target);
  9187. if (this._shouldClearSelection(e, target)) {
  9188. this._clearSelection(e, target, pointer);
  9189. }
  9190. else if (shouldGroup) {
  9191. this._handleGrouping(e, target);
  9192. target = this.getActiveGroup();
  9193. }
  9194. if (target) {
  9195. if (target.selectable && (target.__corner || !shouldGroup)) {
  9196. this._beforeTransform(e, target);
  9197. this._setupCurrentTransform(e, target);
  9198. }
  9199. if (target !== this.getActiveGroup() && target !== this.getActiveObject()) {
  9200. this.deactivateAll();
  9201. target.selectable && this.setActiveObject(target, e);
  9202. }
  9203. }
  9204. this._handleEvent(e, 'down', target ? target : null);
  9205. // we must renderAll so that we update the visuals
  9206. shouldRender && this.renderAll();
  9207. },
  9208. /**
  9209. * @private
  9210. */
  9211. _beforeTransform: function(e, target) {
  9212. this.stateful && target.saveState();
  9213. // determine if it's a drag or rotate case
  9214. if (target._findTargetCorner(this.getPointer(e))) {
  9215. this.onBeforeScaleRotate(target);
  9216. }
  9217. },
  9218. /**
  9219. * @private
  9220. */
  9221. _clearSelection: function(e, target, pointer) {
  9222. this.deactivateAllWithDispatch(e);
  9223. if (target && target.selectable) {
  9224. this.setActiveObject(target, e);
  9225. }
  9226. else if (this.selection) {
  9227. this._groupSelector = {
  9228. ex: pointer.x,
  9229. ey: pointer.y,
  9230. top: 0,
  9231. left: 0
  9232. };
  9233. }
  9234. },
  9235. /**
  9236. * @private
  9237. * @param {Object} target Object for that origin is set to center
  9238. */
  9239. _setOriginToCenter: function(target) {
  9240. this._previousOriginX = this._currentTransform.target.originX;
  9241. this._previousOriginY = this._currentTransform.target.originY;
  9242. var center = target.getCenterPoint();
  9243. target.originX = 'center';
  9244. target.originY = 'center';
  9245. target.left = center.x;
  9246. target.top = center.y;
  9247. this._currentTransform.left = target.left;
  9248. this._currentTransform.top = target.top;
  9249. },
  9250. /**
  9251. * @private
  9252. * @param {Object} target Object for that center is set to origin
  9253. */
  9254. _setCenterToOrigin: function(target) {
  9255. var originPoint = target.translateToOriginPoint(
  9256. target.getCenterPoint(),
  9257. this._previousOriginX,
  9258. this._previousOriginY);
  9259. target.originX = this._previousOriginX;
  9260. target.originY = this._previousOriginY;
  9261. target.left = originPoint.x;
  9262. target.top = originPoint.y;
  9263. this._previousOriginX = null;
  9264. this._previousOriginY = null;
  9265. },
  9266. /**
  9267. * Method that defines the actions when mouse is hovering the canvas.
  9268. * The currentTransform parameter will definde whether the user is rotating/scaling/translating
  9269. * an image or neither of them (only hovering). A group selection is also possible and would cancel
  9270. * all any other type of action.
  9271. * In case of an image transformation only the top canvas will be rendered.
  9272. * @private
  9273. * @param {Event} e Event object fired on mousemove
  9274. */
  9275. __onMouseMove: function (e) {
  9276. var target, pointer;
  9277. if (this.isDrawingMode) {
  9278. this._onMouseMoveInDrawingMode(e);
  9279. return;
  9280. }
  9281. if (typeof e.touches !== 'undefined' && e.touches.length > 1) {
  9282. return;
  9283. }
  9284. var groupSelector = this._groupSelector;
  9285. // We initially clicked in an empty area, so we draw a box for multiple selection
  9286. if (groupSelector) {
  9287. pointer = this.getPointer(e, true);
  9288. groupSelector.left = pointer.x - groupSelector.ex;
  9289. groupSelector.top = pointer.y - groupSelector.ey;
  9290. this.renderTop();
  9291. }
  9292. else if (!this._currentTransform) {
  9293. target = this.findTarget(e);
  9294. this._setCursorFromEvent(e, target);
  9295. }
  9296. else {
  9297. this._transformObject(e);
  9298. }
  9299. this._handleEvent(e, 'move', target ? target : null);
  9300. },
  9301. /**
  9302. * Method that defines actions when an Event Mouse Wheel
  9303. * @param {Event} e Event object fired on mouseup
  9304. */
  9305. __onMouseWheel: function(e) {
  9306. this.fire('mouse:wheel', {
  9307. e: e
  9308. });
  9309. },
  9310. /**
  9311. * @private
  9312. * @param {Event} e Event fired on mousemove
  9313. */
  9314. _transformObject: function(e) {
  9315. var pointer = this.getPointer(e),
  9316. transform = this._currentTransform;
  9317. transform.reset = false;
  9318. transform.target.isMoving = true;
  9319. this._beforeScaleTransform(e, transform);
  9320. this._performTransformAction(e, transform, pointer);
  9321. transform.actionPerformed && this.renderAll();
  9322. },
  9323. /**
  9324. * @private
  9325. */
  9326. _performTransformAction: function(e, transform, pointer) {
  9327. var x = pointer.x,
  9328. y = pointer.y,
  9329. target = transform.target,
  9330. action = transform.action,
  9331. actionPerformed = false;
  9332. if (action === 'rotate') {
  9333. (actionPerformed = this._rotateObject(x, y)) && this._fire('rotating', target, e);
  9334. }
  9335. else if (action === 'scale') {
  9336. (actionPerformed = this._onScale(e, transform, x, y)) && this._fire('scaling', target, e);
  9337. }
  9338. else if (action === 'scaleX') {
  9339. (actionPerformed = this._scaleObject(x, y, 'x')) && this._fire('scaling', target, e);
  9340. }
  9341. else if (action === 'scaleY') {
  9342. (actionPerformed = this._scaleObject(x, y, 'y')) && this._fire('scaling', target, e);
  9343. }
  9344. else if (action === 'skewX') {
  9345. (actionPerformed = this._skewObject(x, y, 'x')) && this._fire('skewing', target, e);
  9346. }
  9347. else if (action === 'skewY') {
  9348. (actionPerformed = this._skewObject(x, y, 'y')) && this._fire('skewing', target, e);
  9349. }
  9350. else {
  9351. actionPerformed = this._translateObject(x, y);
  9352. if (actionPerformed) {
  9353. this._fire('moving', target, e);
  9354. this.setCursor(target.moveCursor || this.moveCursor);
  9355. }
  9356. }
  9357. transform.actionPerformed = actionPerformed;
  9358. },
  9359. /**
  9360. * @private
  9361. */
  9362. _fire: function(eventName, target, e) {
  9363. this.fire('object:' + eventName, { target: target, e: e });
  9364. target.fire(eventName, { e: e });
  9365. },
  9366. /**
  9367. * @private
  9368. */
  9369. _beforeScaleTransform: function(e, transform) {
  9370. if (transform.action === 'scale' || transform.action === 'scaleX' || transform.action === 'scaleY') {
  9371. var centerTransform = this._shouldCenterTransform(transform.target);
  9372. // Switch from a normal resize to center-based
  9373. if ((centerTransform && (transform.originX !== 'center' || transform.originY !== 'center')) ||
  9374. // Switch from center-based resize to normal one
  9375. (!centerTransform && transform.originX === 'center' && transform.originY === 'center')
  9376. ) {
  9377. this._resetCurrentTransform();
  9378. transform.reset = true;
  9379. }
  9380. }
  9381. },
  9382. /**
  9383. * @private
  9384. * @param {Event} e Event object
  9385. * @param {Object} transform current tranform
  9386. * @param {Number} x mouse position x from origin
  9387. * @param {Number} y mouse poistion y from origin
  9388. * @return {Boolean} true if the scaling occurred
  9389. */
  9390. _onScale: function(e, transform, x, y) {
  9391. if ((e[this.uniScaleKey] || this.uniScaleTransform) && !transform.target.get('lockUniScaling')) {
  9392. transform.currentAction = 'scale';
  9393. return this._scaleObject(x, y);
  9394. }
  9395. else {
  9396. // Switch from a normal resize to proportional
  9397. if (!transform.reset && transform.currentAction === 'scale') {
  9398. this._resetCurrentTransform();
  9399. }
  9400. transform.currentAction = 'scaleEqually';
  9401. return this._scaleObject(x, y, 'equally');
  9402. }
  9403. },
  9404. /**
  9405. * Sets the cursor depending on where the canvas is being hovered.
  9406. * Note: very buggy in Opera
  9407. * @param {Event} e Event object
  9408. * @param {Object} target Object that the mouse is hovering, if so.
  9409. */
  9410. _setCursorFromEvent: function (e, target) {
  9411. if (!target) {
  9412. this.setCursor(this.defaultCursor);
  9413. return false;
  9414. }
  9415. var hoverCursor = target.hoverCursor || this.hoverCursor;
  9416. if (!target.selectable) {
  9417. //let's skip _findTargetCorner if object is not selectable
  9418. this.setCursor(hoverCursor);
  9419. }
  9420. else {
  9421. var activeGroup = this.getActiveGroup(),
  9422. // only show proper corner when group selection is not active
  9423. corner = target._findTargetCorner
  9424. && (!activeGroup || !activeGroup.contains(target))
  9425. && target._findTargetCorner(this.getPointer(e, true));
  9426. if (!corner) {
  9427. this.setCursor(hoverCursor);
  9428. }
  9429. else {
  9430. this._setCornerCursor(corner, target, e);
  9431. }
  9432. }
  9433. //actually unclear why it should return something
  9434. //is never evaluated
  9435. return true;
  9436. },
  9437. /**
  9438. * @private
  9439. */
  9440. _setCornerCursor: function(corner, target, e) {
  9441. if (corner in cursorOffset) {
  9442. this.setCursor(this._getRotatedCornerCursor(corner, target, e));
  9443. }
  9444. else if (corner === 'mtr' && target.hasRotatingPoint) {
  9445. this.setCursor(this.rotationCursor);
  9446. }
  9447. else {
  9448. this.setCursor(this.defaultCursor);
  9449. return false;
  9450. }
  9451. },
  9452. /**
  9453. * @private
  9454. */
  9455. _getRotatedCornerCursor: function(corner, target, e) {
  9456. var n = Math.round((target.getAngle() % 360) / 45);
  9457. if (n < 0) {
  9458. n += 8; // full circle ahead
  9459. }
  9460. n += cursorOffset[corner];
  9461. if (e[this.altActionKey] && cursorOffset[corner] % 2 === 0) {
  9462. //if we are holding shift and we are on a mx corner...
  9463. n += 2;
  9464. }
  9465. // normalize n to be from 0 to 7
  9466. n %= 8;
  9467. return this.cursorMap[n];
  9468. }
  9469. });
  9470. })();
  9471. (function() {
  9472. var min = Math.min,
  9473. max = Math.max;
  9474. fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ {
  9475. /**
  9476. * @private
  9477. * @param {Event} e Event object
  9478. * @param {fabric.Object} target
  9479. * @return {Boolean}
  9480. */
  9481. _shouldGroup: function(e, target) {
  9482. var activeObject = this.getActiveObject();
  9483. return e[this.selectionKey] && target && target.selectable &&
  9484. (this.getActiveGroup() || (activeObject && activeObject !== target))
  9485. && this.selection;
  9486. },
  9487. /**
  9488. * @private
  9489. * @param {Event} e Event object
  9490. * @param {fabric.Object} target
  9491. */
  9492. _handleGrouping: function (e, target) {
  9493. var activeGroup = this.getActiveGroup();
  9494. if (target === activeGroup) {
  9495. // if it's a group, find target again, using activeGroup objects
  9496. target = this.findTarget(e, true);
  9497. // if even object is not found, bail out
  9498. if (!target) {
  9499. return;
  9500. }
  9501. }
  9502. if (activeGroup) {
  9503. this._updateActiveGroup(target, e);
  9504. }
  9505. else {
  9506. this._createActiveGroup(target, e);
  9507. }
  9508. if (this._activeGroup) {
  9509. this._activeGroup.saveCoords();
  9510. }
  9511. },
  9512. /**
  9513. * @private
  9514. */
  9515. _updateActiveGroup: function(target, e) {
  9516. var activeGroup = this.getActiveGroup();
  9517. if (activeGroup.contains(target)) {
  9518. activeGroup.removeWithUpdate(target);
  9519. target.set('active', false);
  9520. if (activeGroup.size() === 1) {
  9521. // remove group alltogether if after removal it only contains 1 object
  9522. this.discardActiveGroup(e);
  9523. // activate last remaining object
  9524. this.setActiveObject(activeGroup.item(0));
  9525. return;
  9526. }
  9527. }
  9528. else {
  9529. activeGroup.addWithUpdate(target);
  9530. }
  9531. this.fire('selection:created', { target: activeGroup, e: e });
  9532. activeGroup.set('active', true);
  9533. },
  9534. /**
  9535. * @private
  9536. */
  9537. _createActiveGroup: function(target, e) {
  9538. if (this._activeObject && target !== this._activeObject) {
  9539. var group = this._createGroup(target);
  9540. group.addWithUpdate();
  9541. this.setActiveGroup(group);
  9542. this._activeObject = null;
  9543. this.fire('selection:created', { target: group, e: e });
  9544. }
  9545. target.set('active', true);
  9546. },
  9547. /**
  9548. * @private
  9549. * @param {Object} target
  9550. */
  9551. _createGroup: function(target) {
  9552. var objects = this.getObjects(),
  9553. isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target),
  9554. groupObjects = isActiveLower
  9555. ? [this._activeObject, target]
  9556. : [target, this._activeObject];
  9557. this._activeObject.isEditing && this._activeObject.exitEditing();
  9558. return new fabric.Group(groupObjects, {
  9559. canvas: this
  9560. });
  9561. },
  9562. /**
  9563. * @private
  9564. * @param {Event} e mouse event
  9565. */
  9566. _groupSelectedObjects: function (e) {
  9567. var group = this._collectObjects();
  9568. // do not create group for 1 element only
  9569. if (group.length === 1) {
  9570. this.setActiveObject(group[0], e);
  9571. }
  9572. else if (group.length > 1) {
  9573. group = new fabric.Group(group.reverse(), {
  9574. canvas: this
  9575. });
  9576. group.addWithUpdate();
  9577. this.setActiveGroup(group, e);
  9578. group.saveCoords();
  9579. this.fire('selection:created', { target: group });
  9580. this.renderAll();
  9581. }
  9582. },
  9583. /**
  9584. * @private
  9585. */
  9586. _collectObjects: function() {
  9587. var group = [],
  9588. currentObject,
  9589. x1 = this._groupSelector.ex,
  9590. y1 = this._groupSelector.ey,
  9591. x2 = x1 + this._groupSelector.left,
  9592. y2 = y1 + this._groupSelector.top,
  9593. selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
  9594. selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)),
  9595. isClick = x1 === x2 && y1 === y2;
  9596. for (var i = this._objects.length; i--; ) {
  9597. currentObject = this._objects[i];
  9598. if (!currentObject || !currentObject.selectable || !currentObject.visible) {
  9599. continue;
  9600. }
  9601. if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
  9602. currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2) ||
  9603. currentObject.containsPoint(selectionX1Y1) ||
  9604. currentObject.containsPoint(selectionX2Y2)
  9605. ) {
  9606. currentObject.set('active', true);
  9607. group.push(currentObject);
  9608. // only add one object if it's a click
  9609. if (isClick) {
  9610. break;
  9611. }
  9612. }
  9613. }
  9614. return group;
  9615. },
  9616. /**
  9617. * @private
  9618. */
  9619. _maybeGroupObjects: function(e) {
  9620. if (this.selection && this._groupSelector) {
  9621. this._groupSelectedObjects(e);
  9622. }
  9623. var activeGroup = this.getActiveGroup();
  9624. if (activeGroup) {
  9625. activeGroup.setObjectsCoords().setCoords();
  9626. activeGroup.isMoving = false;
  9627. this.setCursor(this.defaultCursor);
  9628. }
  9629. // clear selection and current transformation
  9630. this._groupSelector = null;
  9631. this._currentTransform = null;
  9632. }
  9633. });
  9634. })();
  9635. (function () {
  9636. var supportQuality = fabric.StaticCanvas.supports('toDataURLWithQuality');
  9637. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  9638. /**
  9639. * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately
  9640. * @param {Object} [options] Options object
  9641. * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
  9642. * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
  9643. * @param {Number} [options.multiplier=1] Multiplier to scale by
  9644. * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
  9645. * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
  9646. * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
  9647. * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
  9648. * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
  9649. * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo}
  9650. * @example <caption>Generate jpeg dataURL with lower quality</caption>
  9651. * var dataURL = canvas.toDataURL({
  9652. * format: 'jpeg',
  9653. * quality: 0.8
  9654. * });
  9655. * @example <caption>Generate cropped png dataURL (clipping of canvas)</caption>
  9656. * var dataURL = canvas.toDataURL({
  9657. * format: 'png',
  9658. * left: 100,
  9659. * top: 100,
  9660. * width: 200,
  9661. * height: 200
  9662. * });
  9663. * @example <caption>Generate double scaled png dataURL</caption>
  9664. * var dataURL = canvas.toDataURL({
  9665. * format: 'png',
  9666. * multiplier: 2
  9667. * });
  9668. */
  9669. toDataURL: function (options) {
  9670. options || (options = { });
  9671. var format = options.format || 'png',
  9672. quality = options.quality || 1,
  9673. multiplier = options.multiplier || 1,
  9674. cropping = {
  9675. left: options.left || 0,
  9676. top: options.top || 0,
  9677. width: options.width || 0,
  9678. height: options.height || 0,
  9679. };
  9680. return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier);
  9681. },
  9682. /**
  9683. * @private
  9684. */
  9685. __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) {
  9686. var origWidth = this.getWidth(),
  9687. origHeight = this.getHeight(),
  9688. scaledWidth = (cropping.width || this.getWidth()) * multiplier,
  9689. scaledHeight = (cropping.height || this.getHeight()) * multiplier,
  9690. zoom = this.getZoom(),
  9691. newZoom = zoom * multiplier,
  9692. vp = this.viewportTransform,
  9693. translateX = (vp[4] - cropping.left) * multiplier,
  9694. translateY = (vp[5] - cropping.top) * multiplier,
  9695. newVp = [newZoom, 0, 0, newZoom, translateX, translateY],
  9696. originalInteractive = this.interactive;
  9697. this.viewportTransform = newVp;
  9698. // setting interactive to false avoid exporting controls
  9699. this.interactive && (this.interactive = false);
  9700. if (origWidth !== scaledWidth || origHeight !== scaledHeight) {
  9701. // this.setDimensions is going to renderAll also;
  9702. this.setDimensions({ width: scaledWidth, height: scaledHeight });
  9703. }
  9704. else {
  9705. this.renderAll();
  9706. }
  9707. var data = this.__toDataURL(format, quality, cropping);
  9708. originalInteractive && (this.interactive = originalInteractive);
  9709. this.viewportTransform = vp;
  9710. //setDimensions with no option object is taking care of:
  9711. //this.width, this.height, this.renderAll()
  9712. this.setDimensions({ width: origWidth, height: origHeight });
  9713. return data;
  9714. },
  9715. /**
  9716. * @private
  9717. */
  9718. __toDataURL: function(format, quality) {
  9719. var canvasEl = this.contextContainer.canvas;
  9720. // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
  9721. if (format === 'jpg') {
  9722. format = 'jpeg';
  9723. }
  9724. var data = supportQuality
  9725. ? canvasEl.toDataURL('image/' + format, quality)
  9726. : canvasEl.toDataURL('image/' + format);
  9727. return data;
  9728. },
  9729. /**
  9730. * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
  9731. * @deprecated since 1.0.13
  9732. * @param {String} format (png|jpeg)
  9733. * @param {Number} multiplier
  9734. * @param {Number} quality (0..1)
  9735. * @return {String}
  9736. */
  9737. toDataURLWithMultiplier: function (format, multiplier, quality) {
  9738. return this.toDataURL({
  9739. format: format,
  9740. multiplier: multiplier,
  9741. quality: quality
  9742. });
  9743. },
  9744. });
  9745. })();
  9746. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  9747. /**
  9748. * Populates canvas with data from the specified dataless JSON.
  9749. * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON}
  9750. * @deprecated since 1.2.2
  9751. * @param {String|Object} json JSON string or object
  9752. * @param {Function} callback Callback, invoked when json is parsed
  9753. * and corresponding objects (e.g: {@link fabric.Image})
  9754. * are initialized
  9755. * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
  9756. * @return {fabric.Canvas} instance
  9757. * @chainable
  9758. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization}
  9759. */
  9760. loadFromDatalessJSON: function (json, callback, reviver) {
  9761. return this.loadFromJSON(json, callback, reviver);
  9762. },
  9763. /**
  9764. * Populates canvas with data from the specified JSON.
  9765. * JSON format must conform to the one of {@link fabric.Canvas#toJSON}
  9766. * @param {String|Object} json JSON string or object
  9767. * @param {Function} callback Callback, invoked when json is parsed
  9768. * and corresponding objects (e.g: {@link fabric.Image})
  9769. * are initialized
  9770. * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created.
  9771. * @return {fabric.Canvas} instance
  9772. * @chainable
  9773. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization}
  9774. * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo}
  9775. * @example <caption>loadFromJSON</caption>
  9776. * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
  9777. * @example <caption>loadFromJSON with reviver</caption>
  9778. * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {
  9779. * // `o` = json object
  9780. * // `object` = fabric.Object instance
  9781. * // ... do some stuff ...
  9782. * });
  9783. */
  9784. loadFromJSON: function (json, callback, reviver) {
  9785. if (!json) {
  9786. return;
  9787. }
  9788. // serialize if it wasn't already
  9789. var serialized = (typeof json === 'string')
  9790. ? JSON.parse(json)
  9791. : fabric.util.object.clone(json);
  9792. this.clear();
  9793. var _this = this;
  9794. this._enlivenObjects(serialized.objects, function () {
  9795. _this._setBgOverlay(serialized, function () {
  9796. // remove parts i cannot set as options
  9797. delete serialized.objects;
  9798. delete serialized.backgroundImage;
  9799. delete serialized.overlayImage;
  9800. delete serialized.background;
  9801. delete serialized.overlay;
  9802. // this._initOptions does too many things to just
  9803. // call it. Normally loading an Object from JSON
  9804. // create the Object instance. Here the Canvas is
  9805. // already an instance and we are just loading things over it
  9806. for (var prop in serialized) {
  9807. _this[prop] = serialized[prop];
  9808. }
  9809. callback && callback();
  9810. });
  9811. }, reviver);
  9812. return this;
  9813. },
  9814. /**
  9815. * @private
  9816. * @param {Object} serialized Object with background and overlay information
  9817. * @param {Function} callback Invoked after all background and overlay images/patterns loaded
  9818. */
  9819. _setBgOverlay: function(serialized, callback) {
  9820. var _this = this,
  9821. loaded = {
  9822. backgroundColor: false,
  9823. overlayColor: false,
  9824. backgroundImage: false,
  9825. overlayImage: false
  9826. };
  9827. if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) {
  9828. callback && callback();
  9829. return;
  9830. }
  9831. var cbIfLoaded = function () {
  9832. if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) {
  9833. _this.renderAll();
  9834. callback && callback();
  9835. }
  9836. };
  9837. this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded);
  9838. this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded);
  9839. this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded);
  9840. this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded);
  9841. cbIfLoaded();
  9842. },
  9843. /**
  9844. * @private
  9845. * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor)
  9846. * @param {(Object|String)} value Value to set
  9847. * @param {Object} loaded Set loaded property to true if property is set
  9848. * @param {Object} callback Callback function to invoke after property is set
  9849. */
  9850. __setBgOverlay: function(property, value, loaded, callback) {
  9851. var _this = this;
  9852. if (!value) {
  9853. loaded[property] = true;
  9854. return;
  9855. }
  9856. if (property === 'backgroundImage' || property === 'overlayImage') {
  9857. fabric.Image.fromObject(value, function(img) {
  9858. _this[property] = img;
  9859. loaded[property] = true;
  9860. callback && callback();
  9861. });
  9862. }
  9863. else {
  9864. this['set' + fabric.util.string.capitalize(property, true)](value, function() {
  9865. loaded[property] = true;
  9866. callback && callback();
  9867. });
  9868. }
  9869. },
  9870. /**
  9871. * @private
  9872. * @param {Array} objects
  9873. * @param {Function} callback
  9874. * @param {Function} [reviver]
  9875. */
  9876. _enlivenObjects: function (objects, callback, reviver) {
  9877. var _this = this;
  9878. if (!objects || objects.length === 0) {
  9879. callback && callback();
  9880. return;
  9881. }
  9882. var renderOnAddRemove = this.renderOnAddRemove;
  9883. this.renderOnAddRemove = false;
  9884. fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
  9885. enlivenedObjects.forEach(function(obj, index) {
  9886. // we splice the array just in case some custom classes restored from JSON
  9887. // will add more object to canvas at canvas init.
  9888. _this.insertAt(obj, index);
  9889. });
  9890. _this.renderOnAddRemove = renderOnAddRemove;
  9891. callback && callback();
  9892. }, null, reviver);
  9893. },
  9894. /**
  9895. * @private
  9896. * @param {String} format
  9897. * @param {Function} callback
  9898. */
  9899. _toDataURL: function (format, callback) {
  9900. this.clone(function (clone) {
  9901. callback(clone.toDataURL(format));
  9902. });
  9903. },
  9904. /**
  9905. * @private
  9906. * @param {String} format
  9907. * @param {Number} multiplier
  9908. * @param {Function} callback
  9909. */
  9910. _toDataURLWithMultiplier: function (format, multiplier, callback) {
  9911. this.clone(function (clone) {
  9912. callback(clone.toDataURLWithMultiplier(format, multiplier));
  9913. });
  9914. },
  9915. /**
  9916. * Clones canvas instance
  9917. * @param {Object} [callback] Receives cloned instance as a first argument
  9918. * @param {Array} [properties] Array of properties to include in the cloned canvas and children
  9919. */
  9920. clone: function (callback, properties) {
  9921. var data = JSON.stringify(this.toJSON(properties));
  9922. this.cloneWithoutData(function(clone) {
  9923. clone.loadFromJSON(data, function() {
  9924. callback && callback(clone);
  9925. });
  9926. });
  9927. },
  9928. /**
  9929. * Clones canvas instance without cloning existing data.
  9930. * This essentially copies canvas dimensions, clipping properties, etc.
  9931. * but leaves data empty (so that you can populate it with your own)
  9932. * @param {Object} [callback] Receives cloned instance as a first argument
  9933. */
  9934. cloneWithoutData: function(callback) {
  9935. var el = fabric.document.createElement('canvas');
  9936. el.width = this.getWidth();
  9937. el.height = this.getHeight();
  9938. var clone = new fabric.Canvas(el);
  9939. clone.clipTo = this.clipTo;
  9940. if (this.backgroundImage) {
  9941. clone.setBackgroundImage(this.backgroundImage.src, function() {
  9942. clone.renderAll();
  9943. callback && callback(clone);
  9944. });
  9945. clone.backgroundImageOpacity = this.backgroundImageOpacity;
  9946. clone.backgroundImageStretch = this.backgroundImageStretch;
  9947. }
  9948. else {
  9949. callback && callback(clone);
  9950. }
  9951. }
  9952. });
  9953. (function(global) {
  9954. 'use strict';
  9955. var fabric = global.fabric || (global.fabric = { }),
  9956. extend = fabric.util.object.extend,
  9957. toFixed = fabric.util.toFixed,
  9958. capitalize = fabric.util.string.capitalize,
  9959. degreesToRadians = fabric.util.degreesToRadians,
  9960. supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
  9961. if (fabric.Object) {
  9962. return;
  9963. }
  9964. /**
  9965. * Root object class from which all 2d shape classes inherit from
  9966. * @class fabric.Object
  9967. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects}
  9968. * @see {@link fabric.Object#initialize} for constructor definition
  9969. *
  9970. * @fires added
  9971. * @fires removed
  9972. *
  9973. * @fires selected
  9974. * @fires deselected
  9975. * @fires modified
  9976. * @fires rotating
  9977. * @fires scaling
  9978. * @fires moving
  9979. * @fires skewing
  9980. *
  9981. * @fires mousedown
  9982. * @fires mouseup
  9983. * @fires mouseover
  9984. * @fires mouseout
  9985. */
  9986. fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ {
  9987. /**
  9988. * Retrieves object's {@link fabric.Object#clipTo|clipping function}
  9989. * @method getClipTo
  9990. * @memberOf fabric.Object.prototype
  9991. * @return {Function}
  9992. */
  9993. /**
  9994. * Sets object's {@link fabric.Object#clipTo|clipping function}
  9995. * @method setClipTo
  9996. * @memberOf fabric.Object.prototype
  9997. * @param {Function} clipTo Clipping function
  9998. * @return {fabric.Object} thisArg
  9999. * @chainable
  10000. */
  10001. /**
  10002. * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix}
  10003. * @method getTransformMatrix
  10004. * @memberOf fabric.Object.prototype
  10005. * @return {Array} transformMatrix
  10006. */
  10007. /**
  10008. * Sets object's {@link fabric.Object#transformMatrix|transformMatrix}
  10009. * @method setTransformMatrix
  10010. * @memberOf fabric.Object.prototype
  10011. * @param {Array} transformMatrix
  10012. * @return {fabric.Object} thisArg
  10013. * @chainable
  10014. */
  10015. /**
  10016. * Retrieves object's {@link fabric.Object#visible|visible} state
  10017. * @method getVisible
  10018. * @memberOf fabric.Object.prototype
  10019. * @return {Boolean} True if visible
  10020. */
  10021. /**
  10022. * Sets object's {@link fabric.Object#visible|visible} state
  10023. * @method setVisible
  10024. * @memberOf fabric.Object.prototype
  10025. * @param {Boolean} value visible value
  10026. * @return {fabric.Object} thisArg
  10027. * @chainable
  10028. */
  10029. /**
  10030. * Retrieves object's {@link fabric.Object#shadow|shadow}
  10031. * @method getShadow
  10032. * @memberOf fabric.Object.prototype
  10033. * @return {Object} Shadow instance
  10034. */
  10035. /**
  10036. * Retrieves object's {@link fabric.Object#stroke|stroke}
  10037. * @method getStroke
  10038. * @memberOf fabric.Object.prototype
  10039. * @return {String} stroke value
  10040. */
  10041. /**
  10042. * Sets object's {@link fabric.Object#stroke|stroke}
  10043. * @method setStroke
  10044. * @memberOf fabric.Object.prototype
  10045. * @param {String} value stroke value
  10046. * @return {fabric.Object} thisArg
  10047. * @chainable
  10048. */
  10049. /**
  10050. * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth}
  10051. * @method getStrokeWidth
  10052. * @memberOf fabric.Object.prototype
  10053. * @return {Number} strokeWidth value
  10054. */
  10055. /**
  10056. * Sets object's {@link fabric.Object#strokeWidth|strokeWidth}
  10057. * @method setStrokeWidth
  10058. * @memberOf fabric.Object.prototype
  10059. * @param {Number} value strokeWidth value
  10060. * @return {fabric.Object} thisArg
  10061. * @chainable
  10062. */
  10063. /**
  10064. * Retrieves object's {@link fabric.Object#originX|originX}
  10065. * @method getOriginX
  10066. * @memberOf fabric.Object.prototype
  10067. * @return {String} originX value
  10068. */
  10069. /**
  10070. * Sets object's {@link fabric.Object#originX|originX}
  10071. * @method setOriginX
  10072. * @memberOf fabric.Object.prototype
  10073. * @param {String} value originX value
  10074. * @return {fabric.Object} thisArg
  10075. * @chainable
  10076. */
  10077. /**
  10078. * Retrieves object's {@link fabric.Object#originY|originY}
  10079. * @method getOriginY
  10080. * @memberOf fabric.Object.prototype
  10081. * @return {String} originY value
  10082. */
  10083. /**
  10084. * Sets object's {@link fabric.Object#originY|originY}
  10085. * @method setOriginY
  10086. * @memberOf fabric.Object.prototype
  10087. * @param {String} value originY value
  10088. * @return {fabric.Object} thisArg
  10089. * @chainable
  10090. */
  10091. /**
  10092. * Retrieves object's {@link fabric.Object#fill|fill}
  10093. * @method getFill
  10094. * @memberOf fabric.Object.prototype
  10095. * @return {String} Fill value
  10096. */
  10097. /**
  10098. * Sets object's {@link fabric.Object#fill|fill}
  10099. * @method setFill
  10100. * @memberOf fabric.Object.prototype
  10101. * @param {String} value Fill value
  10102. * @return {fabric.Object} thisArg
  10103. * @chainable
  10104. */
  10105. /**
  10106. * Retrieves object's {@link fabric.Object#opacity|opacity}
  10107. * @method getOpacity
  10108. * @memberOf fabric.Object.prototype
  10109. * @return {Number} Opacity value (0-1)
  10110. */
  10111. /**
  10112. * Sets object's {@link fabric.Object#opacity|opacity}
  10113. * @method setOpacity
  10114. * @memberOf fabric.Object.prototype
  10115. * @param {Number} value Opacity value (0-1)
  10116. * @return {fabric.Object} thisArg
  10117. * @chainable
  10118. */
  10119. /**
  10120. * Retrieves object's {@link fabric.Object#angle|angle} (in degrees)
  10121. * @method getAngle
  10122. * @memberOf fabric.Object.prototype
  10123. * @return {Number}
  10124. */
  10125. /**
  10126. * Retrieves object's {@link fabric.Object#top|top position}
  10127. * @method getTop
  10128. * @memberOf fabric.Object.prototype
  10129. * @return {Number} Top value (in pixels)
  10130. */
  10131. /**
  10132. * Sets object's {@link fabric.Object#top|top position}
  10133. * @method setTop
  10134. * @memberOf fabric.Object.prototype
  10135. * @param {Number} value Top value (in pixels)
  10136. * @return {fabric.Object} thisArg
  10137. * @chainable
  10138. */
  10139. /**
  10140. * Retrieves object's {@link fabric.Object#left|left position}
  10141. * @method getLeft
  10142. * @memberOf fabric.Object.prototype
  10143. * @return {Number} Left value (in pixels)
  10144. */
  10145. /**
  10146. * Sets object's {@link fabric.Object#left|left position}
  10147. * @method setLeft
  10148. * @memberOf fabric.Object.prototype
  10149. * @param {Number} value Left value (in pixels)
  10150. * @return {fabric.Object} thisArg
  10151. * @chainable
  10152. */
  10153. /**
  10154. * Retrieves object's {@link fabric.Object#scaleX|scaleX} value
  10155. * @method getScaleX
  10156. * @memberOf fabric.Object.prototype
  10157. * @return {Number} scaleX value
  10158. */
  10159. /**
  10160. * Sets object's {@link fabric.Object#scaleX|scaleX} value
  10161. * @method setScaleX
  10162. * @memberOf fabric.Object.prototype
  10163. * @param {Number} value scaleX value
  10164. * @return {fabric.Object} thisArg
  10165. * @chainable
  10166. */
  10167. /**
  10168. * Retrieves object's {@link fabric.Object#scaleY|scaleY} value
  10169. * @method getScaleY
  10170. * @memberOf fabric.Object.prototype
  10171. * @return {Number} scaleY value
  10172. */
  10173. /**
  10174. * Sets object's {@link fabric.Object#scaleY|scaleY} value
  10175. * @method setScaleY
  10176. * @memberOf fabric.Object.prototype
  10177. * @param {Number} value scaleY value
  10178. * @return {fabric.Object} thisArg
  10179. * @chainable
  10180. */
  10181. /**
  10182. * Retrieves object's {@link fabric.Object#flipX|flipX} value
  10183. * @method getFlipX
  10184. * @memberOf fabric.Object.prototype
  10185. * @return {Boolean} flipX value
  10186. */
  10187. /**
  10188. * Sets object's {@link fabric.Object#flipX|flipX} value
  10189. * @method setFlipX
  10190. * @memberOf fabric.Object.prototype
  10191. * @param {Boolean} value flipX value
  10192. * @return {fabric.Object} thisArg
  10193. * @chainable
  10194. */
  10195. /**
  10196. * Retrieves object's {@link fabric.Object#flipY|flipY} value
  10197. * @method getFlipY
  10198. * @memberOf fabric.Object.prototype
  10199. * @return {Boolean} flipY value
  10200. */
  10201. /**
  10202. * Sets object's {@link fabric.Object#flipY|flipY} value
  10203. * @method setFlipY
  10204. * @memberOf fabric.Object.prototype
  10205. * @param {Boolean} value flipY value
  10206. * @return {fabric.Object} thisArg
  10207. * @chainable
  10208. */
  10209. /**
  10210. * Type of an object (rect, circle, path, etc.).
  10211. * Note that this property is meant to be read-only and not meant to be modified.
  10212. * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly.
  10213. * @type String
  10214. * @default
  10215. */
  10216. type: 'object',
  10217. /**
  10218. * Horizontal origin of transformation of an object (one of "left", "right", "center")
  10219. * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
  10220. * @type String
  10221. * @default
  10222. */
  10223. originX: 'left',
  10224. /**
  10225. * Vertical origin of transformation of an object (one of "top", "bottom", "center")
  10226. * See http://jsfiddle.net/1ow02gea/40/ on how originX/originY affect objects in groups
  10227. * @type String
  10228. * @default
  10229. */
  10230. originY: 'top',
  10231. /**
  10232. * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom}
  10233. * @type Number
  10234. * @default
  10235. */
  10236. top: 0,
  10237. /**
  10238. * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right}
  10239. * @type Number
  10240. * @default
  10241. */
  10242. left: 0,
  10243. /**
  10244. * Object width
  10245. * @type Number
  10246. * @default
  10247. */
  10248. width: 0,
  10249. /**
  10250. * Object height
  10251. * @type Number
  10252. * @default
  10253. */
  10254. height: 0,
  10255. /**
  10256. * Object scale factor (horizontal)
  10257. * @type Number
  10258. * @default
  10259. */
  10260. scaleX: 1,
  10261. /**
  10262. * Object scale factor (vertical)
  10263. * @type Number
  10264. * @default
  10265. */
  10266. scaleY: 1,
  10267. /**
  10268. * When true, an object is rendered as flipped horizontally
  10269. * @type Boolean
  10270. * @default
  10271. */
  10272. flipX: false,
  10273. /**
  10274. * When true, an object is rendered as flipped vertically
  10275. * @type Boolean
  10276. * @default
  10277. */
  10278. flipY: false,
  10279. /**
  10280. * Opacity of an object
  10281. * @type Number
  10282. * @default
  10283. */
  10284. opacity: 1,
  10285. /**
  10286. * Angle of rotation of an object (in degrees)
  10287. * @type Number
  10288. * @default
  10289. */
  10290. angle: 0,
  10291. /**
  10292. * Angle of skew on x axes of an object (in degrees)
  10293. * @type Number
  10294. * @default
  10295. */
  10296. skewX: 0,
  10297. /**
  10298. * Angle of skew on y axes of an object (in degrees)
  10299. * @type Number
  10300. * @default
  10301. */
  10302. skewY: 0,
  10303. /**
  10304. * Size of object's controlling corners (in pixels)
  10305. * @type Number
  10306. * @default
  10307. */
  10308. cornerSize: 13,
  10309. /**
  10310. * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill)
  10311. * @type Boolean
  10312. * @default
  10313. */
  10314. transparentCorners: true,
  10315. /**
  10316. * Default cursor value used when hovering over this object on canvas
  10317. * @type String
  10318. * @default
  10319. */
  10320. hoverCursor: null,
  10321. /**
  10322. * Default cursor value used when moving this object on canvas
  10323. * @type String
  10324. * @default
  10325. */
  10326. moveCursor: null,
  10327. /**
  10328. * Padding between object and its controlling borders (in pixels)
  10329. * @type Number
  10330. * @default
  10331. */
  10332. padding: 0,
  10333. /**
  10334. * Color of controlling borders of an object (when it's active)
  10335. * @type String
  10336. * @default
  10337. */
  10338. borderColor: 'rgba(102,153,255,0.75)',
  10339. /**
  10340. * Array specifying dash pattern of an object's borders (hasBorder must be true)
  10341. * @since 1.6.2
  10342. * @type Array
  10343. */
  10344. borderDashArray: null,
  10345. /**
  10346. * Color of controlling corners of an object (when it's active)
  10347. * @type String
  10348. * @default
  10349. */
  10350. cornerColor: 'rgba(102,153,255,0.5)',
  10351. /**
  10352. * Color of controlling corners of an object (when it's active and transparentCorners false)
  10353. * @since 1.6.2
  10354. * @type String
  10355. * @default
  10356. */
  10357. cornerStrokeColor: null,
  10358. /**
  10359. * Specify style of control, 'rect' or 'circle'
  10360. * @since 1.6.2
  10361. * @type String
  10362. */
  10363. cornerStyle: 'rect',
  10364. /**
  10365. * Array specifying dash pattern of an object's control (hasBorder must be true)
  10366. * @since 1.6.2
  10367. * @type Array
  10368. */
  10369. cornerDashArray: null,
  10370. /**
  10371. * When true, this object will use center point as the origin of transformation
  10372. * when being scaled via the controls.
  10373. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  10374. * @since 1.3.4
  10375. * @type Boolean
  10376. * @default
  10377. */
  10378. centeredScaling: false,
  10379. /**
  10380. * When true, this object will use center point as the origin of transformation
  10381. * when being rotated via the controls.
  10382. * <b>Backwards incompatibility note:</b> This property replaces "centerTransform" (Boolean).
  10383. * @since 1.3.4
  10384. * @type Boolean
  10385. * @default
  10386. */
  10387. centeredRotation: true,
  10388. /**
  10389. * Color of object's fill
  10390. * @type String
  10391. * @default
  10392. */
  10393. fill: 'rgb(0,0,0)',
  10394. /**
  10395. * Fill rule used to fill an object
  10396. * accepted values are nonzero, evenodd
  10397. * <b>Backwards incompatibility note:</b> This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead)
  10398. * @type String
  10399. * @default
  10400. */
  10401. fillRule: 'nonzero',
  10402. /**
  10403. * Composite rule used for canvas globalCompositeOperation
  10404. * @type String
  10405. * @default
  10406. */
  10407. globalCompositeOperation: 'source-over',
  10408. /**
  10409. * Background color of an object. Only works with text objects at the moment.
  10410. * @type String
  10411. * @default
  10412. */
  10413. backgroundColor: '',
  10414. /**
  10415. * Selection Background color of an object. colored layer behind the object when it is active.
  10416. * does not mix good with globalCompositeOperation methods.
  10417. * @type String
  10418. * @default
  10419. */
  10420. selectionBackgroundColor: '',
  10421. /**
  10422. * When defined, an object is rendered via stroke and this property specifies its color
  10423. * @type String
  10424. * @default
  10425. */
  10426. stroke: null,
  10427. /**
  10428. * Width of a stroke used to render this object
  10429. * @type Number
  10430. * @default
  10431. */
  10432. strokeWidth: 1,
  10433. /**
  10434. * Array specifying dash pattern of an object's stroke (stroke must be defined)
  10435. * @type Array
  10436. */
  10437. strokeDashArray: null,
  10438. /**
  10439. * Line endings style of an object's stroke (one of "butt", "round", "square")
  10440. * @type String
  10441. * @default
  10442. */
  10443. strokeLineCap: 'butt',
  10444. /**
  10445. * Corner style of an object's stroke (one of "bevil", "round", "miter")
  10446. * @type String
  10447. * @default
  10448. */
  10449. strokeLineJoin: 'miter',
  10450. /**
  10451. * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke
  10452. * @type Number
  10453. * @default
  10454. */
  10455. strokeMiterLimit: 10,
  10456. /**
  10457. * Shadow object representing shadow of this shape
  10458. * @type fabric.Shadow
  10459. * @default
  10460. */
  10461. shadow: null,
  10462. /**
  10463. * Opacity of object's controlling borders when object is active and moving
  10464. * @type Number
  10465. * @default
  10466. */
  10467. borderOpacityWhenMoving: 0.4,
  10468. /**
  10469. * Scale factor of object's controlling borders
  10470. * @type Number
  10471. * @default
  10472. */
  10473. borderScaleFactor: 1,
  10474. /**
  10475. * Transform matrix (similar to SVG's transform matrix)
  10476. * @type Array
  10477. */
  10478. transformMatrix: null,
  10479. /**
  10480. * Minimum allowed scale value of an object
  10481. * @type Number
  10482. * @default
  10483. */
  10484. minScaleLimit: 0.01,
  10485. /**
  10486. * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection).
  10487. * But events still fire on it.
  10488. * @type Boolean
  10489. * @default
  10490. */
  10491. selectable: true,
  10492. /**
  10493. * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4
  10494. * @type Boolean
  10495. * @default
  10496. */
  10497. evented: true,
  10498. /**
  10499. * When set to `false`, an object is not rendered on canvas
  10500. * @type Boolean
  10501. * @default
  10502. */
  10503. visible: true,
  10504. /**
  10505. * When set to `false`, object's controls are not displayed and can not be used to manipulate object
  10506. * @type Boolean
  10507. * @default
  10508. */
  10509. hasControls: true,
  10510. /**
  10511. * When set to `false`, object's controlling borders are not rendered
  10512. * @type Boolean
  10513. * @default
  10514. */
  10515. hasBorders: true,
  10516. /**
  10517. * When set to `false`, object's controlling rotating point will not be visible or selectable
  10518. * @type Boolean
  10519. * @default
  10520. */
  10521. hasRotatingPoint: true,
  10522. /**
  10523. * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`)
  10524. * @type Number
  10525. * @default
  10526. */
  10527. rotatingPointOffset: 40,
  10528. /**
  10529. * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box
  10530. * @type Boolean
  10531. * @default
  10532. */
  10533. perPixelTargetFind: false,
  10534. /**
  10535. * When `false`, default object's values are not included in its serialization
  10536. * @type Boolean
  10537. * @default
  10538. */
  10539. includeDefaultValues: true,
  10540. /**
  10541. * Function that determines clipping of an object (context is passed as a first argument)
  10542. * Note that context origin is at the object's center point (not left/top corner)
  10543. * @type Function
  10544. */
  10545. clipTo: null,
  10546. /**
  10547. * When `true`, object horizontal movement is locked
  10548. * @type Boolean
  10549. * @default
  10550. */
  10551. lockMovementX: false,
  10552. /**
  10553. * When `true`, object vertical movement is locked
  10554. * @type Boolean
  10555. * @default
  10556. */
  10557. lockMovementY: false,
  10558. /**
  10559. * When `true`, object rotation is locked
  10560. * @type Boolean
  10561. * @default
  10562. */
  10563. lockRotation: false,
  10564. /**
  10565. * When `true`, object horizontal scaling is locked
  10566. * @type Boolean
  10567. * @default
  10568. */
  10569. lockScalingX: false,
  10570. /**
  10571. * When `true`, object vertical scaling is locked
  10572. * @type Boolean
  10573. * @default
  10574. */
  10575. lockScalingY: false,
  10576. /**
  10577. * When `true`, object non-uniform scaling is locked
  10578. * @type Boolean
  10579. * @default
  10580. */
  10581. lockUniScaling: false,
  10582. /**
  10583. * When `true`, object horizontal skewing is locked
  10584. * @type Boolean
  10585. * @default
  10586. */
  10587. lockSkewingX: false,
  10588. /**
  10589. * When `true`, object vertical skewing is locked
  10590. * @type Boolean
  10591. * @default
  10592. */
  10593. lockSkewingY: false,
  10594. /**
  10595. * When `true`, object cannot be flipped by scaling into negative values
  10596. * @type Boolean
  10597. * @default
  10598. */
  10599. lockScalingFlip: false,
  10600. /**
  10601. * When `true`, object is not exported in SVG or OBJECT/JSON
  10602. * since 1.6.3
  10603. * @type Boolean
  10604. * @default
  10605. */
  10606. excludeFromExport: false,
  10607. /**
  10608. * List of properties to consider when checking if state
  10609. * of an object is changed (fabric.Object#hasStateChanged)
  10610. * as well as for history (undo/redo) purposes
  10611. * @type Array
  10612. */
  10613. stateProperties: (
  10614. 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
  10615. 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
  10616. 'angle opacity fill fillRule globalCompositeOperation shadow clipTo visible backgroundColor ' +
  10617. 'skewX skewY'
  10618. ).split(' '),
  10619. /**
  10620. * Constructor
  10621. * @param {Object} [options] Options object
  10622. */
  10623. initialize: function(options) {
  10624. if (options) {
  10625. this.setOptions(options);
  10626. }
  10627. },
  10628. /**
  10629. * @private
  10630. * @param {Object} [options] Options object
  10631. */
  10632. _initGradient: function(options) {
  10633. if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) {
  10634. this.set('fill', new fabric.Gradient(options.fill));
  10635. }
  10636. if (options.stroke && options.stroke.colorStops && !(options.stroke instanceof fabric.Gradient)) {
  10637. this.set('stroke', new fabric.Gradient(options.stroke));
  10638. }
  10639. },
  10640. /**
  10641. * @private
  10642. * @param {Object} [options] Options object
  10643. */
  10644. _initPattern: function(options) {
  10645. if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) {
  10646. this.set('fill', new fabric.Pattern(options.fill));
  10647. }
  10648. if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) {
  10649. this.set('stroke', new fabric.Pattern(options.stroke));
  10650. }
  10651. },
  10652. /**
  10653. * @private
  10654. * @param {Object} [options] Options object
  10655. */
  10656. _initClipping: function(options) {
  10657. if (!options.clipTo || typeof options.clipTo !== 'string') {
  10658. return;
  10659. }
  10660. var functionBody = fabric.util.getFunctionBody(options.clipTo);
  10661. if (typeof functionBody !== 'undefined') {
  10662. this.clipTo = new Function('ctx', functionBody);
  10663. }
  10664. },
  10665. /**
  10666. * Sets object's properties from options
  10667. * @param {Object} [options] Options object
  10668. */
  10669. setOptions: function(options) {
  10670. for (var prop in options) {
  10671. this.set(prop, options[prop]);
  10672. }
  10673. this._initGradient(options);
  10674. this._initPattern(options);
  10675. this._initClipping(options);
  10676. },
  10677. /**
  10678. * Transforms context when rendering an object
  10679. * @param {CanvasRenderingContext2D} ctx Context
  10680. * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node
  10681. */
  10682. transform: function(ctx, fromLeft) {
  10683. if (this.group && !this.group._transformDone && this.group === this.canvas._activeGroup) {
  10684. this.group.transform(ctx);
  10685. }
  10686. var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint();
  10687. ctx.translate(center.x, center.y);
  10688. ctx.rotate(degreesToRadians(this.angle));
  10689. ctx.scale(
  10690. this.scaleX * (this.flipX ? -1 : 1),
  10691. this.scaleY * (this.flipY ? -1 : 1)
  10692. );
  10693. ctx.transform(1, 0, Math.tan(degreesToRadians(this.skewX)), 1, 0, 0);
  10694. ctx.transform(1, Math.tan(degreesToRadians(this.skewY)), 0, 1, 0, 0);
  10695. },
  10696. /**
  10697. * Returns an object representation of an instance
  10698. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  10699. * @return {Object} Object representation of an instance
  10700. */
  10701. toObject: function(propertiesToInclude) {
  10702. var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  10703. object = {
  10704. type: this.type,
  10705. originX: this.originX,
  10706. originY: this.originY,
  10707. left: toFixed(this.left, NUM_FRACTION_DIGITS),
  10708. top: toFixed(this.top, NUM_FRACTION_DIGITS),
  10709. width: toFixed(this.width, NUM_FRACTION_DIGITS),
  10710. height: toFixed(this.height, NUM_FRACTION_DIGITS),
  10711. fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill,
  10712. stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke,
  10713. strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS),
  10714. strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray,
  10715. strokeLineCap: this.strokeLineCap,
  10716. strokeLineJoin: this.strokeLineJoin,
  10717. strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS),
  10718. scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS),
  10719. scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS),
  10720. angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS),
  10721. flipX: this.flipX,
  10722. flipY: this.flipY,
  10723. opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS),
  10724. shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow,
  10725. visible: this.visible,
  10726. clipTo: this.clipTo && String(this.clipTo),
  10727. backgroundColor: this.backgroundColor,
  10728. fillRule: this.fillRule,
  10729. globalCompositeOperation: this.globalCompositeOperation,
  10730. transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : this.transformMatrix,
  10731. skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS),
  10732. skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS)
  10733. };
  10734. fabric.util.populateWithProperties(this, object, propertiesToInclude);
  10735. if (!this.includeDefaultValues) {
  10736. object = this._removeDefaultValues(object);
  10737. }
  10738. return object;
  10739. },
  10740. /**
  10741. * Returns (dataless) object representation of an instance
  10742. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  10743. * @return {Object} Object representation of an instance
  10744. */
  10745. toDatalessObject: function(propertiesToInclude) {
  10746. // will be overwritten by subclasses
  10747. return this.toObject(propertiesToInclude);
  10748. },
  10749. /**
  10750. * @private
  10751. * @param {Object} object
  10752. */
  10753. _removeDefaultValues: function(object) {
  10754. var prototype = fabric.util.getKlass(object.type).prototype,
  10755. stateProperties = prototype.stateProperties;
  10756. stateProperties.forEach(function(prop) {
  10757. if (object[prop] === prototype[prop]) {
  10758. delete object[prop];
  10759. }
  10760. var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' &&
  10761. Object.prototype.toString.call(prototype[prop]) === '[object Array]';
  10762. // basically a check for [] === []
  10763. if (isArray && object[prop].length === 0 && prototype[prop].length === 0) {
  10764. delete object[prop];
  10765. }
  10766. });
  10767. return object;
  10768. },
  10769. /**
  10770. * Returns a string representation of an instance
  10771. * @return {String}
  10772. */
  10773. toString: function() {
  10774. return '#<fabric.' + capitalize(this.type) + '>';
  10775. },
  10776. /**
  10777. * Basic getter
  10778. * @param {String} property Property name
  10779. * @return {*} value of a property
  10780. */
  10781. get: function(property) {
  10782. return this[property];
  10783. },
  10784. /**
  10785. * Return the object scale factor counting also the group scaling
  10786. * @return {Object} object with scaleX and scaleY properties
  10787. */
  10788. getObjectScaling: function() {
  10789. var scaleX = this.scaleX, scaleY = this.scaleY;
  10790. if (this.group) {
  10791. var scaling = this.group.getObjectScaling();
  10792. scaleX *= scaling.scaleX;
  10793. scaleY *= scaling.scaleY;
  10794. }
  10795. return { scaleX: scaleX, scaleY: scaleY };
  10796. },
  10797. /**
  10798. * @private
  10799. */
  10800. _setObject: function(obj) {
  10801. for (var prop in obj) {
  10802. this._set(prop, obj[prop]);
  10803. }
  10804. },
  10805. /**
  10806. * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
  10807. * @param {String|Object} key Property name or object (if object, iterate over the object properties)
  10808. * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
  10809. * @return {fabric.Object} thisArg
  10810. * @chainable
  10811. */
  10812. set: function(key, value) {
  10813. if (typeof key === 'object') {
  10814. this._setObject(key);
  10815. }
  10816. else {
  10817. if (typeof value === 'function' && key !== 'clipTo') {
  10818. this._set(key, value(this.get(key)));
  10819. }
  10820. else {
  10821. this._set(key, value);
  10822. }
  10823. }
  10824. return this;
  10825. },
  10826. /**
  10827. * @private
  10828. * @param {String} key
  10829. * @param {*} value
  10830. * @return {fabric.Object} thisArg
  10831. */
  10832. _set: function(key, value) {
  10833. var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY');
  10834. if (shouldConstrainValue) {
  10835. value = this._constrainScale(value);
  10836. }
  10837. if (key === 'scaleX' && value < 0) {
  10838. this.flipX = !this.flipX;
  10839. value *= -1;
  10840. }
  10841. else if (key === 'scaleY' && value < 0) {
  10842. this.flipY = !this.flipY;
  10843. value *= -1;
  10844. }
  10845. else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) {
  10846. value = new fabric.Shadow(value);
  10847. }
  10848. this[key] = value;
  10849. if (key === 'width' || key === 'height') {
  10850. this.minScaleLimit = Math.min(0.1, 1 / Math.max(this.width, this.height));
  10851. }
  10852. return this;
  10853. },
  10854. /**
  10855. * This callback function is called by the parent group of an object every
  10856. * time a non-delegated property changes on the group. It is passed the key
  10857. * and value as parameters. Not adding in this function's signature to avoid
  10858. * Travis build error about unused variables.
  10859. */
  10860. setOnGroup: function() {
  10861. // implemented by sub-classes, as needed.
  10862. },
  10863. /**
  10864. * Toggles specified property from `true` to `false` or from `false` to `true`
  10865. * @param {String} property Property to toggle
  10866. * @return {fabric.Object} thisArg
  10867. * @chainable
  10868. */
  10869. toggle: function(property) {
  10870. var value = this.get(property);
  10871. if (typeof value === 'boolean') {
  10872. this.set(property, !value);
  10873. }
  10874. return this;
  10875. },
  10876. /**
  10877. * Sets sourcePath of an object
  10878. * @param {String} value Value to set sourcePath to
  10879. * @return {fabric.Object} thisArg
  10880. * @chainable
  10881. */
  10882. setSourcePath: function(value) {
  10883. this.sourcePath = value;
  10884. return this;
  10885. },
  10886. /**
  10887. * Retrieves viewportTransform from Object's canvas if possible
  10888. * @method getViewportTransform
  10889. * @memberOf fabric.Object.prototype
  10890. * @return {Boolean} flipY value // TODO
  10891. */
  10892. getViewportTransform: function() {
  10893. if (this.canvas && this.canvas.viewportTransform) {
  10894. return this.canvas.viewportTransform;
  10895. }
  10896. return [1, 0, 0, 1, 0, 0];
  10897. },
  10898. /**
  10899. * Renders an object on a specified context
  10900. * @param {CanvasRenderingContext2D} ctx Context to render on
  10901. * @param {Boolean} [noTransform] When true, context is not transformed
  10902. */
  10903. render: function(ctx, noTransform) {
  10904. // do not render if width/height are zeros or object is not visible
  10905. if ((this.width === 0 && this.height === 0) || !this.visible) {
  10906. return;
  10907. }
  10908. ctx.save();
  10909. //setup fill rule for current object
  10910. this._setupCompositeOperation(ctx);
  10911. this.drawSelectionBackground(ctx);
  10912. if (!noTransform) {
  10913. this.transform(ctx);
  10914. }
  10915. this._setOpacity(ctx);
  10916. this._setShadow(ctx);
  10917. this._renderBackground(ctx);
  10918. this._setStrokeStyles(ctx);
  10919. this._setFillStyles(ctx);
  10920. if (this.transformMatrix) {
  10921. ctx.transform.apply(ctx, this.transformMatrix);
  10922. }
  10923. this.clipTo && fabric.util.clipContext(this, ctx);
  10924. this._render(ctx, noTransform);
  10925. this.clipTo && ctx.restore();
  10926. ctx.restore();
  10927. },
  10928. /**
  10929. * Draws a background for the object big as its width and height;
  10930. * @private
  10931. * @param {CanvasRenderingContext2D} ctx Context to render on
  10932. */
  10933. _renderBackground: function(ctx) {
  10934. if (!this.backgroundColor) {
  10935. return;
  10936. }
  10937. ctx.fillStyle = this.backgroundColor;
  10938. ctx.fillRect(
  10939. -this.width / 2,
  10940. -this.height / 2,
  10941. this.width,
  10942. this.height
  10943. );
  10944. // if there is background color no other shadows
  10945. // should be casted
  10946. this._removeShadow(ctx);
  10947. },
  10948. /**
  10949. * @private
  10950. * @param {CanvasRenderingContext2D} ctx Context to render on
  10951. */
  10952. _setOpacity: function(ctx) {
  10953. if (this.group) {
  10954. this.group._setOpacity(ctx);
  10955. }
  10956. ctx.globalAlpha *= this.opacity;
  10957. },
  10958. _setStrokeStyles: function(ctx) {
  10959. if (this.stroke) {
  10960. ctx.lineWidth = this.strokeWidth;
  10961. ctx.lineCap = this.strokeLineCap;
  10962. ctx.lineJoin = this.strokeLineJoin;
  10963. ctx.miterLimit = this.strokeMiterLimit;
  10964. ctx.strokeStyle = this.stroke.toLive
  10965. ? this.stroke.toLive(ctx, this)
  10966. : this.stroke;
  10967. }
  10968. },
  10969. _setFillStyles: function(ctx) {
  10970. if (this.fill) {
  10971. ctx.fillStyle = this.fill.toLive
  10972. ? this.fill.toLive(ctx, this)
  10973. : this.fill;
  10974. }
  10975. },
  10976. /**
  10977. * @private
  10978. * Sets line dash
  10979. * @param {CanvasRenderingContext2D} ctx Context to set the dash line on
  10980. * @param {Array} dashArray array representing dashes
  10981. * @param {Function} alternative function to call if browaser does not support lineDash
  10982. */
  10983. _setLineDash: function(ctx, dashArray, alternative) {
  10984. if (!dashArray) {
  10985. return;
  10986. }
  10987. // Spec requires the concatenation of two copies the dash list when the number of elements is odd
  10988. if (1 & dashArray.length) {
  10989. dashArray.push.apply(dashArray, dashArray);
  10990. }
  10991. if (supportsLineDash) {
  10992. ctx.setLineDash(dashArray);
  10993. }
  10994. else {
  10995. alternative && alternative(ctx);
  10996. }
  10997. },
  10998. /**
  10999. * Renders controls and borders for the object
  11000. * @param {CanvasRenderingContext2D} ctx Context to render on
  11001. * @param {Boolean} [noTransform] When true, context is not transformed
  11002. */
  11003. _renderControls: function(ctx, noTransform) {
  11004. if (!this.active || noTransform
  11005. || (this.group && this.group !== this.canvas.getActiveGroup())) {
  11006. return;
  11007. }
  11008. var vpt = this.getViewportTransform(),
  11009. matrix = this.calcTransformMatrix(),
  11010. options;
  11011. matrix = fabric.util.multiplyTransformMatrices(vpt, matrix);
  11012. options = fabric.util.qrDecompose(matrix);
  11013. ctx.save();
  11014. ctx.translate(options.translateX, options.translateY);
  11015. ctx.lineWidth = 1 * this.borderScaleFactor;
  11016. ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
  11017. if (this.group && this.group === this.canvas.getActiveGroup()) {
  11018. ctx.rotate(degreesToRadians(options.angle));
  11019. this.drawBordersInGroup(ctx, options);
  11020. }
  11021. else {
  11022. ctx.rotate(degreesToRadians(this.angle));
  11023. this.drawBorders(ctx);
  11024. }
  11025. this.drawControls(ctx);
  11026. ctx.restore();
  11027. },
  11028. /**
  11029. * @private
  11030. * @param {CanvasRenderingContext2D} ctx Context to render on
  11031. */
  11032. _setShadow: function(ctx) {
  11033. if (!this.shadow) {
  11034. return;
  11035. }
  11036. var multX = (this.canvas && this.canvas.viewportTransform[0]) || 1,
  11037. multY = (this.canvas && this.canvas.viewportTransform[3]) || 1,
  11038. scaling = this.getObjectScaling();
  11039. if (this.canvas && this.canvas._isRetinaScaling()) {
  11040. multX *= fabric.devicePixelRatio;
  11041. multY *= fabric.devicePixelRatio;
  11042. }
  11043. ctx.shadowColor = this.shadow.color;
  11044. ctx.shadowBlur = this.shadow.blur * (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4;
  11045. ctx.shadowOffsetX = this.shadow.offsetX * multX * scaling.scaleX;
  11046. ctx.shadowOffsetY = this.shadow.offsetY * multY * scaling.scaleY;
  11047. },
  11048. /**
  11049. * @private
  11050. * @param {CanvasRenderingContext2D} ctx Context to render on
  11051. */
  11052. _removeShadow: function(ctx) {
  11053. if (!this.shadow) {
  11054. return;
  11055. }
  11056. ctx.shadowColor = '';
  11057. ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
  11058. },
  11059. /**
  11060. * @private
  11061. * @param {CanvasRenderingContext2D} ctx Context to render on
  11062. */
  11063. _renderFill: function(ctx) {
  11064. if (!this.fill) {
  11065. return;
  11066. }
  11067. ctx.save();
  11068. if (this.fill.gradientTransform) {
  11069. var g = this.fill.gradientTransform;
  11070. ctx.transform.apply(ctx, g);
  11071. }
  11072. if (this.fill.toLive) {
  11073. ctx.translate(
  11074. -this.width / 2 + this.fill.offsetX || 0,
  11075. -this.height / 2 + this.fill.offsetY || 0);
  11076. }
  11077. if (this.fillRule === 'evenodd') {
  11078. ctx.fill('evenodd');
  11079. }
  11080. else {
  11081. ctx.fill();
  11082. }
  11083. ctx.restore();
  11084. },
  11085. /**
  11086. * @private
  11087. * @param {CanvasRenderingContext2D} ctx Context to render on
  11088. */
  11089. _renderStroke: function(ctx) {
  11090. if (!this.stroke || this.strokeWidth === 0) {
  11091. return;
  11092. }
  11093. if (this.shadow && !this.shadow.affectStroke) {
  11094. this._removeShadow(ctx);
  11095. }
  11096. ctx.save();
  11097. this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
  11098. if (this.stroke.gradientTransform) {
  11099. var g = this.stroke.gradientTransform;
  11100. ctx.transform.apply(ctx, g);
  11101. }
  11102. if (this.stroke.toLive) {
  11103. ctx.translate(
  11104. -this.width / 2 + this.stroke.offsetX || 0,
  11105. -this.height / 2 + this.stroke.offsetY || 0);
  11106. }
  11107. ctx.stroke();
  11108. ctx.restore();
  11109. },
  11110. /**
  11111. * Clones an instance, some objects are async, so using callback method will work for every object.
  11112. * Using the direct return does not work for images and groups.
  11113. * @param {Function} callback Callback is invoked with a clone as a first argument
  11114. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  11115. * @return {fabric.Object} clone of an instance
  11116. */
  11117. clone: function(callback, propertiesToInclude) {
  11118. if (this.constructor.fromObject) {
  11119. return this.constructor.fromObject(this.toObject(propertiesToInclude), callback);
  11120. }
  11121. return new fabric.Object(this.toObject(propertiesToInclude));
  11122. },
  11123. /**
  11124. * Creates an instance of fabric.Image out of an object
  11125. * @param {Function} callback callback, invoked with an instance as a first argument
  11126. * @param {Object} [options] for clone as image, passed to toDataURL
  11127. * @param {Boolean} [options.enableRetinaScaling] enable retina scaling for the cloned image
  11128. * @return {fabric.Object} thisArg
  11129. */
  11130. cloneAsImage: function(callback, options) {
  11131. var dataUrl = this.toDataURL(options);
  11132. fabric.util.loadImage(dataUrl, function(img) {
  11133. if (callback) {
  11134. callback(new fabric.Image(img));
  11135. }
  11136. });
  11137. return this;
  11138. },
  11139. /**
  11140. * Converts an object into a data-url-like string
  11141. * @param {Object} options Options object
  11142. * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png"
  11143. * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg.
  11144. * @param {Number} [options.multiplier=1] Multiplier to scale by
  11145. * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14
  11146. * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14
  11147. * @param {Number} [options.width] Cropping width. Introduced in v1.2.14
  11148. * @param {Number} [options.height] Cropping height. Introduced in v1.2.14
  11149. * @param {Boolean} [options.enableRetina] Enable retina scaling for clone image. Introduce in 1.6.4
  11150. * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format
  11151. */
  11152. toDataURL: function(options) {
  11153. options || (options = { });
  11154. var el = fabric.util.createCanvasElement(),
  11155. boundingRect = this.getBoundingRect();
  11156. el.width = boundingRect.width;
  11157. el.height = boundingRect.height;
  11158. fabric.util.wrapElement(el, 'div');
  11159. var canvas = new fabric.StaticCanvas(el, { enableRetinaScaling: options.enableRetinaScaling });
  11160. // to avoid common confusion https://github.com/kangax/fabric.js/issues/806
  11161. if (options.format === 'jpg') {
  11162. options.format = 'jpeg';
  11163. }
  11164. if (options.format === 'jpeg') {
  11165. canvas.backgroundColor = '#fff';
  11166. }
  11167. var origParams = {
  11168. active: this.get('active'),
  11169. left: this.getLeft(),
  11170. top: this.getTop()
  11171. };
  11172. this.set('active', false);
  11173. this.setPositionByOrigin(new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2), 'center', 'center');
  11174. var originalCanvas = this.canvas;
  11175. canvas.add(this);
  11176. var data = canvas.toDataURL(options);
  11177. this.set(origParams).setCoords();
  11178. this.canvas = originalCanvas;
  11179. canvas.dispose();
  11180. canvas = null;
  11181. return data;
  11182. },
  11183. /**
  11184. * Returns true if specified type is identical to the type of an instance
  11185. * @param {String} type Type to check against
  11186. * @return {Boolean}
  11187. */
  11188. isType: function(type) {
  11189. return this.type === type;
  11190. },
  11191. /**
  11192. * Returns complexity of an instance
  11193. * @return {Number} complexity of this instance
  11194. */
  11195. complexity: function() {
  11196. return 0;
  11197. },
  11198. /**
  11199. * Returns a JSON representation of an instance
  11200. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  11201. * @return {Object} JSON
  11202. */
  11203. toJSON: function(propertiesToInclude) {
  11204. // delegate, not alias
  11205. return this.toObject(propertiesToInclude);
  11206. },
  11207. /**
  11208. * Sets gradient (fill or stroke) of an object
  11209. * <b>Backwards incompatibility note:</b> This method was named "setGradientFill" until v1.1.0
  11210. * @param {String} property Property name 'stroke' or 'fill'
  11211. * @param {Object} [options] Options object
  11212. * @param {String} [options.type] Type of gradient 'radial' or 'linear'
  11213. * @param {Number} [options.x1=0] x-coordinate of start point
  11214. * @param {Number} [options.y1=0] y-coordinate of start point
  11215. * @param {Number} [options.x2=0] x-coordinate of end point
  11216. * @param {Number} [options.y2=0] y-coordinate of end point
  11217. * @param {Number} [options.r1=0] Radius of start point (only for radial gradients)
  11218. * @param {Number} [options.r2=0] Radius of end point (only for radial gradients)
  11219. * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'}
  11220. * @param {Object} [options.gradientTransform] transforMatrix for gradient
  11221. * @return {fabric.Object} thisArg
  11222. * @chainable
  11223. * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo}
  11224. * @example <caption>Set linear gradient</caption>
  11225. * object.setGradient('fill', {
  11226. * type: 'linear',
  11227. * x1: -object.width / 2,
  11228. * y1: 0,
  11229. * x2: object.width / 2,
  11230. * y2: 0,
  11231. * colorStops: {
  11232. * 0: 'red',
  11233. * 0.5: '#005555',
  11234. * 1: 'rgba(0,0,255,0.5)'
  11235. * }
  11236. * });
  11237. * canvas.renderAll();
  11238. * @example <caption>Set radial gradient</caption>
  11239. * object.setGradient('fill', {
  11240. * type: 'radial',
  11241. * x1: 0,
  11242. * y1: 0,
  11243. * x2: 0,
  11244. * y2: 0,
  11245. * r1: object.width / 2,
  11246. * r2: 10,
  11247. * colorStops: {
  11248. * 0: 'red',
  11249. * 0.5: '#005555',
  11250. * 1: 'rgba(0,0,255,0.5)'
  11251. * }
  11252. * });
  11253. * canvas.renderAll();
  11254. */
  11255. setGradient: function(property, options) {
  11256. options || (options = { });
  11257. var gradient = { colorStops: [] };
  11258. gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear');
  11259. gradient.coords = {
  11260. x1: options.x1,
  11261. y1: options.y1,
  11262. x2: options.x2,
  11263. y2: options.y2
  11264. };
  11265. if (options.r1 || options.r2) {
  11266. gradient.coords.r1 = options.r1;
  11267. gradient.coords.r2 = options.r2;
  11268. }
  11269. options.gradientTransform && (gradient.gradientTransform = options.gradientTransform);
  11270. for (var position in options.colorStops) {
  11271. var color = new fabric.Color(options.colorStops[position]);
  11272. gradient.colorStops.push({
  11273. offset: position,
  11274. color: color.toRgb(),
  11275. opacity: color.getAlpha()
  11276. });
  11277. }
  11278. return this.set(property, fabric.Gradient.forObject(this, gradient));
  11279. },
  11280. /**
  11281. * Sets pattern fill of an object
  11282. * @param {Object} options Options object
  11283. * @param {(String|HTMLImageElement)} options.source Pattern source
  11284. * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat)
  11285. * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner
  11286. * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner
  11287. * @return {fabric.Object} thisArg
  11288. * @chainable
  11289. * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo}
  11290. * @example <caption>Set pattern</caption>
  11291. * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) {
  11292. * object.setPatternFill({
  11293. * source: img,
  11294. * repeat: 'repeat'
  11295. * });
  11296. * canvas.renderAll();
  11297. * });
  11298. */
  11299. setPatternFill: function(options) {
  11300. return this.set('fill', new fabric.Pattern(options));
  11301. },
  11302. /**
  11303. * Sets {@link fabric.Object#shadow|shadow} of an object
  11304. * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)")
  11305. * @param {String} [options.color=rgb(0,0,0)] Shadow color
  11306. * @param {Number} [options.blur=0] Shadow blur
  11307. * @param {Number} [options.offsetX=0] Shadow horizontal offset
  11308. * @param {Number} [options.offsetY=0] Shadow vertical offset
  11309. * @return {fabric.Object} thisArg
  11310. * @chainable
  11311. * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo}
  11312. * @example <caption>Set shadow with string notation</caption>
  11313. * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)');
  11314. * canvas.renderAll();
  11315. * @example <caption>Set shadow with object notation</caption>
  11316. * object.setShadow({
  11317. * color: 'red',
  11318. * blur: 10,
  11319. * offsetX: 20,
  11320. * offsetY: 20
  11321. * });
  11322. * canvas.renderAll();
  11323. */
  11324. setShadow: function(options) {
  11325. return this.set('shadow', options ? new fabric.Shadow(options) : null);
  11326. },
  11327. /**
  11328. * Sets "color" of an instance (alias of `set('fill', &hellip;)`)
  11329. * @param {String} color Color value
  11330. * @return {fabric.Object} thisArg
  11331. * @chainable
  11332. */
  11333. setColor: function(color) {
  11334. this.set('fill', color);
  11335. return this;
  11336. },
  11337. /**
  11338. * Sets "angle" of an instance
  11339. * @param {Number} angle Angle value (in degrees)
  11340. * @return {fabric.Object} thisArg
  11341. * @chainable
  11342. */
  11343. setAngle: function(angle) {
  11344. var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation;
  11345. if (shouldCenterOrigin) {
  11346. this._setOriginToCenter();
  11347. }
  11348. this.set('angle', angle);
  11349. if (shouldCenterOrigin) {
  11350. this._resetOrigin();
  11351. }
  11352. return this;
  11353. },
  11354. /**
  11355. * Centers object horizontally on canvas to which it was added last.
  11356. * You might need to call `setCoords` on an object after centering, to update controls area.
  11357. * @return {fabric.Object} thisArg
  11358. * @chainable
  11359. */
  11360. centerH: function () {
  11361. this.canvas && this.canvas.centerObjectH(this);
  11362. return this;
  11363. },
  11364. /**
  11365. * Centers object horizontally on current viewport of canvas to which it was added last.
  11366. * You might need to call `setCoords` on an object after centering, to update controls area.
  11367. * @return {fabric.Object} thisArg
  11368. * @chainable
  11369. */
  11370. viewportCenterH: function () {
  11371. this.canvas && this.canvas.viewportCenterObjectH(this);
  11372. return this;
  11373. },
  11374. /**
  11375. * Centers object vertically on canvas to which it was added last.
  11376. * You might need to call `setCoords` on an object after centering, to update controls area.
  11377. * @return {fabric.Object} thisArg
  11378. * @chainable
  11379. */
  11380. centerV: function () {
  11381. this.canvas && this.canvas.centerObjectV(this);
  11382. return this;
  11383. },
  11384. /**
  11385. * Centers object vertically on current viewport of canvas to which it was added last.
  11386. * You might need to call `setCoords` on an object after centering, to update controls area.
  11387. * @return {fabric.Object} thisArg
  11388. * @chainable
  11389. */
  11390. viewportCenterV: function () {
  11391. this.canvas && this.canvas.viewportCenterObjectV(this);
  11392. return this;
  11393. },
  11394. /**
  11395. * Centers object vertically and horizontally on canvas to which is was added last
  11396. * You might need to call `setCoords` on an object after centering, to update controls area.
  11397. * @return {fabric.Object} thisArg
  11398. * @chainable
  11399. */
  11400. center: function () {
  11401. this.canvas && this.canvas.centerObject(this);
  11402. return this;
  11403. },
  11404. /**
  11405. * Centers object on current viewport of canvas to which it was added last.
  11406. * You might need to call `setCoords` on an object after centering, to update controls area.
  11407. * @return {fabric.Object} thisArg
  11408. * @chainable
  11409. */
  11410. viewportCenter: function () {
  11411. this.canvas && this.canvas.viewportCenterObject(this);
  11412. return this;
  11413. },
  11414. /**
  11415. * Removes object from canvas to which it was added last
  11416. * @return {fabric.Object} thisArg
  11417. * @chainable
  11418. */
  11419. remove: function() {
  11420. this.canvas && this.canvas.remove(this);
  11421. return this;
  11422. },
  11423. /**
  11424. * Returns coordinates of a pointer relative to an object
  11425. * @param {Event} e Event to operate upon
  11426. * @param {Object} [pointer] Pointer to operate upon (instead of event)
  11427. * @return {Object} Coordinates of a pointer (x, y)
  11428. */
  11429. getLocalPointer: function(e, pointer) {
  11430. pointer = pointer || this.canvas.getPointer(e);
  11431. var pClicked = new fabric.Point(pointer.x, pointer.y),
  11432. objectLeftTop = this._getLeftTopCoords();
  11433. if (this.angle) {
  11434. pClicked = fabric.util.rotatePoint(
  11435. pClicked, objectLeftTop, fabric.util.degreesToRadians(-this.angle));
  11436. }
  11437. return {
  11438. x: pClicked.x - objectLeftTop.x,
  11439. y: pClicked.y - objectLeftTop.y
  11440. };
  11441. },
  11442. /**
  11443. * Sets canvas globalCompositeOperation for specific object
  11444. * custom composition operation for the particular object can be specifed using globalCompositeOperation property
  11445. * @param {CanvasRenderingContext2D} ctx Rendering canvas context
  11446. */
  11447. _setupCompositeOperation: function (ctx) {
  11448. if (this.globalCompositeOperation) {
  11449. ctx.globalCompositeOperation = this.globalCompositeOperation;
  11450. }
  11451. }
  11452. });
  11453. fabric.util.createAccessors(fabric.Object);
  11454. /**
  11455. * Alias for {@link fabric.Object.prototype.setAngle}
  11456. * @alias rotate -> setAngle
  11457. * @memberOf fabric.Object
  11458. */
  11459. fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
  11460. extend(fabric.Object.prototype, fabric.Observable);
  11461. /**
  11462. * Defines the number of fraction digits to use when serializing object values.
  11463. * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc.
  11464. * @static
  11465. * @memberOf fabric.Object
  11466. * @constant
  11467. * @type Number
  11468. */
  11469. fabric.Object.NUM_FRACTION_DIGITS = 2;
  11470. /**
  11471. * Unique id used internally when creating SVG elements
  11472. * @static
  11473. * @memberOf fabric.Object
  11474. * @type Number
  11475. */
  11476. fabric.Object.__uid = 0;
  11477. })(typeof exports !== 'undefined' ? exports : this);
  11478. (function() {
  11479. var degreesToRadians = fabric.util.degreesToRadians,
  11480. originXOffset = {
  11481. left: -0.5,
  11482. center: 0,
  11483. right: 0.5
  11484. },
  11485. originYOffset = {
  11486. top: -0.5,
  11487. center: 0,
  11488. bottom: 0.5
  11489. };
  11490. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  11491. /**
  11492. * Translates the coordinates from origin to center coordinates (based on the object's dimensions)
  11493. * @param {fabric.Point} point The point which corresponds to the originX and originY params
  11494. * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right'
  11495. * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom'
  11496. * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right'
  11497. * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom'
  11498. * @return {fabric.Point}
  11499. */
  11500. translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) {
  11501. var x = point.x,
  11502. y = point.y,
  11503. offsetX, offsetY, dim;
  11504. if (typeof fromOriginX === 'string') {
  11505. fromOriginX = originXOffset[fromOriginX];
  11506. }
  11507. else {
  11508. fromOriginX -= 0.5;
  11509. }
  11510. if (typeof toOriginX === 'string') {
  11511. toOriginX = originXOffset[toOriginX];
  11512. }
  11513. else {
  11514. toOriginX -= 0.5;
  11515. }
  11516. offsetX = toOriginX - fromOriginX;
  11517. if (typeof fromOriginY === 'string') {
  11518. fromOriginY = originYOffset[fromOriginY];
  11519. }
  11520. else {
  11521. fromOriginY -= 0.5;
  11522. }
  11523. if (typeof toOriginY === 'string') {
  11524. toOriginY = originYOffset[toOriginY];
  11525. }
  11526. else {
  11527. toOriginY -= 0.5;
  11528. }
  11529. offsetY = toOriginY - fromOriginY;
  11530. if (offsetX || offsetY) {
  11531. dim = this._getTransformedDimensions();
  11532. x = point.x + offsetX * dim.x;
  11533. y = point.y + offsetY * dim.y;
  11534. }
  11535. return new fabric.Point(x, y);
  11536. },
  11537. /**
  11538. * Translates the coordinates from origin to center coordinates (based on the object's dimensions)
  11539. * @param {fabric.Point} point The point which corresponds to the originX and originY params
  11540. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11541. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11542. * @return {fabric.Point}
  11543. */
  11544. translateToCenterPoint: function(point, originX, originY) {
  11545. var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center');
  11546. if (this.angle) {
  11547. return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle));
  11548. }
  11549. return p;
  11550. },
  11551. /**
  11552. * Translates the coordinates from center to origin coordinates (based on the object's dimensions)
  11553. * @param {fabric.Point} center The point which corresponds to center of the object
  11554. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11555. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11556. * @return {fabric.Point}
  11557. */
  11558. translateToOriginPoint: function(center, originX, originY) {
  11559. var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY);
  11560. if (this.angle) {
  11561. return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle));
  11562. }
  11563. return p;
  11564. },
  11565. /**
  11566. * Returns the real center coordinates of the object
  11567. * @return {fabric.Point}
  11568. */
  11569. getCenterPoint: function() {
  11570. var leftTop = new fabric.Point(this.left, this.top);
  11571. return this.translateToCenterPoint(leftTop, this.originX, this.originY);
  11572. },
  11573. /**
  11574. * Returns the coordinates of the object based on center coordinates
  11575. * @param {fabric.Point} point The point which corresponds to the originX and originY params
  11576. * @return {fabric.Point}
  11577. */
  11578. // getOriginPoint: function(center) {
  11579. // return this.translateToOriginPoint(center, this.originX, this.originY);
  11580. // },
  11581. /**
  11582. * Returns the coordinates of the object as if it has a different origin
  11583. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11584. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11585. * @return {fabric.Point}
  11586. */
  11587. getPointByOrigin: function(originX, originY) {
  11588. var center = this.getCenterPoint();
  11589. return this.translateToOriginPoint(center, originX, originY);
  11590. },
  11591. /**
  11592. * Returns the point in local coordinates
  11593. * @param {fabric.Point} point The point relative to the global coordinate system
  11594. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11595. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11596. * @return {fabric.Point}
  11597. */
  11598. toLocalPoint: function(point, originX, originY) {
  11599. var center = this.getCenterPoint(),
  11600. p, p2;
  11601. if (typeof originX !== 'undefined' && typeof originY !== 'undefined' ) {
  11602. p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY);
  11603. }
  11604. else {
  11605. p = new fabric.Point(this.left, this.top);
  11606. }
  11607. p2 = new fabric.Point(point.x, point.y);
  11608. if (this.angle) {
  11609. p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle));
  11610. }
  11611. return p2.subtractEquals(p);
  11612. },
  11613. /**
  11614. * Returns the point in global coordinates
  11615. * @param {fabric.Point} The point relative to the local coordinate system
  11616. * @return {fabric.Point}
  11617. */
  11618. // toGlobalPoint: function(point) {
  11619. // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top));
  11620. // },
  11621. /**
  11622. * Sets the position of the object taking into consideration the object's origin
  11623. * @param {fabric.Point} pos The new position of the object
  11624. * @param {String} originX Horizontal origin: 'left', 'center' or 'right'
  11625. * @param {String} originY Vertical origin: 'top', 'center' or 'bottom'
  11626. * @return {void}
  11627. */
  11628. setPositionByOrigin: function(pos, originX, originY) {
  11629. var center = this.translateToCenterPoint(pos, originX, originY),
  11630. position = this.translateToOriginPoint(center, this.originX, this.originY);
  11631. this.set('left', position.x);
  11632. this.set('top', position.y);
  11633. },
  11634. /**
  11635. * @param {String} to One of 'left', 'center', 'right'
  11636. */
  11637. adjustPosition: function(to) {
  11638. var angle = degreesToRadians(this.angle),
  11639. hypotFull = this.getWidth(),
  11640. xFull = Math.cos(angle) * hypotFull,
  11641. yFull = Math.sin(angle) * hypotFull,
  11642. offsetFrom, offsetTo;
  11643. //TODO: this function does not consider mixed situation like top, center.
  11644. if (typeof this.originX === 'string') {
  11645. offsetFrom = originXOffset[this.originX];
  11646. }
  11647. else {
  11648. offsetFrom = this.originX - 0.5;
  11649. }
  11650. if (typeof to === 'string') {
  11651. offsetTo = originXOffset[to];
  11652. }
  11653. else {
  11654. offsetTo = to - 0.5;
  11655. }
  11656. this.left += xFull * (offsetTo - offsetFrom);
  11657. this.top += yFull * (offsetTo - offsetFrom);
  11658. this.setCoords();
  11659. this.originX = to;
  11660. },
  11661. /**
  11662. * Sets the origin/position of the object to it's center point
  11663. * @private
  11664. * @return {void}
  11665. */
  11666. _setOriginToCenter: function() {
  11667. this._originalOriginX = this.originX;
  11668. this._originalOriginY = this.originY;
  11669. var center = this.getCenterPoint();
  11670. this.originX = 'center';
  11671. this.originY = 'center';
  11672. this.left = center.x;
  11673. this.top = center.y;
  11674. },
  11675. /**
  11676. * Resets the origin/position of the object to it's original origin
  11677. * @private
  11678. * @return {void}
  11679. */
  11680. _resetOrigin: function() {
  11681. var originPoint = this.translateToOriginPoint(
  11682. this.getCenterPoint(),
  11683. this._originalOriginX,
  11684. this._originalOriginY);
  11685. this.originX = this._originalOriginX;
  11686. this.originY = this._originalOriginY;
  11687. this.left = originPoint.x;
  11688. this.top = originPoint.y;
  11689. this._originalOriginX = null;
  11690. this._originalOriginY = null;
  11691. },
  11692. /**
  11693. * @private
  11694. */
  11695. _getLeftTopCoords: function() {
  11696. return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top');
  11697. }
  11698. });
  11699. })();
  11700. (function() {
  11701. function getCoords(oCoords) {
  11702. return [
  11703. new fabric.Point(oCoords.tl.x, oCoords.tl.y),
  11704. new fabric.Point(oCoords.tr.x, oCoords.tr.y),
  11705. new fabric.Point(oCoords.br.x, oCoords.br.y),
  11706. new fabric.Point(oCoords.bl.x, oCoords.bl.y)
  11707. ];
  11708. }
  11709. var degreesToRadians = fabric.util.degreesToRadians,
  11710. multiplyMatrices = fabric.util.multiplyTransformMatrices;
  11711. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  11712. /**
  11713. * Object containing coordinates of object's controls
  11714. * @type Object
  11715. * @default
  11716. */
  11717. oCoords: null,
  11718. /**
  11719. * Checks if object intersects with an area formed by 2 points
  11720. * @param {Object} pointTL top-left point of area
  11721. * @param {Object} pointBR bottom-right point of area
  11722. * @return {Boolean} true if object intersects with an area formed by 2 points
  11723. */
  11724. intersectsWithRect: function(pointTL, pointBR) {
  11725. var oCoords = getCoords(this.oCoords),
  11726. intersection = fabric.Intersection.intersectPolygonRectangle(
  11727. oCoords,
  11728. pointTL,
  11729. pointBR
  11730. );
  11731. return intersection.status === 'Intersection';
  11732. },
  11733. /**
  11734. * Checks if object intersects with another object
  11735. * @param {Object} other Object to test
  11736. * @return {Boolean} true if object intersects with another object
  11737. */
  11738. intersectsWithObject: function(other) {
  11739. var intersection = fabric.Intersection.intersectPolygonPolygon(
  11740. getCoords(this.oCoords),
  11741. getCoords(other.oCoords)
  11742. );
  11743. return intersection.status === 'Intersection'
  11744. || other.isContainedWithinObject(this)
  11745. || this.isContainedWithinObject(other);
  11746. },
  11747. /**
  11748. * Checks if object is fully contained within area of another object
  11749. * @param {Object} other Object to test
  11750. * @return {Boolean} true if object is fully contained within area of another object
  11751. */
  11752. isContainedWithinObject: function(other) {
  11753. var points = getCoords(this.oCoords),
  11754. i = 0;
  11755. for (; i < 4; i++) {
  11756. if (!other.containsPoint(points[i])) {
  11757. return false;
  11758. }
  11759. }
  11760. return true;
  11761. },
  11762. /**
  11763. * Checks if object is fully contained within area formed by 2 points
  11764. * @param {Object} pointTL top-left point of area
  11765. * @param {Object} pointBR bottom-right point of area
  11766. * @return {Boolean} true if object is fully contained within area formed by 2 points
  11767. */
  11768. isContainedWithinRect: function(pointTL, pointBR) {
  11769. var boundingRect = this.getBoundingRect();
  11770. return (
  11771. boundingRect.left >= pointTL.x &&
  11772. boundingRect.left + boundingRect.width <= pointBR.x &&
  11773. boundingRect.top >= pointTL.y &&
  11774. boundingRect.top + boundingRect.height <= pointBR.y
  11775. );
  11776. },
  11777. /**
  11778. * Checks if point is inside the object
  11779. * @param {fabric.Point} point Point to check against
  11780. * @return {Boolean} true if point is inside the object
  11781. */
  11782. containsPoint: function(point) {
  11783. if (!this.oCoords) {
  11784. this.setCoords();
  11785. }
  11786. var lines = this._getImageLines(this.oCoords),
  11787. xPoints = this._findCrossPoints(point, lines);
  11788. // if xPoints is odd then point is inside the object
  11789. return (xPoints !== 0 && xPoints % 2 === 1);
  11790. },
  11791. /**
  11792. * Method that returns an object with the object edges in it, given the coordinates of the corners
  11793. * @private
  11794. * @param {Object} oCoords Coordinates of the object corners
  11795. */
  11796. _getImageLines: function(oCoords) {
  11797. return {
  11798. topline: {
  11799. o: oCoords.tl,
  11800. d: oCoords.tr
  11801. },
  11802. rightline: {
  11803. o: oCoords.tr,
  11804. d: oCoords.br
  11805. },
  11806. bottomline: {
  11807. o: oCoords.br,
  11808. d: oCoords.bl
  11809. },
  11810. leftline: {
  11811. o: oCoords.bl,
  11812. d: oCoords.tl
  11813. }
  11814. };
  11815. },
  11816. /**
  11817. * Helper method to determine how many cross points are between the 4 object edges
  11818. * and the horizontal line determined by a point on canvas
  11819. * @private
  11820. * @param {fabric.Point} point Point to check
  11821. * @param {Object} oCoords Coordinates of the object being evaluated
  11822. */
  11823. // remove yi, not used but left code here just in case.
  11824. _findCrossPoints: function(point, oCoords) {
  11825. var b1, b2, a1, a2, xi, // yi,
  11826. xcount = 0,
  11827. iLine;
  11828. for (var lineKey in oCoords) {
  11829. iLine = oCoords[lineKey];
  11830. // optimisation 1: line below point. no cross
  11831. if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) {
  11832. continue;
  11833. }
  11834. // optimisation 2: line above point. no cross
  11835. if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) {
  11836. continue;
  11837. }
  11838. // optimisation 3: vertical line case
  11839. if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) {
  11840. xi = iLine.o.x;
  11841. // yi = point.y;
  11842. }
  11843. // calculate the intersection point
  11844. else {
  11845. b1 = 0;
  11846. b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x);
  11847. a1 = point.y - b1 * point.x;
  11848. a2 = iLine.o.y - b2 * iLine.o.x;
  11849. xi = -(a1 - a2) / (b1 - b2);
  11850. // yi = a1 + b1 * xi;
  11851. }
  11852. // dont count xi < point.x cases
  11853. if (xi >= point.x) {
  11854. xcount += 1;
  11855. }
  11856. // optimisation 4: specific for square images
  11857. if (xcount === 2) {
  11858. break;
  11859. }
  11860. }
  11861. return xcount;
  11862. },
  11863. /**
  11864. * Returns width of an object's bounding rectangle
  11865. * @deprecated since 1.0.4
  11866. * @return {Number} width value
  11867. */
  11868. getBoundingRectWidth: function() {
  11869. return this.getBoundingRect().width;
  11870. },
  11871. /**
  11872. * Returns height of an object's bounding rectangle
  11873. * @deprecated since 1.0.4
  11874. * @return {Number} height value
  11875. */
  11876. getBoundingRectHeight: function() {
  11877. return this.getBoundingRect().height;
  11878. },
  11879. /**
  11880. * Returns coordinates of object's bounding rectangle (left, top, width, height)
  11881. * @return {Object} Object with left, top, width, height properties
  11882. */
  11883. getBoundingRect: function() {
  11884. this.oCoords || this.setCoords();
  11885. return fabric.util.makeBoundingBoxFromPoints([
  11886. this.oCoords.tl,
  11887. this.oCoords.tr,
  11888. this.oCoords.br,
  11889. this.oCoords.bl
  11890. ]);
  11891. },
  11892. /**
  11893. * Returns width of an object bounding box counting transformations
  11894. * @return {Number} width value
  11895. */
  11896. getWidth: function() {
  11897. return this._getTransformedDimensions().x;
  11898. },
  11899. /**
  11900. * Returns height of an object bounding box counting transformations
  11901. * to be renamed in 2.0
  11902. * @return {Number} height value
  11903. */
  11904. getHeight: function() {
  11905. return this._getTransformedDimensions().y;
  11906. },
  11907. /**
  11908. * Makes sure the scale is valid and modifies it if necessary
  11909. * @private
  11910. * @param {Number} value
  11911. * @return {Number}
  11912. */
  11913. _constrainScale: function(value) {
  11914. if (Math.abs(value) < this.minScaleLimit) {
  11915. if (value < 0) {
  11916. return -this.minScaleLimit;
  11917. }
  11918. else {
  11919. return this.minScaleLimit;
  11920. }
  11921. }
  11922. return value;
  11923. },
  11924. /**
  11925. * Scales an object (equally by x and y)
  11926. * @param {Number} value Scale factor
  11927. * @return {fabric.Object} thisArg
  11928. * @chainable
  11929. */
  11930. scale: function(value) {
  11931. value = this._constrainScale(value);
  11932. if (value < 0) {
  11933. this.flipX = !this.flipX;
  11934. this.flipY = !this.flipY;
  11935. value *= -1;
  11936. }
  11937. this.scaleX = value;
  11938. this.scaleY = value;
  11939. this.setCoords();
  11940. return this;
  11941. },
  11942. /**
  11943. * Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
  11944. * @param {Number} value New width value
  11945. * @return {fabric.Object} thisArg
  11946. * @chainable
  11947. */
  11948. scaleToWidth: function(value) {
  11949. // adjust to bounding rect factor so that rotated shapes would fit as well
  11950. var boundingRectFactor = this.getBoundingRect().width / this.getWidth();
  11951. return this.scale(value / this.width / boundingRectFactor);
  11952. },
  11953. /**
  11954. * Scales an object to a given height, with respect to bounding box (scaling by x/y equally)
  11955. * @param {Number} value New height value
  11956. * @return {fabric.Object} thisArg
  11957. * @chainable
  11958. */
  11959. scaleToHeight: function(value) {
  11960. // adjust to bounding rect factor so that rotated shapes would fit as well
  11961. var boundingRectFactor = this.getBoundingRect().height / this.getHeight();
  11962. return this.scale(value / this.height / boundingRectFactor);
  11963. },
  11964. /**
  11965. * Sets corner position coordinates based on current angle, width and height
  11966. * See https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords
  11967. * @return {fabric.Object} thisArg
  11968. * @chainable
  11969. */
  11970. setCoords: function() {
  11971. var theta = degreesToRadians(this.angle),
  11972. vpt = this.getViewportTransform(),
  11973. dim = this._calculateCurrentDimensions(),
  11974. currentWidth = dim.x, currentHeight = dim.y;
  11975. // If width is negative, make postive. Fixes path selection issue
  11976. if (currentWidth < 0) {
  11977. currentWidth = Math.abs(currentWidth);
  11978. }
  11979. var sinTh = Math.sin(theta),
  11980. cosTh = Math.cos(theta),
  11981. _angle = currentWidth > 0 ? Math.atan(currentHeight / currentWidth) : 0,
  11982. _hypotenuse = (currentWidth / Math.cos(_angle)) / 2,
  11983. offsetX = Math.cos(_angle + theta) * _hypotenuse,
  11984. offsetY = Math.sin(_angle + theta) * _hypotenuse,
  11985. // offset added for rotate and scale actions
  11986. coords = fabric.util.transformPoint(this.getCenterPoint(), vpt),
  11987. tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY),
  11988. tr = new fabric.Point(tl.x + (currentWidth * cosTh), tl.y + (currentWidth * sinTh)),
  11989. bl = new fabric.Point(tl.x - (currentHeight * sinTh), tl.y + (currentHeight * cosTh)),
  11990. br = new fabric.Point(coords.x + offsetX, coords.y + offsetY),
  11991. ml = new fabric.Point((tl.x + bl.x) / 2, (tl.y + bl.y) / 2),
  11992. mt = new fabric.Point((tr.x + tl.x) / 2, (tr.y + tl.y) / 2),
  11993. mr = new fabric.Point((br.x + tr.x) / 2, (br.y + tr.y) / 2),
  11994. mb = new fabric.Point((br.x + bl.x) / 2, (br.y + bl.y) / 2),
  11995. mtr = new fabric.Point(mt.x + sinTh * this.rotatingPointOffset, mt.y - cosTh * this.rotatingPointOffset);
  11996. // debugging
  11997. /* setTimeout(function() {
  11998. canvas.contextTop.fillStyle = 'green';
  11999. canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
  12000. canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
  12001. canvas.contextTop.fillRect(br.x, br.y, 3, 3);
  12002. canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
  12003. canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
  12004. canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
  12005. canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
  12006. canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
  12007. canvas.contextTop.fillRect(mtr.x, mtr.y, 3, 3);
  12008. }, 50); */
  12009. this.oCoords = {
  12010. // corners
  12011. tl: tl, tr: tr, br: br, bl: bl,
  12012. // middle
  12013. ml: ml, mt: mt, mr: mr, mb: mb,
  12014. // rotating point
  12015. mtr: mtr
  12016. };
  12017. // set coordinates of the draggable boxes in the corners used to scale/rotate the image
  12018. this._setCornerCoords && this._setCornerCoords();
  12019. return this;
  12020. },
  12021. /*
  12022. * calculate rotation matrix of an object
  12023. * @return {Array} rotation matrix for the object
  12024. */
  12025. _calcRotateMatrix: function() {
  12026. if (this.angle) {
  12027. var theta = degreesToRadians(this.angle), cos = Math.cos(theta), sin = Math.sin(theta);
  12028. return [cos, sin, -sin, cos, 0, 0];
  12029. }
  12030. return [1, 0, 0, 1, 0, 0];
  12031. },
  12032. /*
  12033. * calculate trasform Matrix that represent current transformation from
  12034. * object properties.
  12035. * @return {Array} matrix Transform Matrix for the object
  12036. */
  12037. calcTransformMatrix: function() {
  12038. var center = this.getCenterPoint(),
  12039. translateMatrix = [1, 0, 0, 1, center.x, center.y],
  12040. rotateMatrix = this._calcRotateMatrix(),
  12041. dimensionMatrix = this._calcDimensionsTransformMatrix(this.skewX, this.skewY, true),
  12042. matrix = this.group ? this.group.calcTransformMatrix() : [1, 0, 0, 1, 0, 0];
  12043. matrix = multiplyMatrices(matrix, translateMatrix);
  12044. matrix = multiplyMatrices(matrix, rotateMatrix);
  12045. matrix = multiplyMatrices(matrix, dimensionMatrix);
  12046. return matrix;
  12047. },
  12048. _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) {
  12049. var skewMatrixX = [1, 0, Math.tan(degreesToRadians(skewX)), 1],
  12050. skewMatrixY = [1, Math.tan(degreesToRadians(skewY)), 0, 1],
  12051. scaleX = this.scaleX * (flipping && this.flipX ? -1 : 1),
  12052. scaleY = this.scaleY * (flipping && this.flipY ? -1 : 1),
  12053. scaleMatrix = [scaleX, 0, 0, scaleY],
  12054. m = multiplyMatrices(scaleMatrix, skewMatrixX, true);
  12055. return multiplyMatrices(m, skewMatrixY, true);
  12056. }
  12057. });
  12058. })();
  12059. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12060. /**
  12061. * Moves an object to the bottom of the stack of drawn objects
  12062. * @return {fabric.Object} thisArg
  12063. * @chainable
  12064. */
  12065. sendToBack: function() {
  12066. if (this.group) {
  12067. fabric.StaticCanvas.prototype.sendToBack.call(this.group, this);
  12068. }
  12069. else {
  12070. this.canvas.sendToBack(this);
  12071. }
  12072. return this;
  12073. },
  12074. /**
  12075. * Moves an object to the top of the stack of drawn objects
  12076. * @return {fabric.Object} thisArg
  12077. * @chainable
  12078. */
  12079. bringToFront: function() {
  12080. if (this.group) {
  12081. fabric.StaticCanvas.prototype.bringToFront.call(this.group, this);
  12082. }
  12083. else {
  12084. this.canvas.bringToFront(this);
  12085. }
  12086. return this;
  12087. },
  12088. /**
  12089. * Moves an object down in stack of drawn objects
  12090. * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object
  12091. * @return {fabric.Object} thisArg
  12092. * @chainable
  12093. */
  12094. sendBackwards: function(intersecting) {
  12095. if (this.group) {
  12096. fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting);
  12097. }
  12098. else {
  12099. this.canvas.sendBackwards(this, intersecting);
  12100. }
  12101. return this;
  12102. },
  12103. /**
  12104. * Moves an object up in stack of drawn objects
  12105. * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object
  12106. * @return {fabric.Object} thisArg
  12107. * @chainable
  12108. */
  12109. bringForward: function(intersecting) {
  12110. if (this.group) {
  12111. fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting);
  12112. }
  12113. else {
  12114. this.canvas.bringForward(this, intersecting);
  12115. }
  12116. return this;
  12117. },
  12118. /**
  12119. * Moves an object to specified level in stack of drawn objects
  12120. * @param {Number} index New position of object
  12121. * @return {fabric.Object} thisArg
  12122. * @chainable
  12123. */
  12124. moveTo: function(index) {
  12125. if (this.group) {
  12126. fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index);
  12127. }
  12128. else {
  12129. this.canvas.moveTo(this, index);
  12130. }
  12131. return this;
  12132. }
  12133. });
  12134. /* _TO_SVG_START_ */
  12135. (function() {
  12136. function getSvgColorString(prop, value) {
  12137. if (!value) {
  12138. return prop + ': none; ';
  12139. }
  12140. else if (value.toLive) {
  12141. return prop + ': url(#SVGID_' + value.id + '); ';
  12142. }
  12143. else {
  12144. var color = new fabric.Color(value),
  12145. str = prop + ': ' + color.toRgb() + '; ',
  12146. opacity = color.getAlpha();
  12147. if (opacity !== 1) {
  12148. //change the color in rgb + opacity
  12149. str += prop + '-opacity: ' + opacity.toString() + '; ';
  12150. }
  12151. return str;
  12152. }
  12153. }
  12154. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12155. /**
  12156. * Returns styles-string for svg-export
  12157. * @param {Boolean} skipShadow a boolean to skip shadow filter output
  12158. * @return {String}
  12159. */
  12160. getSvgStyles: function(skipShadow) {
  12161. var fillRule = this.fillRule,
  12162. strokeWidth = this.strokeWidth ? this.strokeWidth : '0',
  12163. strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none',
  12164. strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt',
  12165. strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter',
  12166. strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4',
  12167. opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1',
  12168. visibility = this.visible ? '' : ' visibility: hidden;',
  12169. filter = skipShadow ? '' : this.getSvgFilter(),
  12170. fill = getSvgColorString('fill', this.fill),
  12171. stroke = getSvgColorString('stroke', this.stroke);
  12172. return [
  12173. stroke,
  12174. 'stroke-width: ', strokeWidth, '; ',
  12175. 'stroke-dasharray: ', strokeDashArray, '; ',
  12176. 'stroke-linecap: ', strokeLineCap, '; ',
  12177. 'stroke-linejoin: ', strokeLineJoin, '; ',
  12178. 'stroke-miterlimit: ', strokeMiterLimit, '; ',
  12179. fill,
  12180. 'fill-rule: ', fillRule, '; ',
  12181. 'opacity: ', opacity, ';',
  12182. filter,
  12183. visibility
  12184. ].join('');
  12185. },
  12186. /**
  12187. * Returns filter for svg shadow
  12188. * @return {String}
  12189. */
  12190. getSvgFilter: function() {
  12191. return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : '';
  12192. },
  12193. /**
  12194. * Returns id attribute for svg output
  12195. * @return {String}
  12196. */
  12197. getSvgId: function() {
  12198. return this.id ? 'id="' + this.id + '" ' : '';
  12199. },
  12200. /**
  12201. * Returns transform-string for svg-export
  12202. * @return {String}
  12203. */
  12204. getSvgTransform: function() {
  12205. if (this.group && this.group.type === 'path-group') {
  12206. return '';
  12207. }
  12208. var toFixed = fabric.util.toFixed,
  12209. angle = this.getAngle(),
  12210. skewX = (this.getSkewX() % 360),
  12211. skewY = (this.getSkewY() % 360),
  12212. center = this.getCenterPoint(),
  12213. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  12214. translatePart = this.type === 'path-group' ? '' : 'translate(' +
  12215. toFixed(center.x, NUM_FRACTION_DIGITS) +
  12216. ' ' +
  12217. toFixed(center.y, NUM_FRACTION_DIGITS) +
  12218. ')',
  12219. anglePart = angle !== 0
  12220. ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')')
  12221. : '',
  12222. scalePart = (this.scaleX === 1 && this.scaleY === 1)
  12223. ? '' :
  12224. (' scale(' +
  12225. toFixed(this.scaleX, NUM_FRACTION_DIGITS) +
  12226. ' ' +
  12227. toFixed(this.scaleY, NUM_FRACTION_DIGITS) +
  12228. ')'),
  12229. skewXPart = skewX !== 0 ? ' skewX(' + toFixed(skewX, NUM_FRACTION_DIGITS) + ')' : '',
  12230. skewYPart = skewY !== 0 ? ' skewY(' + toFixed(skewY, NUM_FRACTION_DIGITS) + ')' : '',
  12231. addTranslateX = this.type === 'path-group' ? this.width : 0,
  12232. flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '',
  12233. addTranslateY = this.type === 'path-group' ? this.height : 0,
  12234. flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : '';
  12235. return [
  12236. translatePart, anglePart, scalePart, flipXPart, flipYPart, skewXPart, skewYPart
  12237. ].join('');
  12238. },
  12239. /**
  12240. * Returns transform-string for svg-export from the transform matrix of single elements
  12241. * @return {String}
  12242. */
  12243. getSvgTransformMatrix: function() {
  12244. return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ') ' : '';
  12245. },
  12246. /**
  12247. * @private
  12248. */
  12249. _createBaseSVGMarkup: function() {
  12250. var markup = [];
  12251. if (this.fill && this.fill.toLive) {
  12252. markup.push(this.fill.toSVG(this, false));
  12253. }
  12254. if (this.stroke && this.stroke.toLive) {
  12255. markup.push(this.stroke.toSVG(this, false));
  12256. }
  12257. if (this.shadow) {
  12258. markup.push(this.shadow.toSVG(this));
  12259. }
  12260. return markup;
  12261. }
  12262. });
  12263. })();
  12264. /* _TO_SVG_END_ */
  12265. (function() {
  12266. var extend = fabric.util.object.extend;
  12267. /*
  12268. Depends on `stateProperties`
  12269. */
  12270. function saveProps(origin, destination, props) {
  12271. var tmpObj = { }, deep = true;
  12272. props.forEach(function(prop) {
  12273. tmpObj[prop] = origin[prop];
  12274. });
  12275. extend(origin[destination], tmpObj, deep);
  12276. }
  12277. function _isEqual(origValue, currentValue) {
  12278. if (!fabric.isLikelyNode && origValue instanceof Element) {
  12279. // avoid checking deep html elements
  12280. return origValue === currentValue;
  12281. }
  12282. else if (origValue instanceof Array) {
  12283. if (origValue.length !== currentValue.length) {
  12284. return false
  12285. }
  12286. var _currentValue = currentValue.concat().sort(),
  12287. _origValue = origValue.concat().sort();
  12288. return !_origValue.some(function(v, i) {
  12289. return !_isEqual(_currentValue[i], v);
  12290. });
  12291. }
  12292. else if (origValue instanceof Object) {
  12293. for (var key in origValue) {
  12294. if (!_isEqual(origValue[key], currentValue[key])) {
  12295. return false;
  12296. }
  12297. }
  12298. return true;
  12299. }
  12300. else {
  12301. return origValue === currentValue;
  12302. }
  12303. }
  12304. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12305. /**
  12306. * Returns true if object state (one of its state properties) was changed
  12307. * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called
  12308. */
  12309. hasStateChanged: function() {
  12310. return !_isEqual(this.originalState, this);
  12311. },
  12312. /**
  12313. * Saves state of an object
  12314. * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
  12315. * @return {fabric.Object} thisArg
  12316. */
  12317. saveState: function(options) {
  12318. saveProps(this, 'originalState', this.stateProperties);
  12319. if (options && options.stateProperties) {
  12320. saveProps(this, 'originalState', options.stateProperties);
  12321. }
  12322. return this;
  12323. },
  12324. /**
  12325. * Setups state of an object
  12326. * @param {Object} [options] Object with additional `stateProperties` array to include when saving state
  12327. * @return {fabric.Object} thisArg
  12328. */
  12329. setupState: function(options) {
  12330. this.originalState = { };
  12331. this.saveState(options);
  12332. return this;
  12333. }
  12334. });
  12335. })();
  12336. (function() {
  12337. var degreesToRadians = fabric.util.degreesToRadians,
  12338. /* eslint-disable camelcase */
  12339. isVML = function() { return typeof G_vmlCanvasManager !== 'undefined'; };
  12340. /* eslint-enable camelcase */
  12341. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12342. /**
  12343. * The object interactivity controls.
  12344. * @private
  12345. */
  12346. _controlsVisibility: null,
  12347. /**
  12348. * Determines which corner has been clicked
  12349. * @private
  12350. * @param {Object} pointer The pointer indicating the mouse position
  12351. * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
  12352. */
  12353. _findTargetCorner: function(pointer) {
  12354. if (!this.hasControls || !this.active) {
  12355. return false;
  12356. }
  12357. var ex = pointer.x,
  12358. ey = pointer.y,
  12359. xPoints,
  12360. lines;
  12361. this.__corner = 0;
  12362. for (var i in this.oCoords) {
  12363. if (!this.isControlVisible(i)) {
  12364. continue;
  12365. }
  12366. if (i === 'mtr' && !this.hasRotatingPoint) {
  12367. continue;
  12368. }
  12369. if (this.get('lockUniScaling') &&
  12370. (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
  12371. continue;
  12372. }
  12373. lines = this._getImageLines(this.oCoords[i].corner);
  12374. // debugging
  12375. // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
  12376. // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
  12377. // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
  12378. // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
  12379. // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
  12380. // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
  12381. // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
  12382. // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
  12383. xPoints = this._findCrossPoints({ x: ex, y: ey }, lines);
  12384. if (xPoints !== 0 && xPoints % 2 === 1) {
  12385. this.__corner = i;
  12386. return i;
  12387. }
  12388. }
  12389. return false;
  12390. },
  12391. /**
  12392. * Sets the coordinates of the draggable boxes in the corners of
  12393. * the image used to scale/rotate it.
  12394. * @private
  12395. */
  12396. _setCornerCoords: function() {
  12397. var coords = this.oCoords,
  12398. newTheta = degreesToRadians(45 - this.angle),
  12399. /* Math.sqrt(2 * Math.pow(this.cornerSize, 2)) / 2, */
  12400. /* 0.707106 stands for sqrt(2)/2 */
  12401. cornerHypotenuse = this.cornerSize * 0.707106,
  12402. cosHalfOffset = cornerHypotenuse * Math.cos(newTheta),
  12403. sinHalfOffset = cornerHypotenuse * Math.sin(newTheta),
  12404. x, y;
  12405. for (var point in coords) {
  12406. x = coords[point].x;
  12407. y = coords[point].y;
  12408. coords[point].corner = {
  12409. tl: {
  12410. x: x - sinHalfOffset,
  12411. y: y - cosHalfOffset
  12412. },
  12413. tr: {
  12414. x: x + cosHalfOffset,
  12415. y: y - sinHalfOffset
  12416. },
  12417. bl: {
  12418. x: x - cosHalfOffset,
  12419. y: y + sinHalfOffset
  12420. },
  12421. br: {
  12422. x: x + sinHalfOffset,
  12423. y: y + cosHalfOffset
  12424. }
  12425. };
  12426. }
  12427. },
  12428. /*
  12429. * Calculate object dimensions from its properties
  12430. * @private
  12431. */
  12432. _getNonTransformedDimensions: function() {
  12433. var strokeWidth = this.strokeWidth,
  12434. w = this.width,
  12435. h = this.height,
  12436. addStrokeToW = true,
  12437. addStrokeToH = true;
  12438. if (this.type === 'line' && this.strokeLineCap === 'butt') {
  12439. addStrokeToH = w;
  12440. addStrokeToW = h;
  12441. }
  12442. if (addStrokeToH) {
  12443. h += h < 0 ? -strokeWidth : strokeWidth;
  12444. }
  12445. if (addStrokeToW) {
  12446. w += w < 0 ? -strokeWidth : strokeWidth;
  12447. }
  12448. return { x: w, y: h };
  12449. },
  12450. /*
  12451. * @private
  12452. */
  12453. _getTransformedDimensions: function(skewX, skewY) {
  12454. if (typeof skewX === 'undefined') {
  12455. skewX = this.skewX;
  12456. }
  12457. if (typeof skewY === 'undefined') {
  12458. skewY = this.skewY;
  12459. }
  12460. var dimensions = this._getNonTransformedDimensions(),
  12461. dimX = dimensions.x / 2, dimY = dimensions.y / 2,
  12462. points = [
  12463. {
  12464. x: -dimX,
  12465. y: -dimY
  12466. },
  12467. {
  12468. x: dimX,
  12469. y: -dimY
  12470. },
  12471. {
  12472. x: -dimX,
  12473. y: dimY
  12474. },
  12475. {
  12476. x: dimX,
  12477. y: dimY
  12478. }],
  12479. i, transformMatrix = this._calcDimensionsTransformMatrix(skewX, skewY, false),
  12480. bbox;
  12481. for (i = 0; i < points.length; i++) {
  12482. points[i] = fabric.util.transformPoint(points[i], transformMatrix);
  12483. }
  12484. bbox = fabric.util.makeBoundingBoxFromPoints(points);
  12485. return { x: bbox.width, y: bbox.height };
  12486. },
  12487. /*
  12488. * private
  12489. */
  12490. _calculateCurrentDimensions: function() {
  12491. var vpt = this.getViewportTransform(),
  12492. dim = this._getTransformedDimensions(),
  12493. w = dim.x, h = dim.y,
  12494. p = fabric.util.transformPoint(new fabric.Point(w, h), vpt, true);
  12495. return p.scalarAdd(2 * this.padding);
  12496. },
  12497. /**
  12498. * Draws a colored layer behind the object, inside its selection borders.
  12499. * Requires public options: padding, selectionBackgroundColor
  12500. * this function is called when the context is transformed
  12501. * @param {CanvasRenderingContext2D} ctx Context to draw on
  12502. * @return {fabric.Object} thisArg
  12503. * @chainable
  12504. */
  12505. drawSelectionBackground: function(ctx) {
  12506. if (!this.selectionBackgroundColor || this.group || !this.active) {
  12507. return this;
  12508. }
  12509. ctx.save();
  12510. var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(),
  12511. vpt = this.canvas.viewportTransform;
  12512. ctx.translate(center.x, center.y);
  12513. ctx.scale(1 / vpt[0], 1 / vpt[3]);
  12514. ctx.rotate(degreesToRadians(this.angle));
  12515. ctx.fillStyle = this.selectionBackgroundColor;
  12516. ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y);
  12517. ctx.restore();
  12518. return this;
  12519. },
  12520. /**
  12521. * Draws borders of an object's bounding box.
  12522. * Requires public properties: width, height
  12523. * Requires public options: padding, borderColor
  12524. * @param {CanvasRenderingContext2D} ctx Context to draw on
  12525. * @return {fabric.Object} thisArg
  12526. * @chainable
  12527. */
  12528. drawBorders: function(ctx) {
  12529. if (!this.hasBorders) {
  12530. return this;
  12531. }
  12532. var wh = this._calculateCurrentDimensions(),
  12533. strokeWidth = 1 / this.borderScaleFactor,
  12534. width = wh.x + strokeWidth,
  12535. height = wh.y + strokeWidth;
  12536. ctx.save();
  12537. ctx.strokeStyle = this.borderColor;
  12538. this._setLineDash(ctx, this.borderDashArray, null);
  12539. ctx.strokeRect(
  12540. -width / 2,
  12541. -height / 2,
  12542. width,
  12543. height
  12544. );
  12545. if (this.hasRotatingPoint && this.isControlVisible('mtr') && !this.get('lockRotation') && this.hasControls) {
  12546. var rotateHeight = -height / 2;
  12547. ctx.beginPath();
  12548. ctx.moveTo(0, rotateHeight);
  12549. ctx.lineTo(0, rotateHeight - this.rotatingPointOffset);
  12550. ctx.closePath();
  12551. ctx.stroke();
  12552. }
  12553. ctx.restore();
  12554. return this;
  12555. },
  12556. /**
  12557. * Draws borders of an object's bounding box when it is inside a group.
  12558. * Requires public properties: width, height
  12559. * Requires public options: padding, borderColor
  12560. * @param {CanvasRenderingContext2D} ctx Context to draw on
  12561. * @param {object} options object representing current object parameters
  12562. * @return {fabric.Object} thisArg
  12563. * @chainable
  12564. */
  12565. drawBordersInGroup: function(ctx, options) {
  12566. if (!this.hasBorders) {
  12567. return this;
  12568. }
  12569. var p = this._getNonTransformedDimensions(),
  12570. matrix = fabric.util.customTransformMatrix(options.scaleX, options.scaleY, options.skewX),
  12571. wh = fabric.util.transformPoint(p, matrix),
  12572. strokeWidth = 1 / this.borderScaleFactor,
  12573. width = wh.x + strokeWidth + 2 * this.padding,
  12574. height = wh.y + strokeWidth + 2 * this.padding;
  12575. ctx.save();
  12576. this._setLineDash(ctx, this.borderDashArray, null);
  12577. ctx.strokeStyle = this.borderColor;
  12578. ctx.strokeRect(
  12579. -width / 2,
  12580. -height / 2,
  12581. width,
  12582. height
  12583. );
  12584. ctx.restore();
  12585. return this;
  12586. },
  12587. /**
  12588. * Draws corners of an object's bounding box.
  12589. * Requires public properties: width, height
  12590. * Requires public options: cornerSize, padding
  12591. * @param {CanvasRenderingContext2D} ctx Context to draw on
  12592. * @return {fabric.Object} thisArg
  12593. * @chainable
  12594. */
  12595. drawControls: function(ctx) {
  12596. if (!this.hasControls) {
  12597. return this;
  12598. }
  12599. var wh = this._calculateCurrentDimensions(),
  12600. width = wh.x,
  12601. height = wh.y,
  12602. scaleOffset = this.cornerSize,
  12603. left = -(width + scaleOffset) / 2,
  12604. top = -(height + scaleOffset) / 2,
  12605. methodName = this.transparentCorners ? 'stroke' : 'fill';
  12606. ctx.save();
  12607. ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
  12608. if (!this.transparentCorners) {
  12609. ctx.strokeStyle = this.cornerStrokeColor;
  12610. }
  12611. this._setLineDash(ctx, this.cornerDashArray, null);
  12612. // top-left
  12613. this._drawControl('tl', ctx, methodName,
  12614. left,
  12615. top);
  12616. // top-right
  12617. this._drawControl('tr', ctx, methodName,
  12618. left + width,
  12619. top);
  12620. // bottom-left
  12621. this._drawControl('bl', ctx, methodName,
  12622. left,
  12623. top + height);
  12624. // bottom-right
  12625. this._drawControl('br', ctx, methodName,
  12626. left + width,
  12627. top + height);
  12628. if (!this.get('lockUniScaling')) {
  12629. // middle-top
  12630. this._drawControl('mt', ctx, methodName,
  12631. left + width / 2,
  12632. top);
  12633. // middle-bottom
  12634. this._drawControl('mb', ctx, methodName,
  12635. left + width / 2,
  12636. top + height);
  12637. // middle-right
  12638. this._drawControl('mr', ctx, methodName,
  12639. left + width,
  12640. top + height / 2);
  12641. // middle-left
  12642. this._drawControl('ml', ctx, methodName,
  12643. left,
  12644. top + height / 2);
  12645. }
  12646. // middle-top-rotate
  12647. if (this.hasRotatingPoint) {
  12648. this._drawControl('mtr', ctx, methodName,
  12649. left + width / 2,
  12650. top - this.rotatingPointOffset);
  12651. }
  12652. ctx.restore();
  12653. return this;
  12654. },
  12655. /**
  12656. * @private
  12657. */
  12658. _drawControl: function(control, ctx, methodName, left, top) {
  12659. if (!this.isControlVisible(control)) {
  12660. return;
  12661. }
  12662. var size = this.cornerSize, stroke = !this.transparentCorners && this.cornerStrokeColor;
  12663. switch (this.cornerStyle) {
  12664. case 'circle':
  12665. ctx.beginPath();
  12666. ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI, false);
  12667. ctx[methodName]();
  12668. if (stroke) {
  12669. ctx.stroke();
  12670. }
  12671. break;
  12672. default:
  12673. isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size);
  12674. ctx[methodName + 'Rect'](left, top, size, size);
  12675. if (stroke) {
  12676. ctx.strokeRect(left, top, size, size);
  12677. }
  12678. }
  12679. },
  12680. /**
  12681. * Returns true if the specified control is visible, false otherwise.
  12682. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
  12683. * @returns {Boolean} true if the specified control is visible, false otherwise
  12684. */
  12685. isControlVisible: function(controlName) {
  12686. return this._getControlsVisibility()[controlName];
  12687. },
  12688. /**
  12689. * Sets the visibility of the specified control.
  12690. * @param {String} controlName The name of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
  12691. * @param {Boolean} visible true to set the specified control visible, false otherwise
  12692. * @return {fabric.Object} thisArg
  12693. * @chainable
  12694. */
  12695. setControlVisible: function(controlName, visible) {
  12696. this._getControlsVisibility()[controlName] = visible;
  12697. return this;
  12698. },
  12699. /**
  12700. * Sets the visibility state of object controls.
  12701. * @param {Object} [options] Options object
  12702. * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
  12703. * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it
  12704. * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it
  12705. * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it
  12706. * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it
  12707. * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it
  12708. * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it
  12709. * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it
  12710. * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it
  12711. * @return {fabric.Object} thisArg
  12712. * @chainable
  12713. */
  12714. setControlsVisibility: function(options) {
  12715. options || (options = { });
  12716. for (var p in options) {
  12717. this.setControlVisible(p, options[p]);
  12718. }
  12719. return this;
  12720. },
  12721. /**
  12722. * Returns the instance of the control visibility set for this object.
  12723. * @private
  12724. * @returns {Object}
  12725. */
  12726. _getControlsVisibility: function() {
  12727. if (!this._controlsVisibility) {
  12728. this._controlsVisibility = {
  12729. tl: true,
  12730. tr: true,
  12731. br: true,
  12732. bl: true,
  12733. ml: true,
  12734. mt: true,
  12735. mr: true,
  12736. mb: true,
  12737. mtr: true
  12738. };
  12739. }
  12740. return this._controlsVisibility;
  12741. }
  12742. });
  12743. })();
  12744. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  12745. /**
  12746. * Animation duration (in ms) for fx* methods
  12747. * @type Number
  12748. * @default
  12749. */
  12750. FX_DURATION: 500,
  12751. /**
  12752. * Centers object horizontally with animation.
  12753. * @param {fabric.Object} object Object to center
  12754. * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
  12755. * @param {Function} [callbacks.onComplete] Invoked on completion
  12756. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  12757. * @return {fabric.Canvas} thisArg
  12758. * @chainable
  12759. */
  12760. fxCenterObjectH: function (object, callbacks) {
  12761. callbacks = callbacks || { };
  12762. var empty = function() { },
  12763. onComplete = callbacks.onComplete || empty,
  12764. onChange = callbacks.onChange || empty,
  12765. _this = this;
  12766. fabric.util.animate({
  12767. startValue: object.get('left'),
  12768. endValue: this.getCenter().left,
  12769. duration: this.FX_DURATION,
  12770. onChange: function(value) {
  12771. object.set('left', value);
  12772. _this.renderAll();
  12773. onChange();
  12774. },
  12775. onComplete: function() {
  12776. object.setCoords();
  12777. onComplete();
  12778. }
  12779. });
  12780. return this;
  12781. },
  12782. /**
  12783. * Centers object vertically with animation.
  12784. * @param {fabric.Object} object Object to center
  12785. * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
  12786. * @param {Function} [callbacks.onComplete] Invoked on completion
  12787. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  12788. * @return {fabric.Canvas} thisArg
  12789. * @chainable
  12790. */
  12791. fxCenterObjectV: function (object, callbacks) {
  12792. callbacks = callbacks || { };
  12793. var empty = function() { },
  12794. onComplete = callbacks.onComplete || empty,
  12795. onChange = callbacks.onChange || empty,
  12796. _this = this;
  12797. fabric.util.animate({
  12798. startValue: object.get('top'),
  12799. endValue: this.getCenter().top,
  12800. duration: this.FX_DURATION,
  12801. onChange: function(value) {
  12802. object.set('top', value);
  12803. _this.renderAll();
  12804. onChange();
  12805. },
  12806. onComplete: function() {
  12807. object.setCoords();
  12808. onComplete();
  12809. }
  12810. });
  12811. return this;
  12812. },
  12813. /**
  12814. * Same as `fabric.Canvas#remove` but animated
  12815. * @param {fabric.Object} object Object to remove
  12816. * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
  12817. * @param {Function} [callbacks.onComplete] Invoked on completion
  12818. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  12819. * @return {fabric.Canvas} thisArg
  12820. * @chainable
  12821. */
  12822. fxRemove: function (object, callbacks) {
  12823. callbacks = callbacks || { };
  12824. var empty = function() { },
  12825. onComplete = callbacks.onComplete || empty,
  12826. onChange = callbacks.onChange || empty,
  12827. _this = this;
  12828. fabric.util.animate({
  12829. startValue: object.get('opacity'),
  12830. endValue: 0,
  12831. duration: this.FX_DURATION,
  12832. onStart: function() {
  12833. object.set('active', false);
  12834. },
  12835. onChange: function(value) {
  12836. object.set('opacity', value);
  12837. _this.renderAll();
  12838. onChange();
  12839. },
  12840. onComplete: function () {
  12841. _this.remove(object);
  12842. onComplete();
  12843. }
  12844. });
  12845. return this;
  12846. }
  12847. });
  12848. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  12849. /**
  12850. * Animates object's properties
  12851. * @param {String|Object} property Property to animate (if string) or properties to animate (if object)
  12852. * @param {Number|Object} value Value to animate property to (if string was given first) or options object
  12853. * @return {fabric.Object} thisArg
  12854. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation}
  12855. * @chainable
  12856. *
  12857. * As object multiple properties
  12858. *
  12859. * object.animate({ left: ..., top: ... });
  12860. * object.animate({ left: ..., top: ... }, { duration: ... });
  12861. *
  12862. * As string one property
  12863. *
  12864. * object.animate('left', ...);
  12865. * object.animate('left', { duration: ... });
  12866. *
  12867. */
  12868. animate: function() {
  12869. if (arguments[0] && typeof arguments[0] === 'object') {
  12870. var propsToAnimate = [], prop, skipCallbacks;
  12871. for (prop in arguments[0]) {
  12872. propsToAnimate.push(prop);
  12873. }
  12874. for (var i = 0, len = propsToAnimate.length; i < len; i++) {
  12875. prop = propsToAnimate[i];
  12876. skipCallbacks = i !== len - 1;
  12877. this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks);
  12878. }
  12879. }
  12880. else {
  12881. this._animate.apply(this, arguments);
  12882. }
  12883. return this;
  12884. },
  12885. /**
  12886. * @private
  12887. * @param {String} property Property to animate
  12888. * @param {String} to Value to animate to
  12889. * @param {Object} [options] Options object
  12890. * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked
  12891. */
  12892. _animate: function(property, to, options, skipCallbacks) {
  12893. var _this = this, propPair;
  12894. to = to.toString();
  12895. if (!options) {
  12896. options = { };
  12897. }
  12898. else {
  12899. options = fabric.util.object.clone(options);
  12900. }
  12901. if (~property.indexOf('.')) {
  12902. propPair = property.split('.');
  12903. }
  12904. var currentValue = propPair
  12905. ? this.get(propPair[0])[propPair[1]]
  12906. : this.get(property);
  12907. if (!('from' in options)) {
  12908. options.from = currentValue;
  12909. }
  12910. if (~to.indexOf('=')) {
  12911. to = currentValue + parseFloat(to.replace('=', ''));
  12912. }
  12913. else {
  12914. to = parseFloat(to);
  12915. }
  12916. fabric.util.animate({
  12917. startValue: options.from,
  12918. endValue: to,
  12919. byValue: options.by,
  12920. easing: options.easing,
  12921. duration: options.duration,
  12922. abort: options.abort && function() {
  12923. return options.abort.call(_this);
  12924. },
  12925. onChange: function(value) {
  12926. if (propPair) {
  12927. _this[propPair[0]][propPair[1]] = value;
  12928. }
  12929. else {
  12930. _this.set(property, value);
  12931. }
  12932. if (skipCallbacks) {
  12933. return;
  12934. }
  12935. options.onChange && options.onChange();
  12936. },
  12937. onComplete: function() {
  12938. if (skipCallbacks) {
  12939. return;
  12940. }
  12941. _this.setCoords();
  12942. options.onComplete && options.onComplete();
  12943. }
  12944. });
  12945. }
  12946. });
  12947. (function(global) {
  12948. 'use strict';
  12949. var fabric = global.fabric || (global.fabric = { }),
  12950. extend = fabric.util.object.extend,
  12951. coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 },
  12952. supportsLineDash = fabric.StaticCanvas.supports('setLineDash');
  12953. if (fabric.Line) {
  12954. fabric.warn('fabric.Line is already defined');
  12955. return;
  12956. }
  12957. /**
  12958. * Line class
  12959. * @class fabric.Line
  12960. * @extends fabric.Object
  12961. * @see {@link fabric.Line#initialize} for constructor definition
  12962. */
  12963. fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ {
  12964. /**
  12965. * Type of an object
  12966. * @type String
  12967. * @default
  12968. */
  12969. type: 'line',
  12970. /**
  12971. * x value or first line edge
  12972. * @type Number
  12973. * @default
  12974. */
  12975. x1: 0,
  12976. /**
  12977. * y value or first line edge
  12978. * @type Number
  12979. * @default
  12980. */
  12981. y1: 0,
  12982. /**
  12983. * x value or second line edge
  12984. * @type Number
  12985. * @default
  12986. */
  12987. x2: 0,
  12988. /**
  12989. * y value or second line edge
  12990. * @type Number
  12991. * @default
  12992. */
  12993. y2: 0,
  12994. /**
  12995. * Constructor
  12996. * @param {Array} [points] Array of points
  12997. * @param {Object} [options] Options object
  12998. * @return {fabric.Line} thisArg
  12999. */
  13000. initialize: function(points, options) {
  13001. options = options || { };
  13002. if (!points) {
  13003. points = [0, 0, 0, 0];
  13004. }
  13005. this.callSuper('initialize', options);
  13006. this.set('x1', points[0]);
  13007. this.set('y1', points[1]);
  13008. this.set('x2', points[2]);
  13009. this.set('y2', points[3]);
  13010. this._setWidthHeight(options);
  13011. },
  13012. /**
  13013. * @private
  13014. * @param {Object} [options] Options
  13015. */
  13016. _setWidthHeight: function(options) {
  13017. options || (options = { });
  13018. this.width = Math.abs(this.x2 - this.x1);
  13019. this.height = Math.abs(this.y2 - this.y1);
  13020. this.left = 'left' in options
  13021. ? options.left
  13022. : this._getLeftToOriginX();
  13023. this.top = 'top' in options
  13024. ? options.top
  13025. : this._getTopToOriginY();
  13026. },
  13027. /**
  13028. * @private
  13029. * @param {String} key
  13030. * @param {*} value
  13031. */
  13032. _set: function(key, value) {
  13033. this.callSuper('_set', key, value);
  13034. if (typeof coordProps[key] !== 'undefined') {
  13035. this._setWidthHeight();
  13036. }
  13037. return this;
  13038. },
  13039. /**
  13040. * @private
  13041. * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line.
  13042. */
  13043. _getLeftToOriginX: makeEdgeToOriginGetter(
  13044. { // property names
  13045. origin: 'originX',
  13046. axis1: 'x1',
  13047. axis2: 'x2',
  13048. dimension: 'width'
  13049. },
  13050. { // possible values of origin
  13051. nearest: 'left',
  13052. center: 'center',
  13053. farthest: 'right'
  13054. }
  13055. ),
  13056. /**
  13057. * @private
  13058. * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line.
  13059. */
  13060. _getTopToOriginY: makeEdgeToOriginGetter(
  13061. { // property names
  13062. origin: 'originY',
  13063. axis1: 'y1',
  13064. axis2: 'y2',
  13065. dimension: 'height'
  13066. },
  13067. { // possible values of origin
  13068. nearest: 'top',
  13069. center: 'center',
  13070. farthest: 'bottom'
  13071. }
  13072. ),
  13073. /**
  13074. * @private
  13075. * @param {CanvasRenderingContext2D} ctx Context to render on
  13076. * @param {Boolean} noTransform
  13077. */
  13078. _render: function(ctx, noTransform) {
  13079. ctx.beginPath();
  13080. if (noTransform) {
  13081. // Line coords are distances from left-top of canvas to origin of line.
  13082. // To render line in a path-group, we need to translate them to
  13083. // distances from center of path-group to center of line.
  13084. var cp = this.getCenterPoint();
  13085. ctx.translate(
  13086. cp.x - this.strokeWidth / 2,
  13087. cp.y - this.strokeWidth / 2
  13088. );
  13089. }
  13090. if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) {
  13091. // move from center (of virtual box) to its left/top corner
  13092. // we can't assume x1, y1 is top left and x2, y2 is bottom right
  13093. var p = this.calcLinePoints();
  13094. ctx.moveTo(p.x1, p.y1);
  13095. ctx.lineTo(p.x2, p.y2);
  13096. }
  13097. ctx.lineWidth = this.strokeWidth;
  13098. // TODO: test this
  13099. // make sure setting "fill" changes color of a line
  13100. // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
  13101. var origStrokeStyle = ctx.strokeStyle;
  13102. ctx.strokeStyle = this.stroke || ctx.fillStyle;
  13103. this.stroke && this._renderStroke(ctx);
  13104. ctx.strokeStyle = origStrokeStyle;
  13105. },
  13106. /**
  13107. * @private
  13108. * @param {CanvasRenderingContext2D} ctx Context to render on
  13109. */
  13110. _renderDashedStroke: function(ctx) {
  13111. var p = this.calcLinePoints();
  13112. ctx.beginPath();
  13113. fabric.util.drawDashedLine(ctx, p.x1, p.y1, p.x2, p.y2, this.strokeDashArray);
  13114. ctx.closePath();
  13115. },
  13116. /**
  13117. * Returns object representation of an instance
  13118. * @methd toObject
  13119. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  13120. * @return {Object} object representation of an instance
  13121. */
  13122. toObject: function(propertiesToInclude) {
  13123. return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints());
  13124. },
  13125. /**
  13126. * Recalculates line points given width and height
  13127. * @private
  13128. */
  13129. calcLinePoints: function() {
  13130. var xMult = this.x1 <= this.x2 ? -1 : 1,
  13131. yMult = this.y1 <= this.y2 ? -1 : 1,
  13132. x1 = (xMult * this.width * 0.5),
  13133. y1 = (yMult * this.height * 0.5),
  13134. x2 = (xMult * this.width * -0.5),
  13135. y2 = (yMult * this.height * -0.5);
  13136. return {
  13137. x1: x1,
  13138. x2: x2,
  13139. y1: y1,
  13140. y2: y2
  13141. };
  13142. },
  13143. /* _TO_SVG_START_ */
  13144. /**
  13145. * Returns SVG representation of an instance
  13146. * @param {Function} [reviver] Method for further parsing of svg representation.
  13147. * @return {String} svg representation of an instance
  13148. */
  13149. toSVG: function(reviver) {
  13150. var markup = this._createBaseSVGMarkup(),
  13151. p = { x1: this.x1, x2: this.x2, y1: this.y1, y2: this.y2 };
  13152. if (!(this.group && this.group.type === 'path-group')) {
  13153. p = this.calcLinePoints();
  13154. }
  13155. markup.push(
  13156. '<line ', this.getSvgId(),
  13157. 'x1="', p.x1,
  13158. '" y1="', p.y1,
  13159. '" x2="', p.x2,
  13160. '" y2="', p.y2,
  13161. '" style="', this.getSvgStyles(),
  13162. '" transform="', this.getSvgTransform(),
  13163. this.getSvgTransformMatrix(),
  13164. '"/>\n'
  13165. );
  13166. return reviver ? reviver(markup.join('')) : markup.join('');
  13167. },
  13168. /* _TO_SVG_END_ */
  13169. /**
  13170. * Returns complexity of an instance
  13171. * @return {Number} complexity
  13172. */
  13173. complexity: function() {
  13174. return 1;
  13175. }
  13176. });
  13177. /* _FROM_SVG_START_ */
  13178. /**
  13179. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement})
  13180. * @static
  13181. * @memberOf fabric.Line
  13182. * @see http://www.w3.org/TR/SVG/shapes.html#LineElement
  13183. */
  13184. fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' '));
  13185. /**
  13186. * Returns fabric.Line instance from an SVG element
  13187. * @static
  13188. * @memberOf fabric.Line
  13189. * @param {SVGElement} element Element to parse
  13190. * @param {Object} [options] Options object
  13191. * @return {fabric.Line} instance of fabric.Line
  13192. */
  13193. fabric.Line.fromElement = function(element, options) {
  13194. var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES),
  13195. points = [
  13196. parsedAttributes.x1 || 0,
  13197. parsedAttributes.y1 || 0,
  13198. parsedAttributes.x2 || 0,
  13199. parsedAttributes.y2 || 0
  13200. ];
  13201. return new fabric.Line(points, extend(parsedAttributes, options));
  13202. };
  13203. /* _FROM_SVG_END_ */
  13204. /**
  13205. * Returns fabric.Line instance from an object representation
  13206. * @static
  13207. * @memberOf fabric.Line
  13208. * @param {Object} object Object to create an instance from
  13209. * @param {function} [callback] invoked with new instance as first argument
  13210. * @return {fabric.Line} instance of fabric.Line
  13211. */
  13212. fabric.Line.fromObject = function(object, callback) {
  13213. var points = [object.x1, object.y1, object.x2, object.y2],
  13214. line = new fabric.Line(points, object);
  13215. callback && callback(line);
  13216. return line;
  13217. };
  13218. /**
  13219. * Produces a function that calculates distance from canvas edge to Line origin.
  13220. */
  13221. function makeEdgeToOriginGetter(propertyNames, originValues) {
  13222. var origin = propertyNames.origin,
  13223. axis1 = propertyNames.axis1,
  13224. axis2 = propertyNames.axis2,
  13225. dimension = propertyNames.dimension,
  13226. nearest = originValues.nearest,
  13227. center = originValues.center,
  13228. farthest = originValues.farthest;
  13229. return function() {
  13230. switch (this.get(origin)) {
  13231. case nearest:
  13232. return Math.min(this.get(axis1), this.get(axis2));
  13233. case center:
  13234. return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension));
  13235. case farthest:
  13236. return Math.max(this.get(axis1), this.get(axis2));
  13237. }
  13238. };
  13239. }
  13240. })(typeof exports !== 'undefined' ? exports : this);
  13241. (function(global) {
  13242. 'use strict';
  13243. var fabric = global.fabric || (global.fabric = { }),
  13244. pi = Math.PI,
  13245. extend = fabric.util.object.extend;
  13246. if (fabric.Circle) {
  13247. fabric.warn('fabric.Circle is already defined.');
  13248. return;
  13249. }
  13250. /**
  13251. * Circle class
  13252. * @class fabric.Circle
  13253. * @extends fabric.Object
  13254. * @see {@link fabric.Circle#initialize} for constructor definition
  13255. */
  13256. fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ {
  13257. /**
  13258. * Type of an object
  13259. * @type String
  13260. * @default
  13261. */
  13262. type: 'circle',
  13263. /**
  13264. * Radius of this circle
  13265. * @type Number
  13266. * @default
  13267. */
  13268. radius: 0,
  13269. /**
  13270. * Start angle of the circle, moving clockwise
  13271. * @type Number
  13272. * @default 0
  13273. */
  13274. startAngle: 0,
  13275. /**
  13276. * End angle of the circle
  13277. * @type Number
  13278. * @default 2Pi
  13279. */
  13280. endAngle: pi * 2,
  13281. /**
  13282. * Constructor
  13283. * @param {Object} [options] Options object
  13284. * @return {fabric.Circle} thisArg
  13285. */
  13286. initialize: function(options) {
  13287. options = options || { };
  13288. this.callSuper('initialize', options);
  13289. this.set('radius', options.radius || 0);
  13290. this.startAngle = options.startAngle || this.startAngle;
  13291. this.endAngle = options.endAngle || this.endAngle;
  13292. },
  13293. /**
  13294. * @private
  13295. * @param {String} key
  13296. * @param {*} value
  13297. * @return {fabric.Circle} thisArg
  13298. */
  13299. _set: function(key, value) {
  13300. this.callSuper('_set', key, value);
  13301. if (key === 'radius') {
  13302. this.setRadius(value);
  13303. }
  13304. return this;
  13305. },
  13306. /**
  13307. * Returns object representation of an instance
  13308. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  13309. * @return {Object} object representation of an instance
  13310. */
  13311. toObject: function(propertiesToInclude) {
  13312. return this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(propertiesToInclude));
  13313. },
  13314. /* _TO_SVG_START_ */
  13315. /**
  13316. * Returns svg representation of an instance
  13317. * @param {Function} [reviver] Method for further parsing of svg representation.
  13318. * @return {String} svg representation of an instance
  13319. */
  13320. toSVG: function(reviver) {
  13321. var markup = this._createBaseSVGMarkup(), x = 0, y = 0,
  13322. angle = (this.endAngle - this.startAngle) % ( 2 * pi);
  13323. if (angle === 0) {
  13324. if (this.group && this.group.type === 'path-group') {
  13325. x = this.left + this.radius;
  13326. y = this.top + this.radius;
  13327. }
  13328. markup.push(
  13329. '<circle ', this.getSvgId(),
  13330. 'cx="' + x + '" cy="' + y + '" ',
  13331. 'r="', this.radius,
  13332. '" style="', this.getSvgStyles(),
  13333. '" transform="', this.getSvgTransform(),
  13334. ' ', this.getSvgTransformMatrix(),
  13335. '"/>\n'
  13336. );
  13337. }
  13338. else {
  13339. var startX = Math.cos(this.startAngle) * this.radius,
  13340. startY = Math.sin(this.startAngle) * this.radius,
  13341. endX = Math.cos(this.endAngle) * this.radius,
  13342. endY = Math.sin(this.endAngle) * this.radius,
  13343. largeFlag = angle > pi ? '1' : '0';
  13344. markup.push(
  13345. '<path d="M ' + startX + ' ' + startY,
  13346. ' A ' + this.radius + ' ' + this.radius,
  13347. ' 0 ', +largeFlag + ' 1', ' ' + endX + ' ' + endY,
  13348. '" style="', this.getSvgStyles(),
  13349. '" transform="', this.getSvgTransform(),
  13350. ' ', this.getSvgTransformMatrix(),
  13351. '"/>\n'
  13352. );
  13353. }
  13354. return reviver ? reviver(markup.join('')) : markup.join('');
  13355. },
  13356. /* _TO_SVG_END_ */
  13357. /**
  13358. * @private
  13359. * @param {CanvasRenderingContext2D} ctx context to render on
  13360. * @param {Boolean} [noTransform] When true, context is not transformed
  13361. */
  13362. _render: function(ctx, noTransform) {
  13363. ctx.beginPath();
  13364. ctx.arc(noTransform ? this.left + this.radius : 0,
  13365. noTransform ? this.top + this.radius : 0,
  13366. this.radius,
  13367. this.startAngle,
  13368. this.endAngle, false);
  13369. this._renderFill(ctx);
  13370. this._renderStroke(ctx);
  13371. },
  13372. /**
  13373. * Returns horizontal radius of an object (according to how an object is scaled)
  13374. * @return {Number}
  13375. */
  13376. getRadiusX: function() {
  13377. return this.get('radius') * this.get('scaleX');
  13378. },
  13379. /**
  13380. * Returns vertical radius of an object (according to how an object is scaled)
  13381. * @return {Number}
  13382. */
  13383. getRadiusY: function() {
  13384. return this.get('radius') * this.get('scaleY');
  13385. },
  13386. /**
  13387. * Sets radius of an object (and updates width accordingly)
  13388. * @return {fabric.Circle} thisArg
  13389. */
  13390. setRadius: function(value) {
  13391. this.radius = value;
  13392. return this.set('width', value * 2).set('height', value * 2);
  13393. },
  13394. /**
  13395. * Returns complexity of an instance
  13396. * @return {Number} complexity of this instance
  13397. */
  13398. complexity: function() {
  13399. return 1;
  13400. }
  13401. });
  13402. /* _FROM_SVG_START_ */
  13403. /**
  13404. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
  13405. * @static
  13406. * @memberOf fabric.Circle
  13407. * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
  13408. */
  13409. fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' '));
  13410. /**
  13411. * Returns {@link fabric.Circle} instance from an SVG element
  13412. * @static
  13413. * @memberOf fabric.Circle
  13414. * @param {SVGElement} element Element to parse
  13415. * @param {Object} [options] Options object
  13416. * @throws {Error} If value of `r` attribute is missing or invalid
  13417. * @return {fabric.Circle} Instance of fabric.Circle
  13418. */
  13419. fabric.Circle.fromElement = function(element, options) {
  13420. options || (options = { });
  13421. var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);
  13422. if (!isValidRadius(parsedAttributes)) {
  13423. throw new Error('value of `r` attribute is required and can not be negative');
  13424. }
  13425. parsedAttributes.left = parsedAttributes.left || 0;
  13426. parsedAttributes.top = parsedAttributes.top || 0;
  13427. var obj = new fabric.Circle(extend(parsedAttributes, options));
  13428. obj.left -= obj.radius;
  13429. obj.top -= obj.radius;
  13430. return obj;
  13431. };
  13432. /**
  13433. * @private
  13434. */
  13435. function isValidRadius(attributes) {
  13436. return (('radius' in attributes) && (attributes.radius >= 0));
  13437. }
  13438. /* _FROM_SVG_END_ */
  13439. /**
  13440. * Returns {@link fabric.Circle} instance from an object representation
  13441. * @static
  13442. * @memberOf fabric.Circle
  13443. * @param {Object} object Object to create an instance from
  13444. * @param {function} [callback] invoked with new instance as first argument
  13445. * @return {Object} Instance of fabric.Circle
  13446. */
  13447. fabric.Circle.fromObject = function(object, callback) {
  13448. var circle = new fabric.Circle(object);
  13449. callback && callback(circle);
  13450. return circle;
  13451. };
  13452. })(typeof exports !== 'undefined' ? exports : this);
  13453. (function(global) {
  13454. 'use strict';
  13455. var fabric = global.fabric || (global.fabric = { });
  13456. if (fabric.Triangle) {
  13457. fabric.warn('fabric.Triangle is already defined');
  13458. return;
  13459. }
  13460. /**
  13461. * Triangle class
  13462. * @class fabric.Triangle
  13463. * @extends fabric.Object
  13464. * @return {fabric.Triangle} thisArg
  13465. * @see {@link fabric.Triangle#initialize} for constructor definition
  13466. */
  13467. fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ {
  13468. /**
  13469. * Type of an object
  13470. * @type String
  13471. * @default
  13472. */
  13473. type: 'triangle',
  13474. /**
  13475. * Constructor
  13476. * @param {Object} [options] Options object
  13477. * @return {Object} thisArg
  13478. */
  13479. initialize: function(options) {
  13480. options = options || { };
  13481. this.callSuper('initialize', options);
  13482. this.set('width', options.width || 100)
  13483. .set('height', options.height || 100);
  13484. },
  13485. /**
  13486. * @private
  13487. * @param {CanvasRenderingContext2D} ctx Context to render on
  13488. */
  13489. _render: function(ctx) {
  13490. var widthBy2 = this.width / 2,
  13491. heightBy2 = this.height / 2;
  13492. ctx.beginPath();
  13493. ctx.moveTo(-widthBy2, heightBy2);
  13494. ctx.lineTo(0, -heightBy2);
  13495. ctx.lineTo(widthBy2, heightBy2);
  13496. ctx.closePath();
  13497. this._renderFill(ctx);
  13498. this._renderStroke(ctx);
  13499. },
  13500. /**
  13501. * @private
  13502. * @param {CanvasRenderingContext2D} ctx Context to render on
  13503. */
  13504. _renderDashedStroke: function(ctx) {
  13505. var widthBy2 = this.width / 2,
  13506. heightBy2 = this.height / 2;
  13507. ctx.beginPath();
  13508. fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray);
  13509. fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray);
  13510. fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray);
  13511. ctx.closePath();
  13512. },
  13513. /* _TO_SVG_START_ */
  13514. /**
  13515. * Returns SVG representation of an instance
  13516. * @param {Function} [reviver] Method for further parsing of svg representation.
  13517. * @return {String} svg representation of an instance
  13518. */
  13519. toSVG: function(reviver) {
  13520. var markup = this._createBaseSVGMarkup(),
  13521. widthBy2 = this.width / 2,
  13522. heightBy2 = this.height / 2,
  13523. points = [
  13524. -widthBy2 + ' ' + heightBy2,
  13525. '0 ' + -heightBy2,
  13526. widthBy2 + ' ' + heightBy2
  13527. ]
  13528. .join(',');
  13529. markup.push(
  13530. '<polygon ', this.getSvgId(),
  13531. 'points="', points,
  13532. '" style="', this.getSvgStyles(),
  13533. '" transform="', this.getSvgTransform(),
  13534. '"/>'
  13535. );
  13536. return reviver ? reviver(markup.join('')) : markup.join('');
  13537. },
  13538. /* _TO_SVG_END_ */
  13539. /**
  13540. * Returns complexity of an instance
  13541. * @return {Number} complexity of this instance
  13542. */
  13543. complexity: function() {
  13544. return 1;
  13545. }
  13546. });
  13547. /**
  13548. * Returns fabric.Triangle instance from an object representation
  13549. * @static
  13550. * @memberOf fabric.Triangle
  13551. * @param {Object} object Object to create an instance from
  13552. * @param {Function} [callback] Callback to invoke when an fabric.Triangle instance is created
  13553. * @return {Object} instance of Canvas.Triangle
  13554. */
  13555. fabric.Triangle.fromObject = function(object, callback) {
  13556. var triangle = new fabric.Triangle(object);
  13557. callback && callback(triangle);
  13558. return triangle;
  13559. };
  13560. })(typeof exports !== 'undefined' ? exports : this);
  13561. (function(global) {
  13562. 'use strict';
  13563. var fabric = global.fabric || (global.fabric = { }),
  13564. piBy2 = Math.PI * 2,
  13565. extend = fabric.util.object.extend;
  13566. if (fabric.Ellipse) {
  13567. fabric.warn('fabric.Ellipse is already defined.');
  13568. return;
  13569. }
  13570. /**
  13571. * Ellipse class
  13572. * @class fabric.Ellipse
  13573. * @extends fabric.Object
  13574. * @return {fabric.Ellipse} thisArg
  13575. * @see {@link fabric.Ellipse#initialize} for constructor definition
  13576. */
  13577. fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ {
  13578. /**
  13579. * Type of an object
  13580. * @type String
  13581. * @default
  13582. */
  13583. type: 'ellipse',
  13584. /**
  13585. * Horizontal radius
  13586. * @type Number
  13587. * @default
  13588. */
  13589. rx: 0,
  13590. /**
  13591. * Vertical radius
  13592. * @type Number
  13593. * @default
  13594. */
  13595. ry: 0,
  13596. /**
  13597. * Constructor
  13598. * @param {Object} [options] Options object
  13599. * @return {fabric.Ellipse} thisArg
  13600. */
  13601. initialize: function(options) {
  13602. options = options || { };
  13603. this.callSuper('initialize', options);
  13604. this.set('rx', options.rx || 0);
  13605. this.set('ry', options.ry || 0);
  13606. },
  13607. /**
  13608. * @private
  13609. * @param {String} key
  13610. * @param {*} value
  13611. * @return {fabric.Ellipse} thisArg
  13612. */
  13613. _set: function(key, value) {
  13614. this.callSuper('_set', key, value);
  13615. switch (key) {
  13616. case 'rx':
  13617. this.rx = value;
  13618. this.set('width', value * 2);
  13619. break;
  13620. case 'ry':
  13621. this.ry = value;
  13622. this.set('height', value * 2);
  13623. break;
  13624. }
  13625. return this;
  13626. },
  13627. /**
  13628. * Returns horizontal radius of an object (according to how an object is scaled)
  13629. * @return {Number}
  13630. */
  13631. getRx: function() {
  13632. return this.get('rx') * this.get('scaleX');
  13633. },
  13634. /**
  13635. * Returns Vertical radius of an object (according to how an object is scaled)
  13636. * @return {Number}
  13637. */
  13638. getRy: function() {
  13639. return this.get('ry') * this.get('scaleY');
  13640. },
  13641. /**
  13642. * Returns object representation of an instance
  13643. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  13644. * @return {Object} object representation of an instance
  13645. */
  13646. toObject: function(propertiesToInclude) {
  13647. return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude));
  13648. },
  13649. /* _TO_SVG_START_ */
  13650. /**
  13651. * Returns svg representation of an instance
  13652. * @param {Function} [reviver] Method for further parsing of svg representation.
  13653. * @return {String} svg representation of an instance
  13654. */
  13655. toSVG: function(reviver) {
  13656. var markup = this._createBaseSVGMarkup(), x = 0, y = 0;
  13657. if (this.group && this.group.type === 'path-group') {
  13658. x = this.left + this.rx;
  13659. y = this.top + this.ry;
  13660. }
  13661. markup.push(
  13662. '<ellipse ', this.getSvgId(),
  13663. 'cx="', x, '" cy="', y, '" ',
  13664. 'rx="', this.rx,
  13665. '" ry="', this.ry,
  13666. '" style="', this.getSvgStyles(),
  13667. '" transform="', this.getSvgTransform(),
  13668. this.getSvgTransformMatrix(),
  13669. '"/>\n'
  13670. );
  13671. return reviver ? reviver(markup.join('')) : markup.join('');
  13672. },
  13673. /* _TO_SVG_END_ */
  13674. /**
  13675. * @private
  13676. * @param {CanvasRenderingContext2D} ctx context to render on
  13677. * @param {Boolean} [noTransform] When true, context is not transformed
  13678. */
  13679. _render: function(ctx, noTransform) {
  13680. ctx.beginPath();
  13681. ctx.save();
  13682. ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0);
  13683. ctx.arc(
  13684. noTransform ? this.left + this.rx : 0,
  13685. noTransform ? (this.top + this.ry) * this.rx / this.ry : 0,
  13686. this.rx,
  13687. 0,
  13688. piBy2,
  13689. false);
  13690. ctx.restore();
  13691. this._renderFill(ctx);
  13692. this._renderStroke(ctx);
  13693. },
  13694. /**
  13695. * Returns complexity of an instance
  13696. * @return {Number} complexity
  13697. */
  13698. complexity: function() {
  13699. return 1;
  13700. }
  13701. });
  13702. /* _FROM_SVG_START_ */
  13703. /**
  13704. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement})
  13705. * @static
  13706. * @memberOf fabric.Ellipse
  13707. * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement
  13708. */
  13709. fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' '));
  13710. /**
  13711. * Returns {@link fabric.Ellipse} instance from an SVG element
  13712. * @static
  13713. * @memberOf fabric.Ellipse
  13714. * @param {SVGElement} element Element to parse
  13715. * @param {Object} [options] Options object
  13716. * @return {fabric.Ellipse}
  13717. */
  13718. fabric.Ellipse.fromElement = function(element, options) {
  13719. options || (options = { });
  13720. var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES);
  13721. parsedAttributes.left = parsedAttributes.left || 0;
  13722. parsedAttributes.top = parsedAttributes.top || 0;
  13723. var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));
  13724. ellipse.top -= ellipse.ry;
  13725. ellipse.left -= ellipse.rx;
  13726. return ellipse;
  13727. };
  13728. /* _FROM_SVG_END_ */
  13729. /**
  13730. * Returns {@link fabric.Ellipse} instance from an object representation
  13731. * @static
  13732. * @memberOf fabric.Ellipse
  13733. * @param {Object} object Object to create an instance from
  13734. * @param {function} [callback] invoked with new instance as first argument
  13735. * @return {fabric.Ellipse}
  13736. */
  13737. fabric.Ellipse.fromObject = function(object, callback) {
  13738. var ellipse = new fabric.Ellipse(object);
  13739. callback && callback(ellipse);
  13740. return ellipse;
  13741. };
  13742. })(typeof exports !== 'undefined' ? exports : this);
  13743. (function(global) {
  13744. 'use strict';
  13745. var fabric = global.fabric || (global.fabric = { }),
  13746. extend = fabric.util.object.extend;
  13747. if (fabric.Rect) {
  13748. fabric.warn('fabric.Rect is already defined');
  13749. return;
  13750. }
  13751. var stateProperties = fabric.Object.prototype.stateProperties.concat();
  13752. stateProperties.push('rx', 'ry', 'x', 'y');
  13753. /**
  13754. * Rectangle class
  13755. * @class fabric.Rect
  13756. * @extends fabric.Object
  13757. * @return {fabric.Rect} thisArg
  13758. * @see {@link fabric.Rect#initialize} for constructor definition
  13759. */
  13760. fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ {
  13761. /**
  13762. * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged})
  13763. * as well as for history (undo/redo) purposes
  13764. * @type Array
  13765. */
  13766. stateProperties: stateProperties,
  13767. /**
  13768. * Type of an object
  13769. * @type String
  13770. * @default
  13771. */
  13772. type: 'rect',
  13773. /**
  13774. * Horizontal border radius
  13775. * @type Number
  13776. * @default
  13777. */
  13778. rx: 0,
  13779. /**
  13780. * Vertical border radius
  13781. * @type Number
  13782. * @default
  13783. */
  13784. ry: 0,
  13785. /**
  13786. * Used to specify dash pattern for stroke on this object
  13787. * @type Array
  13788. */
  13789. strokeDashArray: null,
  13790. /**
  13791. * Constructor
  13792. * @param {Object} [options] Options object
  13793. * @return {Object} thisArg
  13794. */
  13795. initialize: function(options) {
  13796. options = options || { };
  13797. this.callSuper('initialize', options);
  13798. this._initRxRy();
  13799. },
  13800. /**
  13801. * Initializes rx/ry attributes
  13802. * @private
  13803. */
  13804. _initRxRy: function() {
  13805. if (this.rx && !this.ry) {
  13806. this.ry = this.rx;
  13807. }
  13808. else if (this.ry && !this.rx) {
  13809. this.rx = this.ry;
  13810. }
  13811. },
  13812. /**
  13813. * @private
  13814. * @param {CanvasRenderingContext2D} ctx Context to render on
  13815. * @param {Boolean} noTransform
  13816. */
  13817. _render: function(ctx, noTransform) {
  13818. // optimize 1x1 case (used in spray brush)
  13819. if (this.width === 1 && this.height === 1) {
  13820. ctx.fillRect(-0.5, -0.5, 1, 1);
  13821. return;
  13822. }
  13823. var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0,
  13824. ry = this.ry ? Math.min(this.ry, this.height / 2) : 0,
  13825. w = this.width,
  13826. h = this.height,
  13827. x = noTransform ? this.left : -this.width / 2,
  13828. y = noTransform ? this.top : -this.height / 2,
  13829. isRounded = rx !== 0 || ry !== 0,
  13830. /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */
  13831. k = 1 - 0.5522847498;
  13832. ctx.beginPath();
  13833. ctx.moveTo(x + rx, y);
  13834. ctx.lineTo(x + w - rx, y);
  13835. isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry);
  13836. ctx.lineTo(x + w, y + h - ry);
  13837. isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h);
  13838. ctx.lineTo(x + rx, y + h);
  13839. isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry);
  13840. ctx.lineTo(x, y + ry);
  13841. isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y);
  13842. ctx.closePath();
  13843. this._renderFill(ctx);
  13844. this._renderStroke(ctx);
  13845. },
  13846. /**
  13847. * @private
  13848. * @param {CanvasRenderingContext2D} ctx Context to render on
  13849. */
  13850. _renderDashedStroke: function(ctx) {
  13851. var x = -this.width / 2,
  13852. y = -this.height / 2,
  13853. w = this.width,
  13854. h = this.height;
  13855. ctx.beginPath();
  13856. fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
  13857. fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
  13858. fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
  13859. fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
  13860. ctx.closePath();
  13861. },
  13862. /**
  13863. * Returns object representation of an instance
  13864. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  13865. * @return {Object} object representation of an instance
  13866. */
  13867. toObject: function(propertiesToInclude) {
  13868. return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude));
  13869. },
  13870. /* _TO_SVG_START_ */
  13871. /**
  13872. * Returns svg representation of an instance
  13873. * @param {Function} [reviver] Method for further parsing of svg representation.
  13874. * @return {String} svg representation of an instance
  13875. */
  13876. toSVG: function(reviver) {
  13877. var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top;
  13878. if (!(this.group && this.group.type === 'path-group')) {
  13879. x = -this.width / 2;
  13880. y = -this.height / 2;
  13881. }
  13882. markup.push(
  13883. '<rect ', this.getSvgId(),
  13884. 'x="', x, '" y="', y,
  13885. '" rx="', this.get('rx'), '" ry="', this.get('ry'),
  13886. '" width="', this.width, '" height="', this.height,
  13887. '" style="', this.getSvgStyles(),
  13888. '" transform="', this.getSvgTransform(),
  13889. this.getSvgTransformMatrix(),
  13890. '"/>\n');
  13891. return reviver ? reviver(markup.join('')) : markup.join('');
  13892. },
  13893. /* _TO_SVG_END_ */
  13894. /**
  13895. * Returns complexity of an instance
  13896. * @return {Number} complexity
  13897. */
  13898. complexity: function() {
  13899. return 1;
  13900. }
  13901. });
  13902. /* _FROM_SVG_START_ */
  13903. /**
  13904. * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`)
  13905. * @static
  13906. * @memberOf fabric.Rect
  13907. * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement
  13908. */
  13909. fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' '));
  13910. /**
  13911. * Returns {@link fabric.Rect} instance from an SVG element
  13912. * @static
  13913. * @memberOf fabric.Rect
  13914. * @param {SVGElement} element Element to parse
  13915. * @param {Object} [options] Options object
  13916. * @return {fabric.Rect} Instance of fabric.Rect
  13917. */
  13918. fabric.Rect.fromElement = function(element, options) {
  13919. if (!element) {
  13920. return null;
  13921. }
  13922. options = options || { };
  13923. var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);
  13924. parsedAttributes.left = parsedAttributes.left || 0;
  13925. parsedAttributes.top = parsedAttributes.top || 0;
  13926. var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
  13927. rect.visible = rect.visible && rect.width > 0 && rect.height > 0;
  13928. return rect;
  13929. };
  13930. /* _FROM_SVG_END_ */
  13931. /**
  13932. * Returns {@link fabric.Rect} instance from an object representation
  13933. * @static
  13934. * @memberOf fabric.Rect
  13935. * @param {Object} object Object to create an instance from
  13936. * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created
  13937. * @return {Object} instance of fabric.Rect
  13938. */
  13939. fabric.Rect.fromObject = function(object, callback) {
  13940. var rect = new fabric.Rect(object);
  13941. callback && callback(rect);
  13942. return rect;
  13943. };
  13944. })(typeof exports !== 'undefined' ? exports : this);
  13945. (function(global) {
  13946. 'use strict';
  13947. var fabric = global.fabric || (global.fabric = { });
  13948. if (fabric.Polyline) {
  13949. fabric.warn('fabric.Polyline is already defined');
  13950. return;
  13951. }
  13952. /**
  13953. * Polyline class
  13954. * @class fabric.Polyline
  13955. * @extends fabric.Object
  13956. * @see {@link fabric.Polyline#initialize} for constructor definition
  13957. */
  13958. fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ {
  13959. /**
  13960. * Type of an object
  13961. * @type String
  13962. * @default
  13963. */
  13964. type: 'polyline',
  13965. /**
  13966. * Points array
  13967. * @type Array
  13968. * @default
  13969. */
  13970. points: null,
  13971. /**
  13972. * Minimum X from points values, necessary to offset points
  13973. * @type Number
  13974. * @default
  13975. */
  13976. minX: 0,
  13977. /**
  13978. * Minimum Y from points values, necessary to offset points
  13979. * @type Number
  13980. * @default
  13981. */
  13982. minY: 0,
  13983. /**
  13984. * Constructor
  13985. * @param {Array} points Array of points (where each point is an object with x and y)
  13986. * @param {Object} [options] Options object
  13987. * @return {fabric.Polyline} thisArg
  13988. * @example
  13989. * var poly = new fabric.Polyline([
  13990. * { x: 10, y: 10 },
  13991. * { x: 50, y: 30 },
  13992. * { x: 40, y: 70 },
  13993. * { x: 60, y: 50 },
  13994. * { x: 100, y: 150 },
  13995. * { x: 40, y: 100 }
  13996. * ], {
  13997. * stroke: 'red',
  13998. * left: 100,
  13999. * top: 100
  14000. * });
  14001. */
  14002. initialize: function(points, options) {
  14003. return fabric.Polygon.prototype.initialize.call(this, points, options);
  14004. },
  14005. /**
  14006. * @private
  14007. */
  14008. _calcDimensions: function() {
  14009. return fabric.Polygon.prototype._calcDimensions.call(this);
  14010. },
  14011. /**
  14012. * Returns object representation of an instance
  14013. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14014. * @return {Object} Object representation of an instance
  14015. */
  14016. toObject: function(propertiesToInclude) {
  14017. return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude);
  14018. },
  14019. /* _TO_SVG_START_ */
  14020. /**
  14021. * Returns SVG representation of an instance
  14022. * @param {Function} [reviver] Method for further parsing of svg representation.
  14023. * @return {String} svg representation of an instance
  14024. */
  14025. toSVG: function(reviver) {
  14026. return fabric.Polygon.prototype.toSVG.call(this, reviver);
  14027. },
  14028. /* _TO_SVG_END_ */
  14029. /**
  14030. * @private
  14031. * @param {CanvasRenderingContext2D} ctx Context to render on
  14032. * @param {Boolean} noTransform
  14033. */
  14034. _render: function(ctx, noTransform) {
  14035. if (!fabric.Polygon.prototype.commonRender.call(this, ctx, noTransform)) {
  14036. return;
  14037. }
  14038. this._renderFill(ctx);
  14039. this._renderStroke(ctx);
  14040. },
  14041. /**
  14042. * @private
  14043. * @param {CanvasRenderingContext2D} ctx Context to render on
  14044. */
  14045. _renderDashedStroke: function(ctx) {
  14046. var p1, p2;
  14047. ctx.beginPath();
  14048. for (var i = 0, len = this.points.length; i < len; i++) {
  14049. p1 = this.points[i];
  14050. p2 = this.points[i + 1] || p1;
  14051. fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray);
  14052. }
  14053. },
  14054. /**
  14055. * Returns complexity of an instance
  14056. * @return {Number} complexity of this instance
  14057. */
  14058. complexity: function() {
  14059. return this.get('points').length;
  14060. }
  14061. });
  14062. /* _FROM_SVG_START_ */
  14063. /**
  14064. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement})
  14065. * @static
  14066. * @memberOf fabric.Polyline
  14067. * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement
  14068. */
  14069. fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
  14070. /**
  14071. * Returns fabric.Polyline instance from an SVG element
  14072. * @static
  14073. * @memberOf fabric.Polyline
  14074. * @param {SVGElement} element Element to parse
  14075. * @param {Object} [options] Options object
  14076. * @return {fabric.Polyline} Instance of fabric.Polyline
  14077. */
  14078. fabric.Polyline.fromElement = function(element, options) {
  14079. if (!element) {
  14080. return null;
  14081. }
  14082. options || (options = { });
  14083. var points = fabric.parsePointsAttribute(element.getAttribute('points')),
  14084. parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES);
  14085. return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
  14086. };
  14087. /* _FROM_SVG_END_ */
  14088. /**
  14089. * Returns fabric.Polyline instance from an object representation
  14090. * @static
  14091. * @memberOf fabric.Polyline
  14092. * @param {Object} object Object to create an instance from
  14093. * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
  14094. * @return {fabric.Polyline} Instance of fabric.Polyline
  14095. */
  14096. fabric.Polyline.fromObject = function(object, callback) {
  14097. var polyline = new fabric.Polyline(object.points, object);
  14098. callback && callback(polyline);
  14099. return polyline;
  14100. };
  14101. })(typeof exports !== 'undefined' ? exports : this);
  14102. (function(global) {
  14103. 'use strict';
  14104. var fabric = global.fabric || (global.fabric = { }),
  14105. extend = fabric.util.object.extend,
  14106. min = fabric.util.array.min,
  14107. max = fabric.util.array.max,
  14108. toFixed = fabric.util.toFixed;
  14109. if (fabric.Polygon) {
  14110. fabric.warn('fabric.Polygon is already defined');
  14111. return;
  14112. }
  14113. /**
  14114. * Polygon class
  14115. * @class fabric.Polygon
  14116. * @extends fabric.Object
  14117. * @see {@link fabric.Polygon#initialize} for constructor definition
  14118. */
  14119. fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ {
  14120. /**
  14121. * Type of an object
  14122. * @type String
  14123. * @default
  14124. */
  14125. type: 'polygon',
  14126. /**
  14127. * Points array
  14128. * @type Array
  14129. * @default
  14130. */
  14131. points: null,
  14132. /**
  14133. * Minimum X from points values, necessary to offset points
  14134. * @type Number
  14135. * @default
  14136. */
  14137. minX: 0,
  14138. /**
  14139. * Minimum Y from points values, necessary to offset points
  14140. * @type Number
  14141. * @default
  14142. */
  14143. minY: 0,
  14144. /**
  14145. * Constructor
  14146. * @param {Array} points Array of points
  14147. * @param {Object} [options] Options object
  14148. * @return {fabric.Polygon} thisArg
  14149. */
  14150. initialize: function(points, options) {
  14151. options = options || { };
  14152. this.points = points || [];
  14153. this.callSuper('initialize', options);
  14154. this._calcDimensions();
  14155. if (!('top' in options)) {
  14156. this.top = this.minY;
  14157. }
  14158. if (!('left' in options)) {
  14159. this.left = this.minX;
  14160. }
  14161. this.pathOffset = {
  14162. x: this.minX + this.width / 2,
  14163. y: this.minY + this.height / 2
  14164. };
  14165. },
  14166. /**
  14167. * @private
  14168. */
  14169. _calcDimensions: function() {
  14170. var points = this.points,
  14171. minX = min(points, 'x'),
  14172. minY = min(points, 'y'),
  14173. maxX = max(points, 'x'),
  14174. maxY = max(points, 'y');
  14175. this.width = (maxX - minX) || 0;
  14176. this.height = (maxY - minY) || 0;
  14177. this.minX = minX || 0;
  14178. this.minY = minY || 0;
  14179. },
  14180. /**
  14181. * Returns object representation of an instance
  14182. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14183. * @return {Object} Object representation of an instance
  14184. */
  14185. toObject: function(propertiesToInclude) {
  14186. return extend(this.callSuper('toObject', propertiesToInclude), {
  14187. points: this.points.concat()
  14188. });
  14189. },
  14190. /* _TO_SVG_START_ */
  14191. /**
  14192. * Returns svg representation of an instance
  14193. * @param {Function} [reviver] Method for further parsing of svg representation.
  14194. * @return {String} svg representation of an instance
  14195. */
  14196. toSVG: function(reviver) {
  14197. var points = [], addTransform,
  14198. markup = this._createBaseSVGMarkup();
  14199. for (var i = 0, len = this.points.length; i < len; i++) {
  14200. points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
  14201. }
  14202. if (!(this.group && this.group.type === 'path-group')) {
  14203. addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
  14204. }
  14205. markup.push(
  14206. '<', this.type, ' ', this.getSvgId(),
  14207. 'points="', points.join(''),
  14208. '" style="', this.getSvgStyles(),
  14209. '" transform="', this.getSvgTransform(), addTransform,
  14210. ' ', this.getSvgTransformMatrix(),
  14211. '"/>\n'
  14212. );
  14213. return reviver ? reviver(markup.join('')) : markup.join('');
  14214. },
  14215. /* _TO_SVG_END_ */
  14216. /**
  14217. * @private
  14218. * @param {CanvasRenderingContext2D} ctx Context to render on
  14219. * @param {Boolean} noTransform
  14220. */
  14221. _render: function(ctx, noTransform) {
  14222. if (!this.commonRender(ctx, noTransform)) {
  14223. return;
  14224. }
  14225. this._renderFill(ctx);
  14226. if (this.stroke || this.strokeDashArray) {
  14227. ctx.closePath();
  14228. this._renderStroke(ctx);
  14229. }
  14230. },
  14231. /**
  14232. * @private
  14233. * @param {CanvasRenderingContext2D} ctx Context to render on
  14234. * @param {Boolean} noTransform
  14235. */
  14236. commonRender: function(ctx, noTransform) {
  14237. var point, len = this.points.length;
  14238. if (!len || isNaN(this.points[len - 1].y)) {
  14239. // do not draw if no points or odd points
  14240. // NaN comes from parseFloat of a empty string in parser
  14241. return false;
  14242. }
  14243. noTransform || ctx.translate(-this.pathOffset.x, -this.pathOffset.y);
  14244. ctx.beginPath();
  14245. ctx.moveTo(this.points[0].x, this.points[0].y);
  14246. for (var i = 0; i < len; i++) {
  14247. point = this.points[i];
  14248. ctx.lineTo(point.x, point.y);
  14249. }
  14250. return true;
  14251. },
  14252. /**
  14253. * @private
  14254. * @param {CanvasRenderingContext2D} ctx Context to render on
  14255. */
  14256. _renderDashedStroke: function(ctx) {
  14257. fabric.Polyline.prototype._renderDashedStroke.call(this, ctx);
  14258. ctx.closePath();
  14259. },
  14260. /**
  14261. * Returns complexity of an instance
  14262. * @return {Number} complexity of this instance
  14263. */
  14264. complexity: function() {
  14265. return this.points.length;
  14266. }
  14267. });
  14268. /* _FROM_SVG_START_ */
  14269. /**
  14270. * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`)
  14271. * @static
  14272. * @memberOf fabric.Polygon
  14273. * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement
  14274. */
  14275. fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat();
  14276. /**
  14277. * Returns {@link fabric.Polygon} instance from an SVG element
  14278. * @static
  14279. * @memberOf fabric.Polygon
  14280. * @param {SVGElement} element Element to parse
  14281. * @param {Object} [options] Options object
  14282. * @return {fabric.Polygon} Instance of fabric.Polygon
  14283. */
  14284. fabric.Polygon.fromElement = function(element, options) {
  14285. if (!element) {
  14286. return null;
  14287. }
  14288. options || (options = { });
  14289. var points = fabric.parsePointsAttribute(element.getAttribute('points')),
  14290. parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES);
  14291. return new fabric.Polygon(points, extend(parsedAttributes, options));
  14292. };
  14293. /* _FROM_SVG_END_ */
  14294. /**
  14295. * Returns fabric.Polygon instance from an object representation
  14296. * @static
  14297. * @memberOf fabric.Polygon
  14298. * @param {Object} object Object to create an instance from
  14299. * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
  14300. * @return {fabric.Polygon} Instance of fabric.Polygon
  14301. */
  14302. fabric.Polygon.fromObject = function(object, callback) {
  14303. var polygon = new fabric.Polygon(object.points, object);
  14304. callback && callback(polygon);
  14305. return polygon;
  14306. };
  14307. })(typeof exports !== 'undefined' ? exports : this);
  14308. (function(global) {
  14309. 'use strict';
  14310. var fabric = global.fabric || (global.fabric = { }),
  14311. min = fabric.util.array.min,
  14312. max = fabric.util.array.max,
  14313. extend = fabric.util.object.extend,
  14314. _toString = Object.prototype.toString,
  14315. drawArc = fabric.util.drawArc,
  14316. commandLengths = {
  14317. m: 2,
  14318. l: 2,
  14319. h: 1,
  14320. v: 1,
  14321. c: 6,
  14322. s: 4,
  14323. q: 4,
  14324. t: 2,
  14325. a: 7
  14326. },
  14327. repeatedCommands = {
  14328. m: 'l',
  14329. M: 'L'
  14330. };
  14331. if (fabric.Path) {
  14332. fabric.warn('fabric.Path is already defined');
  14333. return;
  14334. }
  14335. /**
  14336. * Path class
  14337. * @class fabric.Path
  14338. * @extends fabric.Object
  14339. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
  14340. * @see {@link fabric.Path#initialize} for constructor definition
  14341. */
  14342. fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ {
  14343. /**
  14344. * Type of an object
  14345. * @type String
  14346. * @default
  14347. */
  14348. type: 'path',
  14349. /**
  14350. * Array of path points
  14351. * @type Array
  14352. * @default
  14353. */
  14354. path: null,
  14355. /**
  14356. * Minimum X from points values, necessary to offset points
  14357. * @type Number
  14358. * @default
  14359. */
  14360. minX: 0,
  14361. /**
  14362. * Minimum Y from points values, necessary to offset points
  14363. * @type Number
  14364. * @default
  14365. */
  14366. minY: 0,
  14367. /**
  14368. * Constructor
  14369. * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
  14370. * @param {Object} [options] Options object
  14371. * @return {fabric.Path} thisArg
  14372. */
  14373. initialize: function(path, options) {
  14374. options = options || { };
  14375. this.setOptions(options);
  14376. if (!path) {
  14377. path = [];
  14378. }
  14379. var fromArray = _toString.call(path) === '[object Array]';
  14380. this.path = fromArray
  14381. ? path
  14382. // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values)
  14383. : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);
  14384. if (!this.path) {
  14385. return;
  14386. }
  14387. if (!fromArray) {
  14388. this.path = this._parsePath();
  14389. }
  14390. this._setPositionDimensions(options);
  14391. if (options.sourcePath) {
  14392. this.setSourcePath(options.sourcePath);
  14393. }
  14394. },
  14395. /**
  14396. * @private
  14397. * @param {Object} options Options object
  14398. */
  14399. _setPositionDimensions: function(options) {
  14400. var calcDim = this._parseDimensions();
  14401. this.minX = calcDim.left;
  14402. this.minY = calcDim.top;
  14403. this.width = calcDim.width;
  14404. this.height = calcDim.height;
  14405. if (typeof options.left === 'undefined') {
  14406. this.left = calcDim.left + (this.originX === 'center'
  14407. ? this.width / 2
  14408. : this.originX === 'right'
  14409. ? this.width
  14410. : 0);
  14411. }
  14412. if (typeof options.top === 'undefined') {
  14413. this.top = calcDim.top + (this.originY === 'center'
  14414. ? this.height / 2
  14415. : this.originY === 'bottom'
  14416. ? this.height
  14417. : 0);
  14418. }
  14419. this.pathOffset = this.pathOffset || {
  14420. x: this.minX + this.width / 2,
  14421. y: this.minY + this.height / 2
  14422. };
  14423. },
  14424. /**
  14425. * @private
  14426. * @param {CanvasRenderingContext2D} ctx context to render path on
  14427. */
  14428. _renderPathCommands: function(ctx) {
  14429. var current, // current instruction
  14430. previous = null,
  14431. subpathStartX = 0,
  14432. subpathStartY = 0,
  14433. x = 0, // current x
  14434. y = 0, // current y
  14435. controlX = 0, // current control point x
  14436. controlY = 0, // current control point y
  14437. tempX,
  14438. tempY,
  14439. l = -this.pathOffset.x,
  14440. t = -this.pathOffset.y;
  14441. if (this.group && this.group.type === 'path-group') {
  14442. l = 0;
  14443. t = 0;
  14444. }
  14445. ctx.beginPath();
  14446. for (var i = 0, len = this.path.length; i < len; ++i) {
  14447. current = this.path[i];
  14448. switch (current[0]) { // first letter
  14449. case 'l': // lineto, relative
  14450. x += current[1];
  14451. y += current[2];
  14452. ctx.lineTo(x + l, y + t);
  14453. break;
  14454. case 'L': // lineto, absolute
  14455. x = current[1];
  14456. y = current[2];
  14457. ctx.lineTo(x + l, y + t);
  14458. break;
  14459. case 'h': // horizontal lineto, relative
  14460. x += current[1];
  14461. ctx.lineTo(x + l, y + t);
  14462. break;
  14463. case 'H': // horizontal lineto, absolute
  14464. x = current[1];
  14465. ctx.lineTo(x + l, y + t);
  14466. break;
  14467. case 'v': // vertical lineto, relative
  14468. y += current[1];
  14469. ctx.lineTo(x + l, y + t);
  14470. break;
  14471. case 'V': // verical lineto, absolute
  14472. y = current[1];
  14473. ctx.lineTo(x + l, y + t);
  14474. break;
  14475. case 'm': // moveTo, relative
  14476. x += current[1];
  14477. y += current[2];
  14478. subpathStartX = x;
  14479. subpathStartY = y;
  14480. ctx.moveTo(x + l, y + t);
  14481. break;
  14482. case 'M': // moveTo, absolute
  14483. x = current[1];
  14484. y = current[2];
  14485. subpathStartX = x;
  14486. subpathStartY = y;
  14487. ctx.moveTo(x + l, y + t);
  14488. break;
  14489. case 'c': // bezierCurveTo, relative
  14490. tempX = x + current[5];
  14491. tempY = y + current[6];
  14492. controlX = x + current[3];
  14493. controlY = y + current[4];
  14494. ctx.bezierCurveTo(
  14495. x + current[1] + l, // x1
  14496. y + current[2] + t, // y1
  14497. controlX + l, // x2
  14498. controlY + t, // y2
  14499. tempX + l,
  14500. tempY + t
  14501. );
  14502. x = tempX;
  14503. y = tempY;
  14504. break;
  14505. case 'C': // bezierCurveTo, absolute
  14506. x = current[5];
  14507. y = current[6];
  14508. controlX = current[3];
  14509. controlY = current[4];
  14510. ctx.bezierCurveTo(
  14511. current[1] + l,
  14512. current[2] + t,
  14513. controlX + l,
  14514. controlY + t,
  14515. x + l,
  14516. y + t
  14517. );
  14518. break;
  14519. case 's': // shorthand cubic bezierCurveTo, relative
  14520. // transform to absolute x,y
  14521. tempX = x + current[3];
  14522. tempY = y + current[4];
  14523. if (previous[0].match(/[CcSs]/) === null) {
  14524. // If there is no previous command or if the previous command was not a C, c, S, or s,
  14525. // the control point is coincident with the current point
  14526. controlX = x;
  14527. controlY = y;
  14528. }
  14529. else {
  14530. // calculate reflection of previous control points
  14531. controlX = 2 * x - controlX;
  14532. controlY = 2 * y - controlY;
  14533. }
  14534. ctx.bezierCurveTo(
  14535. controlX + l,
  14536. controlY + t,
  14537. x + current[1] + l,
  14538. y + current[2] + t,
  14539. tempX + l,
  14540. tempY + t
  14541. );
  14542. // set control point to 2nd one of this command
  14543. // "... the first control point is assumed to be
  14544. // the reflection of the second control point on
  14545. // the previous command relative to the current point."
  14546. controlX = x + current[1];
  14547. controlY = y + current[2];
  14548. x = tempX;
  14549. y = tempY;
  14550. break;
  14551. case 'S': // shorthand cubic bezierCurveTo, absolute
  14552. tempX = current[3];
  14553. tempY = current[4];
  14554. if (previous[0].match(/[CcSs]/) === null) {
  14555. // If there is no previous command or if the previous command was not a C, c, S, or s,
  14556. // the control point is coincident with the current point
  14557. controlX = x;
  14558. controlY = y;
  14559. }
  14560. else {
  14561. // calculate reflection of previous control points
  14562. controlX = 2 * x - controlX;
  14563. controlY = 2 * y - controlY;
  14564. }
  14565. ctx.bezierCurveTo(
  14566. controlX + l,
  14567. controlY + t,
  14568. current[1] + l,
  14569. current[2] + t,
  14570. tempX + l,
  14571. tempY + t
  14572. );
  14573. x = tempX;
  14574. y = tempY;
  14575. // set control point to 2nd one of this command
  14576. // "... the first control point is assumed to be
  14577. // the reflection of the second control point on
  14578. // the previous command relative to the current point."
  14579. controlX = current[1];
  14580. controlY = current[2];
  14581. break;
  14582. case 'q': // quadraticCurveTo, relative
  14583. // transform to absolute x,y
  14584. tempX = x + current[3];
  14585. tempY = y + current[4];
  14586. controlX = x + current[1];
  14587. controlY = y + current[2];
  14588. ctx.quadraticCurveTo(
  14589. controlX + l,
  14590. controlY + t,
  14591. tempX + l,
  14592. tempY + t
  14593. );
  14594. x = tempX;
  14595. y = tempY;
  14596. break;
  14597. case 'Q': // quadraticCurveTo, absolute
  14598. tempX = current[3];
  14599. tempY = current[4];
  14600. ctx.quadraticCurveTo(
  14601. current[1] + l,
  14602. current[2] + t,
  14603. tempX + l,
  14604. tempY + t
  14605. );
  14606. x = tempX;
  14607. y = tempY;
  14608. controlX = current[1];
  14609. controlY = current[2];
  14610. break;
  14611. case 't': // shorthand quadraticCurveTo, relative
  14612. // transform to absolute x,y
  14613. tempX = x + current[1];
  14614. tempY = y + current[2];
  14615. if (previous[0].match(/[QqTt]/) === null) {
  14616. // If there is no previous command or if the previous command was not a Q, q, T or t,
  14617. // assume the control point is coincident with the current point
  14618. controlX = x;
  14619. controlY = y;
  14620. }
  14621. else {
  14622. // calculate reflection of previous control point
  14623. controlX = 2 * x - controlX;
  14624. controlY = 2 * y - controlY;
  14625. }
  14626. ctx.quadraticCurveTo(
  14627. controlX + l,
  14628. controlY + t,
  14629. tempX + l,
  14630. tempY + t
  14631. );
  14632. x = tempX;
  14633. y = tempY;
  14634. break;
  14635. case 'T':
  14636. tempX = current[1];
  14637. tempY = current[2];
  14638. if (previous[0].match(/[QqTt]/) === null) {
  14639. // If there is no previous command or if the previous command was not a Q, q, T or t,
  14640. // assume the control point is coincident with the current point
  14641. controlX = x;
  14642. controlY = y;
  14643. }
  14644. else {
  14645. // calculate reflection of previous control point
  14646. controlX = 2 * x - controlX;
  14647. controlY = 2 * y - controlY;
  14648. }
  14649. ctx.quadraticCurveTo(
  14650. controlX + l,
  14651. controlY + t,
  14652. tempX + l,
  14653. tempY + t
  14654. );
  14655. x = tempX;
  14656. y = tempY;
  14657. break;
  14658. case 'a':
  14659. // TODO: optimize this
  14660. drawArc(ctx, x + l, y + t, [
  14661. current[1],
  14662. current[2],
  14663. current[3],
  14664. current[4],
  14665. current[5],
  14666. current[6] + x + l,
  14667. current[7] + y + t
  14668. ]);
  14669. x += current[6];
  14670. y += current[7];
  14671. break;
  14672. case 'A':
  14673. // TODO: optimize this
  14674. drawArc(ctx, x + l, y + t, [
  14675. current[1],
  14676. current[2],
  14677. current[3],
  14678. current[4],
  14679. current[5],
  14680. current[6] + l,
  14681. current[7] + t
  14682. ]);
  14683. x = current[6];
  14684. y = current[7];
  14685. break;
  14686. case 'z':
  14687. case 'Z':
  14688. x = subpathStartX;
  14689. y = subpathStartY;
  14690. ctx.closePath();
  14691. break;
  14692. }
  14693. previous = current;
  14694. }
  14695. },
  14696. /**
  14697. * @private
  14698. * @param {CanvasRenderingContext2D} ctx context to render path on
  14699. */
  14700. _render: function(ctx) {
  14701. this._renderPathCommands(ctx);
  14702. this._renderFill(ctx);
  14703. this._renderStroke(ctx);
  14704. },
  14705. /**
  14706. * Returns string representation of an instance
  14707. * @return {String} string representation of an instance
  14708. */
  14709. toString: function() {
  14710. return '#<fabric.Path (' + this.complexity() +
  14711. '): { "top": ' + this.top + ', "left": ' + this.left + ' }>';
  14712. },
  14713. /**
  14714. * Returns object representation of an instance
  14715. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14716. * @return {Object} object representation of an instance
  14717. */
  14718. toObject: function(propertiesToInclude) {
  14719. var o = extend(this.callSuper('toObject', ['sourcePath', 'pathOffset'].concat(propertiesToInclude)), {
  14720. path: this.path.map(function(item) { return item.slice() })
  14721. });
  14722. return o;
  14723. },
  14724. /**
  14725. * Returns dataless object representation of an instance
  14726. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  14727. * @return {Object} object representation of an instance
  14728. */
  14729. toDatalessObject: function(propertiesToInclude) {
  14730. var o = this.toObject(propertiesToInclude);
  14731. if (this.sourcePath) {
  14732. o.path = this.sourcePath;
  14733. }
  14734. delete o.sourcePath;
  14735. return o;
  14736. },
  14737. /* _TO_SVG_START_ */
  14738. /**
  14739. * Returns svg representation of an instance
  14740. * @param {Function} [reviver] Method for further parsing of svg representation.
  14741. * @return {String} svg representation of an instance
  14742. */
  14743. toSVG: function(reviver) {
  14744. var chunks = [],
  14745. markup = this._createBaseSVGMarkup(), addTransform = '';
  14746. for (var i = 0, len = this.path.length; i < len; i++) {
  14747. chunks.push(this.path[i].join(' '));
  14748. }
  14749. var path = chunks.join(' ');
  14750. if (!(this.group && this.group.type === 'path-group')) {
  14751. addTransform = ' translate(' + (-this.pathOffset.x) + ', ' + (-this.pathOffset.y) + ') ';
  14752. }
  14753. markup.push(
  14754. '<path ', this.getSvgId(),
  14755. 'd="', path,
  14756. '" style="', this.getSvgStyles(),
  14757. '" transform="', this.getSvgTransform(), addTransform,
  14758. this.getSvgTransformMatrix(), '" stroke-linecap="round" ',
  14759. '/>\n'
  14760. );
  14761. return reviver ? reviver(markup.join('')) : markup.join('');
  14762. },
  14763. /* _TO_SVG_END_ */
  14764. /**
  14765. * Returns number representation of an instance complexity
  14766. * @return {Number} complexity of this instance
  14767. */
  14768. complexity: function() {
  14769. return this.path.length;
  14770. },
  14771. /**
  14772. * @private
  14773. */
  14774. _parsePath: function() {
  14775. var result = [],
  14776. coords = [],
  14777. currentPath,
  14778. parsed,
  14779. re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig,
  14780. match,
  14781. coordsStr;
  14782. for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) {
  14783. currentPath = this.path[i];
  14784. coordsStr = currentPath.slice(1).trim();
  14785. coords.length = 0;
  14786. while ((match = re.exec(coordsStr))) {
  14787. coords.push(match[0]);
  14788. }
  14789. coordsParsed = [currentPath.charAt(0)];
  14790. for (var j = 0, jlen = coords.length; j < jlen; j++) {
  14791. parsed = parseFloat(coords[j]);
  14792. if (!isNaN(parsed)) {
  14793. coordsParsed.push(parsed);
  14794. }
  14795. }
  14796. var command = coordsParsed[0],
  14797. commandLength = commandLengths[command.toLowerCase()],
  14798. repeatedCommand = repeatedCommands[command] || command;
  14799. if (coordsParsed.length - 1 > commandLength) {
  14800. for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) {
  14801. result.push([command].concat(coordsParsed.slice(k, k + commandLength)));
  14802. command = repeatedCommand;
  14803. }
  14804. }
  14805. else {
  14806. result.push(coordsParsed);
  14807. }
  14808. }
  14809. return result;
  14810. },
  14811. /**
  14812. * @private
  14813. */
  14814. _parseDimensions: function() {
  14815. var aX = [],
  14816. aY = [],
  14817. current, // current instruction
  14818. previous = null,
  14819. subpathStartX = 0,
  14820. subpathStartY = 0,
  14821. x = 0, // current x
  14822. y = 0, // current y
  14823. controlX = 0, // current control point x
  14824. controlY = 0, // current control point y
  14825. tempX,
  14826. tempY,
  14827. bounds;
  14828. for (var i = 0, len = this.path.length; i < len; ++i) {
  14829. current = this.path[i];
  14830. switch (current[0]) { // first letter
  14831. case 'l': // lineto, relative
  14832. x += current[1];
  14833. y += current[2];
  14834. bounds = [];
  14835. break;
  14836. case 'L': // lineto, absolute
  14837. x = current[1];
  14838. y = current[2];
  14839. bounds = [];
  14840. break;
  14841. case 'h': // horizontal lineto, relative
  14842. x += current[1];
  14843. bounds = [];
  14844. break;
  14845. case 'H': // horizontal lineto, absolute
  14846. x = current[1];
  14847. bounds = [];
  14848. break;
  14849. case 'v': // vertical lineto, relative
  14850. y += current[1];
  14851. bounds = [];
  14852. break;
  14853. case 'V': // verical lineto, absolute
  14854. y = current[1];
  14855. bounds = [];
  14856. break;
  14857. case 'm': // moveTo, relative
  14858. x += current[1];
  14859. y += current[2];
  14860. subpathStartX = x;
  14861. subpathStartY = y;
  14862. bounds = [];
  14863. break;
  14864. case 'M': // moveTo, absolute
  14865. x = current[1];
  14866. y = current[2];
  14867. subpathStartX = x;
  14868. subpathStartY = y;
  14869. bounds = [];
  14870. break;
  14871. case 'c': // bezierCurveTo, relative
  14872. tempX = x + current[5];
  14873. tempY = y + current[6];
  14874. controlX = x + current[3];
  14875. controlY = y + current[4];
  14876. bounds = fabric.util.getBoundsOfCurve(x, y,
  14877. x + current[1], // x1
  14878. y + current[2], // y1
  14879. controlX, // x2
  14880. controlY, // y2
  14881. tempX,
  14882. tempY
  14883. );
  14884. x = tempX;
  14885. y = tempY;
  14886. break;
  14887. case 'C': // bezierCurveTo, absolute
  14888. x = current[5];
  14889. y = current[6];
  14890. controlX = current[3];
  14891. controlY = current[4];
  14892. bounds = fabric.util.getBoundsOfCurve(x, y,
  14893. current[1],
  14894. current[2],
  14895. controlX,
  14896. controlY,
  14897. x,
  14898. y
  14899. );
  14900. break;
  14901. case 's': // shorthand cubic bezierCurveTo, relative
  14902. // transform to absolute x,y
  14903. tempX = x + current[3];
  14904. tempY = y + current[4];
  14905. if (previous[0].match(/[CcSs]/) === null) {
  14906. // If there is no previous command or if the previous command was not a C, c, S, or s,
  14907. // the control point is coincident with the current point
  14908. controlX = x;
  14909. controlY = y;
  14910. }
  14911. else {
  14912. // calculate reflection of previous control points
  14913. controlX = 2 * x - controlX;
  14914. controlY = 2 * y - controlY;
  14915. }
  14916. bounds = fabric.util.getBoundsOfCurve(x, y,
  14917. controlX,
  14918. controlY,
  14919. x + current[1],
  14920. y + current[2],
  14921. tempX,
  14922. tempY
  14923. );
  14924. // set control point to 2nd one of this command
  14925. // "... the first control point is assumed to be
  14926. // the reflection of the second control point on
  14927. // the previous command relative to the current point."
  14928. controlX = x + current[1];
  14929. controlY = y + current[2];
  14930. x = tempX;
  14931. y = tempY;
  14932. break;
  14933. case 'S': // shorthand cubic bezierCurveTo, absolute
  14934. tempX = current[3];
  14935. tempY = current[4];
  14936. if (previous[0].match(/[CcSs]/) === null) {
  14937. // If there is no previous command or if the previous command was not a C, c, S, or s,
  14938. // the control point is coincident with the current point
  14939. controlX = x;
  14940. controlY = y;
  14941. }
  14942. else {
  14943. // calculate reflection of previous control points
  14944. controlX = 2 * x - controlX;
  14945. controlY = 2 * y - controlY;
  14946. }
  14947. bounds = fabric.util.getBoundsOfCurve(x, y,
  14948. controlX,
  14949. controlY,
  14950. current[1],
  14951. current[2],
  14952. tempX,
  14953. tempY
  14954. );
  14955. x = tempX;
  14956. y = tempY;
  14957. // set control point to 2nd one of this command
  14958. // "... the first control point is assumed to be
  14959. // the reflection of the second control point on
  14960. // the previous command relative to the current point."
  14961. controlX = current[1];
  14962. controlY = current[2];
  14963. break;
  14964. case 'q': // quadraticCurveTo, relative
  14965. // transform to absolute x,y
  14966. tempX = x + current[3];
  14967. tempY = y + current[4];
  14968. controlX = x + current[1];
  14969. controlY = y + current[2];
  14970. bounds = fabric.util.getBoundsOfCurve(x, y,
  14971. controlX,
  14972. controlY,
  14973. controlX,
  14974. controlY,
  14975. tempX,
  14976. tempY
  14977. );
  14978. x = tempX;
  14979. y = tempY;
  14980. break;
  14981. case 'Q': // quadraticCurveTo, absolute
  14982. controlX = current[1];
  14983. controlY = current[2];
  14984. bounds = fabric.util.getBoundsOfCurve(x, y,
  14985. controlX,
  14986. controlY,
  14987. controlX,
  14988. controlY,
  14989. current[3],
  14990. current[4]
  14991. );
  14992. x = current[3];
  14993. y = current[4];
  14994. break;
  14995. case 't': // shorthand quadraticCurveTo, relative
  14996. // transform to absolute x,y
  14997. tempX = x + current[1];
  14998. tempY = y + current[2];
  14999. if (previous[0].match(/[QqTt]/) === null) {
  15000. // If there is no previous command or if the previous command was not a Q, q, T or t,
  15001. // assume the control point is coincident with the current point
  15002. controlX = x;
  15003. controlY = y;
  15004. }
  15005. else {
  15006. // calculate reflection of previous control point
  15007. controlX = 2 * x - controlX;
  15008. controlY = 2 * y - controlY;
  15009. }
  15010. bounds = fabric.util.getBoundsOfCurve(x, y,
  15011. controlX,
  15012. controlY,
  15013. controlX,
  15014. controlY,
  15015. tempX,
  15016. tempY
  15017. );
  15018. x = tempX;
  15019. y = tempY;
  15020. break;
  15021. case 'T':
  15022. tempX = current[1];
  15023. tempY = current[2];
  15024. if (previous[0].match(/[QqTt]/) === null) {
  15025. // If there is no previous command or if the previous command was not a Q, q, T or t,
  15026. // assume the control point is coincident with the current point
  15027. controlX = x;
  15028. controlY = y;
  15029. }
  15030. else {
  15031. // calculate reflection of previous control point
  15032. controlX = 2 * x - controlX;
  15033. controlY = 2 * y - controlY;
  15034. }
  15035. bounds = fabric.util.getBoundsOfCurve(x, y,
  15036. controlX,
  15037. controlY,
  15038. controlX,
  15039. controlY,
  15040. tempX,
  15041. tempY
  15042. );
  15043. x = tempX;
  15044. y = tempY;
  15045. break;
  15046. case 'a':
  15047. // TODO: optimize this
  15048. bounds = fabric.util.getBoundsOfArc(x, y,
  15049. current[1],
  15050. current[2],
  15051. current[3],
  15052. current[4],
  15053. current[5],
  15054. current[6] + x,
  15055. current[7] + y
  15056. );
  15057. x += current[6];
  15058. y += current[7];
  15059. break;
  15060. case 'A':
  15061. // TODO: optimize this
  15062. bounds = fabric.util.getBoundsOfArc(x, y,
  15063. current[1],
  15064. current[2],
  15065. current[3],
  15066. current[4],
  15067. current[5],
  15068. current[6],
  15069. current[7]
  15070. );
  15071. x = current[6];
  15072. y = current[7];
  15073. break;
  15074. case 'z':
  15075. case 'Z':
  15076. x = subpathStartX;
  15077. y = subpathStartY;
  15078. break;
  15079. }
  15080. previous = current;
  15081. bounds.forEach(function (point) {
  15082. aX.push(point.x);
  15083. aY.push(point.y);
  15084. });
  15085. aX.push(x);
  15086. aY.push(y);
  15087. }
  15088. var minX = min(aX) || 0,
  15089. minY = min(aY) || 0,
  15090. maxX = max(aX) || 0,
  15091. maxY = max(aY) || 0,
  15092. deltaX = maxX - minX,
  15093. deltaY = maxY - minY,
  15094. o = {
  15095. left: minX,
  15096. top: minY,
  15097. width: deltaX,
  15098. height: deltaY
  15099. };
  15100. return o;
  15101. }
  15102. });
  15103. /**
  15104. * Creates an instance of fabric.Path from an object
  15105. * @static
  15106. * @memberOf fabric.Path
  15107. * @param {Object} object
  15108. * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created
  15109. */
  15110. fabric.Path.fromObject = function(object, callback) {
  15111. // remove this pattern rom 2.0, accept just object.
  15112. var path;
  15113. if (typeof object.path === 'string') {
  15114. fabric.loadSVGFromURL(object.path, function (elements) {
  15115. var pathUrl = object.path;
  15116. path = elements[0];
  15117. delete object.path;
  15118. fabric.util.object.extend(path, object);
  15119. path.setSourcePath(pathUrl);
  15120. callback && callback(path);
  15121. });
  15122. }
  15123. else {
  15124. path = new fabric.Path(object.path, object);
  15125. callback && callback(path);
  15126. return path;
  15127. }
  15128. };
  15129. /* _FROM_SVG_START_ */
  15130. /**
  15131. * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
  15132. * @static
  15133. * @memberOf fabric.Path
  15134. * @see http://www.w3.org/TR/SVG/paths.html#PathElement
  15135. */
  15136. fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']);
  15137. /**
  15138. * Creates an instance of fabric.Path from an SVG <path> element
  15139. * @static
  15140. * @memberOf fabric.Path
  15141. * @param {SVGElement} element to parse
  15142. * @param {Function} callback Callback to invoke when an fabric.Path instance is created
  15143. * @param {Object} [options] Options object
  15144. */
  15145. fabric.Path.fromElement = function(element, callback, options) {
  15146. var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES);
  15147. callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options)));
  15148. };
  15149. /* _FROM_SVG_END_ */
  15150. /**
  15151. * Indicates that instances of this type are async
  15152. * @static
  15153. * @memberOf fabric.Path
  15154. * @type Boolean
  15155. * @default
  15156. */
  15157. fabric.Path.async = true;
  15158. })(typeof exports !== 'undefined' ? exports : this);
  15159. (function(global) {
  15160. 'use strict';
  15161. var fabric = global.fabric || (global.fabric = { }),
  15162. extend = fabric.util.object.extend,
  15163. invoke = fabric.util.array.invoke,
  15164. parentToObject = fabric.Object.prototype.toObject;
  15165. if (fabric.PathGroup) {
  15166. fabric.warn('fabric.PathGroup is already defined');
  15167. return;
  15168. }
  15169. /**
  15170. * Path group class
  15171. * @class fabric.PathGroup
  15172. * @extends fabric.Path
  15173. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup}
  15174. * @see {@link fabric.PathGroup#initialize} for constructor definition
  15175. */
  15176. fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ {
  15177. /**
  15178. * Type of an object
  15179. * @type String
  15180. * @default
  15181. */
  15182. type: 'path-group',
  15183. /**
  15184. * Fill value
  15185. * @type String
  15186. * @default
  15187. */
  15188. fill: '',
  15189. /**
  15190. * Constructor
  15191. * @param {Array} paths
  15192. * @param {Object} [options] Options object
  15193. * @return {fabric.PathGroup} thisArg
  15194. */
  15195. initialize: function(paths, options) {
  15196. options = options || { };
  15197. this.paths = paths || [];
  15198. for (var i = this.paths.length; i--;) {
  15199. this.paths[i].group = this;
  15200. }
  15201. if (options.toBeParsed) {
  15202. this.parseDimensionsFromPaths(options);
  15203. delete options.toBeParsed;
  15204. }
  15205. this.setOptions(options);
  15206. this.setCoords();
  15207. if (options.sourcePath) {
  15208. this.setSourcePath(options.sourcePath);
  15209. }
  15210. },
  15211. /**
  15212. * Calculate width and height based on paths contained
  15213. */
  15214. parseDimensionsFromPaths: function(options) {
  15215. var points, p, xC = [], yC = [], path, height, width,
  15216. m;
  15217. for (var j = this.paths.length; j--;) {
  15218. path = this.paths[j];
  15219. height = path.height + path.strokeWidth;
  15220. width = path.width + path.strokeWidth;
  15221. points = [
  15222. { x: path.left, y: path.top },
  15223. { x: path.left + width, y: path.top },
  15224. { x: path.left, y: path.top + height },
  15225. { x: path.left + width, y: path.top + height }
  15226. ];
  15227. m = this.paths[j].transformMatrix;
  15228. for (var i = 0; i < points.length; i++) {
  15229. p = points[i];
  15230. if (m) {
  15231. p = fabric.util.transformPoint(p, m, false);
  15232. }
  15233. xC.push(p.x);
  15234. yC.push(p.y);
  15235. }
  15236. }
  15237. options.width = Math.max.apply(null, xC);
  15238. options.height = Math.max.apply(null, yC);
  15239. },
  15240. /**
  15241. * Renders this group on a specified context
  15242. * @param {CanvasRenderingContext2D} ctx Context to render this instance on
  15243. */
  15244. render: function(ctx) {
  15245. // do not render if object is not visible
  15246. if (!this.visible) {
  15247. return;
  15248. }
  15249. ctx.save();
  15250. if (this.transformMatrix) {
  15251. ctx.transform.apply(ctx, this.transformMatrix);
  15252. }
  15253. this.transform(ctx);
  15254. this._setShadow(ctx);
  15255. this.clipTo && fabric.util.clipContext(this, ctx);
  15256. ctx.translate(-this.width / 2, -this.height / 2);
  15257. for (var i = 0, l = this.paths.length; i < l; ++i) {
  15258. this.paths[i].render(ctx, true);
  15259. }
  15260. this.clipTo && ctx.restore();
  15261. ctx.restore();
  15262. },
  15263. /**
  15264. * Sets certain property to a certain value
  15265. * @param {String} prop
  15266. * @param {*} value
  15267. * @return {fabric.PathGroup} thisArg
  15268. */
  15269. _set: function(prop, value) {
  15270. if (prop === 'fill' && value && this.isSameColor()) {
  15271. var i = this.paths.length;
  15272. while (i--) {
  15273. this.paths[i]._set(prop, value);
  15274. }
  15275. }
  15276. return this.callSuper('_set', prop, value);
  15277. },
  15278. /**
  15279. * Returns object representation of this path group
  15280. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15281. * @return {Object} object representation of an instance
  15282. */
  15283. toObject: function(propertiesToInclude) {
  15284. var o = extend(parentToObject.call(this, ['sourcePath'].concat(propertiesToInclude)), {
  15285. paths: invoke(this.getObjects(), 'toObject', propertiesToInclude)
  15286. });
  15287. return o;
  15288. },
  15289. /**
  15290. * Returns dataless object representation of this path group
  15291. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15292. * @return {Object} dataless object representation of an instance
  15293. */
  15294. toDatalessObject: function(propertiesToInclude) {
  15295. var o = this.toObject(propertiesToInclude);
  15296. if (this.sourcePath) {
  15297. o.paths = this.sourcePath;
  15298. }
  15299. return o;
  15300. },
  15301. /* _TO_SVG_START_ */
  15302. /**
  15303. * Returns svg representation of an instance
  15304. * @param {Function} [reviver] Method for further parsing of svg representation.
  15305. * @return {String} svg representation of an instance
  15306. */
  15307. toSVG: function(reviver) {
  15308. var objects = this.getObjects(),
  15309. p = this.getPointByOrigin('left', 'top'),
  15310. translatePart = 'translate(' + p.x + ' ' + p.y + ')',
  15311. markup = this._createBaseSVGMarkup();
  15312. markup.push(
  15313. '<g ', this.getSvgId(),
  15314. 'style="', this.getSvgStyles(), '" ',
  15315. 'transform="', this.getSvgTransformMatrix(), translatePart, this.getSvgTransform(), '" ',
  15316. '>\n'
  15317. );
  15318. for (var i = 0, len = objects.length; i < len; i++) {
  15319. markup.push('\t', objects[i].toSVG(reviver));
  15320. }
  15321. markup.push('</g>\n');
  15322. return reviver ? reviver(markup.join('')) : markup.join('');
  15323. },
  15324. /* _TO_SVG_END_ */
  15325. /**
  15326. * Returns a string representation of this path group
  15327. * @return {String} string representation of an object
  15328. */
  15329. toString: function() {
  15330. return '#<fabric.PathGroup (' + this.complexity() +
  15331. '): { top: ' + this.top + ', left: ' + this.left + ' }>';
  15332. },
  15333. /**
  15334. * Returns true if all paths in this group are of same color
  15335. * @return {Boolean} true if all paths are of the same color (`fill`)
  15336. */
  15337. isSameColor: function() {
  15338. var firstPathFill = this.getObjects()[0].get('fill') || '';
  15339. if (typeof firstPathFill !== 'string') {
  15340. return false;
  15341. }
  15342. firstPathFill = firstPathFill.toLowerCase();
  15343. return this.getObjects().every(function(path) {
  15344. var pathFill = path.get('fill') || '';
  15345. return typeof pathFill === 'string' && (pathFill).toLowerCase() === firstPathFill;
  15346. });
  15347. },
  15348. /**
  15349. * Returns number representation of object's complexity
  15350. * @return {Number} complexity
  15351. */
  15352. complexity: function() {
  15353. return this.paths.reduce(function(total, path) {
  15354. return total + ((path && path.complexity) ? path.complexity() : 0);
  15355. }, 0);
  15356. },
  15357. /**
  15358. * Returns all paths in this path group
  15359. * @return {Array} array of path objects included in this path group
  15360. */
  15361. getObjects: function() {
  15362. return this.paths;
  15363. }
  15364. });
  15365. /**
  15366. * Creates fabric.PathGroup instance from an object representation
  15367. * @static
  15368. * @memberOf fabric.PathGroup
  15369. * @param {Object} object Object to create an instance from
  15370. * @param {Function} [callback] Callback to invoke when an fabric.PathGroup instance is created
  15371. */
  15372. fabric.PathGroup.fromObject = function(object, callback) {
  15373. // remove this pattern from 2.0 accepts only object
  15374. if (typeof object.paths === 'string') {
  15375. fabric.loadSVGFromURL(object.paths, function (elements) {
  15376. var pathUrl = object.paths;
  15377. delete object.paths;
  15378. var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl);
  15379. callback(pathGroup);
  15380. });
  15381. }
  15382. else {
  15383. fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) {
  15384. delete object.paths;
  15385. callback(new fabric.PathGroup(enlivenedObjects, object));
  15386. });
  15387. }
  15388. };
  15389. /**
  15390. * Indicates that instances of this type are async
  15391. * @static
  15392. * @memberOf fabric.PathGroup
  15393. * @type Boolean
  15394. * @default
  15395. */
  15396. fabric.PathGroup.async = true;
  15397. })(typeof exports !== 'undefined' ? exports : this);
  15398. (function(global) {
  15399. 'use strict';
  15400. var fabric = global.fabric || (global.fabric = { }),
  15401. extend = fabric.util.object.extend,
  15402. min = fabric.util.array.min,
  15403. max = fabric.util.array.max,
  15404. invoke = fabric.util.array.invoke;
  15405. if (fabric.Group) {
  15406. return;
  15407. }
  15408. // lock-related properties, for use in fabric.Group#get
  15409. // to enable locking behavior on group
  15410. // when one of its objects has lock-related properties set
  15411. var _lockProperties = {
  15412. lockMovementX: true,
  15413. lockMovementY: true,
  15414. lockRotation: true,
  15415. lockScalingX: true,
  15416. lockScalingY: true,
  15417. lockUniScaling: true
  15418. };
  15419. /**
  15420. * Group class
  15421. * @class fabric.Group
  15422. * @extends fabric.Object
  15423. * @mixes fabric.Collection
  15424. * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups}
  15425. * @see {@link fabric.Group#initialize} for constructor definition
  15426. */
  15427. fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ {
  15428. /**
  15429. * Type of an object
  15430. * @type String
  15431. * @default
  15432. */
  15433. type: 'group',
  15434. /**
  15435. * Width of stroke
  15436. * @type Number
  15437. * @default
  15438. */
  15439. strokeWidth: 0,
  15440. /**
  15441. * Indicates if click events should also check for subtargets
  15442. * @type Boolean
  15443. * @default
  15444. */
  15445. subTargetCheck: false,
  15446. /**
  15447. * Constructor
  15448. * @param {Object} objects Group objects
  15449. * @param {Object} [options] Options object
  15450. * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already.
  15451. * @return {Object} thisArg
  15452. */
  15453. initialize: function(objects, options, isAlreadyGrouped) {
  15454. options = options || { };
  15455. this._objects = [];
  15456. // if objects enclosed in a group have been grouped already,
  15457. // we cannot change properties of objects.
  15458. // Thus we need to set options to group without objects,
  15459. // because delegatedProperties propagate to objects.
  15460. isAlreadyGrouped && this.callSuper('initialize', options);
  15461. this._objects = objects || [];
  15462. for (var i = this._objects.length; i--; ) {
  15463. this._objects[i].group = this;
  15464. }
  15465. this.originalState = { };
  15466. if (options.originX) {
  15467. this.originX = options.originX;
  15468. }
  15469. if (options.originY) {
  15470. this.originY = options.originY;
  15471. }
  15472. if (isAlreadyGrouped) {
  15473. // do not change coordinate of objects enclosed in a group,
  15474. // because objects coordinate system have been group coodinate system already.
  15475. this._updateObjectsCoords(true);
  15476. }
  15477. else {
  15478. this._calcBounds();
  15479. this._updateObjectsCoords();
  15480. this.callSuper('initialize', options);
  15481. }
  15482. this.setCoords();
  15483. this.saveCoords();
  15484. },
  15485. /**
  15486. * @private
  15487. * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change
  15488. */
  15489. _updateObjectsCoords: function(skipCoordsChange) {
  15490. for (var i = this._objects.length; i--; ){
  15491. this._updateObjectCoords(this._objects[i], skipCoordsChange);
  15492. }
  15493. },
  15494. /**
  15495. * @private
  15496. * @param {Object} object
  15497. * @param {Boolean} [skipCoordsChange] if true, coordinates of object dose not change
  15498. */
  15499. _updateObjectCoords: function(object, skipCoordsChange) {
  15500. // do not display corners of objects enclosed in a group
  15501. object.__origHasControls = object.hasControls;
  15502. object.hasControls = false;
  15503. if (skipCoordsChange) {
  15504. return;
  15505. }
  15506. var objectLeft = object.getLeft(),
  15507. objectTop = object.getTop(),
  15508. center = this.getCenterPoint();
  15509. object.set({
  15510. originalLeft: objectLeft,
  15511. originalTop: objectTop,
  15512. left: objectLeft - center.x,
  15513. top: objectTop - center.y
  15514. });
  15515. object.setCoords();
  15516. },
  15517. /**
  15518. * Returns string represenation of a group
  15519. * @return {String}
  15520. */
  15521. toString: function() {
  15522. return '#<fabric.Group: (' + this.complexity() + ')>';
  15523. },
  15524. /**
  15525. * Adds an object to a group; Then recalculates group's dimension, position.
  15526. * @param {Object} object
  15527. * @return {fabric.Group} thisArg
  15528. * @chainable
  15529. */
  15530. addWithUpdate: function(object) {
  15531. this._restoreObjectsState();
  15532. fabric.util.resetObjectTransform(this);
  15533. if (object) {
  15534. this._objects.push(object);
  15535. object.group = this;
  15536. object._set('canvas', this.canvas);
  15537. }
  15538. // since _restoreObjectsState set objects inactive
  15539. this.forEachObject(this._setObjectActive, this);
  15540. this._calcBounds();
  15541. this._updateObjectsCoords();
  15542. return this;
  15543. },
  15544. /**
  15545. * @private
  15546. */
  15547. _setObjectActive: function(object) {
  15548. object.set('active', true);
  15549. object.group = this;
  15550. },
  15551. /**
  15552. * Removes an object from a group; Then recalculates group's dimension, position.
  15553. * @param {Object} object
  15554. * @return {fabric.Group} thisArg
  15555. * @chainable
  15556. */
  15557. removeWithUpdate: function(object) {
  15558. this._restoreObjectsState();
  15559. fabric.util.resetObjectTransform(this);
  15560. // since _restoreObjectsState set objects inactive
  15561. this.forEachObject(this._setObjectActive, this);
  15562. this.remove(object);
  15563. this._calcBounds();
  15564. this._updateObjectsCoords();
  15565. return this;
  15566. },
  15567. /**
  15568. * @private
  15569. */
  15570. _onObjectAdded: function(object) {
  15571. object.group = this;
  15572. object._set('canvas', this.canvas);
  15573. },
  15574. /**
  15575. * @private
  15576. */
  15577. _onObjectRemoved: function(object) {
  15578. delete object.group;
  15579. object.set('active', false);
  15580. },
  15581. /**
  15582. * Properties that are delegated to group objects when reading/writing
  15583. * @param {Object} delegatedProperties
  15584. */
  15585. delegatedProperties: {
  15586. fill: true,
  15587. stroke: true,
  15588. strokeWidth: true,
  15589. fontFamily: true,
  15590. fontWeight: true,
  15591. fontSize: true,
  15592. fontStyle: true,
  15593. lineHeight: true,
  15594. textDecoration: true,
  15595. textAlign: true,
  15596. backgroundColor: true
  15597. },
  15598. /**
  15599. * @private
  15600. */
  15601. _set: function(key, value) {
  15602. var i = this._objects.length;
  15603. if (this.delegatedProperties[key] || key === 'canvas') {
  15604. while (i--) {
  15605. this._objects[i].set(key, value);
  15606. }
  15607. }
  15608. else {
  15609. while (i--) {
  15610. this._objects[i].setOnGroup(key, value);
  15611. }
  15612. }
  15613. this.callSuper('_set', key, value);
  15614. },
  15615. /**
  15616. * Returns object representation of an instance
  15617. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  15618. * @return {Object} object representation of an instance
  15619. */
  15620. toObject: function(propertiesToInclude) {
  15621. return extend(this.callSuper('toObject', propertiesToInclude), {
  15622. objects: invoke(this._objects, 'toObject', propertiesToInclude)
  15623. });
  15624. },
  15625. /**
  15626. * Renders instance on a given context
  15627. * @param {CanvasRenderingContext2D} ctx context to render instance on
  15628. */
  15629. render: function(ctx) {
  15630. // do not render if object is not visible
  15631. if (!this.visible) {
  15632. return;
  15633. }
  15634. ctx.save();
  15635. if (this.transformMatrix) {
  15636. ctx.transform.apply(ctx, this.transformMatrix);
  15637. }
  15638. this.transform(ctx);
  15639. this._setShadow(ctx);
  15640. this.clipTo && fabric.util.clipContext(this, ctx);
  15641. this._transformDone = true;
  15642. // the array is now sorted in order of highest first, so start from end
  15643. for (var i = 0, len = this._objects.length; i < len; i++) {
  15644. this._renderObject(this._objects[i], ctx);
  15645. }
  15646. this.clipTo && ctx.restore();
  15647. ctx.restore();
  15648. this._transformDone = false;
  15649. },
  15650. /**
  15651. * Renders controls and borders for the object
  15652. * @param {CanvasRenderingContext2D} ctx Context to render on
  15653. * @param {Boolean} [noTransform] When true, context is not transformed
  15654. */
  15655. _renderControls: function(ctx, noTransform) {
  15656. this.callSuper('_renderControls', ctx, noTransform);
  15657. for (var i = 0, len = this._objects.length; i < len; i++) {
  15658. this._objects[i]._renderControls(ctx);
  15659. }
  15660. },
  15661. /**
  15662. * @private
  15663. */
  15664. _renderObject: function(object, ctx) {
  15665. // do not render if object is not visible
  15666. if (!object.visible) {
  15667. return;
  15668. }
  15669. var originalHasRotatingPoint = object.hasRotatingPoint;
  15670. object.hasRotatingPoint = false;
  15671. object.render(ctx);
  15672. object.hasRotatingPoint = originalHasRotatingPoint;
  15673. },
  15674. /**
  15675. * Retores original state of each of group objects (original state is that which was before group was created).
  15676. * @private
  15677. * @return {fabric.Group} thisArg
  15678. * @chainable
  15679. */
  15680. _restoreObjectsState: function() {
  15681. this._objects.forEach(this._restoreObjectState, this);
  15682. return this;
  15683. },
  15684. /**
  15685. * Realises the transform from this group onto the supplied object
  15686. * i.e. it tells you what would happen if the supplied object was in
  15687. * the group, and then the group was destroyed. It mutates the supplied
  15688. * object.
  15689. * @param {fabric.Object} object
  15690. * @return {fabric.Object} transformedObject
  15691. */
  15692. realizeTransform: function(object) {
  15693. var matrix = object.calcTransformMatrix(),
  15694. options = fabric.util.qrDecompose(matrix),
  15695. center = new fabric.Point(options.translateX, options.translateY);
  15696. object.scaleX = options.scaleX;
  15697. object.scaleY = options.scaleY;
  15698. object.skewX = options.skewX;
  15699. object.skewY = options.skewY;
  15700. object.angle = options.angle;
  15701. object.flipX = false;
  15702. object.flipY = false;
  15703. object.setPositionByOrigin(center, 'center', 'center');
  15704. return object;
  15705. },
  15706. /**
  15707. * Restores original state of a specified object in group
  15708. * @private
  15709. * @param {fabric.Object} object
  15710. * @return {fabric.Group} thisArg
  15711. */
  15712. _restoreObjectState: function(object) {
  15713. this.realizeTransform(object);
  15714. object.setCoords();
  15715. object.hasControls = object.__origHasControls;
  15716. delete object.__origHasControls;
  15717. object.set('active', false);
  15718. delete object.group;
  15719. return this;
  15720. },
  15721. /**
  15722. * Destroys a group (restoring state of its objects)
  15723. * @return {fabric.Group} thisArg
  15724. * @chainable
  15725. */
  15726. destroy: function() {
  15727. return this._restoreObjectsState();
  15728. },
  15729. /**
  15730. * Saves coordinates of this instance (to be used together with `hasMoved`)
  15731. * @saveCoords
  15732. * @return {fabric.Group} thisArg
  15733. * @chainable
  15734. */
  15735. saveCoords: function() {
  15736. this._originalLeft = this.get('left');
  15737. this._originalTop = this.get('top');
  15738. return this;
  15739. },
  15740. /**
  15741. * Checks whether this group was moved (since `saveCoords` was called last)
  15742. * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
  15743. */
  15744. hasMoved: function() {
  15745. return this._originalLeft !== this.get('left') ||
  15746. this._originalTop !== this.get('top');
  15747. },
  15748. /**
  15749. * Sets coordinates of all group objects
  15750. * @return {fabric.Group} thisArg
  15751. * @chainable
  15752. */
  15753. setObjectsCoords: function() {
  15754. this.forEachObject(function(object) {
  15755. object.setCoords();
  15756. });
  15757. return this;
  15758. },
  15759. /**
  15760. * @private
  15761. */
  15762. _calcBounds: function(onlyWidthHeight) {
  15763. var aX = [],
  15764. aY = [],
  15765. o, prop,
  15766. props = ['tr', 'br', 'bl', 'tl'],
  15767. i = 0, iLen = this._objects.length,
  15768. j, jLen = props.length;
  15769. for ( ; i < iLen; ++i) {
  15770. o = this._objects[i];
  15771. o.setCoords();
  15772. for (j = 0; j < jLen; j++) {
  15773. prop = props[j];
  15774. aX.push(o.oCoords[prop].x);
  15775. aY.push(o.oCoords[prop].y);
  15776. }
  15777. }
  15778. this.set(this._getBounds(aX, aY, onlyWidthHeight));
  15779. },
  15780. /**
  15781. * @private
  15782. */
  15783. _getBounds: function(aX, aY, onlyWidthHeight) {
  15784. var ivt = fabric.util.invertTransform(this.getViewportTransform()),
  15785. minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt),
  15786. maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt),
  15787. obj = {
  15788. width: (maxXY.x - minXY.x) || 0,
  15789. height: (maxXY.y - minXY.y) || 0
  15790. };
  15791. if (!onlyWidthHeight) {
  15792. obj.left = minXY.x || 0;
  15793. obj.top = minXY.y || 0;
  15794. if (this.originX === 'center') {
  15795. obj.left += obj.width / 2;
  15796. }
  15797. if (this.originX === 'right') {
  15798. obj.left += obj.width;
  15799. }
  15800. if (this.originY === 'center') {
  15801. obj.top += obj.height / 2;
  15802. }
  15803. if (this.originY === 'bottom') {
  15804. obj.top += obj.height;
  15805. }
  15806. }
  15807. return obj;
  15808. },
  15809. /* _TO_SVG_START_ */
  15810. /**
  15811. * Returns svg representation of an instance
  15812. * @param {Function} [reviver] Method for further parsing of svg representation.
  15813. * @return {String} svg representation of an instance
  15814. */
  15815. toSVG: function(reviver) {
  15816. var markup = this._createBaseSVGMarkup();
  15817. markup.push(
  15818. '<g ', this.getSvgId(), 'transform="',
  15819. /* avoiding styles intentionally */
  15820. this.getSvgTransform(),
  15821. this.getSvgTransformMatrix(),
  15822. '" style="',
  15823. this.getSvgFilter(),
  15824. '">\n'
  15825. );
  15826. for (var i = 0, len = this._objects.length; i < len; i++) {
  15827. markup.push('\t', this._objects[i].toSVG(reviver));
  15828. }
  15829. markup.push('</g>\n');
  15830. return reviver ? reviver(markup.join('')) : markup.join('');
  15831. },
  15832. /* _TO_SVG_END_ */
  15833. /**
  15834. * Returns requested property
  15835. * @param {String} prop Property to get
  15836. * @return {*}
  15837. */
  15838. get: function(prop) {
  15839. if (prop in _lockProperties) {
  15840. if (this[prop]) {
  15841. return this[prop];
  15842. }
  15843. else {
  15844. for (var i = 0, len = this._objects.length; i < len; i++) {
  15845. if (this._objects[i][prop]) {
  15846. return true;
  15847. }
  15848. }
  15849. return false;
  15850. }
  15851. }
  15852. else {
  15853. if (prop in this.delegatedProperties) {
  15854. return this._objects[0] && this._objects[0].get(prop);
  15855. }
  15856. return this[prop];
  15857. }
  15858. }
  15859. });
  15860. /**
  15861. * Returns {@link fabric.Group} instance from an object representation
  15862. * @static
  15863. * @memberOf fabric.Group
  15864. * @param {Object} object Object to create a group from
  15865. * @param {Function} [callback] Callback to invoke when an group instance is created
  15866. */
  15867. fabric.Group.fromObject = function(object, callback) {
  15868. fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
  15869. delete object.objects;
  15870. callback && callback(new fabric.Group(enlivenedObjects, object, true));
  15871. });
  15872. };
  15873. /**
  15874. * Indicates that instances of this type are async
  15875. * @static
  15876. * @memberOf fabric.Group
  15877. * @type Boolean
  15878. * @default
  15879. */
  15880. fabric.Group.async = true;
  15881. })(typeof exports !== 'undefined' ? exports : this);
  15882. (function(global) {
  15883. 'use strict';
  15884. var extend = fabric.util.object.extend;
  15885. if (!global.fabric) {
  15886. global.fabric = { };
  15887. }
  15888. if (global.fabric.Image) {
  15889. fabric.warn('fabric.Image is already defined.');
  15890. return;
  15891. }
  15892. var stateProperties = fabric.Object.prototype.stateProperties.concat();
  15893. stateProperties.push(
  15894. 'alignX',
  15895. 'alignY',
  15896. 'meetOrSlice'
  15897. );
  15898. /**
  15899. * Image class
  15900. * @class fabric.Image
  15901. * @extends fabric.Object
  15902. * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#images}
  15903. * @see {@link fabric.Image#initialize} for constructor definition
  15904. */
  15905. fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ {
  15906. /**
  15907. * Type of an object
  15908. * @type String
  15909. * @default
  15910. */
  15911. type: 'image',
  15912. /**
  15913. * crossOrigin value (one of "", "anonymous", "use-credentials")
  15914. * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
  15915. * @type String
  15916. * @default
  15917. */
  15918. crossOrigin: '',
  15919. /**
  15920. * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
  15921. * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  15922. * This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
  15923. * @type String
  15924. * @default
  15925. */
  15926. alignX: 'none',
  15927. /**
  15928. * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
  15929. * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  15930. * This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
  15931. * @type String
  15932. * @default
  15933. */
  15934. alignY: 'none',
  15935. /**
  15936. * meetOrSlice value, part of preserveAspectRatio (one of "meet", "slice").
  15937. * if meet the image is always fully visibile, if slice the viewport is always filled with image.
  15938. * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  15939. * @type String
  15940. * @default
  15941. */
  15942. meetOrSlice: 'meet',
  15943. /**
  15944. * Width of a stroke.
  15945. * For image quality a stroke multiple of 2 gives better results.
  15946. * @type Number
  15947. * @default
  15948. */
  15949. strokeWidth: 0,
  15950. /**
  15951. * private
  15952. * contains last value of scaleX to detect
  15953. * if the Image got resized after the last Render
  15954. * @type Number
  15955. */
  15956. _lastScaleX: 1,
  15957. /**
  15958. * private
  15959. * contains last value of scaleY to detect
  15960. * if the Image got resized after the last Render
  15961. * @type Number
  15962. */
  15963. _lastScaleY: 1,
  15964. /**
  15965. * minimum scale factor under which any resizeFilter is triggered to resize the image
  15966. * 0 will disable the automatic resize. 1 will trigger automatically always.
  15967. * number bigger than 1 can be used in case we want to scale with some filter above
  15968. * the natural image dimensions
  15969. * @type Number
  15970. */
  15971. minimumScaleTrigger: 0.5,
  15972. /**
  15973. * List of properties to consider when checking if
  15974. * state of an object is changed ({@link fabric.Object#hasStateChanged})
  15975. * as well as for history (undo/redo) purposes
  15976. * @type Array
  15977. */
  15978. stateProperties: stateProperties,
  15979. /**
  15980. * Constructor
  15981. * @param {HTMLImageElement | String} element Image element
  15982. * @param {Object} [options] Options object
  15983. * @param {function} [callback] callback function to call after eventual filters applied.
  15984. * @return {fabric.Image} thisArg
  15985. */
  15986. initialize: function(element, options, callback) {
  15987. options || (options = { });
  15988. this.filters = [];
  15989. this.resizeFilters = [];
  15990. this.callSuper('initialize', options);
  15991. this._initElement(element, options, callback);
  15992. },
  15993. /**
  15994. * Returns image element which this instance if based on
  15995. * @return {HTMLImageElement} Image element
  15996. */
  15997. getElement: function() {
  15998. return this._element;
  15999. },
  16000. /**
  16001. * Sets image element for this instance to a specified one.
  16002. * If filters defined they are applied to new image.
  16003. * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area.
  16004. * @param {HTMLImageElement} element
  16005. * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated
  16006. * @param {Object} [options] Options object
  16007. * @return {fabric.Image} thisArg
  16008. * @chainable
  16009. */
  16010. setElement: function(element, callback, options) {
  16011. var _callback, _this;
  16012. this._element = element;
  16013. this._originalElement = element;
  16014. this._initConfig(options);
  16015. if (this.resizeFilters.length === 0) {
  16016. _callback = callback;
  16017. }
  16018. else {
  16019. _this = this;
  16020. _callback = function() {
  16021. _this.applyFilters(callback, _this.resizeFilters, _this._filteredEl || _this._originalElement, true);
  16022. };
  16023. }
  16024. if (this.filters.length !== 0) {
  16025. this.applyFilters(_callback);
  16026. }
  16027. else if (_callback) {
  16028. _callback(this);
  16029. }
  16030. return this;
  16031. },
  16032. /**
  16033. * Sets crossOrigin value (on an instance and corresponding image element)
  16034. * @return {fabric.Image} thisArg
  16035. * @chainable
  16036. */
  16037. setCrossOrigin: function(value) {
  16038. this.crossOrigin = value;
  16039. this._element.crossOrigin = value;
  16040. return this;
  16041. },
  16042. /**
  16043. * Returns original size of an image
  16044. * @return {Object} Object with "width" and "height" properties
  16045. */
  16046. getOriginalSize: function() {
  16047. var element = this.getElement();
  16048. return {
  16049. width: element.width,
  16050. height: element.height
  16051. };
  16052. },
  16053. /**
  16054. * @private
  16055. * @param {CanvasRenderingContext2D} ctx Context to render on
  16056. */
  16057. _stroke: function(ctx) {
  16058. if (!this.stroke || this.strokeWidth === 0) {
  16059. return;
  16060. }
  16061. var w = this.width / 2, h = this.height / 2;
  16062. ctx.beginPath();
  16063. ctx.moveTo(-w, -h);
  16064. ctx.lineTo(w, -h);
  16065. ctx.lineTo(w, h);
  16066. ctx.lineTo(-w, h);
  16067. ctx.lineTo(-w, -h);
  16068. ctx.closePath();
  16069. },
  16070. /**
  16071. * @private
  16072. * @param {CanvasRenderingContext2D} ctx Context to render on
  16073. */
  16074. _renderDashedStroke: function(ctx) {
  16075. var x = -this.width / 2,
  16076. y = -this.height / 2,
  16077. w = this.width,
  16078. h = this.height;
  16079. ctx.save();
  16080. this._setStrokeStyles(ctx);
  16081. ctx.beginPath();
  16082. fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray);
  16083. fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray);
  16084. fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray);
  16085. fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray);
  16086. ctx.closePath();
  16087. ctx.restore();
  16088. },
  16089. /**
  16090. * Returns object representation of an instance
  16091. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  16092. * @return {Object} Object representation of an instance
  16093. */
  16094. toObject: function(propertiesToInclude) {
  16095. var filters = [], resizeFilters = [],
  16096. scaleX = 1, scaleY = 1;
  16097. this.filters.forEach(function(filterObj) {
  16098. if (filterObj) {
  16099. if (filterObj.type === 'Resize') {
  16100. scaleX *= filterObj.scaleX;
  16101. scaleY *= filterObj.scaleY;
  16102. }
  16103. filters.push(filterObj.toObject());
  16104. }
  16105. });
  16106. this.resizeFilters.forEach(function(filterObj) {
  16107. filterObj && resizeFilters.push(filterObj.toObject());
  16108. });
  16109. var object = extend(
  16110. this.callSuper(
  16111. 'toObject',
  16112. ['crossOrigin', 'alignX', 'alignY', 'meetOrSlice'].concat(propertiesToInclude)
  16113. ), {
  16114. src: this.getSrc(),
  16115. filters: filters,
  16116. resizeFilters: resizeFilters,
  16117. });
  16118. object.width /= scaleX;
  16119. object.height /= scaleY;
  16120. return object;
  16121. },
  16122. /* _TO_SVG_START_ */
  16123. /**
  16124. * Returns SVG representation of an instance
  16125. * @param {Function} [reviver] Method for further parsing of svg representation.
  16126. * @return {String} svg representation of an instance
  16127. */
  16128. toSVG: function(reviver) {
  16129. var markup = this._createBaseSVGMarkup(), x = -this.width / 2, y = -this.height / 2,
  16130. preserveAspectRatio = 'none', filtered = true;
  16131. if (this.group && this.group.type === 'path-group') {
  16132. x = this.left;
  16133. y = this.top;
  16134. }
  16135. if (this.alignX !== 'none' && this.alignY !== 'none') {
  16136. preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
  16137. }
  16138. markup.push(
  16139. '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
  16140. '<image ', this.getSvgId(), 'xlink:href="', this.getSvgSrc(filtered),
  16141. '" x="', x, '" y="', y,
  16142. '" style="', this.getSvgStyles(),
  16143. // we're essentially moving origin of transformation from top/left corner to the center of the shape
  16144. // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
  16145. // so that object's center aligns with container's left/top
  16146. '" width="', this.width,
  16147. '" height="', this.height,
  16148. '" preserveAspectRatio="', preserveAspectRatio, '"',
  16149. '></image>\n'
  16150. );
  16151. if (this.stroke || this.strokeDashArray) {
  16152. var origFill = this.fill;
  16153. this.fill = null;
  16154. markup.push(
  16155. '<rect ',
  16156. 'x="', x, '" y="', y,
  16157. '" width="', this.width, '" height="', this.height,
  16158. '" style="', this.getSvgStyles(),
  16159. '"/>\n'
  16160. );
  16161. this.fill = origFill;
  16162. }
  16163. markup.push('</g>\n');
  16164. return reviver ? reviver(markup.join('')) : markup.join('');
  16165. },
  16166. /* _TO_SVG_END_ */
  16167. /**
  16168. * Returns source of an image
  16169. * @param {Boolean} filtered indicates if the src is needed for svg
  16170. * @return {String} Source of an image
  16171. */
  16172. getSrc: function(filtered) {
  16173. var element = filtered ? this._element : this._originalElement;
  16174. if (element) {
  16175. return fabric.isLikelyNode ? element._src : element.src;
  16176. }
  16177. else {
  16178. return this.src || '';
  16179. }
  16180. },
  16181. /**
  16182. * Sets source of an image
  16183. * @param {String} src Source string (URL)
  16184. * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
  16185. * @param {Object} [options] Options object
  16186. * @return {fabric.Image} thisArg
  16187. * @chainable
  16188. */
  16189. setSrc: function(src, callback, options) {
  16190. fabric.util.loadImage(src, function(img) {
  16191. return this.setElement(img, callback, options);
  16192. }, this, options && options.crossOrigin);
  16193. },
  16194. /**
  16195. * Returns string representation of an instance
  16196. * @return {String} String representation of an instance
  16197. */
  16198. toString: function() {
  16199. return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
  16200. },
  16201. /**
  16202. * Applies filters assigned to this image (from "filters" array)
  16203. * @method applyFilters
  16204. * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated
  16205. * @param {Array} filters to be applied
  16206. * @param {fabric.Image} imgElement image to filter ( default to this._element )
  16207. * @param {Boolean} forResizing
  16208. * @return {CanvasElement} canvasEl to be drawn immediately
  16209. * @chainable
  16210. */
  16211. applyFilters: function(callback, filters, imgElement, forResizing) {
  16212. filters = filters || this.filters;
  16213. imgElement = imgElement || this._originalElement;
  16214. if (!imgElement) {
  16215. return;
  16216. }
  16217. var replacement = fabric.util.createImage(),
  16218. retinaScaling = this.canvas ? this.canvas.getRetinaScaling() : fabric.devicePixelRatio,
  16219. minimumScale = this.minimumScaleTrigger / retinaScaling,
  16220. _this = this, scaleX, scaleY;
  16221. if (filters.length === 0) {
  16222. this._element = imgElement;
  16223. callback && callback(this);
  16224. return imgElement;
  16225. }
  16226. var canvasEl = fabric.util.createCanvasElement();
  16227. canvasEl.width = imgElement.width;
  16228. canvasEl.height = imgElement.height;
  16229. canvasEl.getContext('2d').drawImage(imgElement, 0, 0, imgElement.width, imgElement.height);
  16230. filters.forEach(function(filter) {
  16231. if (!filter) {
  16232. return;
  16233. }
  16234. if (forResizing) {
  16235. scaleX = _this.scaleX < minimumScale ? _this.scaleX : 1;
  16236. scaleY = _this.scaleY < minimumScale ? _this.scaleY : 1;
  16237. if (scaleX * retinaScaling < 1) {
  16238. scaleX *= retinaScaling;
  16239. }
  16240. if (scaleY * retinaScaling < 1) {
  16241. scaleY *= retinaScaling;
  16242. }
  16243. }
  16244. else {
  16245. scaleX = filter.scaleX;
  16246. scaleY = filter.scaleY;
  16247. }
  16248. filter.applyTo(canvasEl, scaleX, scaleY);
  16249. if (!forResizing && filter.type === 'Resize') {
  16250. _this.width *= filter.scaleX;
  16251. _this.height *= filter.scaleY;
  16252. }
  16253. });
  16254. /** @ignore */
  16255. replacement.width = canvasEl.width;
  16256. replacement.height = canvasEl.height;
  16257. if (fabric.isLikelyNode) {
  16258. replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression);
  16259. // onload doesn't fire in some node versions, so we invoke callback manually
  16260. _this._element = replacement;
  16261. !forResizing && (_this._filteredEl = replacement);
  16262. callback && callback(_this);
  16263. }
  16264. else {
  16265. replacement.onload = function() {
  16266. _this._element = replacement;
  16267. !forResizing && (_this._filteredEl = replacement);
  16268. callback && callback(_this);
  16269. replacement.onload = canvasEl = null;
  16270. };
  16271. replacement.src = canvasEl.toDataURL('image/png');
  16272. }
  16273. return canvasEl;
  16274. },
  16275. /**
  16276. * @private
  16277. * @param {CanvasRenderingContext2D} ctx Context to render on
  16278. * @param {Boolean} noTransform
  16279. */
  16280. _render: function(ctx, noTransform) {
  16281. var x, y, imageMargins = this._findMargins(), elementToDraw;
  16282. x = (noTransform ? this.left : -this.width / 2);
  16283. y = (noTransform ? this.top : -this.height / 2);
  16284. if (this.meetOrSlice === 'slice') {
  16285. ctx.beginPath();
  16286. ctx.rect(x, y, this.width, this.height);
  16287. ctx.clip();
  16288. }
  16289. if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {
  16290. this._lastScaleX = this.scaleX;
  16291. this._lastScaleY = this.scaleY;
  16292. elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);
  16293. }
  16294. else {
  16295. elementToDraw = this._element;
  16296. }
  16297. elementToDraw && ctx.drawImage(elementToDraw,
  16298. x + imageMargins.marginX,
  16299. y + imageMargins.marginY,
  16300. imageMargins.width,
  16301. imageMargins.height
  16302. );
  16303. this._stroke(ctx);
  16304. this._renderStroke(ctx);
  16305. },
  16306. /**
  16307. * @private, needed to check if image needs resize
  16308. */
  16309. _needsResize: function() {
  16310. return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
  16311. },
  16312. /**
  16313. * @private
  16314. */
  16315. _findMargins: function() {
  16316. var width = this.width, height = this.height, scales,
  16317. scale, marginX = 0, marginY = 0;
  16318. if (this.alignX !== 'none' || this.alignY !== 'none') {
  16319. scales = [this.width / this._element.width, this.height / this._element.height];
  16320. scale = this.meetOrSlice === 'meet'
  16321. ? Math.min.apply(null, scales) : Math.max.apply(null, scales);
  16322. width = this._element.width * scale;
  16323. height = this._element.height * scale;
  16324. if (this.alignX === 'Mid') {
  16325. marginX = (this.width - width) / 2;
  16326. }
  16327. if (this.alignX === 'Max') {
  16328. marginX = this.width - width;
  16329. }
  16330. if (this.alignY === 'Mid') {
  16331. marginY = (this.height - height) / 2;
  16332. }
  16333. if (this.alignY === 'Max') {
  16334. marginY = this.height - height;
  16335. }
  16336. }
  16337. return {
  16338. width: width,
  16339. height: height,
  16340. marginX: marginX,
  16341. marginY: marginY
  16342. };
  16343. },
  16344. /**
  16345. * @private
  16346. */
  16347. _resetWidthHeight: function() {
  16348. var element = this.getElement();
  16349. this.set('width', element.width);
  16350. this.set('height', element.height);
  16351. },
  16352. /**
  16353. * The Image class's initialization method. This method is automatically
  16354. * called by the constructor.
  16355. * @private
  16356. * @param {HTMLImageElement|String} element The element representing the image
  16357. * @param {Object} [options] Options object
  16358. */
  16359. _initElement: function(element, options, callback) {
  16360. this.setElement(fabric.util.getById(element), callback, options);
  16361. fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
  16362. },
  16363. /**
  16364. * @private
  16365. * @param {Object} [options] Options object
  16366. */
  16367. _initConfig: function(options) {
  16368. options || (options = { });
  16369. this.setOptions(options);
  16370. this._setWidthHeight(options);
  16371. if (this._element && this.crossOrigin) {
  16372. this._element.crossOrigin = this.crossOrigin;
  16373. }
  16374. },
  16375. /**
  16376. * @private
  16377. * @param {Array} filters to be initialized
  16378. * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created
  16379. */
  16380. _initFilters: function(filters, callback) {
  16381. if (filters && filters.length) {
  16382. fabric.util.enlivenObjects(filters, function(enlivenedObjects) {
  16383. callback && callback(enlivenedObjects);
  16384. }, 'fabric.Image.filters');
  16385. }
  16386. else {
  16387. callback && callback();
  16388. }
  16389. },
  16390. /**
  16391. * @private
  16392. * @param {Object} [options] Object with width/height properties
  16393. */
  16394. _setWidthHeight: function(options) {
  16395. this.width = 'width' in options
  16396. ? options.width
  16397. : (this.getElement()
  16398. ? this.getElement().width || 0
  16399. : 0);
  16400. this.height = 'height' in options
  16401. ? options.height
  16402. : (this.getElement()
  16403. ? this.getElement().height || 0
  16404. : 0);
  16405. },
  16406. /**
  16407. * Returns complexity of an instance
  16408. * @return {Number} complexity of this instance
  16409. */
  16410. complexity: function() {
  16411. return 1;
  16412. }
  16413. });
  16414. /**
  16415. * Default CSS class name for canvas
  16416. * @static
  16417. * @type String
  16418. * @default
  16419. */
  16420. fabric.Image.CSS_CANVAS = 'canvas-img';
  16421. /**
  16422. * Alias for getSrc
  16423. * @static
  16424. */
  16425. fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;
  16426. /**
  16427. * Creates an instance of fabric.Image from its object representation
  16428. * @static
  16429. * @param {Object} object Object to create an instance from
  16430. * @param {Function} callback Callback to invoke when an image instance is created
  16431. */
  16432. fabric.Image.fromObject = function(object, callback) {
  16433. fabric.util.loadImage(object.src, function(img) {
  16434. fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) {
  16435. object.filters = filters || [];
  16436. fabric.Image.prototype._initFilters.call(object, object.resizeFilters, function(resizeFilters) {
  16437. object.resizeFilters = resizeFilters || [];
  16438. return new fabric.Image(img, object, callback);
  16439. });
  16440. });
  16441. }, null, object.crossOrigin);
  16442. };
  16443. /**
  16444. * Creates an instance of fabric.Image from an URL string
  16445. * @static
  16446. * @param {String} url URL to create an image from
  16447. * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)
  16448. * @param {Object} [imgOptions] Options object
  16449. */
  16450. fabric.Image.fromURL = function(url, callback, imgOptions) {
  16451. fabric.util.loadImage(url, function(img) {
  16452. callback && callback(new fabric.Image(img, imgOptions));
  16453. }, null, imgOptions && imgOptions.crossOrigin);
  16454. };
  16455. /* _FROM_SVG_START_ */
  16456. /**
  16457. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})
  16458. * @static
  16459. * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement}
  16460. */
  16461. fabric.Image.ATTRIBUTE_NAMES =
  16462. fabric.SHARED_ATTRIBUTES.concat('x y width height preserveAspectRatio xlink:href'.split(' '));
  16463. /**
  16464. * Returns {@link fabric.Image} instance from an SVG element
  16465. * @static
  16466. * @param {SVGElement} element Element to parse
  16467. * @param {Function} callback Callback to execute when fabric.Image object is created
  16468. * @param {Object} [options] Options object
  16469. * @return {fabric.Image} Instance of fabric.Image
  16470. */
  16471. fabric.Image.fromElement = function(element, callback, options) {
  16472. var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES),
  16473. preserveAR;
  16474. if (parsedAttributes.preserveAspectRatio) {
  16475. preserveAR = fabric.util.parsePreserveAspectRatioAttribute(parsedAttributes.preserveAspectRatio);
  16476. extend(parsedAttributes, preserveAR);
  16477. }
  16478. fabric.Image.fromURL(parsedAttributes['xlink:href'], callback,
  16479. extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
  16480. };
  16481. /* _FROM_SVG_END_ */
  16482. /**
  16483. * Indicates that instances of this type are async
  16484. * @static
  16485. * @type Boolean
  16486. * @default
  16487. */
  16488. fabric.Image.async = true;
  16489. /**
  16490. * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9
  16491. * @static
  16492. * @type Number
  16493. * @default
  16494. */
  16495. fabric.Image.pngCompression = 1;
  16496. })(typeof exports !== 'undefined' ? exports : this);
  16497. fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ {
  16498. /**
  16499. * @private
  16500. * @return {Number} angle value
  16501. */
  16502. _getAngleValueForStraighten: function() {
  16503. var angle = this.getAngle() % 360;
  16504. if (angle > 0) {
  16505. return Math.round((angle - 1) / 90) * 90;
  16506. }
  16507. return Math.round(angle / 90) * 90;
  16508. },
  16509. /**
  16510. * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer)
  16511. * @return {fabric.Object} thisArg
  16512. * @chainable
  16513. */
  16514. straighten: function() {
  16515. this.setAngle(this._getAngleValueForStraighten());
  16516. return this;
  16517. },
  16518. /**
  16519. * Same as {@link fabric.Object.prototype.straighten} but with animation
  16520. * @param {Object} callbacks Object with callback functions
  16521. * @param {Function} [callbacks.onComplete] Invoked on completion
  16522. * @param {Function} [callbacks.onChange] Invoked on every step of animation
  16523. * @return {fabric.Object} thisArg
  16524. * @chainable
  16525. */
  16526. fxStraighten: function(callbacks) {
  16527. callbacks = callbacks || { };
  16528. var empty = function() { },
  16529. onComplete = callbacks.onComplete || empty,
  16530. onChange = callbacks.onChange || empty,
  16531. _this = this;
  16532. fabric.util.animate({
  16533. startValue: this.get('angle'),
  16534. endValue: this._getAngleValueForStraighten(),
  16535. duration: this.FX_DURATION,
  16536. onChange: function(value) {
  16537. _this.setAngle(value);
  16538. onChange();
  16539. },
  16540. onComplete: function() {
  16541. _this.setCoords();
  16542. onComplete();
  16543. },
  16544. onStart: function() {
  16545. _this.set('active', false);
  16546. }
  16547. });
  16548. return this;
  16549. }
  16550. });
  16551. fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ {
  16552. /**
  16553. * Straightens object, then rerenders canvas
  16554. * @param {fabric.Object} object Object to straighten
  16555. * @return {fabric.Canvas} thisArg
  16556. * @chainable
  16557. */
  16558. straightenObject: function (object) {
  16559. object.straighten();
  16560. this.renderAll();
  16561. return this;
  16562. },
  16563. /**
  16564. * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated
  16565. * @param {fabric.Object} object Object to straighten
  16566. * @return {fabric.Canvas} thisArg
  16567. * @chainable
  16568. */
  16569. fxStraightenObject: function (object) {
  16570. object.fxStraighten({
  16571. onChange: this.renderAll.bind(this)
  16572. });
  16573. return this;
  16574. }
  16575. });
  16576. /**
  16577. * @namespace fabric.Image.filters
  16578. * @memberOf fabric.Image
  16579. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters}
  16580. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  16581. */
  16582. fabric.Image.filters = fabric.Image.filters || { };
  16583. /**
  16584. * Root filter class from which all filter classes inherit from
  16585. * @class fabric.Image.filters.BaseFilter
  16586. * @memberOf fabric.Image.filters
  16587. */
  16588. fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ {
  16589. /**
  16590. * Filter type
  16591. * @param {String} type
  16592. * @default
  16593. */
  16594. type: 'BaseFilter',
  16595. /**
  16596. * Constructor
  16597. * @param {Object} [options] Options object
  16598. */
  16599. initialize: function(options) {
  16600. if (options) {
  16601. this.setOptions(options);
  16602. }
  16603. },
  16604. /**
  16605. * Sets filter's properties from options
  16606. * @param {Object} [options] Options object
  16607. */
  16608. setOptions: function(options) {
  16609. for (var prop in options) {
  16610. this[prop] = options[prop];
  16611. }
  16612. },
  16613. /**
  16614. * Returns object representation of an instance
  16615. * @return {Object} Object representation of an instance
  16616. */
  16617. toObject: function() {
  16618. return { type: this.type };
  16619. },
  16620. /**
  16621. * Returns a JSON representation of an instance
  16622. * @return {Object} JSON
  16623. */
  16624. toJSON: function() {
  16625. // delegate, not alias
  16626. return this.toObject();
  16627. }
  16628. });
  16629. (function(global) {
  16630. 'use strict';
  16631. var fabric = global.fabric || (global.fabric = { }),
  16632. extend = fabric.util.object.extend,
  16633. filters = fabric.Image.filters,
  16634. createClass = fabric.util.createClass;
  16635. /**
  16636. * Brightness filter class
  16637. * @class fabric.Image.filters.Brightness
  16638. * @memberOf fabric.Image.filters
  16639. * @extends fabric.Image.filters.BaseFilter
  16640. * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition
  16641. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  16642. * @example
  16643. * var filter = new fabric.Image.filters.Brightness({
  16644. * brightness: 200
  16645. * });
  16646. * object.filters.push(filter);
  16647. * object.applyFilters(canvas.renderAll.bind(canvas));
  16648. */
  16649. filters.Brightness = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ {
  16650. /**
  16651. * Filter type
  16652. * @param {String} type
  16653. * @default
  16654. */
  16655. type: 'Brightness',
  16656. /**
  16657. * Constructor
  16658. * @memberOf fabric.Image.filters.Brightness.prototype
  16659. * @param {Object} [options] Options object
  16660. * @param {Number} [options.brightness=0] Value to brighten the image up (-255..255)
  16661. */
  16662. initialize: function(options) {
  16663. options = options || { };
  16664. this.brightness = options.brightness || 0;
  16665. },
  16666. /**
  16667. * Applies filter to canvas element
  16668. * @param {Object} canvasEl Canvas element to apply filter to
  16669. */
  16670. applyTo: function(canvasEl) {
  16671. var context = canvasEl.getContext('2d'),
  16672. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  16673. data = imageData.data,
  16674. brightness = this.brightness;
  16675. for (var i = 0, len = data.length; i < len; i += 4) {
  16676. data[i] += brightness;
  16677. data[i + 1] += brightness;
  16678. data[i + 2] += brightness;
  16679. }
  16680. context.putImageData(imageData, 0, 0);
  16681. },
  16682. /**
  16683. * Returns object representation of an instance
  16684. * @return {Object} Object representation of an instance
  16685. */
  16686. toObject: function() {
  16687. return extend(this.callSuper('toObject'), {
  16688. brightness: this.brightness
  16689. });
  16690. }
  16691. });
  16692. /**
  16693. * Returns filter instance from an object representation
  16694. * @static
  16695. * @param {Object} object Object to create an instance from
  16696. * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness
  16697. */
  16698. fabric.Image.filters.Brightness.fromObject = function(object) {
  16699. return new fabric.Image.filters.Brightness(object);
  16700. };
  16701. })(typeof exports !== 'undefined' ? exports : this);
  16702. (function(global) {
  16703. 'use strict';
  16704. var fabric = global.fabric || (global.fabric = { }),
  16705. extend = fabric.util.object.extend,
  16706. filters = fabric.Image.filters,
  16707. createClass = fabric.util.createClass;
  16708. /**
  16709. * Adapted from <a href="http://www.html5rocks.com/en/tutorials/canvas/imagefilters/">html5rocks article</a>
  16710. * @class fabric.Image.filters.Convolute
  16711. * @memberOf fabric.Image.filters
  16712. * @extends fabric.Image.filters.BaseFilter
  16713. * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition
  16714. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  16715. * @example <caption>Sharpen filter</caption>
  16716. * var filter = new fabric.Image.filters.Convolute({
  16717. * matrix: [ 0, -1, 0,
  16718. * -1, 5, -1,
  16719. * 0, -1, 0 ]
  16720. * });
  16721. * object.filters.push(filter);
  16722. * object.applyFilters(canvas.renderAll.bind(canvas));
  16723. * @example <caption>Blur filter</caption>
  16724. * var filter = new fabric.Image.filters.Convolute({
  16725. * matrix: [ 1/9, 1/9, 1/9,
  16726. * 1/9, 1/9, 1/9,
  16727. * 1/9, 1/9, 1/9 ]
  16728. * });
  16729. * object.filters.push(filter);
  16730. * object.applyFilters(canvas.renderAll.bind(canvas));
  16731. * @example <caption>Emboss filter</caption>
  16732. * var filter = new fabric.Image.filters.Convolute({
  16733. * matrix: [ 1, 1, 1,
  16734. * 1, 0.7, -1,
  16735. * -1, -1, -1 ]
  16736. * });
  16737. * object.filters.push(filter);
  16738. * object.applyFilters(canvas.renderAll.bind(canvas));
  16739. * @example <caption>Emboss filter with opaqueness</caption>
  16740. * var filter = new fabric.Image.filters.Convolute({
  16741. * opaque: true,
  16742. * matrix: [ 1, 1, 1,
  16743. * 1, 0.7, -1,
  16744. * -1, -1, -1 ]
  16745. * });
  16746. * object.filters.push(filter);
  16747. * object.applyFilters(canvas.renderAll.bind(canvas));
  16748. */
  16749. filters.Convolute = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ {
  16750. /**
  16751. * Filter type
  16752. * @param {String} type
  16753. * @default
  16754. */
  16755. type: 'Convolute',
  16756. /**
  16757. * Constructor
  16758. * @memberOf fabric.Image.filters.Convolute.prototype
  16759. * @param {Object} [options] Options object
  16760. * @param {Boolean} [options.opaque=false] Opaque value (true/false)
  16761. * @param {Array} [options.matrix] Filter matrix
  16762. */
  16763. initialize: function(options) {
  16764. options = options || { };
  16765. this.opaque = options.opaque;
  16766. this.matrix = options.matrix || [
  16767. 0, 0, 0,
  16768. 0, 1, 0,
  16769. 0, 0, 0
  16770. ];
  16771. },
  16772. /**
  16773. * Applies filter to canvas element
  16774. * @param {Object} canvasEl Canvas element to apply filter to
  16775. */
  16776. applyTo: function(canvasEl) {
  16777. var weights = this.matrix,
  16778. context = canvasEl.getContext('2d'),
  16779. pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  16780. side = Math.round(Math.sqrt(weights.length)),
  16781. halfSide = Math.floor(side / 2),
  16782. src = pixels.data,
  16783. sw = pixels.width,
  16784. sh = pixels.height,
  16785. output = context.createImageData(sw, sh),
  16786. dst = output.data,
  16787. // go through the destination image pixels
  16788. alphaFac = this.opaque ? 1 : 0,
  16789. r, g, b, a, dstOff,
  16790. scx, scy, srcOff, wt;
  16791. for (var y = 0; y < sh; y++) {
  16792. for (var x = 0; x < sw; x++) {
  16793. dstOff = (y * sw + x) * 4;
  16794. // calculate the weighed sum of the source image pixels that
  16795. // fall under the convolution matrix
  16796. r = 0; g = 0; b = 0; a = 0;
  16797. for (var cy = 0; cy < side; cy++) {
  16798. for (var cx = 0; cx < side; cx++) {
  16799. scy = y + cy - halfSide;
  16800. scx = x + cx - halfSide;
  16801. // eslint-disable-next-line max-depth
  16802. if (scy < 0 || scy > sh || scx < 0 || scx > sw) {
  16803. continue;
  16804. }
  16805. srcOff = (scy * sw + scx) * 4;
  16806. wt = weights[cy * side + cx];
  16807. r += src[srcOff] * wt;
  16808. g += src[srcOff + 1] * wt;
  16809. b += src[srcOff + 2] * wt;
  16810. a += src[srcOff + 3] * wt;
  16811. }
  16812. }
  16813. dst[dstOff] = r;
  16814. dst[dstOff + 1] = g;
  16815. dst[dstOff + 2] = b;
  16816. dst[dstOff + 3] = a + alphaFac * (255 - a);
  16817. }
  16818. }
  16819. context.putImageData(output, 0, 0);
  16820. },
  16821. /**
  16822. * Returns object representation of an instance
  16823. * @return {Object} Object representation of an instance
  16824. */
  16825. toObject: function() {
  16826. return extend(this.callSuper('toObject'), {
  16827. opaque: this.opaque,
  16828. matrix: this.matrix
  16829. });
  16830. }
  16831. });
  16832. /**
  16833. * Returns filter instance from an object representation
  16834. * @static
  16835. * @param {Object} object Object to create an instance from
  16836. * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute
  16837. */
  16838. fabric.Image.filters.Convolute.fromObject = function(object) {
  16839. return new fabric.Image.filters.Convolute(object);
  16840. };
  16841. })(typeof exports !== 'undefined' ? exports : this);
  16842. (function(global) {
  16843. 'use strict';
  16844. var fabric = global.fabric || (global.fabric = { }),
  16845. extend = fabric.util.object.extend,
  16846. filters = fabric.Image.filters,
  16847. createClass = fabric.util.createClass;
  16848. /**
  16849. * GradientTransparency filter class
  16850. * @class fabric.Image.filters.GradientTransparency
  16851. * @memberOf fabric.Image.filters
  16852. * @extends fabric.Image.filters.BaseFilter
  16853. * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition
  16854. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  16855. * @example
  16856. * var filter = new fabric.Image.filters.GradientTransparency({
  16857. * threshold: 200
  16858. * });
  16859. * object.filters.push(filter);
  16860. * object.applyFilters(canvas.renderAll.bind(canvas));
  16861. */
  16862. // eslint-disable-next-line max-len
  16863. filters.GradientTransparency = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ {
  16864. /**
  16865. * Filter type
  16866. * @param {String} type
  16867. * @default
  16868. */
  16869. type: 'GradientTransparency',
  16870. /**
  16871. * Constructor
  16872. * @memberOf fabric.Image.filters.GradientTransparency.prototype
  16873. * @param {Object} [options] Options object
  16874. * @param {Number} [options.threshold=100] Threshold value
  16875. */
  16876. initialize: function(options) {
  16877. options = options || { };
  16878. this.threshold = options.threshold || 100;
  16879. },
  16880. /**
  16881. * Applies filter to canvas element
  16882. * @param {Object} canvasEl Canvas element to apply filter to
  16883. */
  16884. applyTo: function(canvasEl) {
  16885. var context = canvasEl.getContext('2d'),
  16886. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  16887. data = imageData.data,
  16888. threshold = this.threshold,
  16889. total = data.length;
  16890. for (var i = 0, len = data.length; i < len; i += 4) {
  16891. data[i + 3] = threshold + 255 * (total - i) / total;
  16892. }
  16893. context.putImageData(imageData, 0, 0);
  16894. },
  16895. /**
  16896. * Returns object representation of an instance
  16897. * @return {Object} Object representation of an instance
  16898. */
  16899. toObject: function() {
  16900. return extend(this.callSuper('toObject'), {
  16901. threshold: this.threshold
  16902. });
  16903. }
  16904. });
  16905. /**
  16906. * Returns filter instance from an object representation
  16907. * @static
  16908. * @param {Object} object Object to create an instance from
  16909. * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency
  16910. */
  16911. fabric.Image.filters.GradientTransparency.fromObject = function(object) {
  16912. return new fabric.Image.filters.GradientTransparency(object);
  16913. };
  16914. })(typeof exports !== 'undefined' ? exports : this);
  16915. (function(global) {
  16916. 'use strict';
  16917. var fabric = global.fabric || (global.fabric = { }),
  16918. filters = fabric.Image.filters,
  16919. createClass = fabric.util.createClass;
  16920. /**
  16921. * Grayscale image filter class
  16922. * @class fabric.Image.filters.Grayscale
  16923. * @memberOf fabric.Image.filters
  16924. * @extends fabric.Image.filters.BaseFilter
  16925. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  16926. * @example
  16927. * var filter = new fabric.Image.filters.Grayscale();
  16928. * object.filters.push(filter);
  16929. * object.applyFilters(canvas.renderAll.bind(canvas));
  16930. */
  16931. filters.Grayscale = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ {
  16932. /**
  16933. * Filter type
  16934. * @param {String} type
  16935. * @default
  16936. */
  16937. type: 'Grayscale',
  16938. /**
  16939. * Applies filter to canvas element
  16940. * @memberOf fabric.Image.filters.Grayscale.prototype
  16941. * @param {Object} canvasEl Canvas element to apply filter to
  16942. */
  16943. applyTo: function(canvasEl) {
  16944. var context = canvasEl.getContext('2d'),
  16945. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  16946. data = imageData.data,
  16947. len = imageData.width * imageData.height * 4,
  16948. index = 0,
  16949. average;
  16950. while (index < len) {
  16951. average = (data[index] + data[index + 1] + data[index + 2]) / 3;
  16952. data[index] = average;
  16953. data[index + 1] = average;
  16954. data[index + 2] = average;
  16955. index += 4;
  16956. }
  16957. context.putImageData(imageData, 0, 0);
  16958. }
  16959. });
  16960. /**
  16961. * Returns filter instance from an object representation
  16962. * @static
  16963. * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale
  16964. */
  16965. fabric.Image.filters.Grayscale.fromObject = function() {
  16966. return new fabric.Image.filters.Grayscale();
  16967. };
  16968. })(typeof exports !== 'undefined' ? exports : this);
  16969. (function(global) {
  16970. 'use strict';
  16971. var fabric = global.fabric || (global.fabric = { }),
  16972. filters = fabric.Image.filters,
  16973. createClass = fabric.util.createClass;
  16974. /**
  16975. * Invert filter class
  16976. * @class fabric.Image.filters.Invert
  16977. * @memberOf fabric.Image.filters
  16978. * @extends fabric.Image.filters.BaseFilter
  16979. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  16980. * @example
  16981. * var filter = new fabric.Image.filters.Invert();
  16982. * object.filters.push(filter);
  16983. * object.applyFilters(canvas.renderAll.bind(canvas));
  16984. */
  16985. filters.Invert = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ {
  16986. /**
  16987. * Filter type
  16988. * @param {String} type
  16989. * @default
  16990. */
  16991. type: 'Invert',
  16992. /**
  16993. * Applies filter to canvas element
  16994. * @memberOf fabric.Image.filters.Invert.prototype
  16995. * @param {Object} canvasEl Canvas element to apply filter to
  16996. */
  16997. applyTo: function(canvasEl) {
  16998. var context = canvasEl.getContext('2d'),
  16999. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17000. data = imageData.data,
  17001. iLen = data.length, i;
  17002. for (i = 0; i < iLen; i += 4) {
  17003. data[i] = 255 - data[i];
  17004. data[i + 1] = 255 - data[i + 1];
  17005. data[i + 2] = 255 - data[i + 2];
  17006. }
  17007. context.putImageData(imageData, 0, 0);
  17008. }
  17009. });
  17010. /**
  17011. * Returns filter instance from an object representation
  17012. * @static
  17013. * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert
  17014. */
  17015. fabric.Image.filters.Invert.fromObject = function() {
  17016. return new fabric.Image.filters.Invert();
  17017. };
  17018. })(typeof exports !== 'undefined' ? exports : this);
  17019. (function(global) {
  17020. 'use strict';
  17021. var fabric = global.fabric || (global.fabric = { }),
  17022. extend = fabric.util.object.extend,
  17023. filters = fabric.Image.filters,
  17024. createClass = fabric.util.createClass;
  17025. /**
  17026. * Mask filter class
  17027. * See http://resources.aleph-1.com/mask/
  17028. * @class fabric.Image.filters.Mask
  17029. * @memberOf fabric.Image.filters
  17030. * @extends fabric.Image.filters.BaseFilter
  17031. * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition
  17032. */
  17033. filters.Mask = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ {
  17034. /**
  17035. * Filter type
  17036. * @param {String} type
  17037. * @default
  17038. */
  17039. type: 'Mask',
  17040. /**
  17041. * Constructor
  17042. * @memberOf fabric.Image.filters.Mask.prototype
  17043. * @param {Object} [options] Options object
  17044. * @param {fabric.Image} [options.mask] Mask image object
  17045. * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)
  17046. */
  17047. initialize: function(options) {
  17048. options = options || { };
  17049. this.mask = options.mask;
  17050. this.channel = [0, 1, 2, 3].indexOf(options.channel) > -1 ? options.channel : 0;
  17051. },
  17052. /**
  17053. * Applies filter to canvas element
  17054. * @param {Object} canvasEl Canvas element to apply filter to
  17055. */
  17056. applyTo: function(canvasEl) {
  17057. if (!this.mask) {
  17058. return;
  17059. }
  17060. var context = canvasEl.getContext('2d'),
  17061. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17062. data = imageData.data,
  17063. maskEl = this.mask.getElement(),
  17064. maskCanvasEl = fabric.util.createCanvasElement(),
  17065. channel = this.channel,
  17066. i,
  17067. iLen = imageData.width * imageData.height * 4;
  17068. maskCanvasEl.width = canvasEl.width;
  17069. maskCanvasEl.height = canvasEl.height;
  17070. maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, canvasEl.width, canvasEl.height);
  17071. var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, canvasEl.width, canvasEl.height),
  17072. maskData = maskImageData.data;
  17073. for (i = 0; i < iLen; i += 4) {
  17074. data[i + 3] = maskData[i + channel];
  17075. }
  17076. context.putImageData(imageData, 0, 0);
  17077. },
  17078. /**
  17079. * Returns object representation of an instance
  17080. * @return {Object} Object representation of an instance
  17081. */
  17082. toObject: function() {
  17083. return extend(this.callSuper('toObject'), {
  17084. mask: this.mask.toObject(),
  17085. channel: this.channel
  17086. });
  17087. }
  17088. });
  17089. /**
  17090. * Returns filter instance from an object representation
  17091. * @static
  17092. * @param {Object} object Object to create an instance from
  17093. * @param {Function} [callback] Callback to invoke when a mask filter instance is created
  17094. */
  17095. fabric.Image.filters.Mask.fromObject = function(object, callback) {
  17096. fabric.util.loadImage(object.mask.src, function(img) {
  17097. object.mask = new fabric.Image(img, object.mask);
  17098. callback && callback(new fabric.Image.filters.Mask(object));
  17099. });
  17100. };
  17101. /**
  17102. * Indicates that instances of this type are async
  17103. * @static
  17104. * @type Boolean
  17105. * @default
  17106. */
  17107. fabric.Image.filters.Mask.async = true;
  17108. })(typeof exports !== 'undefined' ? exports : this);
  17109. (function(global) {
  17110. 'use strict';
  17111. var fabric = global.fabric || (global.fabric = { }),
  17112. extend = fabric.util.object.extend,
  17113. filters = fabric.Image.filters,
  17114. createClass = fabric.util.createClass;
  17115. /**
  17116. * Noise filter class
  17117. * @class fabric.Image.filters.Noise
  17118. * @memberOf fabric.Image.filters
  17119. * @extends fabric.Image.filters.BaseFilter
  17120. * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition
  17121. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17122. * @example
  17123. * var filter = new fabric.Image.filters.Noise({
  17124. * noise: 700
  17125. * });
  17126. * object.filters.push(filter);
  17127. * object.applyFilters(canvas.renderAll.bind(canvas));
  17128. */
  17129. filters.Noise = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ {
  17130. /**
  17131. * Filter type
  17132. * @param {String} type
  17133. * @default
  17134. */
  17135. type: 'Noise',
  17136. /**
  17137. * Constructor
  17138. * @memberOf fabric.Image.filters.Noise.prototype
  17139. * @param {Object} [options] Options object
  17140. * @param {Number} [options.noise=0] Noise value
  17141. */
  17142. initialize: function(options) {
  17143. options = options || { };
  17144. this.noise = options.noise || 0;
  17145. },
  17146. /**
  17147. * Applies filter to canvas element
  17148. * @param {Object} canvasEl Canvas element to apply filter to
  17149. */
  17150. applyTo: function(canvasEl) {
  17151. var context = canvasEl.getContext('2d'),
  17152. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17153. data = imageData.data,
  17154. noise = this.noise, rand;
  17155. for (var i = 0, len = data.length; i < len; i += 4) {
  17156. rand = (0.5 - Math.random()) * noise;
  17157. data[i] += rand;
  17158. data[i + 1] += rand;
  17159. data[i + 2] += rand;
  17160. }
  17161. context.putImageData(imageData, 0, 0);
  17162. },
  17163. /**
  17164. * Returns object representation of an instance
  17165. * @return {Object} Object representation of an instance
  17166. */
  17167. toObject: function() {
  17168. return extend(this.callSuper('toObject'), {
  17169. noise: this.noise
  17170. });
  17171. }
  17172. });
  17173. /**
  17174. * Returns filter instance from an object representation
  17175. * @static
  17176. * @param {Object} object Object to create an instance from
  17177. * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise
  17178. */
  17179. fabric.Image.filters.Noise.fromObject = function(object) {
  17180. return new fabric.Image.filters.Noise(object);
  17181. };
  17182. })(typeof exports !== 'undefined' ? exports : this);
  17183. (function(global) {
  17184. 'use strict';
  17185. var fabric = global.fabric || (global.fabric = { }),
  17186. extend = fabric.util.object.extend,
  17187. filters = fabric.Image.filters,
  17188. createClass = fabric.util.createClass;
  17189. /**
  17190. * Pixelate filter class
  17191. * @class fabric.Image.filters.Pixelate
  17192. * @memberOf fabric.Image.filters
  17193. * @extends fabric.Image.filters.BaseFilter
  17194. * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition
  17195. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17196. * @example
  17197. * var filter = new fabric.Image.filters.Pixelate({
  17198. * blocksize: 8
  17199. * });
  17200. * object.filters.push(filter);
  17201. * object.applyFilters(canvas.renderAll.bind(canvas));
  17202. */
  17203. filters.Pixelate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ {
  17204. /**
  17205. * Filter type
  17206. * @param {String} type
  17207. * @default
  17208. */
  17209. type: 'Pixelate',
  17210. /**
  17211. * Constructor
  17212. * @memberOf fabric.Image.filters.Pixelate.prototype
  17213. * @param {Object} [options] Options object
  17214. * @param {Number} [options.blocksize=4] Blocksize for pixelate
  17215. */
  17216. initialize: function(options) {
  17217. options = options || { };
  17218. this.blocksize = options.blocksize || 4;
  17219. },
  17220. /**
  17221. * Applies filter to canvas element
  17222. * @param {Object} canvasEl Canvas element to apply filter to
  17223. */
  17224. applyTo: function(canvasEl) {
  17225. var context = canvasEl.getContext('2d'),
  17226. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17227. data = imageData.data,
  17228. iLen = imageData.height,
  17229. jLen = imageData.width,
  17230. index, i, j, r, g, b, a;
  17231. for (i = 0; i < iLen; i += this.blocksize) {
  17232. for (j = 0; j < jLen; j += this.blocksize) {
  17233. index = (i * 4) * jLen + (j * 4);
  17234. r = data[index];
  17235. g = data[index + 1];
  17236. b = data[index + 2];
  17237. a = data[index + 3];
  17238. /*
  17239. blocksize: 4
  17240. [1,x,x,x,1]
  17241. [x,x,x,x,1]
  17242. [x,x,x,x,1]
  17243. [x,x,x,x,1]
  17244. [1,1,1,1,1]
  17245. */
  17246. for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) {
  17247. for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) {
  17248. index = (_i * 4) * jLen + (_j * 4);
  17249. data[index] = r;
  17250. data[index + 1] = g;
  17251. data[index + 2] = b;
  17252. data[index + 3] = a;
  17253. }
  17254. }
  17255. }
  17256. }
  17257. context.putImageData(imageData, 0, 0);
  17258. },
  17259. /**
  17260. * Returns object representation of an instance
  17261. * @return {Object} Object representation of an instance
  17262. */
  17263. toObject: function() {
  17264. return extend(this.callSuper('toObject'), {
  17265. blocksize: this.blocksize
  17266. });
  17267. }
  17268. });
  17269. /**
  17270. * Returns filter instance from an object representation
  17271. * @static
  17272. * @param {Object} object Object to create an instance from
  17273. * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate
  17274. */
  17275. fabric.Image.filters.Pixelate.fromObject = function(object) {
  17276. return new fabric.Image.filters.Pixelate(object);
  17277. };
  17278. })(typeof exports !== 'undefined' ? exports : this);
  17279. (function(global) {
  17280. 'use strict';
  17281. var fabric = global.fabric || (global.fabric = { }),
  17282. extend = fabric.util.object.extend,
  17283. filters = fabric.Image.filters,
  17284. createClass = fabric.util.createClass;
  17285. /**
  17286. * Remove white filter class
  17287. * @class fabric.Image.filters.RemoveWhite
  17288. * @memberOf fabric.Image.filters
  17289. * @extends fabric.Image.filters.BaseFilter
  17290. * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition
  17291. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17292. * @example
  17293. * var filter = new fabric.Image.filters.RemoveWhite({
  17294. * threshold: 40,
  17295. * distance: 140
  17296. * });
  17297. * object.filters.push(filter);
  17298. * object.applyFilters(canvas.renderAll.bind(canvas));
  17299. */
  17300. filters.RemoveWhite = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ {
  17301. /**
  17302. * Filter type
  17303. * @param {String} type
  17304. * @default
  17305. */
  17306. type: 'RemoveWhite',
  17307. /**
  17308. * Constructor
  17309. * @memberOf fabric.Image.filters.RemoveWhite.prototype
  17310. * @param {Object} [options] Options object
  17311. * @param {Number} [options.threshold=30] Threshold value
  17312. * @param {Number} [options.distance=20] Distance value
  17313. */
  17314. initialize: function(options) {
  17315. options = options || { };
  17316. this.threshold = options.threshold || 30;
  17317. this.distance = options.distance || 20;
  17318. },
  17319. /**
  17320. * Applies filter to canvas element
  17321. * @param {Object} canvasEl Canvas element to apply filter to
  17322. */
  17323. applyTo: function(canvasEl) {
  17324. var context = canvasEl.getContext('2d'),
  17325. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17326. data = imageData.data,
  17327. threshold = this.threshold,
  17328. distance = this.distance,
  17329. limit = 255 - threshold,
  17330. abs = Math.abs,
  17331. r, g, b;
  17332. for (var i = 0, len = data.length; i < len; i += 4) {
  17333. r = data[i];
  17334. g = data[i + 1];
  17335. b = data[i + 2];
  17336. if (r > limit &&
  17337. g > limit &&
  17338. b > limit &&
  17339. abs(r - g) < distance &&
  17340. abs(r - b) < distance &&
  17341. abs(g - b) < distance
  17342. ) {
  17343. data[i + 3] = 0;
  17344. }
  17345. }
  17346. context.putImageData(imageData, 0, 0);
  17347. },
  17348. /**
  17349. * Returns object representation of an instance
  17350. * @return {Object} Object representation of an instance
  17351. */
  17352. toObject: function() {
  17353. return extend(this.callSuper('toObject'), {
  17354. threshold: this.threshold,
  17355. distance: this.distance
  17356. });
  17357. }
  17358. });
  17359. /**
  17360. * Returns filter instance from an object representation
  17361. * @static
  17362. * @param {Object} object Object to create an instance from
  17363. * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite
  17364. */
  17365. fabric.Image.filters.RemoveWhite.fromObject = function(object) {
  17366. return new fabric.Image.filters.RemoveWhite(object);
  17367. };
  17368. })(typeof exports !== 'undefined' ? exports : this);
  17369. (function(global) {
  17370. 'use strict';
  17371. var fabric = global.fabric || (global.fabric = { }),
  17372. filters = fabric.Image.filters,
  17373. createClass = fabric.util.createClass;
  17374. /**
  17375. * Sepia filter class
  17376. * @class fabric.Image.filters.Sepia
  17377. * @memberOf fabric.Image.filters
  17378. * @extends fabric.Image.filters.BaseFilter
  17379. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17380. * @example
  17381. * var filter = new fabric.Image.filters.Sepia();
  17382. * object.filters.push(filter);
  17383. * object.applyFilters(canvas.renderAll.bind(canvas));
  17384. */
  17385. filters.Sepia = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ {
  17386. /**
  17387. * Filter type
  17388. * @param {String} type
  17389. * @default
  17390. */
  17391. type: 'Sepia',
  17392. /**
  17393. * Applies filter to canvas element
  17394. * @memberOf fabric.Image.filters.Sepia.prototype
  17395. * @param {Object} canvasEl Canvas element to apply filter to
  17396. */
  17397. applyTo: function(canvasEl) {
  17398. var context = canvasEl.getContext('2d'),
  17399. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17400. data = imageData.data,
  17401. iLen = data.length, i, avg;
  17402. for (i = 0; i < iLen; i += 4) {
  17403. avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
  17404. data[i] = avg + 100;
  17405. data[i + 1] = avg + 50;
  17406. data[i + 2] = avg + 255;
  17407. }
  17408. context.putImageData(imageData, 0, 0);
  17409. }
  17410. });
  17411. /**
  17412. * Returns filter instance from an object representation
  17413. * @static
  17414. * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia
  17415. */
  17416. fabric.Image.filters.Sepia.fromObject = function() {
  17417. return new fabric.Image.filters.Sepia();
  17418. };
  17419. })(typeof exports !== 'undefined' ? exports : this);
  17420. (function(global) {
  17421. 'use strict';
  17422. var fabric = global.fabric || (global.fabric = { }),
  17423. filters = fabric.Image.filters,
  17424. createClass = fabric.util.createClass;
  17425. /**
  17426. * Sepia2 filter class
  17427. * @class fabric.Image.filters.Sepia2
  17428. * @memberOf fabric.Image.filters
  17429. * @extends fabric.Image.filters.BaseFilter
  17430. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17431. * @example
  17432. * var filter = new fabric.Image.filters.Sepia2();
  17433. * object.filters.push(filter);
  17434. * object.applyFilters(canvas.renderAll.bind(canvas));
  17435. */
  17436. filters.Sepia2 = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ {
  17437. /**
  17438. * Filter type
  17439. * @param {String} type
  17440. * @default
  17441. */
  17442. type: 'Sepia2',
  17443. /**
  17444. * Applies filter to canvas element
  17445. * @memberOf fabric.Image.filters.Sepia.prototype
  17446. * @param {Object} canvasEl Canvas element to apply filter to
  17447. */
  17448. applyTo: function(canvasEl) {
  17449. var context = canvasEl.getContext('2d'),
  17450. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17451. data = imageData.data,
  17452. iLen = data.length, i, r, g, b;
  17453. for (i = 0; i < iLen; i += 4) {
  17454. r = data[i];
  17455. g = data[i + 1];
  17456. b = data[i + 2];
  17457. data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351;
  17458. data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203;
  17459. data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140;
  17460. }
  17461. context.putImageData(imageData, 0, 0);
  17462. }
  17463. });
  17464. /**
  17465. * Returns filter instance from an object representation
  17466. * @static
  17467. * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2
  17468. */
  17469. fabric.Image.filters.Sepia2.fromObject = function() {
  17470. return new fabric.Image.filters.Sepia2();
  17471. };
  17472. })(typeof exports !== 'undefined' ? exports : this);
  17473. (function(global) {
  17474. 'use strict';
  17475. var fabric = global.fabric || (global.fabric = { }),
  17476. extend = fabric.util.object.extend,
  17477. filters = fabric.Image.filters,
  17478. createClass = fabric.util.createClass;
  17479. /**
  17480. * Tint filter class
  17481. * Adapted from <a href="https://github.com/mezzoblue/PaintbrushJS">https://github.com/mezzoblue/PaintbrushJS</a>
  17482. * @class fabric.Image.filters.Tint
  17483. * @memberOf fabric.Image.filters
  17484. * @extends fabric.Image.filters.BaseFilter
  17485. * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition
  17486. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17487. * @example <caption>Tint filter with hex color and opacity</caption>
  17488. * var filter = new fabric.Image.filters.Tint({
  17489. * color: '#3513B0',
  17490. * opacity: 0.5
  17491. * });
  17492. * object.filters.push(filter);
  17493. * object.applyFilters(canvas.renderAll.bind(canvas));
  17494. * @example <caption>Tint filter with rgba color</caption>
  17495. * var filter = new fabric.Image.filters.Tint({
  17496. * color: 'rgba(53, 21, 176, 0.5)'
  17497. * });
  17498. * object.filters.push(filter);
  17499. * object.applyFilters(canvas.renderAll.bind(canvas));
  17500. */
  17501. filters.Tint = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ {
  17502. /**
  17503. * Filter type
  17504. * @param {String} type
  17505. * @default
  17506. */
  17507. type: 'Tint',
  17508. /**
  17509. * Constructor
  17510. * @memberOf fabric.Image.filters.Tint.prototype
  17511. * @param {Object} [options] Options object
  17512. * @param {String} [options.color=#000000] Color to tint the image with
  17513. * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1)
  17514. */
  17515. initialize: function(options) {
  17516. options = options || { };
  17517. this.color = options.color || '#000000';
  17518. this.opacity = typeof options.opacity !== 'undefined'
  17519. ? options.opacity
  17520. : new fabric.Color(this.color).getAlpha();
  17521. },
  17522. /**
  17523. * Applies filter to canvas element
  17524. * @param {Object} canvasEl Canvas element to apply filter to
  17525. */
  17526. applyTo: function(canvasEl) {
  17527. var context = canvasEl.getContext('2d'),
  17528. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17529. data = imageData.data,
  17530. iLen = data.length, i,
  17531. tintR, tintG, tintB,
  17532. r, g, b, alpha1,
  17533. source;
  17534. source = new fabric.Color(this.color).getSource();
  17535. tintR = source[0] * this.opacity;
  17536. tintG = source[1] * this.opacity;
  17537. tintB = source[2] * this.opacity;
  17538. alpha1 = 1 - this.opacity;
  17539. for (i = 0; i < iLen; i += 4) {
  17540. r = data[i];
  17541. g = data[i + 1];
  17542. b = data[i + 2];
  17543. // alpha compositing
  17544. data[i] = tintR + r * alpha1;
  17545. data[i + 1] = tintG + g * alpha1;
  17546. data[i + 2] = tintB + b * alpha1;
  17547. }
  17548. context.putImageData(imageData, 0, 0);
  17549. },
  17550. /**
  17551. * Returns object representation of an instance
  17552. * @return {Object} Object representation of an instance
  17553. */
  17554. toObject: function() {
  17555. return extend(this.callSuper('toObject'), {
  17556. color: this.color,
  17557. opacity: this.opacity
  17558. });
  17559. }
  17560. });
  17561. /**
  17562. * Returns filter instance from an object representation
  17563. * @static
  17564. * @param {Object} object Object to create an instance from
  17565. * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint
  17566. */
  17567. fabric.Image.filters.Tint.fromObject = function(object) {
  17568. return new fabric.Image.filters.Tint(object);
  17569. };
  17570. })(typeof exports !== 'undefined' ? exports : this);
  17571. (function(global) {
  17572. 'use strict';
  17573. var fabric = global.fabric || (global.fabric = { }),
  17574. extend = fabric.util.object.extend,
  17575. filters = fabric.Image.filters,
  17576. createClass = fabric.util.createClass;
  17577. /**
  17578. * Multiply filter class
  17579. * Adapted from <a href="http://www.laurenscorijn.com/articles/colormath-basics">http://www.laurenscorijn.com/articles/colormath-basics</a>
  17580. * @class fabric.Image.filters.Multiply
  17581. * @memberOf fabric.Image.filters
  17582. * @extends fabric.Image.filters.BaseFilter
  17583. * @example <caption>Multiply filter with hex color</caption>
  17584. * var filter = new fabric.Image.filters.Multiply({
  17585. * color: '#F0F'
  17586. * });
  17587. * object.filters.push(filter);
  17588. * object.applyFilters(canvas.renderAll.bind(canvas));
  17589. * @example <caption>Multiply filter with rgb color</caption>
  17590. * var filter = new fabric.Image.filters.Multiply({
  17591. * color: 'rgb(53, 21, 176)'
  17592. * });
  17593. * object.filters.push(filter);
  17594. * object.applyFilters(canvas.renderAll.bind(canvas));
  17595. */
  17596. filters.Multiply = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ {
  17597. /**
  17598. * Filter type
  17599. * @param {String} type
  17600. * @default
  17601. */
  17602. type: 'Multiply',
  17603. /**
  17604. * Constructor
  17605. * @memberOf fabric.Image.filters.Multiply.prototype
  17606. * @param {Object} [options] Options object
  17607. * @param {String} [options.color=#000000] Color to multiply the image pixels with
  17608. */
  17609. initialize: function(options) {
  17610. options = options || { };
  17611. this.color = options.color || '#000000';
  17612. },
  17613. /**
  17614. * Applies filter to canvas element
  17615. * @param {Object} canvasEl Canvas element to apply filter to
  17616. */
  17617. applyTo: function(canvasEl) {
  17618. var context = canvasEl.getContext('2d'),
  17619. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17620. data = imageData.data,
  17621. iLen = data.length, i,
  17622. source;
  17623. source = new fabric.Color(this.color).getSource();
  17624. for (i = 0; i < iLen; i += 4) {
  17625. data[i] *= source[0] / 255;
  17626. data[i + 1] *= source[1] / 255;
  17627. data[i + 2] *= source[2] / 255;
  17628. }
  17629. context.putImageData(imageData, 0, 0);
  17630. },
  17631. /**
  17632. * Returns object representation of an instance
  17633. * @return {Object} Object representation of an instance
  17634. */
  17635. toObject: function() {
  17636. return extend(this.callSuper('toObject'), {
  17637. color: this.color
  17638. });
  17639. }
  17640. });
  17641. /**
  17642. * Returns filter instance from an object representation
  17643. * @static
  17644. * @param {Object} object Object to create an instance from
  17645. * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply
  17646. */
  17647. fabric.Image.filters.Multiply.fromObject = function(object) {
  17648. return new fabric.Image.filters.Multiply(object);
  17649. };
  17650. })(typeof exports !== 'undefined' ? exports : this);
  17651. (function(global) {
  17652. 'use strict';
  17653. var fabric = global.fabric,
  17654. filters = fabric.Image.filters,
  17655. createClass = fabric.util.createClass;
  17656. /**
  17657. * Color Blend filter class
  17658. * @class fabric.Image.filter.Blend
  17659. * @memberOf fabric.Image.filters
  17660. * @extends fabric.Image.filters.BaseFilter
  17661. * @example
  17662. * var filter = new fabric.Image.filters.Blend({
  17663. * color: '#000',
  17664. * mode: 'multiply'
  17665. * });
  17666. *
  17667. * var filter = new fabric.Image.filters.Blend({
  17668. * image: fabricImageObject,
  17669. * mode: 'multiply',
  17670. * alpha: 0.5
  17671. * });
  17672. * object.filters.push(filter);
  17673. * object.applyFilters(canvas.renderAll.bind(canvas));
  17674. */
  17675. filters.Blend = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ {
  17676. type: 'Blend',
  17677. initialize: function(options) {
  17678. options = options || {};
  17679. this.color = options.color || '#000';
  17680. this.image = options.image || false;
  17681. this.mode = options.mode || 'multiply';
  17682. this.alpha = options.alpha || 1;
  17683. },
  17684. applyTo: function(canvasEl) {
  17685. var context = canvasEl.getContext('2d'),
  17686. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  17687. data = imageData.data,
  17688. tr, tg, tb,
  17689. r, g, b,
  17690. _r, _g, _b,
  17691. source,
  17692. isImage = false;
  17693. if (this.image) {
  17694. // Blend images
  17695. isImage = true;
  17696. var _el = fabric.util.createCanvasElement();
  17697. _el.width = this.image.width;
  17698. _el.height = this.image.height;
  17699. var tmpCanvas = new fabric.StaticCanvas(_el);
  17700. tmpCanvas.add(this.image);
  17701. var context2 = tmpCanvas.getContext('2d');
  17702. source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data;
  17703. }
  17704. else {
  17705. // Blend color
  17706. source = new fabric.Color(this.color).getSource();
  17707. tr = source[0] * this.alpha;
  17708. tg = source[1] * this.alpha;
  17709. tb = source[2] * this.alpha;
  17710. }
  17711. for (var i = 0, len = data.length; i < len; i += 4) {
  17712. r = data[i];
  17713. g = data[i + 1];
  17714. b = data[i + 2];
  17715. if (isImage) {
  17716. tr = source[i] * this.alpha;
  17717. tg = source[i + 1] * this.alpha;
  17718. tb = source[i + 2] * this.alpha;
  17719. }
  17720. switch (this.mode) {
  17721. case 'multiply':
  17722. data[i] = r * tr / 255;
  17723. data[i + 1] = g * tg / 255;
  17724. data[i + 2] = b * tb / 255;
  17725. break;
  17726. case 'screen':
  17727. data[i] = 1 - (1 - r) * (1 - tr);
  17728. data[i + 1] = 1 - (1 - g) * (1 - tg);
  17729. data[i + 2] = 1 - (1 - b) * (1 - tb);
  17730. break;
  17731. case 'add':
  17732. data[i] = Math.min(255, r + tr);
  17733. data[i + 1] = Math.min(255, g + tg);
  17734. data[i + 2] = Math.min(255, b + tb);
  17735. break;
  17736. case 'diff':
  17737. case 'difference':
  17738. data[i] = Math.abs(r - tr);
  17739. data[i + 1] = Math.abs(g - tg);
  17740. data[i + 2] = Math.abs(b - tb);
  17741. break;
  17742. case 'subtract':
  17743. _r = r - tr;
  17744. _g = g - tg;
  17745. _b = b - tb;
  17746. data[i] = (_r < 0) ? 0 : _r;
  17747. data[i + 1] = (_g < 0) ? 0 : _g;
  17748. data[i + 2] = (_b < 0) ? 0 : _b;
  17749. break;
  17750. case 'darken':
  17751. data[i] = Math.min(r, tr);
  17752. data[i + 1] = Math.min(g, tg);
  17753. data[i + 2] = Math.min(b, tb);
  17754. break;
  17755. case 'lighten':
  17756. data[i] = Math.max(r, tr);
  17757. data[i + 1] = Math.max(g, tg);
  17758. data[i + 2] = Math.max(b, tb);
  17759. break;
  17760. }
  17761. }
  17762. context.putImageData(imageData, 0, 0);
  17763. },
  17764. /**
  17765. * Returns object representation of an instance
  17766. * @return {Object} Object representation of an instance
  17767. */
  17768. toObject: function() {
  17769. return {
  17770. color: this.color,
  17771. image: this.image,
  17772. mode: this.mode,
  17773. alpha: this.alpha
  17774. };
  17775. }
  17776. });
  17777. fabric.Image.filters.Blend.fromObject = function(object) {
  17778. return new fabric.Image.filters.Blend(object);
  17779. };
  17780. })(typeof exports !== 'undefined' ? exports : this);
  17781. (function(global) {
  17782. 'use strict';
  17783. var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor,
  17784. sqrt = Math.sqrt, abs = Math.abs, max = Math.max, round = Math.round, sin = Math.sin,
  17785. ceil = Math.ceil,
  17786. filters = fabric.Image.filters,
  17787. createClass = fabric.util.createClass;
  17788. /**
  17789. * Resize image filter class
  17790. * @class fabric.Image.filters.Resize
  17791. * @memberOf fabric.Image.filters
  17792. * @extends fabric.Image.filters.BaseFilter
  17793. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  17794. * @example
  17795. * var filter = new fabric.Image.filters.Resize();
  17796. * object.filters.push(filter);
  17797. * object.applyFilters(canvas.renderAll.bind(canvas));
  17798. */
  17799. filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ {
  17800. /**
  17801. * Filter type
  17802. * @param {String} type
  17803. * @default
  17804. */
  17805. type: 'Resize',
  17806. /**
  17807. * Resize type
  17808. * @param {String} resizeType
  17809. * @default
  17810. */
  17811. resizeType: 'hermite',
  17812. /**
  17813. * Scale factor for resizing, x axis
  17814. * @param {Number} scaleX
  17815. * @default
  17816. */
  17817. scaleX: 0,
  17818. /**
  17819. * Scale factor for resizing, y axis
  17820. * @param {Number} scaleY
  17821. * @default
  17822. */
  17823. scaleY: 0,
  17824. /**
  17825. * LanczosLobes parameter for lanczos filter
  17826. * @param {Number} lanczosLobes
  17827. * @default
  17828. */
  17829. lanczosLobes: 3,
  17830. /**
  17831. * Applies filter to canvas element
  17832. * @memberOf fabric.Image.filters.Resize.prototype
  17833. * @param {Object} canvasEl Canvas element to apply filter to
  17834. * @param {Number} scaleX
  17835. * @param {Number} scaleY
  17836. */
  17837. applyTo: function(canvasEl, scaleX, scaleY) {
  17838. if (scaleX === 1 && scaleY === 1) {
  17839. return;
  17840. }
  17841. this.rcpScaleX = 1 / scaleX;
  17842. this.rcpScaleY = 1 / scaleY;
  17843. var oW = canvasEl.width, oH = canvasEl.height,
  17844. dW = round(oW * scaleX), dH = round(oH * scaleY),
  17845. imageData;
  17846. if (this.resizeType === 'sliceHack') {
  17847. imageData = this.sliceByTwo(canvasEl, oW, oH, dW, dH);
  17848. }
  17849. if (this.resizeType === 'hermite') {
  17850. imageData = this.hermiteFastResize(canvasEl, oW, oH, dW, dH);
  17851. }
  17852. if (this.resizeType === 'bilinear') {
  17853. imageData = this.bilinearFiltering(canvasEl, oW, oH, dW, dH);
  17854. }
  17855. if (this.resizeType === 'lanczos') {
  17856. imageData = this.lanczosResize(canvasEl, oW, oH, dW, dH);
  17857. }
  17858. canvasEl.width = dW;
  17859. canvasEl.height = dH;
  17860. canvasEl.getContext('2d').putImageData(imageData, 0, 0);
  17861. },
  17862. /**
  17863. * Filter sliceByTwo
  17864. * @param {Object} canvasEl Canvas element to apply filter to
  17865. * @param {Number} oW Original Width
  17866. * @param {Number} oH Original Height
  17867. * @param {Number} dW Destination Width
  17868. * @param {Number} dH Destination Height
  17869. * @returns {ImageData}
  17870. */
  17871. sliceByTwo: function(canvasEl, oW, oH, dW, dH) {
  17872. var context = canvasEl.getContext('2d'), imageData,
  17873. multW = 0.5, multH = 0.5, signW = 1, signH = 1,
  17874. doneW = false, doneH = false, stepW = oW, stepH = oH,
  17875. tmpCanvas = fabric.util.createCanvasElement(),
  17876. tmpCtx = tmpCanvas.getContext('2d');
  17877. dW = floor(dW);
  17878. dH = floor(dH);
  17879. tmpCanvas.width = max(dW, oW);
  17880. tmpCanvas.height = max(dH, oH);
  17881. if (dW > oW) {
  17882. multW = 2;
  17883. signW = -1;
  17884. }
  17885. if (dH > oH) {
  17886. multH = 2;
  17887. signH = -1;
  17888. }
  17889. imageData = context.getImageData(0, 0, oW, oH);
  17890. canvasEl.width = max(dW, oW);
  17891. canvasEl.height = max(dH, oH);
  17892. context.putImageData(imageData, 0, 0);
  17893. while (!doneW || !doneH) {
  17894. oW = stepW;
  17895. oH = stepH;
  17896. if (dW * signW < floor(stepW * multW * signW)) {
  17897. stepW = floor(stepW * multW);
  17898. }
  17899. else {
  17900. stepW = dW;
  17901. doneW = true;
  17902. }
  17903. if (dH * signH < floor(stepH * multH * signH)) {
  17904. stepH = floor(stepH * multH);
  17905. }
  17906. else {
  17907. stepH = dH;
  17908. doneH = true;
  17909. }
  17910. imageData = context.getImageData(0, 0, oW, oH);
  17911. tmpCtx.putImageData(imageData, 0, 0);
  17912. context.clearRect(0, 0, stepW, stepH);
  17913. context.drawImage(tmpCanvas, 0, 0, oW, oH, 0, 0, stepW, stepH);
  17914. }
  17915. return context.getImageData(0, 0, dW, dH);
  17916. },
  17917. /**
  17918. * Filter lanczosResize
  17919. * @param {Object} canvasEl Canvas element to apply filter to
  17920. * @param {Number} oW Original Width
  17921. * @param {Number} oH Original Height
  17922. * @param {Number} dW Destination Width
  17923. * @param {Number} dH Destination Height
  17924. * @returns {ImageData}
  17925. */
  17926. lanczosResize: function(canvasEl, oW, oH, dW, dH) {
  17927. function lanczosCreate(lobes) {
  17928. return function(x) {
  17929. if (x > lobes) {
  17930. return 0;
  17931. }
  17932. x *= Math.PI;
  17933. if (abs(x) < 1e-16) {
  17934. return 1;
  17935. }
  17936. var xx = x / lobes;
  17937. return sin(x) * sin(xx) / x / xx;
  17938. };
  17939. }
  17940. function process(u) {
  17941. var v, i, weight, idx, a, red, green,
  17942. blue, alpha, fX, fY;
  17943. center.x = (u + 0.5) * ratioX;
  17944. icenter.x = floor(center.x);
  17945. for (v = 0; v < dH; v++) {
  17946. center.y = (v + 0.5) * ratioY;
  17947. icenter.y = floor(center.y);
  17948. a = 0; red = 0; green = 0; blue = 0; alpha = 0;
  17949. for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) {
  17950. if (i < 0 || i >= oW) {
  17951. continue;
  17952. }
  17953. fX = floor(1000 * abs(i - center.x));
  17954. if (!cacheLanc[fX]) {
  17955. cacheLanc[fX] = { };
  17956. }
  17957. for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) {
  17958. if (j < 0 || j >= oH) {
  17959. continue;
  17960. }
  17961. fY = floor(1000 * abs(j - center.y));
  17962. if (!cacheLanc[fX][fY]) {
  17963. cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000);
  17964. }
  17965. weight = cacheLanc[fX][fY];
  17966. if (weight > 0) {
  17967. idx = (j * oW + i) * 4;
  17968. a += weight;
  17969. red += weight * srcData[idx];
  17970. green += weight * srcData[idx + 1];
  17971. blue += weight * srcData[idx + 2];
  17972. alpha += weight * srcData[idx + 3];
  17973. }
  17974. }
  17975. }
  17976. idx = (v * dW + u) * 4;
  17977. destData[idx] = red / a;
  17978. destData[idx + 1] = green / a;
  17979. destData[idx + 2] = blue / a;
  17980. destData[idx + 3] = alpha / a;
  17981. }
  17982. if (++u < dW) {
  17983. return process(u);
  17984. }
  17985. else {
  17986. return destImg;
  17987. }
  17988. }
  17989. var context = canvasEl.getContext('2d'),
  17990. srcImg = context.getImageData(0, 0, oW, oH),
  17991. destImg = context.getImageData(0, 0, dW, dH),
  17992. srcData = srcImg.data, destData = destImg.data,
  17993. lanczos = lanczosCreate(this.lanczosLobes),
  17994. ratioX = this.rcpScaleX, ratioY = this.rcpScaleY,
  17995. rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY,
  17996. range2X = ceil(ratioX * this.lanczosLobes / 2),
  17997. range2Y = ceil(ratioY * this.lanczosLobes / 2),
  17998. cacheLanc = { }, center = { }, icenter = { };
  17999. return process(0);
  18000. },
  18001. /**
  18002. * bilinearFiltering
  18003. * @param {Object} canvasEl Canvas element to apply filter to
  18004. * @param {Number} oW Original Width
  18005. * @param {Number} oH Original Height
  18006. * @param {Number} dW Destination Width
  18007. * @param {Number} dH Destination Height
  18008. * @returns {ImageData}
  18009. */
  18010. bilinearFiltering: function(canvasEl, oW, oH, dW, dH) {
  18011. var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl,
  18012. color, offset = 0, origPix, ratioX = this.rcpScaleX,
  18013. ratioY = this.rcpScaleY, context = canvasEl.getContext('2d'),
  18014. w4 = 4 * (oW - 1), img = context.getImageData(0, 0, oW, oH),
  18015. pixels = img.data, destImage = context.getImageData(0, 0, dW, dH),
  18016. destPixels = destImage.data;
  18017. for (i = 0; i < dH; i++) {
  18018. for (j = 0; j < dW; j++) {
  18019. x = floor(ratioX * j);
  18020. y = floor(ratioY * i);
  18021. xDiff = ratioX * j - x;
  18022. yDiff = ratioY * i - y;
  18023. origPix = 4 * (y * oW + x);
  18024. for (chnl = 0; chnl < 4; chnl++) {
  18025. a = pixels[origPix + chnl];
  18026. b = pixels[origPix + 4 + chnl];
  18027. c = pixels[origPix + w4 + chnl];
  18028. d = pixels[origPix + w4 + 4 + chnl];
  18029. color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) +
  18030. c * yDiff * (1 - xDiff) + d * xDiff * yDiff;
  18031. destPixels[offset++] = color;
  18032. }
  18033. }
  18034. }
  18035. return destImage;
  18036. },
  18037. /**
  18038. * hermiteFastResize
  18039. * @param {Object} canvasEl Canvas element to apply filter to
  18040. * @param {Number} oW Original Width
  18041. * @param {Number} oH Original Height
  18042. * @param {Number} dW Destination Width
  18043. * @param {Number} dH Destination Height
  18044. * @returns {ImageData}
  18045. */
  18046. hermiteFastResize: function(canvasEl, oW, oH, dW, dH) {
  18047. var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY,
  18048. ratioWHalf = ceil(ratioW / 2),
  18049. ratioHHalf = ceil(ratioH / 2),
  18050. context = canvasEl.getContext('2d'),
  18051. img = context.getImageData(0, 0, oW, oH), data = img.data,
  18052. img2 = context.getImageData(0, 0, dW, dH), data2 = img2.data;
  18053. for (var j = 0; j < dH; j++) {
  18054. for (var i = 0; i < dW; i++) {
  18055. var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0,
  18056. gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH;
  18057. for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) {
  18058. var dy = abs(centerY - (yy + 0.5)) / ratioHHalf,
  18059. centerX = (i + 0.5) * ratioW, w0 = dy * dy;
  18060. for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) {
  18061. var dx = abs(centerX - (xx + 0.5)) / ratioWHalf,
  18062. w = sqrt(w0 + dx * dx);
  18063. /* eslint-disable max-depth */
  18064. if (w > 1 && w < -1) {
  18065. continue;
  18066. }
  18067. //hermite filter
  18068. weight = 2 * w * w * w - 3 * w * w + 1;
  18069. if (weight > 0) {
  18070. dx = 4 * (xx + yy * oW);
  18071. //alpha
  18072. gxA += weight * data[dx + 3];
  18073. weightsAlpha += weight;
  18074. //colors
  18075. if (data[dx + 3] < 255) {
  18076. weight = weight * data[dx + 3] / 250;
  18077. }
  18078. gxR += weight * data[dx];
  18079. gxG += weight * data[dx + 1];
  18080. gxB += weight * data[dx + 2];
  18081. weights += weight;
  18082. }
  18083. /* eslint-enable max-depth */
  18084. }
  18085. }
  18086. data2[x2] = gxR / weights;
  18087. data2[x2 + 1] = gxG / weights;
  18088. data2[x2 + 2] = gxB / weights;
  18089. data2[x2 + 3] = gxA / weightsAlpha;
  18090. }
  18091. }
  18092. return img2;
  18093. },
  18094. /**
  18095. * Returns object representation of an instance
  18096. * @return {Object} Object representation of an instance
  18097. */
  18098. toObject: function() {
  18099. return {
  18100. type: this.type,
  18101. scaleX: this.scaleX,
  18102. scaleY: this.scaleY,
  18103. resizeType: this.resizeType,
  18104. lanczosLobes: this.lanczosLobes
  18105. };
  18106. }
  18107. });
  18108. /**
  18109. * Returns filter instance from an object representation
  18110. * @static
  18111. * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize
  18112. */
  18113. fabric.Image.filters.Resize.fromObject = function(object) {
  18114. return new fabric.Image.filters.Resize(object);
  18115. };
  18116. })(typeof exports !== 'undefined' ? exports : this);
  18117. (function(global) {
  18118. 'use strict';
  18119. var fabric = global.fabric || (global.fabric = { }),
  18120. extend = fabric.util.object.extend,
  18121. filters = fabric.Image.filters,
  18122. createClass = fabric.util.createClass;
  18123. /**
  18124. * Color Matrix filter class
  18125. * @class fabric.Image.filters.ColorMatrix
  18126. * @memberOf fabric.Image.filters
  18127. * @extends fabric.Image.filters.BaseFilter
  18128. * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition
  18129. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18130. * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php}
  18131. * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl}
  18132. * @example <caption>Kodachrome filter</caption>
  18133. * var filter = new fabric.Image.filters.ColorMatrix({
  18134. * matrix: [
  18135. 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502,
  18136. -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203,
  18137. -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946,
  18138. 0, 0, 0, 1, 0
  18139. ]
  18140. * });
  18141. * object.filters.push(filter);
  18142. * object.applyFilters(canvas.renderAll.bind(canvas));
  18143. */
  18144. filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ {
  18145. /**
  18146. * Filter type
  18147. * @param {String} type
  18148. * @default
  18149. */
  18150. type: 'ColorMatrix',
  18151. /**
  18152. * Constructor
  18153. * @memberOf fabric.Image.filters.ColorMatrix.prototype
  18154. * @param {Object} [options] Options object
  18155. * @param {Array} [options.matrix] Color Matrix to modify the image data with
  18156. */
  18157. initialize: function( options ) {
  18158. options || ( options = {} );
  18159. this.matrix = options.matrix || [
  18160. 1, 0, 0, 0, 0,
  18161. 0, 1, 0, 0, 0,
  18162. 0, 0, 1, 0, 0,
  18163. 0, 0, 0, 1, 0
  18164. ];
  18165. },
  18166. /**
  18167. * Applies filter to canvas element
  18168. * @param {Object} canvasEl Canvas element to apply filter to
  18169. */
  18170. applyTo: function( canvasEl ) {
  18171. var context = canvasEl.getContext( '2d' ),
  18172. imageData = context.getImageData( 0, 0, canvasEl.width, canvasEl.height ),
  18173. data = imageData.data,
  18174. iLen = data.length,
  18175. i,
  18176. r,
  18177. g,
  18178. b,
  18179. a,
  18180. m = this.matrix;
  18181. for ( i = 0; i < iLen; i += 4 ) {
  18182. r = data[ i ];
  18183. g = data[ i + 1 ];
  18184. b = data[ i + 2 ];
  18185. a = data[ i + 3 ];
  18186. data[ i ] = r * m[ 0 ] + g * m[ 1 ] + b * m[ 2 ] + a * m[ 3 ] + m[ 4 ];
  18187. data[ i + 1 ] = r * m[ 5 ] + g * m[ 6 ] + b * m[ 7 ] + a * m[ 8 ] + m[ 9 ];
  18188. data[ i + 2 ] = r * m[ 10 ] + g * m[ 11 ] + b * m[ 12 ] + a * m[ 13 ] + m[ 14 ];
  18189. data[ i + 3 ] = r * m[ 15 ] + g * m[ 16 ] + b * m[ 17 ] + a * m[ 18 ] + m[ 19 ];
  18190. }
  18191. context.putImageData( imageData, 0, 0 );
  18192. },
  18193. /**
  18194. * Returns object representation of an instance
  18195. * @return {Object} Object representation of an instance
  18196. */
  18197. toObject: function() {
  18198. return extend(this.callSuper('toObject'), {
  18199. type: this.type,
  18200. matrix: this.matrix
  18201. });
  18202. }
  18203. });
  18204. /**
  18205. * Returns filter instance from an object representation
  18206. * @static
  18207. * @param {Object} object Object to create an instance from
  18208. * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix
  18209. */
  18210. fabric.Image.filters.ColorMatrix.fromObject = function( object ) {
  18211. return new fabric.Image.filters.ColorMatrix( object );
  18212. };
  18213. })(typeof exports !== 'undefined' ? exports : this);
  18214. (function(global) {
  18215. 'use strict';
  18216. var fabric = global.fabric || (global.fabric = { }),
  18217. extend = fabric.util.object.extend,
  18218. filters = fabric.Image.filters,
  18219. createClass = fabric.util.createClass;
  18220. /**
  18221. * Contrast filter class
  18222. * @class fabric.Image.filters.Contrast
  18223. * @memberOf fabric.Image.filters
  18224. * @extends fabric.Image.filters.BaseFilter
  18225. * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition
  18226. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18227. * @example
  18228. * var filter = new fabric.Image.filters.Contrast({
  18229. * contrast: 40
  18230. * });
  18231. * object.filters.push(filter);
  18232. * object.applyFilters(canvas.renderAll.bind(canvas));
  18233. */
  18234. filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ {
  18235. /**
  18236. * Filter type
  18237. * @param {String} type
  18238. * @default
  18239. */
  18240. type: 'Contrast',
  18241. /**
  18242. * Constructor
  18243. * @memberOf fabric.Image.filters.Contrast.prototype
  18244. * @param {Object} [options] Options object
  18245. * @param {Number} [options.contrast=0] Value to contrast the image up (-255...255)
  18246. */
  18247. initialize: function(options) {
  18248. options = options || { };
  18249. this.contrast = options.contrast || 0;
  18250. },
  18251. /**
  18252. * Applies filter to canvas element
  18253. * @param {Object} canvasEl Canvas element to apply filter to
  18254. */
  18255. applyTo: function(canvasEl) {
  18256. var context = canvasEl.getContext('2d'),
  18257. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18258. data = imageData.data,
  18259. contrastF = 259 * (this.contrast + 255) / (255 * (259 - this.contrast));
  18260. for (var i = 0, len = data.length; i < len; i += 4) {
  18261. data[i] = contrastF * (data[i] - 128) + 128;
  18262. data[i + 1] = contrastF * (data[i + 1] - 128) + 128;
  18263. data[i + 2] = contrastF * (data[i + 2] - 128) + 128;
  18264. }
  18265. context.putImageData(imageData, 0, 0);
  18266. },
  18267. /**
  18268. * Returns object representation of an instance
  18269. * @return {Object} Object representation of an instance
  18270. */
  18271. toObject: function() {
  18272. return extend(this.callSuper('toObject'), {
  18273. contrast: this.contrast
  18274. });
  18275. }
  18276. });
  18277. /**
  18278. * Returns filter instance from an object representation
  18279. * @static
  18280. * @param {Object} object Object to create an instance from
  18281. * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast
  18282. */
  18283. fabric.Image.filters.Contrast.fromObject = function(object) {
  18284. return new fabric.Image.filters.Contrast(object);
  18285. };
  18286. })(typeof exports !== 'undefined' ? exports : this);
  18287. (function(global) {
  18288. 'use strict';
  18289. var fabric = global.fabric || (global.fabric = { }),
  18290. extend = fabric.util.object.extend,
  18291. filters = fabric.Image.filters,
  18292. createClass = fabric.util.createClass;
  18293. /**
  18294. * Saturate filter class
  18295. * @class fabric.Image.filters.Saturate
  18296. * @memberOf fabric.Image.filters
  18297. * @extends fabric.Image.filters.BaseFilter
  18298. * @see {@link fabric.Image.filters.Saturate#initialize} for constructor definition
  18299. * @see {@link http://fabricjs.com/image-filters|ImageFilters demo}
  18300. * @example
  18301. * var filter = new fabric.Image.filters.Saturate({
  18302. * saturate: 100
  18303. * });
  18304. * object.filters.push(filter);
  18305. * object.applyFilters(canvas.renderAll.bind(canvas));
  18306. */
  18307. filters.Saturate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturate.prototype */ {
  18308. /**
  18309. * Filter type
  18310. * @param {String} type
  18311. * @default
  18312. */
  18313. type: 'Saturate',
  18314. /**
  18315. * Constructor
  18316. * @memberOf fabric.Image.filters.Saturate.prototype
  18317. * @param {Object} [options] Options object
  18318. * @param {Number} [options.saturate=0] Value to saturate the image (-100...100)
  18319. */
  18320. initialize: function(options) {
  18321. options = options || { };
  18322. this.saturate = options.saturate || 0;
  18323. },
  18324. /**
  18325. * Applies filter to canvas element
  18326. * @param {Object} canvasEl Canvas element to apply filter to
  18327. */
  18328. applyTo: function(canvasEl) {
  18329. var context = canvasEl.getContext('2d'),
  18330. imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
  18331. data = imageData.data,
  18332. max, adjust = -this.saturate * 0.01;
  18333. for (var i = 0, len = data.length; i < len; i += 4) {
  18334. max = Math.max(data[i], data[i + 1], data[i + 2]);
  18335. data[i] += max !== data[i] ? (max - data[i]) * adjust : 0;
  18336. data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0;
  18337. data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0;
  18338. }
  18339. context.putImageData(imageData, 0, 0);
  18340. },
  18341. /**
  18342. * Returns object representation of an instance
  18343. * @return {Object} Object representation of an instance
  18344. */
  18345. toObject: function() {
  18346. return extend(this.callSuper('toObject'), {
  18347. saturate: this.saturate
  18348. });
  18349. }
  18350. });
  18351. /**
  18352. * Returns filter instance from an object representation
  18353. * @static
  18354. * @param {Object} object Object to create an instance from
  18355. * @return {fabric.Image.filters.Saturate} Instance of fabric.Image.filters.Saturate
  18356. */
  18357. fabric.Image.filters.Saturate.fromObject = function(object) {
  18358. return new fabric.Image.filters.Saturate(object);
  18359. };
  18360. })(typeof exports !== 'undefined' ? exports : this);
  18361. (function(global) {
  18362. 'use strict';
  18363. var fabric = global.fabric || (global.fabric = { }),
  18364. clone = fabric.util.object.clone,
  18365. toFixed = fabric.util.toFixed,
  18366. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS,
  18367. MIN_TEXT_WIDTH = 2;
  18368. if (fabric.Text) {
  18369. fabric.warn('fabric.Text is already defined');
  18370. return;
  18371. }
  18372. var stateProperties = fabric.Object.prototype.stateProperties.concat();
  18373. stateProperties.push(
  18374. 'fontFamily',
  18375. 'fontWeight',
  18376. 'fontSize',
  18377. 'text',
  18378. 'textDecoration',
  18379. 'textAlign',
  18380. 'fontStyle',
  18381. 'lineHeight',
  18382. 'textBackgroundColor'
  18383. );
  18384. /**
  18385. * Text class
  18386. * @class fabric.Text
  18387. * @extends fabric.Object
  18388. * @return {fabric.Text} thisArg
  18389. * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text}
  18390. * @see {@link fabric.Text#initialize} for constructor definition
  18391. */
  18392. fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ {
  18393. /**
  18394. * Properties which when set cause object to change dimensions
  18395. * @type Object
  18396. * @private
  18397. */
  18398. _dimensionAffectingProps: {
  18399. fontSize: true,
  18400. fontWeight: true,
  18401. fontFamily: true,
  18402. fontStyle: true,
  18403. lineHeight: true,
  18404. text: true,
  18405. charSpacing: true,
  18406. textAlign: true,
  18407. strokeWidth: false,
  18408. },
  18409. /**
  18410. * @private
  18411. */
  18412. _reNewline: /\r?\n/,
  18413. /**
  18414. * Use this regular expression to filter for whitespace that is not a new line.
  18415. * Mostly used when text is 'justify' aligned.
  18416. * @private
  18417. */
  18418. _reSpacesAndTabs: /[ \t\r]+/g,
  18419. /**
  18420. * Retrieves object's fontSize
  18421. * @method getFontSize
  18422. * @memberOf fabric.Text.prototype
  18423. * @return {String} Font size (in pixels)
  18424. */
  18425. /**
  18426. * Sets object's fontSize
  18427. * Does not update the object .width and .height,
  18428. * call ._initDimensions() to update the values.
  18429. * @method setFontSize
  18430. * @memberOf fabric.Text.prototype
  18431. * @param {Number} fontSize Font size (in pixels)
  18432. * @return {fabric.Text}
  18433. * @chainable
  18434. */
  18435. /**
  18436. * Retrieves object's fontWeight
  18437. * @method getFontWeight
  18438. * @memberOf fabric.Text.prototype
  18439. * @return {(String|Number)} Font weight
  18440. */
  18441. /**
  18442. * Sets object's fontWeight
  18443. * Does not update the object .width and .height,
  18444. * call ._initDimensions() to update the values.
  18445. * @method setFontWeight
  18446. * @memberOf fabric.Text.prototype
  18447. * @param {(Number|String)} fontWeight Font weight
  18448. * @return {fabric.Text}
  18449. * @chainable
  18450. */
  18451. /**
  18452. * Retrieves object's fontFamily
  18453. * @method getFontFamily
  18454. * @memberOf fabric.Text.prototype
  18455. * @return {String} Font family
  18456. */
  18457. /**
  18458. * Sets object's fontFamily
  18459. * Does not update the object .width and .height,
  18460. * call ._initDimensions() to update the values.
  18461. * @method setFontFamily
  18462. * @memberOf fabric.Text.prototype
  18463. * @param {String} fontFamily Font family
  18464. * @return {fabric.Text}
  18465. * @chainable
  18466. */
  18467. /**
  18468. * Retrieves object's text
  18469. * @method getText
  18470. * @memberOf fabric.Text.prototype
  18471. * @return {String} text
  18472. */
  18473. /**
  18474. * Sets object's text
  18475. * Does not update the object .width and .height,
  18476. * call ._initDimensions() to update the values.
  18477. * @method setText
  18478. * @memberOf fabric.Text.prototype
  18479. * @param {String} text Text
  18480. * @return {fabric.Text}
  18481. * @chainable
  18482. */
  18483. /**
  18484. * Retrieves object's textDecoration
  18485. * @method getTextDecoration
  18486. * @memberOf fabric.Text.prototype
  18487. * @return {String} Text decoration
  18488. */
  18489. /**
  18490. * Sets object's textDecoration
  18491. * @method setTextDecoration
  18492. * @memberOf fabric.Text.prototype
  18493. * @param {String} textDecoration Text decoration
  18494. * @return {fabric.Text}
  18495. * @chainable
  18496. */
  18497. /**
  18498. * Retrieves object's fontStyle
  18499. * @method getFontStyle
  18500. * @memberOf fabric.Text.prototype
  18501. * @return {String} Font style
  18502. */
  18503. /**
  18504. * Sets object's fontStyle
  18505. * Does not update the object .width and .height,
  18506. * call ._initDimensions() to update the values.
  18507. * @method setFontStyle
  18508. * @memberOf fabric.Text.prototype
  18509. * @param {String} fontStyle Font style
  18510. * @return {fabric.Text}
  18511. * @chainable
  18512. */
  18513. /**
  18514. * Retrieves object's lineHeight
  18515. * @method getLineHeight
  18516. * @memberOf fabric.Text.prototype
  18517. * @return {Number} Line height
  18518. */
  18519. /**
  18520. * Sets object's lineHeight
  18521. * @method setLineHeight
  18522. * @memberOf fabric.Text.prototype
  18523. * @param {Number} lineHeight Line height
  18524. * @return {fabric.Text}
  18525. * @chainable
  18526. */
  18527. /**
  18528. * Retrieves object's textAlign
  18529. * @method getTextAlign
  18530. * @memberOf fabric.Text.prototype
  18531. * @return {String} Text alignment
  18532. */
  18533. /**
  18534. * Sets object's textAlign
  18535. * @method setTextAlign
  18536. * @memberOf fabric.Text.prototype
  18537. * @param {String} textAlign Text alignment
  18538. * @return {fabric.Text}
  18539. * @chainable
  18540. */
  18541. /**
  18542. * Retrieves object's textBackgroundColor
  18543. * @method getTextBackgroundColor
  18544. * @memberOf fabric.Text.prototype
  18545. * @return {String} Text background color
  18546. */
  18547. /**
  18548. * Sets object's textBackgroundColor
  18549. * @method setTextBackgroundColor
  18550. * @memberOf fabric.Text.prototype
  18551. * @param {String} textBackgroundColor Text background color
  18552. * @return {fabric.Text}
  18553. * @chainable
  18554. */
  18555. /**
  18556. * Type of an object
  18557. * @type String
  18558. * @default
  18559. */
  18560. type: 'text',
  18561. /**
  18562. * Font size (in pixels)
  18563. * @type Number
  18564. * @default
  18565. */
  18566. fontSize: 40,
  18567. /**
  18568. * Font weight (e.g. bold, normal, 400, 600, 800)
  18569. * @type {(Number|String)}
  18570. * @default
  18571. */
  18572. fontWeight: 'normal',
  18573. /**
  18574. * Font family
  18575. * @type String
  18576. * @default
  18577. */
  18578. fontFamily: 'Times New Roman',
  18579. /**
  18580. * Text decoration Possible values: "", "underline", "overline" or "line-through".
  18581. * @type String
  18582. * @default
  18583. */
  18584. textDecoration: '',
  18585. /**
  18586. * Text alignment. Possible values: "left", "center", "right" or "justify".
  18587. * @type String
  18588. * @default
  18589. */
  18590. textAlign: 'left',
  18591. /**
  18592. * Font style . Possible values: "", "normal", "italic" or "oblique".
  18593. * @type String
  18594. * @default
  18595. */
  18596. fontStyle: '',
  18597. /**
  18598. * Line height
  18599. * @type Number
  18600. * @default
  18601. */
  18602. lineHeight: 1.16,
  18603. /**
  18604. * Background color of text lines
  18605. * @type String
  18606. * @default
  18607. */
  18608. textBackgroundColor: '',
  18609. /**
  18610. * List of properties to consider when checking if
  18611. * state of an object is changed ({@link fabric.Object#hasStateChanged})
  18612. * as well as for history (undo/redo) purposes
  18613. * @type Array
  18614. */
  18615. stateProperties: stateProperties,
  18616. /**
  18617. * When defined, an object is rendered via stroke and this property specifies its color.
  18618. * <b>Backwards incompatibility note:</b> This property was named "strokeStyle" until v1.1.6
  18619. * @type String
  18620. * @default
  18621. */
  18622. stroke: null,
  18623. /**
  18624. * Shadow object representing shadow of this shape.
  18625. * <b>Backwards incompatibility note:</b> This property was named "textShadow" (String) until v1.2.11
  18626. * @type fabric.Shadow
  18627. * @default
  18628. */
  18629. shadow: null,
  18630. /**
  18631. * @private
  18632. */
  18633. _fontSizeFraction: 0.25,
  18634. /**
  18635. * Text Line proportion to font Size (in pixels)
  18636. * @type Number
  18637. * @default
  18638. */
  18639. _fontSizeMult: 1.13,
  18640. /**
  18641. * additional space between characters
  18642. * expressed in thousands of em unit
  18643. * @type Number
  18644. * @default
  18645. */
  18646. charSpacing: 0,
  18647. /**
  18648. * Constructor
  18649. * @param {String} text Text string
  18650. * @param {Object} [options] Options object
  18651. * @return {fabric.Text} thisArg
  18652. */
  18653. initialize: function(text, options) {
  18654. options = options || { };
  18655. this.text = text;
  18656. this.__skipDimension = true;
  18657. this.setOptions(options);
  18658. this.__skipDimension = false;
  18659. this._initDimensions();
  18660. },
  18661. /**
  18662. * Initialize text dimensions. Render all text on given context
  18663. * or on a offscreen canvas to get the text width with measureText.
  18664. * Updates this.width and this.height with the proper values.
  18665. * Does not return dimensions.
  18666. * @param {CanvasRenderingContext2D} [ctx] Context to render on
  18667. * @private
  18668. */
  18669. _initDimensions: function(ctx) {
  18670. if (this.__skipDimension) {
  18671. return;
  18672. }
  18673. if (!ctx) {
  18674. ctx = fabric.util.createCanvasElement().getContext('2d');
  18675. this._setTextStyles(ctx);
  18676. }
  18677. this._textLines = this._splitTextIntoLines();
  18678. this._clearCache();
  18679. this.width = this._getTextWidth(ctx) || this.cursorWidth || MIN_TEXT_WIDTH;
  18680. this.height = this._getTextHeight(ctx);
  18681. },
  18682. /**
  18683. * Returns string representation of an instance
  18684. * @return {String} String representation of text object
  18685. */
  18686. toString: function() {
  18687. return '#<fabric.Text (' + this.complexity() +
  18688. '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';
  18689. },
  18690. /**
  18691. * @private
  18692. * @param {CanvasRenderingContext2D} ctx Context to render on
  18693. */
  18694. _render: function(ctx) {
  18695. this.clipTo && fabric.util.clipContext(this, ctx);
  18696. this._setOpacity(ctx);
  18697. this._setShadow(ctx);
  18698. this._setupCompositeOperation(ctx);
  18699. this._renderTextBackground(ctx);
  18700. this._setStrokeStyles(ctx);
  18701. this._setFillStyles(ctx);
  18702. this._renderText(ctx);
  18703. this._renderTextDecoration(ctx);
  18704. this.clipTo && ctx.restore();
  18705. },
  18706. /**
  18707. * @private
  18708. * @param {CanvasRenderingContext2D} ctx Context to render on
  18709. */
  18710. _renderText: function(ctx) {
  18711. this._renderTextFill(ctx);
  18712. this._renderTextStroke(ctx);
  18713. },
  18714. /**
  18715. * @private
  18716. * @param {CanvasRenderingContext2D} ctx Context to render on
  18717. */
  18718. _setTextStyles: function(ctx) {
  18719. ctx.textBaseline = 'alphabetic';
  18720. ctx.font = this._getFontDeclaration();
  18721. },
  18722. /**
  18723. * @private
  18724. * @return {Number} Height of fabric.Text object
  18725. */
  18726. _getTextHeight: function() {
  18727. return this._getHeightOfSingleLine() + (this._textLines.length - 1) * this._getHeightOfLine();
  18728. },
  18729. /**
  18730. * @private
  18731. * @param {CanvasRenderingContext2D} ctx Context to render on
  18732. * @return {Number} Maximum width of fabric.Text object
  18733. */
  18734. _getTextWidth: function(ctx) {
  18735. var maxWidth = this._getLineWidth(ctx, 0);
  18736. for (var i = 1, len = this._textLines.length; i < len; i++) {
  18737. var currentLineWidth = this._getLineWidth(ctx, i);
  18738. if (currentLineWidth > maxWidth) {
  18739. maxWidth = currentLineWidth;
  18740. }
  18741. }
  18742. return maxWidth;
  18743. },
  18744. /*
  18745. * Calculate object dimensions from its properties
  18746. * @override
  18747. * @private
  18748. */
  18749. _getNonTransformedDimensions: function() {
  18750. return { x: this.width, y: this.height };
  18751. },
  18752. /**
  18753. * @private
  18754. * @param {String} method Method name ("fillText" or "strokeText")
  18755. * @param {CanvasRenderingContext2D} ctx Context to render on
  18756. * @param {String} chars Chars to render
  18757. * @param {Number} left Left position of text
  18758. * @param {Number} top Top position of text
  18759. */
  18760. _renderChars: function(method, ctx, chars, left, top) {
  18761. // remove Text word from method var
  18762. var shortM = method.slice(0, -4), char, width;
  18763. if (this[shortM].toLive) {
  18764. var offsetX = -this.width / 2 + this[shortM].offsetX || 0,
  18765. offsetY = -this.height / 2 + this[shortM].offsetY || 0;
  18766. ctx.save();
  18767. ctx.translate(offsetX, offsetY);
  18768. left -= offsetX;
  18769. top -= offsetY;
  18770. }
  18771. if (this.charSpacing !== 0) {
  18772. var additionalSpace = this._getWidthOfCharSpacing();
  18773. chars = chars.split('');
  18774. for (var i = 0, len = chars.length; i < len; i++) {
  18775. char = chars[i];
  18776. width = ctx.measureText(char).width + additionalSpace;
  18777. ctx[method](char, left, top);
  18778. left += width > 0 ? width : 0;
  18779. }
  18780. }
  18781. else {
  18782. ctx[method](chars, left, top);
  18783. }
  18784. this[shortM].toLive && ctx.restore();
  18785. },
  18786. /**
  18787. * @private
  18788. * @param {String} method Method name ("fillText" or "strokeText")
  18789. * @param {CanvasRenderingContext2D} ctx Context to render on
  18790. * @param {String} line Text to render
  18791. * @param {Number} left Left position of text
  18792. * @param {Number} top Top position of text
  18793. * @param {Number} lineIndex Index of a line in a text
  18794. */
  18795. _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
  18796. // lift the line by quarter of fontSize
  18797. top -= this.fontSize * this._fontSizeFraction;
  18798. // short-circuit
  18799. var lineWidth = this._getLineWidth(ctx, lineIndex);
  18800. if (this.textAlign !== 'justify' || this.width < lineWidth) {
  18801. this._renderChars(method, ctx, line, left, top, lineIndex);
  18802. return;
  18803. }
  18804. // stretch the line
  18805. var words = line.split(/\s+/),
  18806. charOffset = 0,
  18807. wordsWidth = this._getWidthOfWords(ctx, words.join(' '), lineIndex, 0),
  18808. widthDiff = this.width - wordsWidth,
  18809. numSpaces = words.length - 1,
  18810. spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0,
  18811. leftOffset = 0, word;
  18812. for (var i = 0, len = words.length; i < len; i++) {
  18813. while (line[charOffset] === ' ' && charOffset < line.length) {
  18814. charOffset++;
  18815. }
  18816. word = words[i];
  18817. this._renderChars(method, ctx, word, left + leftOffset, top, lineIndex, charOffset);
  18818. leftOffset += this._getWidthOfWords(ctx, word, lineIndex, charOffset) + spaceWidth;
  18819. charOffset += word.length;
  18820. }
  18821. },
  18822. /**
  18823. * @private
  18824. * @param {CanvasRenderingContext2D} ctx Context to render on
  18825. * @param {String} word
  18826. */
  18827. _getWidthOfWords: function (ctx, word) {
  18828. var width = ctx.measureText(word).width, charCount, additionalSpace;
  18829. if (this.charSpacing !== 0) {
  18830. charCount = word.split('').length;
  18831. additionalSpace = charCount * this._getWidthOfCharSpacing();
  18832. width += additionalSpace;
  18833. }
  18834. return width > 0 ? width : 0;
  18835. },
  18836. /**
  18837. * @private
  18838. * @return {Number} Left offset
  18839. */
  18840. _getLeftOffset: function() {
  18841. return -this.width / 2;
  18842. },
  18843. /**
  18844. * @private
  18845. * @return {Number} Top offset
  18846. */
  18847. _getTopOffset: function() {
  18848. return -this.height / 2;
  18849. },
  18850. /**
  18851. * Returns true because text has no style
  18852. */
  18853. isEmptyStyles: function() {
  18854. return true;
  18855. },
  18856. /**
  18857. * @private
  18858. * @param {CanvasRenderingContext2D} ctx Context to render on
  18859. * @param {String} method Method name ("fillText" or "strokeText")
  18860. */
  18861. _renderTextCommon: function(ctx, method) {
  18862. var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset();
  18863. for (var i = 0, len = this._textLines.length; i < len; i++) {
  18864. var heightOfLine = this._getHeightOfLine(ctx, i),
  18865. maxHeight = heightOfLine / this.lineHeight,
  18866. lineWidth = this._getLineWidth(ctx, i),
  18867. leftOffset = this._getLineLeftOffset(lineWidth);
  18868. this._renderTextLine(
  18869. method,
  18870. ctx,
  18871. this._textLines[i],
  18872. left + leftOffset,
  18873. top + lineHeights + maxHeight,
  18874. i
  18875. );
  18876. lineHeights += heightOfLine;
  18877. }
  18878. },
  18879. /**
  18880. * @private
  18881. * @param {CanvasRenderingContext2D} ctx Context to render on
  18882. */
  18883. _renderTextFill: function(ctx) {
  18884. if (!this.fill && this.isEmptyStyles()) {
  18885. return;
  18886. }
  18887. this._renderTextCommon(ctx, 'fillText');
  18888. },
  18889. /**
  18890. * @private
  18891. * @param {CanvasRenderingContext2D} ctx Context to render on
  18892. */
  18893. _renderTextStroke: function(ctx) {
  18894. if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) {
  18895. return;
  18896. }
  18897. if (this.shadow && !this.shadow.affectStroke) {
  18898. this._removeShadow(ctx);
  18899. }
  18900. ctx.save();
  18901. this._setLineDash(ctx, this.strokedashArray);
  18902. ctx.beginPath();
  18903. this._renderTextCommon(ctx, 'strokeText');
  18904. ctx.closePath();
  18905. ctx.restore();
  18906. },
  18907. /**
  18908. * @private
  18909. * @return {Number} height of line
  18910. */
  18911. _getHeightOfLine: function() {
  18912. return this._getHeightOfSingleLine() * this.lineHeight;
  18913. },
  18914. /**
  18915. * @private
  18916. * @return {Number} height of line without lineHeight
  18917. */
  18918. _getHeightOfSingleLine: function() {
  18919. return this.fontSize * this._fontSizeMult;
  18920. },
  18921. /**
  18922. * @private
  18923. * @param {CanvasRenderingContext2D} ctx Context to render on
  18924. */
  18925. _renderTextBackground: function(ctx) {
  18926. this._renderBackground(ctx);
  18927. this._renderTextLinesBackground(ctx);
  18928. },
  18929. /**
  18930. * @private
  18931. * @param {CanvasRenderingContext2D} ctx Context to render on
  18932. */
  18933. _renderTextLinesBackground: function(ctx) {
  18934. if (!this.textBackgroundColor) {
  18935. return;
  18936. }
  18937. var lineTopOffset = 0, heightOfLine,
  18938. lineWidth, lineLeftOffset;
  18939. ctx.fillStyle = this.textBackgroundColor;
  18940. for (var i = 0, len = this._textLines.length; i < len; i++) {
  18941. heightOfLine = this._getHeightOfLine(ctx, i);
  18942. lineWidth = this._getLineWidth(ctx, i);
  18943. if (lineWidth > 0) {
  18944. lineLeftOffset = this._getLineLeftOffset(lineWidth);
  18945. ctx.fillRect(
  18946. this._getLeftOffset() + lineLeftOffset,
  18947. this._getTopOffset() + lineTopOffset,
  18948. lineWidth,
  18949. heightOfLine / this.lineHeight
  18950. );
  18951. }
  18952. lineTopOffset += heightOfLine;
  18953. }
  18954. // if there is text background color no
  18955. // other shadows should be casted
  18956. this._removeShadow(ctx);
  18957. },
  18958. /**
  18959. * @private
  18960. * @param {Number} lineWidth Width of text line
  18961. * @return {Number} Line left offset
  18962. */
  18963. _getLineLeftOffset: function(lineWidth) {
  18964. if (this.textAlign === 'center') {
  18965. return (this.width - lineWidth) / 2;
  18966. }
  18967. if (this.textAlign === 'right') {
  18968. return this.width - lineWidth;
  18969. }
  18970. return 0;
  18971. },
  18972. /**
  18973. * @private
  18974. */
  18975. _clearCache: function() {
  18976. this.__lineWidths = [];
  18977. this.__lineHeights = [];
  18978. },
  18979. /**
  18980. * @private
  18981. */
  18982. _shouldClearCache: function() {
  18983. var shouldClear = false;
  18984. if (this._forceClearCache) {
  18985. this._forceClearCache = false;
  18986. return true;
  18987. }
  18988. for (var prop in this._dimensionAffectingProps) {
  18989. if (this['__' + prop] !== this[prop]) {
  18990. this['__' + prop] = this[prop];
  18991. shouldClear = true;
  18992. }
  18993. }
  18994. return shouldClear;
  18995. },
  18996. /**
  18997. * @private
  18998. * @param {CanvasRenderingContext2D} ctx Context to render on
  18999. * @param {Number} lineIndex line number
  19000. * @return {Number} Line width
  19001. */
  19002. _getLineWidth: function(ctx, lineIndex) {
  19003. if (this.__lineWidths[lineIndex]) {
  19004. return this.__lineWidths[lineIndex] === -1 ? this.width : this.__lineWidths[lineIndex];
  19005. }
  19006. var width, wordCount, line = this._textLines[lineIndex];
  19007. if (line === '') {
  19008. width = 0;
  19009. }
  19010. else {
  19011. width = this._measureLine(ctx, lineIndex);
  19012. }
  19013. this.__lineWidths[lineIndex] = width;
  19014. if (width && this.textAlign === 'justify') {
  19015. wordCount = line.split(/\s+/);
  19016. if (wordCount.length > 1) {
  19017. this.__lineWidths[lineIndex] = -1;
  19018. }
  19019. }
  19020. return width;
  19021. },
  19022. _getWidthOfCharSpacing: function() {
  19023. if (this.charSpacing !== 0) {
  19024. return this.fontSize * this.charSpacing / 1000;
  19025. }
  19026. return 0;
  19027. },
  19028. /**
  19029. * @private
  19030. * @param {CanvasRenderingContext2D} ctx Context to render on
  19031. * @param {Number} lineIndex line number
  19032. * @return {Number} Line width
  19033. */
  19034. _measureLine: function(ctx, lineIndex) {
  19035. var line = this._textLines[lineIndex],
  19036. width = ctx.measureText(line).width,
  19037. additionalSpace = 0, charCount, finalWidth;
  19038. if (this.charSpacing !== 0) {
  19039. charCount = line.split('').length;
  19040. additionalSpace = (charCount - 1) * this._getWidthOfCharSpacing();
  19041. }
  19042. finalWidth = width + additionalSpace;
  19043. return finalWidth > 0 ? finalWidth : 0;
  19044. },
  19045. /**
  19046. * @private
  19047. * @param {CanvasRenderingContext2D} ctx Context to render on
  19048. */
  19049. _renderTextDecoration: function(ctx) {
  19050. if (!this.textDecoration) {
  19051. return;
  19052. }
  19053. var halfOfVerticalBox = this.height / 2,
  19054. _this = this, offsets = [];
  19055. /** @ignore */
  19056. function renderLinesAtOffset(offsets) {
  19057. var i, lineHeight = 0, len, j, oLen, lineWidth,
  19058. lineLeftOffset, heightOfLine;
  19059. for (i = 0, len = _this._textLines.length; i < len; i++) {
  19060. lineWidth = _this._getLineWidth(ctx, i);
  19061. lineLeftOffset = _this._getLineLeftOffset(lineWidth);
  19062. heightOfLine = _this._getHeightOfLine(ctx, i);
  19063. for (j = 0, oLen = offsets.length; j < oLen; j++) {
  19064. ctx.fillRect(
  19065. _this._getLeftOffset() + lineLeftOffset,
  19066. lineHeight + (_this._fontSizeMult - 1 + offsets[j] ) * _this.fontSize - halfOfVerticalBox,
  19067. lineWidth,
  19068. _this.fontSize / 15);
  19069. }
  19070. lineHeight += heightOfLine;
  19071. }
  19072. }
  19073. if (this.textDecoration.indexOf('underline') > -1) {
  19074. offsets.push(0.85); // 1 - 3/16
  19075. }
  19076. if (this.textDecoration.indexOf('line-through') > -1) {
  19077. offsets.push(0.43);
  19078. }
  19079. if (this.textDecoration.indexOf('overline') > -1) {
  19080. offsets.push(-0.12);
  19081. }
  19082. if (offsets.length > 0) {
  19083. renderLinesAtOffset(offsets);
  19084. }
  19085. },
  19086. /**
  19087. * return font declaration string for canvas context
  19088. * @returns {String} font declaration formatted for canvas context.
  19089. */
  19090. _getFontDeclaration: function() {
  19091. return [
  19092. // node-canvas needs "weight style", while browsers need "style weight"
  19093. (fabric.isLikelyNode ? this.fontWeight : this.fontStyle),
  19094. (fabric.isLikelyNode ? this.fontStyle : this.fontWeight),
  19095. this.fontSize + 'px',
  19096. (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)
  19097. ].join(' ');
  19098. },
  19099. /**
  19100. * Renders text instance on a specified context
  19101. * @param {CanvasRenderingContext2D} ctx Context to render on
  19102. * @param {Boolean} noTransform
  19103. */
  19104. render: function(ctx, noTransform) {
  19105. // do not render if object is not visible
  19106. if (!this.visible) {
  19107. return;
  19108. }
  19109. ctx.save();
  19110. this._setTextStyles(ctx);
  19111. if (this._shouldClearCache()) {
  19112. this._initDimensions(ctx);
  19113. }
  19114. this.drawSelectionBackground(ctx);
  19115. if (!noTransform) {
  19116. this.transform(ctx);
  19117. }
  19118. if (this.transformMatrix) {
  19119. ctx.transform.apply(ctx, this.transformMatrix);
  19120. }
  19121. if (this.group && this.group.type === 'path-group') {
  19122. ctx.translate(this.left, this.top);
  19123. }
  19124. this._render(ctx);
  19125. ctx.restore();
  19126. },
  19127. /**
  19128. * Returns the text as an array of lines.
  19129. * @returns {Array} Lines in the text
  19130. */
  19131. _splitTextIntoLines: function() {
  19132. return this.text.split(this._reNewline);
  19133. },
  19134. /**
  19135. * Returns object representation of an instance
  19136. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  19137. * @return {Object} Object representation of an instance
  19138. */
  19139. toObject: function(propertiesToInclude) {
  19140. var additionalProperties = [
  19141. 'text',
  19142. 'fontSize',
  19143. 'fontWeight',
  19144. 'fontFamily',
  19145. 'fontStyle',
  19146. 'lineHeight',
  19147. 'textDecoration',
  19148. 'textAlign',
  19149. 'textBackgroundColor',
  19150. 'charSpacing'
  19151. ].concat(propertiesToInclude);
  19152. return this.callSuper('toObject', additionalProperties);
  19153. },
  19154. /* _TO_SVG_START_ */
  19155. /**
  19156. * Returns SVG representation of an instance
  19157. * @param {Function} [reviver] Method for further parsing of svg representation.
  19158. * @return {String} svg representation of an instance
  19159. */
  19160. toSVG: function(reviver) {
  19161. if (!this.ctx) {
  19162. this.ctx = fabric.util.createCanvasElement().getContext('2d');
  19163. }
  19164. var markup = this._createBaseSVGMarkup(),
  19165. offsets = this._getSVGLeftTopOffsets(this.ctx),
  19166. textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft);
  19167. this._wrapSVGTextAndBg(markup, textAndBg);
  19168. return reviver ? reviver(markup.join('')) : markup.join('');
  19169. },
  19170. /**
  19171. * @private
  19172. */
  19173. _getSVGLeftTopOffsets: function(ctx) {
  19174. var lineTop = this._getHeightOfLine(ctx, 0),
  19175. textLeft = -this.width / 2,
  19176. textTop = 0;
  19177. return {
  19178. textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0),
  19179. textTop: textTop + (this.group && this.group.type === 'path-group' ? -this.top : 0),
  19180. lineTop: lineTop
  19181. };
  19182. },
  19183. /**
  19184. * @private
  19185. */
  19186. _wrapSVGTextAndBg: function(markup, textAndBg) {
  19187. var noShadow = true, filter = this.getSvgFilter(),
  19188. style = filter === '' ? '' : ' style="' + filter + '"';
  19189. markup.push(
  19190. '\t<g ', this.getSvgId(), 'transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '"',
  19191. style, '>\n',
  19192. textAndBg.textBgRects.join(''),
  19193. '\t\t<text ',
  19194. (this.fontFamily ? 'font-family="' + this.fontFamily.replace(/"/g, '\'') + '" ' : ''),
  19195. (this.fontSize ? 'font-size="' + this.fontSize + '" ' : ''),
  19196. (this.fontStyle ? 'font-style="' + this.fontStyle + '" ' : ''),
  19197. (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ' : ''),
  19198. (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ' : ''),
  19199. 'style="', this.getSvgStyles(noShadow), '" >\n',
  19200. textAndBg.textSpans.join(''),
  19201. '\t\t</text>\n',
  19202. '\t</g>\n'
  19203. );
  19204. },
  19205. /**
  19206. * @private
  19207. * @param {Number} textTopOffset Text top offset
  19208. * @param {Number} textLeftOffset Text left offset
  19209. * @return {Object}
  19210. */
  19211. _getSVGTextAndBg: function(textTopOffset, textLeftOffset) {
  19212. var textSpans = [],
  19213. textBgRects = [],
  19214. height = 0;
  19215. // bounding-box background
  19216. this._setSVGBg(textBgRects);
  19217. // text and text-background
  19218. for (var i = 0, len = this._textLines.length; i < len; i++) {
  19219. if (this.textBackgroundColor) {
  19220. this._setSVGTextLineBg(textBgRects, i, textLeftOffset, textTopOffset, height);
  19221. }
  19222. this._setSVGTextLineText(i, textSpans, height, textLeftOffset, textTopOffset, textBgRects);
  19223. height += this._getHeightOfLine(this.ctx, i);
  19224. }
  19225. return {
  19226. textSpans: textSpans,
  19227. textBgRects: textBgRects
  19228. };
  19229. },
  19230. _setSVGTextLineText: function(i, textSpans, height, textLeftOffset, textTopOffset) {
  19231. var yPos = this.fontSize * (this._fontSizeMult - this._fontSizeFraction)
  19232. - textTopOffset + height - this.height / 2;
  19233. if (this.textAlign === 'justify') {
  19234. // i call from here to do not intefere with IText
  19235. this._setSVGTextLineJustifed(i, textSpans, yPos, textLeftOffset);
  19236. return;
  19237. }
  19238. textSpans.push(
  19239. '\t\t\t<tspan x="',
  19240. toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS), '" ',
  19241. 'y="',
  19242. toFixed(yPos, NUM_FRACTION_DIGITS),
  19243. '" ',
  19244. // doing this on <tspan> elements since setting opacity
  19245. // on containing <text> one doesn't work in Illustrator
  19246. this._getFillAttributes(this.fill), '>',
  19247. fabric.util.string.escapeXml(this._textLines[i]),
  19248. '</tspan>\n'
  19249. );
  19250. },
  19251. _setSVGTextLineJustifed: function(i, textSpans, yPos, textLeftOffset) {
  19252. var ctx = fabric.util.createCanvasElement().getContext('2d');
  19253. this._setTextStyles(ctx);
  19254. var line = this._textLines[i],
  19255. words = line.split(/\s+/),
  19256. wordsWidth = this._getWidthOfWords(ctx, words.join('')),
  19257. widthDiff = this.width - wordsWidth,
  19258. numSpaces = words.length - 1,
  19259. spaceWidth = numSpaces > 0 ? widthDiff / numSpaces : 0,
  19260. word, attributes = this._getFillAttributes(this.fill),
  19261. len;
  19262. textLeftOffset += this._getLineLeftOffset(this._getLineWidth(ctx, i));
  19263. for (i = 0, len = words.length; i < len; i++) {
  19264. word = words[i];
  19265. textSpans.push(
  19266. '\t\t\t<tspan x="',
  19267. toFixed(textLeftOffset, NUM_FRACTION_DIGITS), '" ',
  19268. 'y="',
  19269. toFixed(yPos, NUM_FRACTION_DIGITS),
  19270. '" ',
  19271. // doing this on <tspan> elements since setting opacity
  19272. // on containing <text> one doesn't work in Illustrator
  19273. attributes, '>',
  19274. fabric.util.string.escapeXml(word),
  19275. '</tspan>\n'
  19276. );
  19277. textLeftOffset += this._getWidthOfWords(ctx, word) + spaceWidth;
  19278. }
  19279. },
  19280. _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, textTopOffset, height) {
  19281. textBgRects.push(
  19282. '\t\t<rect ',
  19283. this._getFillAttributes(this.textBackgroundColor),
  19284. ' x="',
  19285. toFixed(textLeftOffset + this._getLineLeftOffset(this._getLineWidth(this.ctx, i)), NUM_FRACTION_DIGITS),
  19286. '" y="',
  19287. toFixed(height - this.height / 2, NUM_FRACTION_DIGITS),
  19288. '" width="',
  19289. toFixed(this._getLineWidth(this.ctx, i), NUM_FRACTION_DIGITS),
  19290. '" height="',
  19291. toFixed(this._getHeightOfLine(this.ctx, i) / this.lineHeight, NUM_FRACTION_DIGITS),
  19292. '"></rect>\n');
  19293. },
  19294. _setSVGBg: function(textBgRects) {
  19295. if (this.backgroundColor) {
  19296. textBgRects.push(
  19297. '\t\t<rect ',
  19298. this._getFillAttributes(this.backgroundColor),
  19299. ' x="',
  19300. toFixed(-this.width / 2, NUM_FRACTION_DIGITS),
  19301. '" y="',
  19302. toFixed(-this.height / 2, NUM_FRACTION_DIGITS),
  19303. '" width="',
  19304. toFixed(this.width, NUM_FRACTION_DIGITS),
  19305. '" height="',
  19306. toFixed(this.height, NUM_FRACTION_DIGITS),
  19307. '"></rect>\n');
  19308. }
  19309. },
  19310. /**
  19311. * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
  19312. * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
  19313. *
  19314. * @private
  19315. * @param {*} value
  19316. * @return {String}
  19317. */
  19318. _getFillAttributes: function(value) {
  19319. var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : '';
  19320. if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {
  19321. return 'fill="' + value + '"';
  19322. }
  19323. return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';
  19324. },
  19325. /* _TO_SVG_END_ */
  19326. /**
  19327. * Sets specified property to a specified value
  19328. * @param {String} key
  19329. * @param {*} value
  19330. * @return {fabric.Text} thisArg
  19331. * @chainable
  19332. */
  19333. _set: function(key, value) {
  19334. this.callSuper('_set', key, value);
  19335. if (key in this._dimensionAffectingProps) {
  19336. this._initDimensions();
  19337. this.setCoords();
  19338. }
  19339. },
  19340. /**
  19341. * Returns complexity of an instance
  19342. * @return {Number} complexity
  19343. */
  19344. complexity: function() {
  19345. return 1;
  19346. }
  19347. });
  19348. /* _FROM_SVG_START_ */
  19349. /**
  19350. * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})
  19351. * @static
  19352. * @memberOf fabric.Text
  19353. * @see: http://www.w3.org/TR/SVG/text.html#TextElement
  19354. */
  19355. fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(
  19356. 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' '));
  19357. /**
  19358. * Default SVG font size
  19359. * @static
  19360. * @memberOf fabric.Text
  19361. */
  19362. fabric.Text.DEFAULT_SVG_FONT_SIZE = 16;
  19363. /**
  19364. * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
  19365. * @static
  19366. * @memberOf fabric.Text
  19367. * @param {SVGElement} element Element to parse
  19368. * @param {Object} [options] Options object
  19369. * @return {fabric.Text} Instance of fabric.Text
  19370. */
  19371. fabric.Text.fromElement = function(element, options) {
  19372. if (!element) {
  19373. return null;
  19374. }
  19375. var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
  19376. options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);
  19377. options.top = options.top || 0;
  19378. options.left = options.left || 0;
  19379. if ('dx' in parsedAttributes) {
  19380. options.left += parsedAttributes.dx;
  19381. }
  19382. if ('dy' in parsedAttributes) {
  19383. options.top += parsedAttributes.dy;
  19384. }
  19385. if (!('fontSize' in options)) {
  19386. options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
  19387. }
  19388. if (!options.originX) {
  19389. options.originX = 'left';
  19390. }
  19391. var textContent = '';
  19392. // The XML is not properly parsed in IE9 so a workaround to get
  19393. // textContent is through firstChild.data. Another workaround would be
  19394. // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does)
  19395. if (!('textContent' in element)) {
  19396. if ('firstChild' in element && element.firstChild !== null) {
  19397. if ('data' in element.firstChild && element.firstChild.data !== null) {
  19398. textContent = element.firstChild.data;
  19399. }
  19400. }
  19401. }
  19402. else {
  19403. textContent = element.textContent;
  19404. }
  19405. textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' ');
  19406. var text = new fabric.Text(textContent, options),
  19407. textHeightScaleFactor = text.getHeight() / text.height,
  19408. lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height,
  19409. scaledDiff = lineHeightDiff * textHeightScaleFactor,
  19410. textHeight = text.getHeight() + scaledDiff,
  19411. offX = 0;
  19412. /*
  19413. Adjust positioning:
  19414. x/y attributes in SVG correspond to the bottom-left corner of text bounding box
  19415. top/left properties in Fabric correspond to center point of text bounding box
  19416. */
  19417. if (text.originX === 'left') {
  19418. offX = text.getWidth() / 2;
  19419. }
  19420. if (text.originX === 'right') {
  19421. offX = -text.getWidth() / 2;
  19422. }
  19423. text.set({
  19424. left: text.getLeft() + offX,
  19425. top: text.getTop() - textHeight / 2 + text.fontSize * (0.18 + text._fontSizeFraction) / text.lineHeight /* 0.3 is the old lineHeight */
  19426. });
  19427. return text;
  19428. };
  19429. /* _FROM_SVG_END_ */
  19430. /**
  19431. * Returns fabric.Text instance from an object representation
  19432. * @static
  19433. * @memberOf fabric.Text
  19434. * @param {Object} object Object to create an instance from
  19435. * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created
  19436. * @return {fabric.Text} Instance of fabric.Text
  19437. */
  19438. fabric.Text.fromObject = function(object, callback) {
  19439. var text = new fabric.Text(object.text, clone(object));
  19440. callback && callback(text);
  19441. return text;
  19442. };
  19443. fabric.util.createAccessors(fabric.Text);
  19444. })(typeof exports !== 'undefined' ? exports : this);
  19445. (function() {
  19446. var clone = fabric.util.object.clone;
  19447. /**
  19448. * IText class (introduced in <b>v1.4</b>) Events are also fired with "text:"
  19449. * prefix when observing canvas.
  19450. * @class fabric.IText
  19451. * @extends fabric.Text
  19452. * @mixes fabric.Observable
  19453. *
  19454. * @fires changed
  19455. * @fires selection:changed
  19456. * @fires editing:entered
  19457. * @fires editing:exited
  19458. *
  19459. * @return {fabric.IText} thisArg
  19460. * @see {@link fabric.IText#initialize} for constructor definition
  19461. *
  19462. * <p>Supported key combinations:</p>
  19463. * <pre>
  19464. * Move cursor: left, right, up, down
  19465. * Select character: shift + left, shift + right
  19466. * Select text vertically: shift + up, shift + down
  19467. * Move cursor by word: alt + left, alt + right
  19468. * Select words: shift + alt + left, shift + alt + right
  19469. * Move cursor to line start/end: cmd + left, cmd + right or home, end
  19470. * Select till start/end of line: cmd + shift + left, cmd + shift + right or shift + home, shift + end
  19471. * Jump to start/end of text: cmd + up, cmd + down
  19472. * Select till start/end of text: cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
  19473. * Delete character: backspace
  19474. * Delete word: alt + backspace
  19475. * Delete line: cmd + backspace
  19476. * Forward delete: delete
  19477. * Copy text: ctrl/cmd + c
  19478. * Paste text: ctrl/cmd + v
  19479. * Cut text: ctrl/cmd + x
  19480. * Select entire text: ctrl/cmd + a
  19481. * Quit editing tab or esc
  19482. * </pre>
  19483. *
  19484. * <p>Supported mouse/touch combination</p>
  19485. * <pre>
  19486. * Position cursor: click/touch
  19487. * Create selection: click/touch & drag
  19488. * Create selection: click & shift + click
  19489. * Select word: double click
  19490. * Select line: triple click
  19491. * </pre>
  19492. */
  19493. fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ {
  19494. /**
  19495. * Type of an object
  19496. * @type String
  19497. * @default
  19498. */
  19499. type: 'i-text',
  19500. /**
  19501. * Index where text selection starts (or where cursor is when there is no selection)
  19502. * @type Number
  19503. * @default
  19504. */
  19505. selectionStart: 0,
  19506. /**
  19507. * Index where text selection ends
  19508. * @type Number
  19509. * @default
  19510. */
  19511. selectionEnd: 0,
  19512. /**
  19513. * Color of text selection
  19514. * @type String
  19515. * @default
  19516. */
  19517. selectionColor: 'rgba(17,119,255,0.3)',
  19518. /**
  19519. * Indicates whether text is in editing mode
  19520. * @type Boolean
  19521. * @default
  19522. */
  19523. isEditing: false,
  19524. /**
  19525. * Indicates whether a text can be edited
  19526. * @type Boolean
  19527. * @default
  19528. */
  19529. editable: true,
  19530. /**
  19531. * Border color of text object while it's in editing mode
  19532. * @type String
  19533. * @default
  19534. */
  19535. editingBorderColor: 'rgba(102,153,255,0.25)',
  19536. /**
  19537. * Width of cursor (in px)
  19538. * @type Number
  19539. * @default
  19540. */
  19541. cursorWidth: 2,
  19542. /**
  19543. * Color of default cursor (when not overwritten by character style)
  19544. * @type String
  19545. * @default
  19546. */
  19547. cursorColor: '#333',
  19548. /**
  19549. * Delay between cursor blink (in ms)
  19550. * @type Number
  19551. * @default
  19552. */
  19553. cursorDelay: 1000,
  19554. /**
  19555. * Duration of cursor fadein (in ms)
  19556. * @type Number
  19557. * @default
  19558. */
  19559. cursorDuration: 600,
  19560. /**
  19561. * Object containing character styles
  19562. * (where top-level properties corresponds to line number and 2nd-level properties -- to char number in a line)
  19563. * @type Object
  19564. * @default
  19565. */
  19566. styles: null,
  19567. /**
  19568. * Indicates whether internal text char widths can be cached
  19569. * @type Boolean
  19570. * @default
  19571. */
  19572. caching: true,
  19573. /**
  19574. * @private
  19575. */
  19576. _reSpace: /\s|\n/,
  19577. /**
  19578. * @private
  19579. */
  19580. _currentCursorOpacity: 0,
  19581. /**
  19582. * @private
  19583. */
  19584. _selectionDirection: null,
  19585. /**
  19586. * @private
  19587. */
  19588. _abortCursorAnimation: false,
  19589. /**
  19590. * @private
  19591. */
  19592. __widthOfSpace: [],
  19593. /**
  19594. * Constructor
  19595. * @param {String} text Text string
  19596. * @param {Object} [options] Options object
  19597. * @return {fabric.IText} thisArg
  19598. */
  19599. initialize: function(text, options) {
  19600. this.styles = options ? (options.styles || { }) : { };
  19601. this.callSuper('initialize', text, options);
  19602. this.initBehavior();
  19603. },
  19604. /**
  19605. * @private
  19606. */
  19607. _clearCache: function() {
  19608. this.callSuper('_clearCache');
  19609. this.__widthOfSpace = [];
  19610. },
  19611. /**
  19612. * Returns true if object has no styling
  19613. */
  19614. isEmptyStyles: function() {
  19615. if (!this.styles) {
  19616. return true;
  19617. }
  19618. var obj = this.styles;
  19619. for (var p1 in obj) {
  19620. for (var p2 in obj[p1]) {
  19621. // eslint-disable-next-line no-unused-vars
  19622. for (var p3 in obj[p1][p2]) {
  19623. return false;
  19624. }
  19625. }
  19626. }
  19627. return true;
  19628. },
  19629. /**
  19630. * Sets selection start (left boundary of a selection)
  19631. * @param {Number} index Index to set selection start to
  19632. */
  19633. setSelectionStart: function(index) {
  19634. index = Math.max(index, 0);
  19635. this._updateAndFire('selectionStart', index);
  19636. },
  19637. /**
  19638. * Sets selection end (right boundary of a selection)
  19639. * @param {Number} index Index to set selection end to
  19640. */
  19641. setSelectionEnd: function(index) {
  19642. index = Math.min(index, this.text.length);
  19643. this._updateAndFire('selectionEnd', index);
  19644. },
  19645. /**
  19646. * @private
  19647. * @param {String} property 'selectionStart' or 'selectionEnd'
  19648. * @param {Number} index new position of property
  19649. */
  19650. _updateAndFire: function(property, index) {
  19651. if (this[property] !== index) {
  19652. this._fireSelectionChanged();
  19653. this[property] = index;
  19654. }
  19655. this._updateTextarea();
  19656. },
  19657. /**
  19658. * Fires the even of selection changed
  19659. * @private
  19660. */
  19661. _fireSelectionChanged: function() {
  19662. this.fire('selection:changed');
  19663. this.canvas && this.canvas.fire('text:selection:changed', { target: this });
  19664. },
  19665. /**
  19666. * Gets style of a current selection/cursor (at the start position)
  19667. * @param {Number} [startIndex] Start index to get styles at
  19668. * @param {Number} [endIndex] End index to get styles at
  19669. * @return {Object} styles Style object at a specified (or current) index
  19670. */
  19671. getSelectionStyles: function(startIndex, endIndex) {
  19672. if (arguments.length === 2) {
  19673. var styles = [];
  19674. for (var i = startIndex; i < endIndex; i++) {
  19675. styles.push(this.getSelectionStyles(i));
  19676. }
  19677. return styles;
  19678. }
  19679. var loc = this.get2DCursorLocation(startIndex),
  19680. style = this._getStyleDeclaration(loc.lineIndex, loc.charIndex);
  19681. return style || {};
  19682. },
  19683. /**
  19684. * Sets style of a current selection
  19685. * @param {Object} [styles] Styles object
  19686. * @return {fabric.IText} thisArg
  19687. * @chainable
  19688. */
  19689. setSelectionStyles: function(styles) {
  19690. if (this.selectionStart === this.selectionEnd) {
  19691. this._extendStyles(this.selectionStart, styles);
  19692. }
  19693. else {
  19694. for (var i = this.selectionStart; i < this.selectionEnd; i++) {
  19695. this._extendStyles(i, styles);
  19696. }
  19697. }
  19698. /* not included in _extendStyles to avoid clearing cache more than once */
  19699. this._forceClearCache = true;
  19700. return this;
  19701. },
  19702. /**
  19703. * @private
  19704. */
  19705. _extendStyles: function(index, styles) {
  19706. var loc = this.get2DCursorLocation(index);
  19707. if (!this._getLineStyle(loc.lineIndex)) {
  19708. this._setLineStyle(loc.lineIndex, {});
  19709. }
  19710. if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) {
  19711. this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {});
  19712. }
  19713. fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles);
  19714. },
  19715. /**
  19716. * @private
  19717. * @param {CanvasRenderingContext2D} ctx Context to render on
  19718. * @param {Boolean} noTransform
  19719. */
  19720. render: function(ctx, noTransform) {
  19721. this.clearContextTop();
  19722. this.callSuper('render', ctx, noTransform);
  19723. },
  19724. /**
  19725. * @private
  19726. * @param {CanvasRenderingContext2D} ctx Context to render on
  19727. */
  19728. _render: function(ctx) {
  19729. this.callSuper('_render', ctx);
  19730. this.ctx = ctx;
  19731. // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor
  19732. // the correct position but not at every cursor animation.
  19733. this.cursorOffsetCache = { };
  19734. this.renderCursorOrSelection();
  19735. },
  19736. /**
  19737. * Prepare and clean the contextTop
  19738. */
  19739. clearContextTop: function() {
  19740. if (!this.active || !this.isEditing) {
  19741. return;
  19742. }
  19743. if (this.canvas && this.canvas.contextTop) {
  19744. var ctx = this.canvas.contextTop;
  19745. ctx.save();
  19746. ctx.transform.apply(ctx, this.canvas.viewportTransform);
  19747. this.transform(ctx);
  19748. this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
  19749. this._clearTextArea(ctx);
  19750. ctx.restore();
  19751. }
  19752. },
  19753. /**
  19754. * Renders cursor or selection (depending on what exists)
  19755. */
  19756. renderCursorOrSelection: function() {
  19757. if (!this.active || !this.isEditing) {
  19758. return;
  19759. }
  19760. var chars = this.text.split(''),
  19761. boundaries, ctx;
  19762. if (this.canvas && this.canvas.contextTop) {
  19763. ctx = this.canvas.contextTop;
  19764. ctx.save();
  19765. ctx.transform.apply(ctx, this.canvas.viewportTransform);
  19766. this.transform(ctx);
  19767. this.transformMatrix && ctx.transform.apply(ctx, this.transformMatrix);
  19768. this._clearTextArea(ctx);
  19769. }
  19770. else {
  19771. ctx = this.ctx;
  19772. ctx.save();
  19773. }
  19774. if (this.selectionStart === this.selectionEnd) {
  19775. boundaries = this._getCursorBoundaries(chars, 'cursor');
  19776. this.renderCursor(boundaries, ctx);
  19777. }
  19778. else {
  19779. boundaries = this._getCursorBoundaries(chars, 'selection');
  19780. this.renderSelection(chars, boundaries, ctx);
  19781. }
  19782. ctx.restore();
  19783. },
  19784. _clearTextArea: function(ctx) {
  19785. // we add 4 pixel, to be sure to do not leave any pixel out
  19786. var width = this.width + 4, height = this.height + 4;
  19787. ctx.clearRect(-width / 2, -height / 2, width, height);
  19788. },
  19789. /**
  19790. * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start)
  19791. * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
  19792. */
  19793. get2DCursorLocation: function(selectionStart) {
  19794. if (typeof selectionStart === 'undefined') {
  19795. selectionStart = this.selectionStart;
  19796. }
  19797. var len = this._textLines.length;
  19798. for (var i = 0; i < len; i++) {
  19799. if (selectionStart <= this._textLines[i].length) {
  19800. return {
  19801. lineIndex: i,
  19802. charIndex: selectionStart
  19803. };
  19804. }
  19805. selectionStart -= this._textLines[i].length + 1;
  19806. }
  19807. return {
  19808. lineIndex: i - 1,
  19809. charIndex: this._textLines[i - 1].length < selectionStart ? this._textLines[i - 1].length : selectionStart
  19810. };
  19811. },
  19812. /**
  19813. * Returns complete style of char at the current cursor
  19814. * @param {Number} lineIndex Line index
  19815. * @param {Number} charIndex Char index
  19816. * @return {Object} Character style
  19817. */
  19818. getCurrentCharStyle: function(lineIndex, charIndex) {
  19819. var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
  19820. return {
  19821. fontSize: style && style.fontSize || this.fontSize,
  19822. fill: style && style.fill || this.fill,
  19823. textBackgroundColor: style && style.textBackgroundColor || this.textBackgroundColor,
  19824. textDecoration: style && style.textDecoration || this.textDecoration,
  19825. fontFamily: style && style.fontFamily || this.fontFamily,
  19826. fontWeight: style && style.fontWeight || this.fontWeight,
  19827. fontStyle: style && style.fontStyle || this.fontStyle,
  19828. stroke: style && style.stroke || this.stroke,
  19829. strokeWidth: style && style.strokeWidth || this.strokeWidth
  19830. };
  19831. },
  19832. /**
  19833. * Returns fontSize of char at the current cursor
  19834. * @param {Number} lineIndex Line index
  19835. * @param {Number} charIndex Char index
  19836. * @return {Number} Character font size
  19837. */
  19838. getCurrentCharFontSize: function(lineIndex, charIndex) {
  19839. var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
  19840. return style && style.fontSize ? style.fontSize : this.fontSize;
  19841. },
  19842. /**
  19843. * Returns color (fill) of char at the current cursor
  19844. * @param {Number} lineIndex Line index
  19845. * @param {Number} charIndex Char index
  19846. * @return {String} Character color (fill)
  19847. */
  19848. getCurrentCharColor: function(lineIndex, charIndex) {
  19849. var style = this._getStyleDeclaration(lineIndex, charIndex === 0 ? 0 : charIndex - 1);
  19850. return style && style.fill ? style.fill : this.cursorColor;
  19851. },
  19852. /**
  19853. * Returns cursor boundaries (left, top, leftOffset, topOffset)
  19854. * @private
  19855. * @param {Array} chars Array of characters
  19856. * @param {String} typeOfBoundaries
  19857. */
  19858. _getCursorBoundaries: function(chars, typeOfBoundaries) {
  19859. // left/top are left/top of entire text box
  19860. // leftOffset/topOffset are offset from that left/top point of a text box
  19861. var left = Math.round(this._getLeftOffset()),
  19862. top = this._getTopOffset(),
  19863. offsets = this._getCursorBoundariesOffsets(
  19864. chars, typeOfBoundaries);
  19865. return {
  19866. left: left,
  19867. top: top,
  19868. leftOffset: offsets.left + offsets.lineLeft,
  19869. topOffset: offsets.top
  19870. };
  19871. },
  19872. /**
  19873. * @private
  19874. */
  19875. _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {
  19876. if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) {
  19877. return this.cursorOffsetCache;
  19878. }
  19879. var lineLeftOffset = 0,
  19880. lineIndex = 0,
  19881. charIndex = 0,
  19882. topOffset = 0,
  19883. leftOffset = 0,
  19884. boundaries;
  19885. for (var i = 0; i < this.selectionStart; i++) {
  19886. if (chars[i] === '\n') {
  19887. leftOffset = 0;
  19888. topOffset += this._getHeightOfLine(this.ctx, lineIndex);
  19889. lineIndex++;
  19890. charIndex = 0;
  19891. }
  19892. else {
  19893. leftOffset += this._getWidthOfChar(this.ctx, chars[i], lineIndex, charIndex);
  19894. charIndex++;
  19895. }
  19896. lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex));
  19897. }
  19898. if (typeOfBoundaries === 'cursor') {
  19899. topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, lineIndex) / this.lineHeight
  19900. - this.getCurrentCharFontSize(lineIndex, charIndex) * (1 - this._fontSizeFraction);
  19901. }
  19902. if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) {
  19903. leftOffset -= this._getWidthOfCharSpacing();
  19904. }
  19905. boundaries = {
  19906. top: topOffset,
  19907. left: leftOffset > 0 ? leftOffset : 0,
  19908. lineLeft: lineLeftOffset
  19909. };
  19910. this.cursorOffsetCache = boundaries;
  19911. return this.cursorOffsetCache;
  19912. },
  19913. /**
  19914. * Renders cursor
  19915. * @param {Object} boundaries
  19916. * @param {CanvasRenderingContext2D} ctx transformed context to draw on
  19917. */
  19918. renderCursor: function(boundaries, ctx) {
  19919. var cursorLocation = this.get2DCursorLocation(),
  19920. lineIndex = cursorLocation.lineIndex,
  19921. charIndex = cursorLocation.charIndex,
  19922. charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
  19923. leftOffset = (lineIndex === 0 && charIndex === 0)
  19924. ? this._getLineLeftOffset(this._getLineWidth(ctx, lineIndex))
  19925. : boundaries.leftOffset,
  19926. multiplier = this.scaleX * this.canvas.getZoom(),
  19927. cursorWidth = this.cursorWidth / multiplier;
  19928. ctx.fillStyle = this.getCurrentCharColor(lineIndex, charIndex);
  19929. ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity;
  19930. ctx.fillRect(
  19931. boundaries.left + leftOffset - cursorWidth / 2,
  19932. boundaries.top + boundaries.topOffset,
  19933. cursorWidth,
  19934. charHeight);
  19935. },
  19936. /**
  19937. * Renders text selection
  19938. * @param {Array} chars Array of characters
  19939. * @param {Object} boundaries Object with left/top/leftOffset/topOffset
  19940. * @param {CanvasRenderingContext2D} ctx transformed context to draw on
  19941. */
  19942. renderSelection: function(chars, boundaries, ctx) {
  19943. ctx.fillStyle = this.selectionColor;
  19944. var start = this.get2DCursorLocation(this.selectionStart),
  19945. end = this.get2DCursorLocation(this.selectionEnd),
  19946. startLine = start.lineIndex,
  19947. endLine = end.lineIndex;
  19948. for (var i = startLine; i <= endLine; i++) {
  19949. var lineOffset = this._getLineLeftOffset(this._getLineWidth(ctx, i)) || 0,
  19950. lineHeight = this._getHeightOfLine(this.ctx, i),
  19951. realLineHeight = 0, boxWidth = 0, line = this._textLines[i];
  19952. if (i === startLine) {
  19953. for (var j = 0, len = line.length; j < len; j++) {
  19954. if (j >= start.charIndex && (i !== endLine || j < end.charIndex)) {
  19955. boxWidth += this._getWidthOfChar(ctx, line[j], i, j);
  19956. }
  19957. if (j < start.charIndex) {
  19958. lineOffset += this._getWidthOfChar(ctx, line[j], i, j);
  19959. }
  19960. }
  19961. if (j === line.length) {
  19962. boxWidth -= this._getWidthOfCharSpacing();
  19963. }
  19964. }
  19965. else if (i > startLine && i < endLine) {
  19966. boxWidth += this._getLineWidth(ctx, i) || 5;
  19967. }
  19968. else if (i === endLine) {
  19969. for (var j2 = 0, j2len = end.charIndex; j2 < j2len; j2++) {
  19970. boxWidth += this._getWidthOfChar(ctx, line[j2], i, j2);
  19971. }
  19972. if (end.charIndex === line.length) {
  19973. boxWidth -= this._getWidthOfCharSpacing();
  19974. }
  19975. }
  19976. realLineHeight = lineHeight;
  19977. if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) {
  19978. lineHeight /= this.lineHeight;
  19979. }
  19980. ctx.fillRect(
  19981. boundaries.left + lineOffset,
  19982. boundaries.top + boundaries.topOffset,
  19983. boxWidth > 0 ? boxWidth : 0,
  19984. lineHeight);
  19985. boundaries.topOffset += realLineHeight;
  19986. }
  19987. },
  19988. /**
  19989. * @private
  19990. * @param {String} method
  19991. * @param {CanvasRenderingContext2D} ctx Context to render on
  19992. * @param {String} line Content of the line
  19993. * @param {Number} left
  19994. * @param {Number} top
  19995. * @param {Number} lineIndex
  19996. * @param {Number} charOffset
  19997. */
  19998. _renderChars: function(method, ctx, line, left, top, lineIndex, charOffset) {
  19999. if (this.isEmptyStyles()) {
  20000. return this._renderCharsFast(method, ctx, line, left, top);
  20001. }
  20002. charOffset = charOffset || 0;
  20003. // set proper line offset
  20004. var lineHeight = this._getHeightOfLine(ctx, lineIndex),
  20005. prevStyle,
  20006. thisStyle,
  20007. charsToRender = '';
  20008. ctx.save();
  20009. top -= lineHeight / this.lineHeight * this._fontSizeFraction;
  20010. for (var i = charOffset, len = line.length + charOffset; i <= len; i++) {
  20011. prevStyle = prevStyle || this.getCurrentCharStyle(lineIndex, i);
  20012. thisStyle = this.getCurrentCharStyle(lineIndex, i + 1);
  20013. if (this._hasStyleChanged(prevStyle, thisStyle) || i === len) {
  20014. this._renderChar(method, ctx, lineIndex, i - 1, charsToRender, left, top, lineHeight);
  20015. charsToRender = '';
  20016. prevStyle = thisStyle;
  20017. }
  20018. charsToRender += line[i - charOffset];
  20019. }
  20020. ctx.restore();
  20021. },
  20022. /**
  20023. * @private
  20024. * @param {String} method
  20025. * @param {CanvasRenderingContext2D} ctx Context to render on
  20026. * @param {String} line Content of the line
  20027. * @param {Number} left Left coordinate
  20028. * @param {Number} top Top coordinate
  20029. */
  20030. _renderCharsFast: function(method, ctx, line, left, top) {
  20031. if (method === 'fillText' && this.fill) {
  20032. this.callSuper('_renderChars', method, ctx, line, left, top);
  20033. }
  20034. if (method === 'strokeText' && ((this.stroke && this.strokeWidth > 0) || this.skipFillStrokeCheck)) {
  20035. this.callSuper('_renderChars', method, ctx, line, left, top);
  20036. }
  20037. },
  20038. /**
  20039. * @private
  20040. * @param {String} method
  20041. * @param {CanvasRenderingContext2D} ctx Context to render on
  20042. * @param {Number} lineIndex
  20043. * @param {Number} i
  20044. * @param {String} _char
  20045. * @param {Number} left Left coordinate
  20046. * @param {Number} top Top coordinate
  20047. * @param {Number} lineHeight Height of the line
  20048. */
  20049. _renderChar: function(method, ctx, lineIndex, i, _char, left, top, lineHeight) {
  20050. var charWidth, charHeight, shouldFill, shouldStroke,
  20051. decl = this._getStyleDeclaration(lineIndex, i),
  20052. offset, textDecoration, chars, additionalSpace, _charWidth;
  20053. if (decl) {
  20054. charHeight = this._getHeightOfChar(ctx, _char, lineIndex, i);
  20055. shouldStroke = decl.stroke;
  20056. shouldFill = decl.fill;
  20057. textDecoration = decl.textDecoration;
  20058. }
  20059. else {
  20060. charHeight = this.fontSize;
  20061. }
  20062. shouldStroke = (shouldStroke || this.stroke) && method === 'strokeText';
  20063. shouldFill = (shouldFill || this.fill) && method === 'fillText';
  20064. decl && ctx.save();
  20065. charWidth = this._applyCharStylesGetWidth(ctx, _char, lineIndex, i, decl || null);
  20066. textDecoration = textDecoration || this.textDecoration;
  20067. if (decl && decl.textBackgroundColor) {
  20068. this._removeShadow(ctx);
  20069. }
  20070. if (this.charSpacing !== 0) {
  20071. additionalSpace = this._getWidthOfCharSpacing();
  20072. chars = _char.split('');
  20073. charWidth = 0;
  20074. for (var j = 0, len = chars.length, char; j < len; j++) {
  20075. char = chars[j];
  20076. shouldFill && ctx.fillText(char, left + charWidth, top);
  20077. shouldStroke && ctx.strokeText(char, left + charWidth, top);
  20078. _charWidth = ctx.measureText(char).width + additionalSpace;
  20079. charWidth += _charWidth > 0 ? _charWidth : 0;
  20080. }
  20081. }
  20082. else {
  20083. shouldFill && ctx.fillText(_char, left, top);
  20084. shouldStroke && ctx.strokeText(_char, left, top);
  20085. }
  20086. if (textDecoration || textDecoration !== '') {
  20087. offset = this._fontSizeFraction * lineHeight / this.lineHeight;
  20088. this._renderCharDecoration(ctx, textDecoration, left, top, offset, charWidth, charHeight);
  20089. }
  20090. decl && ctx.restore();
  20091. ctx.translate(charWidth, 0);
  20092. },
  20093. /**
  20094. * @private
  20095. * @param {Object} prevStyle
  20096. * @param {Object} thisStyle
  20097. */
  20098. _hasStyleChanged: function(prevStyle, thisStyle) {
  20099. return (prevStyle.fill !== thisStyle.fill ||
  20100. prevStyle.fontSize !== thisStyle.fontSize ||
  20101. prevStyle.textBackgroundColor !== thisStyle.textBackgroundColor ||
  20102. prevStyle.textDecoration !== thisStyle.textDecoration ||
  20103. prevStyle.fontFamily !== thisStyle.fontFamily ||
  20104. prevStyle.fontWeight !== thisStyle.fontWeight ||
  20105. prevStyle.fontStyle !== thisStyle.fontStyle ||
  20106. prevStyle.stroke !== thisStyle.stroke ||
  20107. prevStyle.strokeWidth !== thisStyle.strokeWidth
  20108. );
  20109. },
  20110. /**
  20111. * @private
  20112. * @param {CanvasRenderingContext2D} ctx Context to render on
  20113. */
  20114. _renderCharDecoration: function(ctx, textDecoration, left, top, offset, charWidth, charHeight) {
  20115. if (!textDecoration) {
  20116. return;
  20117. }
  20118. var decorationWeight = charHeight / 15,
  20119. positions = {
  20120. underline: top + charHeight / 10,
  20121. 'line-through': top - charHeight * (this._fontSizeFraction + this._fontSizeMult - 1) + decorationWeight,
  20122. overline: top - (this._fontSizeMult - this._fontSizeFraction) * charHeight
  20123. },
  20124. decorations = ['underline', 'line-through', 'overline'], i, decoration;
  20125. for (i = 0; i < decorations.length; i++) {
  20126. decoration = decorations[i];
  20127. if (textDecoration.indexOf(decoration) > -1) {
  20128. ctx.fillRect(left, positions[decoration], charWidth , decorationWeight);
  20129. }
  20130. }
  20131. },
  20132. /**
  20133. * @private
  20134. * @param {String} method
  20135. * @param {CanvasRenderingContext2D} ctx Context to render on
  20136. * @param {String} line
  20137. * @param {Number} left
  20138. * @param {Number} top
  20139. * @param {Number} lineIndex
  20140. */
  20141. _renderTextLine: function(method, ctx, line, left, top, lineIndex) {
  20142. // to "cancel" this.fontSize subtraction in fabric.Text#_renderTextLine
  20143. // the adding 0.03 is just to align text with itext by overlap test
  20144. if (!this.isEmptyStyles()) {
  20145. top += this.fontSize * (this._fontSizeFraction + 0.03);
  20146. }
  20147. this.callSuper('_renderTextLine', method, ctx, line, left, top, lineIndex);
  20148. },
  20149. /**
  20150. * @private
  20151. * @param {CanvasRenderingContext2D} ctx Context to render on
  20152. */
  20153. _renderTextDecoration: function(ctx) {
  20154. if (this.isEmptyStyles()) {
  20155. return this.callSuper('_renderTextDecoration', ctx);
  20156. }
  20157. },
  20158. /**
  20159. * @private
  20160. * @param {CanvasRenderingContext2D} ctx Context to render on
  20161. */
  20162. _renderTextLinesBackground: function(ctx) {
  20163. this.callSuper('_renderTextLinesBackground', ctx);
  20164. var lineTopOffset = 0, heightOfLine,
  20165. lineWidth, lineLeftOffset,
  20166. leftOffset = this._getLeftOffset(),
  20167. topOffset = this._getTopOffset(),
  20168. line, _char, style;
  20169. for (var i = 0, len = this._textLines.length; i < len; i++) {
  20170. heightOfLine = this._getHeightOfLine(ctx, i);
  20171. line = this._textLines[i];
  20172. if (line === '' || !this.styles || !this._getLineStyle(i)) {
  20173. lineTopOffset += heightOfLine;
  20174. continue;
  20175. }
  20176. lineWidth = this._getLineWidth(ctx, i);
  20177. lineLeftOffset = this._getLineLeftOffset(lineWidth);
  20178. for (var j = 0, jlen = line.length; j < jlen; j++) {
  20179. style = this._getStyleDeclaration(i, j);
  20180. if (!style || !style.textBackgroundColor) {
  20181. continue;
  20182. }
  20183. _char = line[j];
  20184. ctx.fillStyle = style.textBackgroundColor;
  20185. ctx.fillRect(
  20186. leftOffset + lineLeftOffset + this._getWidthOfCharsAt(ctx, i, j),
  20187. topOffset + lineTopOffset,
  20188. this._getWidthOfChar(ctx, _char, i, j),
  20189. heightOfLine / this.lineHeight
  20190. );
  20191. }
  20192. lineTopOffset += heightOfLine;
  20193. }
  20194. },
  20195. /**
  20196. * @private
  20197. */
  20198. _getCacheProp: function(_char, styleDeclaration) {
  20199. return _char +
  20200. styleDeclaration.fontSize +
  20201. styleDeclaration.fontWeight +
  20202. styleDeclaration.fontStyle;
  20203. },
  20204. /**
  20205. * @private
  20206. * @param {String} fontFamily name
  20207. * @return {Object} reference to cache
  20208. */
  20209. _getFontCache: function(fontFamily) {
  20210. if (!fabric.charWidthsCache[fontFamily]) {
  20211. fabric.charWidthsCache[fontFamily] = { };
  20212. }
  20213. return fabric.charWidthsCache[fontFamily];
  20214. },
  20215. /**
  20216. * @private
  20217. * @param {CanvasRenderingContext2D} ctx Context to render on
  20218. * @param {String} _char
  20219. * @param {Number} lineIndex
  20220. * @param {Number} charIndex
  20221. * @param {Object} [decl]
  20222. */
  20223. _applyCharStylesGetWidth: function(ctx, _char, lineIndex, charIndex, decl) {
  20224. var charDecl = decl || this._getStyleDeclaration(lineIndex, charIndex),
  20225. styleDeclaration = clone(charDecl),
  20226. width, cacheProp, charWidthsCache;
  20227. this._applyFontStyles(styleDeclaration);
  20228. charWidthsCache = this._getFontCache(styleDeclaration.fontFamily);
  20229. cacheProp = this._getCacheProp(_char, styleDeclaration);
  20230. // short-circuit if no styles for this char
  20231. // global style from object is always applyed and handled by save and restore
  20232. if (!charDecl && charWidthsCache[cacheProp] && this.caching) {
  20233. return charWidthsCache[cacheProp];
  20234. }
  20235. if (typeof styleDeclaration.shadow === 'string') {
  20236. styleDeclaration.shadow = new fabric.Shadow(styleDeclaration.shadow);
  20237. }
  20238. var fill = styleDeclaration.fill || this.fill;
  20239. ctx.fillStyle = fill.toLive
  20240. ? fill.toLive(ctx, this)
  20241. : fill;
  20242. if (styleDeclaration.stroke) {
  20243. ctx.strokeStyle = (styleDeclaration.stroke && styleDeclaration.stroke.toLive)
  20244. ? styleDeclaration.stroke.toLive(ctx, this)
  20245. : styleDeclaration.stroke;
  20246. }
  20247. ctx.lineWidth = styleDeclaration.strokeWidth || this.strokeWidth;
  20248. ctx.font = this._getFontDeclaration.call(styleDeclaration);
  20249. //if we want this._setShadow.call to work with styleDeclarion
  20250. //we have to add those references
  20251. if (styleDeclaration.shadow) {
  20252. styleDeclaration.scaleX = this.scaleX;
  20253. styleDeclaration.scaleY = this.scaleY;
  20254. styleDeclaration.canvas = this.canvas;
  20255. styleDeclaration.getObjectScaling = this.getObjectScaling;
  20256. this._setShadow.call(styleDeclaration, ctx);
  20257. }
  20258. if (!this.caching || !charWidthsCache[cacheProp]) {
  20259. width = ctx.measureText(_char).width;
  20260. this.caching && (charWidthsCache[cacheProp] = width);
  20261. return width;
  20262. }
  20263. return charWidthsCache[cacheProp];
  20264. },
  20265. /**
  20266. * @private
  20267. * @param {Object} styleDeclaration
  20268. */
  20269. _applyFontStyles: function(styleDeclaration) {
  20270. if (!styleDeclaration.fontFamily) {
  20271. styleDeclaration.fontFamily = this.fontFamily;
  20272. }
  20273. if (!styleDeclaration.fontSize) {
  20274. styleDeclaration.fontSize = this.fontSize;
  20275. }
  20276. if (!styleDeclaration.fontWeight) {
  20277. styleDeclaration.fontWeight = this.fontWeight;
  20278. }
  20279. if (!styleDeclaration.fontStyle) {
  20280. styleDeclaration.fontStyle = this.fontStyle;
  20281. }
  20282. },
  20283. /**
  20284. * @param {Number} lineIndex
  20285. * @param {Number} charIndex
  20286. * @param {Boolean} [returnCloneOrEmpty=false]
  20287. * @private
  20288. */
  20289. _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) {
  20290. if (returnCloneOrEmpty) {
  20291. return (this.styles[lineIndex] && this.styles[lineIndex][charIndex])
  20292. ? clone(this.styles[lineIndex][charIndex])
  20293. : { };
  20294. }
  20295. return this.styles[lineIndex] && this.styles[lineIndex][charIndex] ? this.styles[lineIndex][charIndex] : null;
  20296. },
  20297. /**
  20298. * @param {Number} lineIndex
  20299. * @param {Number} charIndex
  20300. * @param {Object} style
  20301. * @private
  20302. */
  20303. _setStyleDeclaration: function(lineIndex, charIndex, style) {
  20304. this.styles[lineIndex][charIndex] = style;
  20305. },
  20306. /**
  20307. *
  20308. * @param {Number} lineIndex
  20309. * @param {Number} charIndex
  20310. * @private
  20311. */
  20312. _deleteStyleDeclaration: function(lineIndex, charIndex) {
  20313. delete this.styles[lineIndex][charIndex];
  20314. },
  20315. /**
  20316. * @param {Number} lineIndex
  20317. * @private
  20318. */
  20319. _getLineStyle: function(lineIndex) {
  20320. return this.styles[lineIndex];
  20321. },
  20322. /**
  20323. * @param {Number} lineIndex
  20324. * @param {Object} style
  20325. * @private
  20326. */
  20327. _setLineStyle: function(lineIndex, style) {
  20328. this.styles[lineIndex] = style;
  20329. },
  20330. /**
  20331. * @param {Number} lineIndex
  20332. * @private
  20333. */
  20334. _deleteLineStyle: function(lineIndex) {
  20335. delete this.styles[lineIndex];
  20336. },
  20337. /**
  20338. * @private
  20339. * @param {CanvasRenderingContext2D} ctx Context to render on
  20340. */
  20341. _getWidthOfChar: function(ctx, _char, lineIndex, charIndex) {
  20342. if (!this._isMeasuring && this.textAlign === 'justify' && this._reSpacesAndTabs.test(_char)) {
  20343. return this._getWidthOfSpace(ctx, lineIndex);
  20344. }
  20345. ctx.save();
  20346. var width = this._applyCharStylesGetWidth(ctx, _char, lineIndex, charIndex);
  20347. if (this.charSpacing !== 0) {
  20348. width += this._getWidthOfCharSpacing();
  20349. }
  20350. ctx.restore();
  20351. return width > 0 ? width : 0
  20352. },
  20353. /**
  20354. * @private
  20355. * @param {CanvasRenderingContext2D} ctx Context to render on
  20356. * @param {Number} lineIndex
  20357. * @param {Number} charIndex
  20358. */
  20359. _getHeightOfChar: function(ctx, lineIndex, charIndex) {
  20360. var style = this._getStyleDeclaration(lineIndex, charIndex);
  20361. return style && style.fontSize ? style.fontSize : this.fontSize;
  20362. },
  20363. /**
  20364. * @private
  20365. * @param {CanvasRenderingContext2D} ctx Context to render on
  20366. * @param {Number} lineIndex
  20367. * @param {Number} charIndex
  20368. */
  20369. _getWidthOfCharsAt: function(ctx, lineIndex, charIndex) {
  20370. var width = 0, i, _char;
  20371. for (i = 0; i < charIndex; i++) {
  20372. _char = this._textLines[lineIndex][i];
  20373. width += this._getWidthOfChar(ctx, _char, lineIndex, i);
  20374. }
  20375. return width;
  20376. },
  20377. /**
  20378. * @private
  20379. * @param {CanvasRenderingContext2D} ctx Context to render on
  20380. * @param {Number} lineIndex line number
  20381. * @return {Number} Line width
  20382. */
  20383. _measureLine: function(ctx, lineIndex) {
  20384. this._isMeasuring = true;
  20385. var width = this._getWidthOfCharsAt(ctx, lineIndex, this._textLines[lineIndex].length);
  20386. if (this.charSpacing !== 0) {
  20387. width -= this._getWidthOfCharSpacing();
  20388. }
  20389. this._isMeasuring = false;
  20390. return width > 0 ? width : 0;
  20391. },
  20392. /**
  20393. * @private
  20394. * @param {CanvasRenderingContext2D} ctx Context to render on
  20395. * @param {Number} lineIndex
  20396. */
  20397. _getWidthOfSpace: function (ctx, lineIndex) {
  20398. if (this.__widthOfSpace[lineIndex]) {
  20399. return this.__widthOfSpace[lineIndex];
  20400. }
  20401. var line = this._textLines[lineIndex],
  20402. wordsWidth = this._getWidthOfWords(ctx, line, lineIndex, 0),
  20403. widthDiff = this.width - wordsWidth,
  20404. numSpaces = line.length - line.replace(this._reSpacesAndTabs, '').length,
  20405. width = Math.max(widthDiff / numSpaces, ctx.measureText(' ').width);
  20406. this.__widthOfSpace[lineIndex] = width;
  20407. return width;
  20408. },
  20409. /**
  20410. * @private
  20411. * @param {CanvasRenderingContext2D} ctx Context to render on
  20412. * @param {String} line
  20413. * @param {Number} lineIndex
  20414. * @param {Number} charOffset
  20415. */
  20416. _getWidthOfWords: function (ctx, line, lineIndex, charOffset) {
  20417. var width = 0;
  20418. for (var charIndex = 0; charIndex < line.length; charIndex++) {
  20419. var _char = line[charIndex];
  20420. if (!_char.match(/\s/)) {
  20421. width += this._getWidthOfChar(ctx, _char, lineIndex, charIndex + charOffset);
  20422. }
  20423. }
  20424. return width;
  20425. },
  20426. /**
  20427. * @private
  20428. * @param {CanvasRenderingContext2D} ctx Context to render on
  20429. */
  20430. _getHeightOfLine: function(ctx, lineIndex) {
  20431. if (this.__lineHeights[lineIndex]) {
  20432. return this.__lineHeights[lineIndex];
  20433. }
  20434. var line = this._textLines[lineIndex],
  20435. maxHeight = this._getHeightOfChar(ctx, lineIndex, 0);
  20436. for (var i = 1, len = line.length; i < len; i++) {
  20437. var currentCharHeight = this._getHeightOfChar(ctx, lineIndex, i);
  20438. if (currentCharHeight > maxHeight) {
  20439. maxHeight = currentCharHeight;
  20440. }
  20441. }
  20442. this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult;
  20443. return this.__lineHeights[lineIndex];
  20444. },
  20445. /**
  20446. * @private
  20447. * @param {CanvasRenderingContext2D} ctx Context to render on
  20448. */
  20449. _getTextHeight: function(ctx) {
  20450. var lineHeight, height = 0;
  20451. for (var i = 0, len = this._textLines.length; i < len; i++) {
  20452. lineHeight = this._getHeightOfLine(ctx, i);
  20453. height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight);
  20454. }
  20455. return height;
  20456. },
  20457. /**
  20458. * Returns object representation of an instance
  20459. * @method toObject
  20460. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  20461. * @return {Object} object representation of an instance
  20462. */
  20463. toObject: function(propertiesToInclude) {
  20464. return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
  20465. styles: clone(this.styles, true)
  20466. });
  20467. }
  20468. });
  20469. /**
  20470. * Returns fabric.IText instance from an object representation
  20471. * @static
  20472. * @memberOf fabric.IText
  20473. * @param {Object} object Object to create an instance from
  20474. * @param {function} [callback] invoked with new instance as argument
  20475. * @return {fabric.IText} instance of fabric.IText
  20476. */
  20477. fabric.IText.fromObject = function(object, callback) {
  20478. var iText = new fabric.IText(object.text, clone(object));
  20479. callback && callback(iText);
  20480. return iText;
  20481. };
  20482. })();
  20483. (function() {
  20484. var clone = fabric.util.object.clone;
  20485. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  20486. /**
  20487. * Initializes all the interactive behavior of IText
  20488. */
  20489. initBehavior: function() {
  20490. this.initAddedHandler();
  20491. this.initRemovedHandler();
  20492. this.initCursorSelectionHandlers();
  20493. this.initDoubleClickSimulation();
  20494. this.mouseMoveHandler = this.mouseMoveHandler.bind(this);
  20495. },
  20496. /**
  20497. * Initializes "selected" event handler
  20498. */
  20499. initSelectedHandler: function() {
  20500. this.on('selected', function() {
  20501. var _this = this;
  20502. setTimeout(function() {
  20503. _this.selected = true;
  20504. }, 100);
  20505. });
  20506. },
  20507. /**
  20508. * Initializes "added" event handler
  20509. */
  20510. initAddedHandler: function() {
  20511. var _this = this;
  20512. this.on('added', function() {
  20513. var canvas = _this.canvas;
  20514. if (canvas) {
  20515. if (!canvas._hasITextHandlers) {
  20516. canvas._hasITextHandlers = true;
  20517. _this._initCanvasHandlers(canvas);
  20518. }
  20519. canvas._iTextInstances = canvas._iTextInstances || [];
  20520. canvas._iTextInstances.push(_this);
  20521. }
  20522. });
  20523. },
  20524. initRemovedHandler: function() {
  20525. var _this = this;
  20526. this.on('removed', function() {
  20527. var canvas = _this.canvas;
  20528. if (canvas) {
  20529. canvas._iTextInstances = canvas._iTextInstances || [];
  20530. fabric.util.removeFromArray(canvas._iTextInstances, _this);
  20531. if (canvas._iTextInstances.length === 0) {
  20532. canvas._hasITextHandlers = false;
  20533. _this._removeCanvasHandlers(canvas);
  20534. }
  20535. }
  20536. });
  20537. },
  20538. /**
  20539. * register canvas event to manage exiting on other instances
  20540. * @private
  20541. */
  20542. _initCanvasHandlers: function(canvas) {
  20543. canvas._canvasITextSelectionClearedHanlder = (function() {
  20544. fabric.IText.prototype.exitEditingOnOthers(canvas);
  20545. }).bind(this);
  20546. canvas._mouseUpITextHandler = (function() {
  20547. if (canvas._iTextInstances) {
  20548. canvas._iTextInstances.forEach(function(obj) {
  20549. obj.__isMousedown = false;
  20550. });
  20551. }
  20552. }).bind(this);
  20553. canvas.on('selection:cleared', canvas._canvasITextSelectionClearedHanlder);
  20554. canvas.on('object:selected', canvas._canvasITextSelectionClearedHanlder);
  20555. canvas.on('mouse:up', canvas._mouseUpITextHandler);
  20556. },
  20557. /**
  20558. * remove canvas event to manage exiting on other instances
  20559. * @private
  20560. */
  20561. _removeCanvasHandlers: function(canvas) {
  20562. canvas.off('selection:cleared', canvas._canvasITextSelectionClearedHanlder);
  20563. canvas.off('object:selected', canvas._canvasITextSelectionClearedHanlder);
  20564. canvas.off('mouse:up', canvas._mouseUpITextHandler);
  20565. },
  20566. /**
  20567. * @private
  20568. */
  20569. _tick: function() {
  20570. this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete');
  20571. },
  20572. /**
  20573. * @private
  20574. */
  20575. _animateCursor: function(obj, targetOpacity, duration, completeMethod) {
  20576. var tickState;
  20577. tickState = {
  20578. isAborted: false,
  20579. abort: function() {
  20580. this.isAborted = true;
  20581. },
  20582. };
  20583. obj.animate('_currentCursorOpacity', targetOpacity, {
  20584. duration: duration,
  20585. onComplete: function() {
  20586. if (!tickState.isAborted) {
  20587. obj[completeMethod]();
  20588. }
  20589. },
  20590. onChange: function() {
  20591. // we do not want to animate a selection, only cursor
  20592. if (obj.canvas && obj.selectionStart === obj.selectionEnd) {
  20593. obj.renderCursorOrSelection();
  20594. }
  20595. },
  20596. abort: function() {
  20597. return tickState.isAborted;
  20598. }
  20599. });
  20600. return tickState;
  20601. },
  20602. /**
  20603. * @private
  20604. */
  20605. _onTickComplete: function() {
  20606. var _this = this;
  20607. if (this._cursorTimeout1) {
  20608. clearTimeout(this._cursorTimeout1);
  20609. }
  20610. this._cursorTimeout1 = setTimeout(function() {
  20611. _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick');
  20612. }, 100);
  20613. },
  20614. /**
  20615. * Initializes delayed cursor
  20616. */
  20617. initDelayedCursor: function(restart) {
  20618. var _this = this,
  20619. delay = restart ? 0 : this.cursorDelay;
  20620. this.abortCursorAnimation();
  20621. this._currentCursorOpacity = 1;
  20622. this._cursorTimeout2 = setTimeout(function() {
  20623. _this._tick();
  20624. }, delay);
  20625. },
  20626. /**
  20627. * Aborts cursor animation and clears all timeouts
  20628. */
  20629. abortCursorAnimation: function() {
  20630. var shouldClear = this._currentTickState || this._currentTickCompleteState;
  20631. this._currentTickState && this._currentTickState.abort();
  20632. this._currentTickCompleteState && this._currentTickCompleteState.abort();
  20633. clearTimeout(this._cursorTimeout1);
  20634. clearTimeout(this._cursorTimeout2);
  20635. this._currentCursorOpacity = 0;
  20636. // to clear just itext area we need to transform the context
  20637. // it may not be worth it
  20638. if (shouldClear) {
  20639. this.canvas && this.canvas.clearContext(this.canvas.contextTop || this.ctx);
  20640. }
  20641. },
  20642. /**
  20643. * Selects entire text
  20644. */
  20645. selectAll: function() {
  20646. this.selectionStart = 0;
  20647. this.selectionEnd = this.text.length;
  20648. this._fireSelectionChanged();
  20649. this._updateTextarea();
  20650. },
  20651. /**
  20652. * Returns selected text
  20653. * @return {String}
  20654. */
  20655. getSelectedText: function() {
  20656. return this.text.slice(this.selectionStart, this.selectionEnd);
  20657. },
  20658. /**
  20659. * Find new selection index representing start of current word according to current selection index
  20660. * @param {Number} startFrom Surrent selection index
  20661. * @return {Number} New selection index
  20662. */
  20663. findWordBoundaryLeft: function(startFrom) {
  20664. var offset = 0, index = startFrom - 1;
  20665. // remove space before cursor first
  20666. if (this._reSpace.test(this.text.charAt(index))) {
  20667. while (this._reSpace.test(this.text.charAt(index))) {
  20668. offset++;
  20669. index--;
  20670. }
  20671. }
  20672. while (/\S/.test(this.text.charAt(index)) && index > -1) {
  20673. offset++;
  20674. index--;
  20675. }
  20676. return startFrom - offset;
  20677. },
  20678. /**
  20679. * Find new selection index representing end of current word according to current selection index
  20680. * @param {Number} startFrom Current selection index
  20681. * @return {Number} New selection index
  20682. */
  20683. findWordBoundaryRight: function(startFrom) {
  20684. var offset = 0, index = startFrom;
  20685. // remove space after cursor first
  20686. if (this._reSpace.test(this.text.charAt(index))) {
  20687. while (this._reSpace.test(this.text.charAt(index))) {
  20688. offset++;
  20689. index++;
  20690. }
  20691. }
  20692. while (/\S/.test(this.text.charAt(index)) && index < this.text.length) {
  20693. offset++;
  20694. index++;
  20695. }
  20696. return startFrom + offset;
  20697. },
  20698. /**
  20699. * Find new selection index representing start of current line according to current selection index
  20700. * @param {Number} startFrom Current selection index
  20701. * @return {Number} New selection index
  20702. */
  20703. findLineBoundaryLeft: function(startFrom) {
  20704. var offset = 0, index = startFrom - 1;
  20705. while (!/\n/.test(this.text.charAt(index)) && index > -1) {
  20706. offset++;
  20707. index--;
  20708. }
  20709. return startFrom - offset;
  20710. },
  20711. /**
  20712. * Find new selection index representing end of current line according to current selection index
  20713. * @param {Number} startFrom Current selection index
  20714. * @return {Number} New selection index
  20715. */
  20716. findLineBoundaryRight: function(startFrom) {
  20717. var offset = 0, index = startFrom;
  20718. while (!/\n/.test(this.text.charAt(index)) && index < this.text.length) {
  20719. offset++;
  20720. index++;
  20721. }
  20722. return startFrom + offset;
  20723. },
  20724. /**
  20725. * Returns number of newlines in selected text
  20726. * @return {Number} Number of newlines in selected text
  20727. */
  20728. getNumNewLinesInSelectedText: function() {
  20729. var selectedText = this.getSelectedText(),
  20730. numNewLines = 0;
  20731. for (var i = 0, len = selectedText.length; i < len; i++) {
  20732. if (selectedText[i] === '\n') {
  20733. numNewLines++;
  20734. }
  20735. }
  20736. return numNewLines;
  20737. },
  20738. /**
  20739. * Finds index corresponding to beginning or end of a word
  20740. * @param {Number} selectionStart Index of a character
  20741. * @param {Number} direction 1 or -1
  20742. * @return {Number} Index of the beginning or end of a word
  20743. */
  20744. searchWordBoundary: function(selectionStart, direction) {
  20745. var index = this._reSpace.test(this.text.charAt(selectionStart)) ? selectionStart - 1 : selectionStart,
  20746. _char = this.text.charAt(index),
  20747. reNonWord = /[ \n\.,;!\?\-]/;
  20748. while (!reNonWord.test(_char) && index > 0 && index < this.text.length) {
  20749. index += direction;
  20750. _char = this.text.charAt(index);
  20751. }
  20752. if (reNonWord.test(_char) && _char !== '\n') {
  20753. index += direction === 1 ? 0 : 1;
  20754. }
  20755. return index;
  20756. },
  20757. /**
  20758. * Selects a word based on the index
  20759. * @param {Number} selectionStart Index of a character
  20760. */
  20761. selectWord: function(selectionStart) {
  20762. selectionStart = selectionStart || this.selectionStart;
  20763. var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */
  20764. newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */
  20765. this.selectionStart = newSelectionStart;
  20766. this.selectionEnd = newSelectionEnd;
  20767. this._fireSelectionChanged();
  20768. this._updateTextarea();
  20769. this.renderCursorOrSelection();
  20770. },
  20771. /**
  20772. * Selects a line based on the index
  20773. * @param {Number} selectionStart Index of a character
  20774. */
  20775. selectLine: function(selectionStart) {
  20776. selectionStart = selectionStart || this.selectionStart;
  20777. var newSelectionStart = this.findLineBoundaryLeft(selectionStart),
  20778. newSelectionEnd = this.findLineBoundaryRight(selectionStart);
  20779. this.selectionStart = newSelectionStart;
  20780. this.selectionEnd = newSelectionEnd;
  20781. this._fireSelectionChanged();
  20782. this._updateTextarea();
  20783. },
  20784. /**
  20785. * Enters editing state
  20786. * @return {fabric.IText} thisArg
  20787. * @chainable
  20788. */
  20789. enterEditing: function(e) {
  20790. if (this.isEditing || !this.editable) {
  20791. return;
  20792. }
  20793. if (this.canvas) {
  20794. this.exitEditingOnOthers(this.canvas);
  20795. }
  20796. this.isEditing = true;
  20797. this.initHiddenTextarea(e);
  20798. this.hiddenTextarea.focus();
  20799. this._updateTextarea();
  20800. this._saveEditingProps();
  20801. this._setEditingProps();
  20802. this._textBeforeEdit = this.text;
  20803. this._tick();
  20804. this.fire('editing:entered');
  20805. if (!this.canvas) {
  20806. return this;
  20807. }
  20808. this.canvas.fire('text:editing:entered', { target: this });
  20809. this.initMouseMoveHandler();
  20810. this.canvas.renderAll();
  20811. return this;
  20812. },
  20813. exitEditingOnOthers: function(canvas) {
  20814. if (canvas._iTextInstances) {
  20815. canvas._iTextInstances.forEach(function(obj) {
  20816. obj.selected = false;
  20817. if (obj.isEditing) {
  20818. obj.exitEditing();
  20819. }
  20820. });
  20821. }
  20822. },
  20823. /**
  20824. * Initializes "mousemove" event handler
  20825. */
  20826. initMouseMoveHandler: function() {
  20827. this.canvas.on('mouse:move', this.mouseMoveHandler);
  20828. },
  20829. /**
  20830. * @private
  20831. */
  20832. mouseMoveHandler: function(options) {
  20833. if (!this.__isMousedown || !this.isEditing) {
  20834. return;
  20835. }
  20836. var newSelectionStart = this.getSelectionStartFromPointer(options.e),
  20837. currentStart = this.selectionStart,
  20838. currentEnd = this.selectionEnd;
  20839. if (newSelectionStart === this.__selectionStartOnMouseDown) {
  20840. return;
  20841. }
  20842. if (newSelectionStart > this.__selectionStartOnMouseDown) {
  20843. this.selectionStart = this.__selectionStartOnMouseDown;
  20844. this.selectionEnd = newSelectionStart;
  20845. }
  20846. else {
  20847. this.selectionStart = newSelectionStart;
  20848. this.selectionEnd = this.__selectionStartOnMouseDown;
  20849. }
  20850. if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) {
  20851. this._fireSelectionChanged();
  20852. this._updateTextarea();
  20853. this.renderCursorOrSelection();
  20854. }
  20855. },
  20856. /**
  20857. * @private
  20858. */
  20859. _setEditingProps: function() {
  20860. this.hoverCursor = 'text';
  20861. if (this.canvas) {
  20862. this.canvas.defaultCursor = this.canvas.moveCursor = 'text';
  20863. }
  20864. this.borderColor = this.editingBorderColor;
  20865. this.hasControls = this.selectable = false;
  20866. this.lockMovementX = this.lockMovementY = true;
  20867. },
  20868. /**
  20869. * @private
  20870. */
  20871. _updateTextarea: function() {
  20872. if (!this.hiddenTextarea || this.inCompositionMode) {
  20873. return;
  20874. }
  20875. this.cursorOffsetCache = { };
  20876. this.hiddenTextarea.value = this.text;
  20877. this.hiddenTextarea.selectionStart = this.selectionStart;
  20878. this.hiddenTextarea.selectionEnd = this.selectionEnd;
  20879. if (this.selectionStart === this.selectionEnd) {
  20880. var style = this._calcTextareaPosition();
  20881. this.hiddenTextarea.style.left = style.left;
  20882. this.hiddenTextarea.style.top = style.top;
  20883. this.hiddenTextarea.style.fontSize = style.fontSize;
  20884. }
  20885. },
  20886. /**
  20887. * @private
  20888. * @return {Object} style contains style for hiddenTextarea
  20889. */
  20890. _calcTextareaPosition: function() {
  20891. if (!this.canvas) {
  20892. return { x: 1, y: 1 };
  20893. }
  20894. var chars = this.text.split(''),
  20895. boundaries = this._getCursorBoundaries(chars, 'cursor'),
  20896. cursorLocation = this.get2DCursorLocation(),
  20897. lineIndex = cursorLocation.lineIndex,
  20898. charIndex = cursorLocation.charIndex,
  20899. charHeight = this.getCurrentCharFontSize(lineIndex, charIndex),
  20900. leftOffset = (lineIndex === 0 && charIndex === 0)
  20901. ? this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex))
  20902. : boundaries.leftOffset,
  20903. m = this.calcTransformMatrix(),
  20904. p = {
  20905. x: boundaries.left + leftOffset,
  20906. y: boundaries.top + boundaries.topOffset + charHeight
  20907. },
  20908. upperCanvas = this.canvas.upperCanvasEl,
  20909. maxWidth = upperCanvas.width - charHeight,
  20910. maxHeight = upperCanvas.height - charHeight;
  20911. p = fabric.util.transformPoint(p, m);
  20912. p = fabric.util.transformPoint(p, this.canvas.viewportTransform);
  20913. if (p.x < 0) {
  20914. p.x = 0;
  20915. }
  20916. if (p.x > maxWidth) {
  20917. p.x = maxWidth;
  20918. }
  20919. if (p.y < 0) {
  20920. p.y = 0;
  20921. }
  20922. if (p.y > maxHeight) {
  20923. p.y = maxHeight;
  20924. }
  20925. // add canvas offset on document
  20926. p.x += this.canvas._offset.left;
  20927. p.y += this.canvas._offset.top;
  20928. return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight };
  20929. },
  20930. /**
  20931. * @private
  20932. */
  20933. _saveEditingProps: function() {
  20934. this._savedProps = {
  20935. hasControls: this.hasControls,
  20936. borderColor: this.borderColor,
  20937. lockMovementX: this.lockMovementX,
  20938. lockMovementY: this.lockMovementY,
  20939. hoverCursor: this.hoverCursor,
  20940. defaultCursor: this.canvas && this.canvas.defaultCursor,
  20941. moveCursor: this.canvas && this.canvas.moveCursor
  20942. };
  20943. },
  20944. /**
  20945. * @private
  20946. */
  20947. _restoreEditingProps: function() {
  20948. if (!this._savedProps) {
  20949. return;
  20950. }
  20951. this.hoverCursor = this._savedProps.overCursor;
  20952. this.hasControls = this._savedProps.hasControls;
  20953. this.borderColor = this._savedProps.borderColor;
  20954. this.lockMovementX = this._savedProps.lockMovementX;
  20955. this.lockMovementY = this._savedProps.lockMovementY;
  20956. if (this.canvas) {
  20957. this.canvas.defaultCursor = this._savedProps.defaultCursor;
  20958. this.canvas.moveCursor = this._savedProps.moveCursor;
  20959. }
  20960. },
  20961. /**
  20962. * Exits from editing state
  20963. * @return {fabric.IText} thisArg
  20964. * @chainable
  20965. */
  20966. exitEditing: function() {
  20967. var isTextChanged = (this._textBeforeEdit !== this.text);
  20968. this.selected = false;
  20969. this.isEditing = false;
  20970. this.selectable = true;
  20971. this.selectionEnd = this.selectionStart;
  20972. this.hiddenTextarea && this.canvas && this.hiddenTextarea.parentNode.removeChild(this.hiddenTextarea);
  20973. this.hiddenTextarea = null;
  20974. this.abortCursorAnimation();
  20975. this._restoreEditingProps();
  20976. this._currentCursorOpacity = 0;
  20977. this.fire('editing:exited');
  20978. isTextChanged && this.fire('modified');
  20979. if (this.canvas) {
  20980. this.canvas.off('mouse:move', this.mouseMoveHandler);
  20981. this.canvas.fire('text:editing:exited', { target: this });
  20982. isTextChanged && this.canvas.fire('object:modified', { target: this });
  20983. }
  20984. return this;
  20985. },
  20986. /**
  20987. * @private
  20988. */
  20989. _removeExtraneousStyles: function() {
  20990. for (var prop in this.styles) {
  20991. if (!this._textLines[prop]) {
  20992. delete this.styles[prop];
  20993. }
  20994. }
  20995. },
  20996. /**
  20997. * @private
  20998. */
  20999. _removeCharsFromTo: function(start, end) {
  21000. while (end !== start) {
  21001. this._removeSingleCharAndStyle(start + 1);
  21002. end--;
  21003. }
  21004. this.selectionStart = start;
  21005. this.selectionEnd = start;
  21006. },
  21007. _removeSingleCharAndStyle: function(index) {
  21008. var isBeginningOfLine = this.text[index - 1] === '\n',
  21009. indexStyle = isBeginningOfLine ? index : index - 1;
  21010. this.removeStyleObject(isBeginningOfLine, indexStyle);
  21011. this.text = this.text.slice(0, index - 1) +
  21012. this.text.slice(index);
  21013. this._textLines = this._splitTextIntoLines();
  21014. },
  21015. /**
  21016. * Inserts characters where cursor is (replacing selection if one exists)
  21017. * @param {String} _chars Characters to insert
  21018. * @param {Boolean} useCopiedStyle use fabric.copiedTextStyle
  21019. */
  21020. insertChars: function(_chars, useCopiedStyle) {
  21021. var style;
  21022. if (this.selectionEnd - this.selectionStart > 1) {
  21023. this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
  21024. }
  21025. //short circuit for block paste
  21026. if (!useCopiedStyle && this.isEmptyStyles()) {
  21027. this.insertChar(_chars, false);
  21028. return;
  21029. }
  21030. for (var i = 0, len = _chars.length; i < len; i++) {
  21031. if (useCopiedStyle) {
  21032. style = fabric.copiedTextStyle[i];
  21033. }
  21034. this.insertChar(_chars[i], i < len - 1, style);
  21035. }
  21036. },
  21037. /**
  21038. * Inserts a character where cursor is
  21039. * @param {String} _char Characters to insert
  21040. * @param {Boolean} skipUpdate trigger rendering and updates at the end of text insert
  21041. * @param {Object} styleObject Style to be inserted for the new char
  21042. */
  21043. insertChar: function(_char, skipUpdate, styleObject) {
  21044. var isEndOfLine = this.text[this.selectionStart] === '\n';
  21045. this.text = this.text.slice(0, this.selectionStart) +
  21046. _char + this.text.slice(this.selectionEnd);
  21047. this._textLines = this._splitTextIntoLines();
  21048. this.insertStyleObjects(_char, isEndOfLine, styleObject);
  21049. this.selectionStart += _char.length;
  21050. this.selectionEnd = this.selectionStart;
  21051. if (skipUpdate) {
  21052. return;
  21053. }
  21054. this._updateTextarea();
  21055. this.setCoords();
  21056. this._fireSelectionChanged();
  21057. this.fire('changed');
  21058. this.canvas && this.canvas.fire('text:changed', { target: this });
  21059. this.canvas && this.canvas.renderAll();
  21060. },
  21061. /**
  21062. * Inserts new style object
  21063. * @param {Number} lineIndex Index of a line
  21064. * @param {Number} charIndex Index of a char
  21065. * @param {Boolean} isEndOfLine True if it's end of line
  21066. */
  21067. insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
  21068. this.shiftLineStyles(lineIndex, +1);
  21069. if (!this.styles[lineIndex + 1]) {
  21070. this.styles[lineIndex + 1] = {};
  21071. }
  21072. var currentCharStyle = {},
  21073. newLineStyles = {};
  21074. if (this.styles[lineIndex] && this.styles[lineIndex][charIndex - 1]) {
  21075. currentCharStyle = this.styles[lineIndex][charIndex - 1];
  21076. }
  21077. // if there's nothing after cursor,
  21078. // we clone current char style onto the next (otherwise empty) line
  21079. if (isEndOfLine) {
  21080. newLineStyles[0] = clone(currentCharStyle);
  21081. this.styles[lineIndex + 1] = newLineStyles;
  21082. }
  21083. // otherwise we clone styles of all chars
  21084. // after cursor onto the next line, from the beginning
  21085. else {
  21086. for (var index in this.styles[lineIndex]) {
  21087. if (parseInt(index, 10) >= charIndex) {
  21088. newLineStyles[parseInt(index, 10) - charIndex] = this.styles[lineIndex][index];
  21089. // remove lines from the previous line since they're on a new line now
  21090. delete this.styles[lineIndex][index];
  21091. }
  21092. }
  21093. this.styles[lineIndex + 1] = newLineStyles;
  21094. }
  21095. this._forceClearCache = true;
  21096. },
  21097. /**
  21098. * Inserts style object for a given line/char index
  21099. * @param {Number} lineIndex Index of a line
  21100. * @param {Number} charIndex Index of a char
  21101. * @param {Object} [style] Style object to insert, if given
  21102. */
  21103. insertCharStyleObject: function(lineIndex, charIndex, style) {
  21104. var currentLineStyles = this.styles[lineIndex],
  21105. currentLineStylesCloned = clone(currentLineStyles);
  21106. if (charIndex === 0 && !style) {
  21107. charIndex = 1;
  21108. }
  21109. // shift all char styles by 1 forward
  21110. // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4
  21111. for (var index in currentLineStylesCloned) {
  21112. var numericIndex = parseInt(index, 10);
  21113. if (numericIndex >= charIndex) {
  21114. currentLineStyles[numericIndex + 1] = currentLineStylesCloned[numericIndex];
  21115. // only delete the style if there was nothing moved there
  21116. if (!currentLineStylesCloned[numericIndex - 1]) {
  21117. delete currentLineStyles[numericIndex];
  21118. }
  21119. }
  21120. }
  21121. this.styles[lineIndex][charIndex] =
  21122. style || clone(currentLineStyles[charIndex - 1]);
  21123. this._forceClearCache = true;
  21124. },
  21125. /**
  21126. * Inserts style object(s)
  21127. * @param {String} _chars Characters at the location where style is inserted
  21128. * @param {Boolean} isEndOfLine True if it's end of line
  21129. * @param {Object} [styleObject] Style to insert
  21130. */
  21131. insertStyleObjects: function(_chars, isEndOfLine, styleObject) {
  21132. // removed shortcircuit over isEmptyStyles
  21133. var cursorLocation = this.get2DCursorLocation(),
  21134. lineIndex = cursorLocation.lineIndex,
  21135. charIndex = cursorLocation.charIndex;
  21136. if (!this._getLineStyle(lineIndex)) {
  21137. this._setLineStyle(lineIndex, {});
  21138. }
  21139. if (_chars === '\n') {
  21140. this.insertNewlineStyleObject(lineIndex, charIndex, isEndOfLine);
  21141. }
  21142. else {
  21143. this.insertCharStyleObject(lineIndex, charIndex, styleObject);
  21144. }
  21145. },
  21146. /**
  21147. * Shifts line styles up or down
  21148. * @param {Number} lineIndex Index of a line
  21149. * @param {Number} offset Can be -1 or +1
  21150. */
  21151. shiftLineStyles: function(lineIndex, offset) {
  21152. // shift all line styles by 1 upward
  21153. var clonedStyles = clone(this.styles);
  21154. for (var line in this.styles) {
  21155. var numericLine = parseInt(line, 10);
  21156. if (numericLine > lineIndex) {
  21157. this.styles[numericLine + offset] = clonedStyles[numericLine];
  21158. if (!clonedStyles[numericLine - offset]) {
  21159. delete this.styles[numericLine];
  21160. }
  21161. }
  21162. }
  21163. //TODO: evaluate if delete old style lines with offset -1
  21164. },
  21165. /**
  21166. * Removes style object
  21167. * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
  21168. * @param {Number} [index] Optional index. When not given, current selectionStart is used.
  21169. */
  21170. removeStyleObject: function(isBeginningOfLine, index) {
  21171. var cursorLocation = this.get2DCursorLocation(index),
  21172. lineIndex = cursorLocation.lineIndex,
  21173. charIndex = cursorLocation.charIndex;
  21174. this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex);
  21175. },
  21176. _getTextOnPreviousLine: function(lIndex) {
  21177. return this._textLines[lIndex - 1];
  21178. },
  21179. _removeStyleObject: function(isBeginningOfLine, cursorLocation, lineIndex, charIndex) {
  21180. if (isBeginningOfLine) {
  21181. var textOnPreviousLine = this._getTextOnPreviousLine(cursorLocation.lineIndex),
  21182. newCharIndexOnPrevLine = textOnPreviousLine ? textOnPreviousLine.length : 0;
  21183. if (!this.styles[lineIndex - 1]) {
  21184. this.styles[lineIndex - 1] = {};
  21185. }
  21186. for (charIndex in this.styles[lineIndex]) {
  21187. this.styles[lineIndex - 1][parseInt(charIndex, 10) + newCharIndexOnPrevLine]
  21188. = this.styles[lineIndex][charIndex];
  21189. }
  21190. this.shiftLineStyles(cursorLocation.lineIndex, -1);
  21191. }
  21192. else {
  21193. var currentLineStyles = this.styles[lineIndex];
  21194. if (currentLineStyles) {
  21195. delete currentLineStyles[charIndex];
  21196. }
  21197. var currentLineStylesCloned = clone(currentLineStyles);
  21198. // shift all styles by 1 backwards
  21199. for (var i in currentLineStylesCloned) {
  21200. var numericIndex = parseInt(i, 10);
  21201. if (numericIndex >= charIndex && numericIndex !== 0) {
  21202. currentLineStyles[numericIndex - 1] = currentLineStylesCloned[numericIndex];
  21203. delete currentLineStyles[numericIndex];
  21204. }
  21205. }
  21206. }
  21207. },
  21208. /**
  21209. * Inserts new line
  21210. */
  21211. insertNewline: function() {
  21212. this.insertChars('\n');
  21213. },
  21214. /**
  21215. * Set the selectionStart and selectionEnd according to the ne postion of cursor
  21216. * mimic the key - mouse navigation when shift is pressed.
  21217. */
  21218. setSelectionStartEndWithShift: function(start, end, newSelection) {
  21219. if (newSelection <= start) {
  21220. if (end === start) {
  21221. this._selectionDirection = 'left';
  21222. }
  21223. else if (this._selectionDirection === 'right') {
  21224. this._selectionDirection = 'left';
  21225. this.selectionEnd = start;
  21226. }
  21227. this.selectionStart = newSelection;
  21228. }
  21229. else if (newSelection > start && newSelection < end) {
  21230. if (this._selectionDirection === 'right') {
  21231. this.selectionEnd = newSelection;
  21232. }
  21233. else {
  21234. this.selectionStart = newSelection;
  21235. }
  21236. }
  21237. else {
  21238. // newSelection is > selection start and end
  21239. if (end === start) {
  21240. this._selectionDirection = 'right';
  21241. }
  21242. else if (this._selectionDirection === 'left') {
  21243. this._selectionDirection = 'right';
  21244. this.selectionStart = end;
  21245. }
  21246. this.selectionEnd = newSelection;
  21247. }
  21248. },
  21249. setSelectionInBoundaries: function() {
  21250. var length = this.text.length;
  21251. if (this.selectionStart > length) {
  21252. this.selectionStart = length;
  21253. }
  21254. else if (this.selectionStart < 0) {
  21255. this.selectionStart = 0;
  21256. }
  21257. if (this.selectionEnd > length) {
  21258. this.selectionEnd = length;
  21259. }
  21260. else if (this.selectionEnd < 0) {
  21261. this.selectionEnd = 0;
  21262. }
  21263. }
  21264. });
  21265. })();
  21266. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  21267. /**
  21268. * Initializes "dbclick" event handler
  21269. */
  21270. initDoubleClickSimulation: function() {
  21271. // for double click
  21272. this.__lastClickTime = +new Date();
  21273. // for triple click
  21274. this.__lastLastClickTime = +new Date();
  21275. this.__lastPointer = { };
  21276. this.on('mousedown', this.onMouseDown.bind(this));
  21277. },
  21278. onMouseDown: function(options) {
  21279. this.__newClickTime = +new Date();
  21280. var newPointer = this.canvas.getPointer(options.e);
  21281. if (this.isTripleClick(newPointer)) {
  21282. this.fire('tripleclick', options);
  21283. this._stopEvent(options.e);
  21284. }
  21285. else if (this.isDoubleClick(newPointer)) {
  21286. this.fire('dblclick', options);
  21287. this._stopEvent(options.e);
  21288. }
  21289. this.__lastLastClickTime = this.__lastClickTime;
  21290. this.__lastClickTime = this.__newClickTime;
  21291. this.__lastPointer = newPointer;
  21292. this.__lastIsEditing = this.isEditing;
  21293. this.__lastSelected = this.selected;
  21294. },
  21295. isDoubleClick: function(newPointer) {
  21296. return this.__newClickTime - this.__lastClickTime < 500 &&
  21297. this.__lastPointer.x === newPointer.x &&
  21298. this.__lastPointer.y === newPointer.y && this.__lastIsEditing;
  21299. },
  21300. isTripleClick: function(newPointer) {
  21301. return this.__newClickTime - this.__lastClickTime < 500 &&
  21302. this.__lastClickTime - this.__lastLastClickTime < 500 &&
  21303. this.__lastPointer.x === newPointer.x &&
  21304. this.__lastPointer.y === newPointer.y;
  21305. },
  21306. /**
  21307. * @private
  21308. */
  21309. _stopEvent: function(e) {
  21310. e.preventDefault && e.preventDefault();
  21311. e.stopPropagation && e.stopPropagation();
  21312. },
  21313. /**
  21314. * Initializes event handlers related to cursor or selection
  21315. */
  21316. initCursorSelectionHandlers: function() {
  21317. this.initSelectedHandler();
  21318. this.initMousedownHandler();
  21319. this.initMouseupHandler();
  21320. this.initClicks();
  21321. },
  21322. /**
  21323. * Initializes double and triple click event handlers
  21324. */
  21325. initClicks: function() {
  21326. this.on('dblclick', function(options) {
  21327. this.selectWord(this.getSelectionStartFromPointer(options.e));
  21328. });
  21329. this.on('tripleclick', function(options) {
  21330. this.selectLine(this.getSelectionStartFromPointer(options.e));
  21331. });
  21332. },
  21333. /**
  21334. * Initializes "mousedown" event handler
  21335. */
  21336. initMousedownHandler: function() {
  21337. this.on('mousedown', function(options) {
  21338. if (!this.editable) {
  21339. return;
  21340. }
  21341. var pointer = this.canvas.getPointer(options.e);
  21342. this.__mousedownX = pointer.x;
  21343. this.__mousedownY = pointer.y;
  21344. this.__isMousedown = true;
  21345. if (this.selected) {
  21346. this.setCursorByClick(options.e);
  21347. }
  21348. if (this.isEditing) {
  21349. this.__selectionStartOnMouseDown = this.selectionStart;
  21350. if (this.selectionStart === this.selectionEnd) {
  21351. this.abortCursorAnimation();
  21352. }
  21353. this.renderCursorOrSelection();
  21354. }
  21355. });
  21356. },
  21357. /**
  21358. * @private
  21359. */
  21360. _isObjectMoved: function(e) {
  21361. var pointer = this.canvas.getPointer(e);
  21362. return this.__mousedownX !== pointer.x ||
  21363. this.__mousedownY !== pointer.y;
  21364. },
  21365. /**
  21366. * Initializes "mouseup" event handler
  21367. */
  21368. initMouseupHandler: function() {
  21369. this.on('mouseup', function(options) {
  21370. this.__isMousedown = false;
  21371. if (!this.editable || this._isObjectMoved(options.e)) {
  21372. return;
  21373. }
  21374. if (this.__lastSelected && !this.__corner) {
  21375. this.enterEditing(options.e);
  21376. if (this.selectionStart === this.selectionEnd) {
  21377. this.initDelayedCursor(true);
  21378. }
  21379. else {
  21380. this.renderCursorOrSelection();
  21381. }
  21382. }
  21383. this.selected = true;
  21384. });
  21385. },
  21386. /**
  21387. * Changes cursor location in a text depending on passed pointer (x/y) object
  21388. * @param {Event} e Event object
  21389. */
  21390. setCursorByClick: function(e) {
  21391. var newSelection = this.getSelectionStartFromPointer(e),
  21392. start = this.selectionStart, end = this.selectionEnd;
  21393. if (e.shiftKey) {
  21394. this.setSelectionStartEndWithShift(start, end, newSelection);
  21395. }
  21396. else {
  21397. this.selectionStart = newSelection;
  21398. this.selectionEnd = newSelection;
  21399. }
  21400. this._fireSelectionChanged();
  21401. this._updateTextarea();
  21402. },
  21403. /**
  21404. * Returns index of a character corresponding to where an object was clicked
  21405. * @param {Event} e Event object
  21406. * @return {Number} Index of a character
  21407. */
  21408. getSelectionStartFromPointer: function(e) {
  21409. var mouseOffset = this.getLocalPointer(e),
  21410. prevWidth = 0,
  21411. width = 0,
  21412. height = 0,
  21413. charIndex = 0,
  21414. newSelectionStart,
  21415. line;
  21416. for (var i = 0, len = this._textLines.length; i < len; i++) {
  21417. line = this._textLines[i];
  21418. height += this._getHeightOfLine(this.ctx, i) * this.scaleY;
  21419. var widthOfLine = this._getLineWidth(this.ctx, i),
  21420. lineLeftOffset = this._getLineLeftOffset(widthOfLine);
  21421. width = lineLeftOffset * this.scaleX;
  21422. for (var j = 0, jlen = line.length; j < jlen; j++) {
  21423. prevWidth = width;
  21424. width += this._getWidthOfChar(this.ctx, line[j], i, this.flipX ? jlen - j : j) *
  21425. this.scaleX;
  21426. if (height <= mouseOffset.y || width <= mouseOffset.x) {
  21427. charIndex++;
  21428. continue;
  21429. }
  21430. return this._getNewSelectionStartFromOffset(
  21431. mouseOffset, prevWidth, width, charIndex + i, jlen);
  21432. }
  21433. if (mouseOffset.y < height) {
  21434. //this happens just on end of lines.
  21435. return this._getNewSelectionStartFromOffset(
  21436. mouseOffset, prevWidth, width, charIndex + i - 1, jlen);
  21437. }
  21438. }
  21439. // clicked somewhere after all chars, so set at the end
  21440. if (typeof newSelectionStart === 'undefined') {
  21441. return this.text.length;
  21442. }
  21443. },
  21444. /**
  21445. * @private
  21446. */
  21447. _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) {
  21448. var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth,
  21449. distanceBtwNextCharAndCursor = width - mouseOffset.x,
  21450. offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor ? 0 : 1,
  21451. newSelectionStart = index + offset;
  21452. // if object is horizontally flipped, mirror cursor location from the end
  21453. if (this.flipX) {
  21454. newSelectionStart = jlen - newSelectionStart;
  21455. }
  21456. if (newSelectionStart > this.text.length) {
  21457. newSelectionStart = this.text.length;
  21458. }
  21459. return newSelectionStart;
  21460. }
  21461. });
  21462. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  21463. /**
  21464. * Initializes hidden textarea (needed to bring up keyboard in iOS)
  21465. */
  21466. initHiddenTextarea: function() {
  21467. this.hiddenTextarea = fabric.document.createElement('textarea');
  21468. this.hiddenTextarea.setAttribute('autocapitalize', 'off');
  21469. var style = this._calcTextareaPosition();
  21470. this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top + '; left: ' + style.left + ';'
  21471. + ' opacity: 0; width: 0px; height: 0px; z-index: -999;';
  21472. fabric.document.body.appendChild(this.hiddenTextarea);
  21473. fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this));
  21474. fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this));
  21475. fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this));
  21476. fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this));
  21477. fabric.util.addListener(this.hiddenTextarea, 'cut', this.cut.bind(this));
  21478. fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this));
  21479. fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this));
  21480. fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this));
  21481. fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this));
  21482. if (!this._clickHandlerInitialized && this.canvas) {
  21483. fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this));
  21484. this._clickHandlerInitialized = true;
  21485. }
  21486. },
  21487. /**
  21488. * @private
  21489. */
  21490. _keysMap: {
  21491. 8: 'removeChars',
  21492. 9: 'exitEditing',
  21493. 27: 'exitEditing',
  21494. 13: 'insertNewline',
  21495. 33: 'moveCursorUp',
  21496. 34: 'moveCursorDown',
  21497. 35: 'moveCursorRight',
  21498. 36: 'moveCursorLeft',
  21499. 37: 'moveCursorLeft',
  21500. 38: 'moveCursorUp',
  21501. 39: 'moveCursorRight',
  21502. 40: 'moveCursorDown',
  21503. 46: 'forwardDelete'
  21504. },
  21505. /**
  21506. * @private
  21507. */
  21508. _ctrlKeysMapUp: {
  21509. 67: 'copy',
  21510. 88: 'cut'
  21511. },
  21512. /**
  21513. * @private
  21514. */
  21515. _ctrlKeysMapDown: {
  21516. 65: 'selectAll'
  21517. },
  21518. onClick: function() {
  21519. // No need to trigger click event here, focus is enough to have the keyboard appear on Android
  21520. this.hiddenTextarea && this.hiddenTextarea.focus();
  21521. },
  21522. /**
  21523. * Handles keyup event
  21524. * @param {Event} e Event object
  21525. */
  21526. onKeyDown: function(e) {
  21527. if (!this.isEditing) {
  21528. return;
  21529. }
  21530. if (e.keyCode in this._keysMap) {
  21531. this[this._keysMap[e.keyCode]](e);
  21532. }
  21533. else if ((e.keyCode in this._ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) {
  21534. this[this._ctrlKeysMapDown[e.keyCode]](e);
  21535. }
  21536. else {
  21537. return;
  21538. }
  21539. e.stopImmediatePropagation();
  21540. e.preventDefault();
  21541. this.canvas && this.canvas.renderAll();
  21542. },
  21543. /**
  21544. * Handles keyup event
  21545. * We handle KeyUp because ie11 and edge have difficulties copy/pasting
  21546. * if a copy/cut event fired, keyup is dismissed
  21547. * @param {Event} e Event object
  21548. */
  21549. onKeyUp: function(e) {
  21550. if (!this.isEditing || this._copyDone) {
  21551. this._copyDone = false;
  21552. return;
  21553. }
  21554. if ((e.keyCode in this._ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) {
  21555. this[this._ctrlKeysMapUp[e.keyCode]](e);
  21556. }
  21557. else {
  21558. return;
  21559. }
  21560. e.stopImmediatePropagation();
  21561. e.preventDefault();
  21562. this.canvas && this.canvas.renderAll();
  21563. },
  21564. /**
  21565. * Handles onInput event
  21566. * @param {Event} e Event object
  21567. */
  21568. onInput: function(e) {
  21569. if (!this.isEditing || this.inCompositionMode) {
  21570. return;
  21571. }
  21572. var offset = this.selectionStart || 0,
  21573. offsetEnd = this.selectionEnd || 0,
  21574. textLength = this.text.length,
  21575. newTextLength = this.hiddenTextarea.value.length,
  21576. diff, charsToInsert, start;
  21577. if (newTextLength > textLength) {
  21578. //we added some character
  21579. start = this._selectionDirection === 'left' ? offsetEnd : offset;
  21580. diff = newTextLength - textLength;
  21581. charsToInsert = this.hiddenTextarea.value.slice(start, start + diff);
  21582. }
  21583. else {
  21584. //we selected a portion of text and then input something else.
  21585. //Internet explorer does not trigger this else
  21586. diff = newTextLength - textLength + offsetEnd - offset;
  21587. charsToInsert = this.hiddenTextarea.value.slice(offset, offset + diff);
  21588. }
  21589. this.insertChars(charsToInsert);
  21590. e.stopPropagation();
  21591. },
  21592. /**
  21593. * Composition start
  21594. */
  21595. onCompositionStart: function() {
  21596. this.inCompositionMode = true;
  21597. this.prevCompositionLength = 0;
  21598. this.compositionStart = this.selectionStart;
  21599. },
  21600. /**
  21601. * Composition end
  21602. */
  21603. onCompositionEnd: function() {
  21604. this.inCompositionMode = false;
  21605. },
  21606. /**
  21607. * Composition update
  21608. */
  21609. onCompositionUpdate: function(e) {
  21610. var data = e.data;
  21611. this.selectionStart = this.compositionStart;
  21612. this.selectionEnd = this.selectionEnd === this.selectionStart ?
  21613. this.compositionStart + this.prevCompositionLength : this.selectionEnd;
  21614. this.insertChars(data, false);
  21615. this.prevCompositionLength = data.length;
  21616. },
  21617. /**
  21618. * Forward delete
  21619. */
  21620. forwardDelete: function(e) {
  21621. if (this.selectionStart === this.selectionEnd) {
  21622. if (this.selectionStart === this.text.length) {
  21623. return;
  21624. }
  21625. this.moveCursorRight(e);
  21626. }
  21627. this.removeChars(e);
  21628. },
  21629. /**
  21630. * Copies selected text
  21631. * @param {Event} e Event object
  21632. */
  21633. copy: function(e) {
  21634. if (this.selectionStart === this.selectionEnd) {
  21635. //do not cut-copy if no selection
  21636. return;
  21637. }
  21638. var selectedText = this.getSelectedText(),
  21639. clipboardData = this._getClipboardData(e);
  21640. // Check for backward compatibility with old browsers
  21641. if (clipboardData) {
  21642. clipboardData.setData('text', selectedText);
  21643. }
  21644. fabric.copiedText = selectedText;
  21645. fabric.copiedTextStyle = this.getSelectionStyles(
  21646. this.selectionStart,
  21647. this.selectionEnd);
  21648. e.stopImmediatePropagation();
  21649. e.preventDefault();
  21650. this._copyDone = true;
  21651. },
  21652. /**
  21653. * Pastes text
  21654. * @param {Event} e Event object
  21655. */
  21656. paste: function(e) {
  21657. var copiedText = null,
  21658. clipboardData = this._getClipboardData(e),
  21659. useCopiedStyle = true;
  21660. // Check for backward compatibility with old browsers
  21661. if (clipboardData) {
  21662. copiedText = clipboardData.getData('text').replace(/\r/g, '');
  21663. if (!fabric.copiedTextStyle || fabric.copiedText !== copiedText) {
  21664. useCopiedStyle = false;
  21665. }
  21666. }
  21667. else {
  21668. copiedText = fabric.copiedText;
  21669. }
  21670. if (copiedText) {
  21671. this.insertChars(copiedText, useCopiedStyle);
  21672. }
  21673. e.stopImmediatePropagation();
  21674. e.preventDefault();
  21675. },
  21676. /**
  21677. * Cuts text
  21678. * @param {Event} e Event object
  21679. */
  21680. cut: function(e) {
  21681. if (this.selectionStart === this.selectionEnd) {
  21682. return;
  21683. }
  21684. this.copy(e);
  21685. this.removeChars(e);
  21686. },
  21687. /**
  21688. * @private
  21689. * @param {Event} e Event object
  21690. * @return {Object} Clipboard data object
  21691. */
  21692. _getClipboardData: function(e) {
  21693. return (e && e.clipboardData) || fabric.window.clipboardData;
  21694. },
  21695. /**
  21696. * Finds the width in pixels before the cursor on the same line
  21697. * @private
  21698. * @param {Number} lineIndex
  21699. * @param {Number} charIndex
  21700. * @return {Number} widthBeforeCursor width before cursor
  21701. */
  21702. _getWidthBeforeCursor: function(lineIndex, charIndex) {
  21703. var textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex),
  21704. widthOfLine = this._getLineWidth(this.ctx, lineIndex),
  21705. widthBeforeCursor = this._getLineLeftOffset(widthOfLine), _char;
  21706. for (var i = 0, len = textBeforeCursor.length; i < len; i++) {
  21707. _char = textBeforeCursor[i];
  21708. widthBeforeCursor += this._getWidthOfChar(this.ctx, _char, lineIndex, i);
  21709. }
  21710. return widthBeforeCursor;
  21711. },
  21712. /**
  21713. * Gets start offset of a selection
  21714. * @param {Event} e Event object
  21715. * @param {Boolean} isRight
  21716. * @return {Number}
  21717. */
  21718. getDownCursorOffset: function(e, isRight) {
  21719. var selectionProp = this._getSelectionForOffset(e, isRight),
  21720. cursorLocation = this.get2DCursorLocation(selectionProp),
  21721. lineIndex = cursorLocation.lineIndex;
  21722. // if on last line, down cursor goes to end of line
  21723. if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) {
  21724. // move to the end of a text
  21725. return this.text.length - selectionProp;
  21726. }
  21727. var charIndex = cursorLocation.charIndex,
  21728. widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),
  21729. indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor),
  21730. textAfterCursor = this._textLines[lineIndex].slice(charIndex);
  21731. return textAfterCursor.length + indexOnOtherLine + 2;
  21732. },
  21733. /**
  21734. * private
  21735. * Helps finding if the offset should be counted from Start or End
  21736. * @param {Event} e Event object
  21737. * @param {Boolean} isRight
  21738. * @return {Number}
  21739. */
  21740. _getSelectionForOffset: function(e, isRight) {
  21741. if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) {
  21742. return this.selectionEnd;
  21743. }
  21744. else {
  21745. return this.selectionStart;
  21746. }
  21747. },
  21748. /**
  21749. * @param {Event} e Event object
  21750. * @param {Boolean} isRight
  21751. * @return {Number}
  21752. */
  21753. getUpCursorOffset: function(e, isRight) {
  21754. var selectionProp = this._getSelectionForOffset(e, isRight),
  21755. cursorLocation = this.get2DCursorLocation(selectionProp),
  21756. lineIndex = cursorLocation.lineIndex;
  21757. if (lineIndex === 0 || e.metaKey || e.keyCode === 33) {
  21758. // if on first line, up cursor goes to start of line
  21759. return -selectionProp;
  21760. }
  21761. var charIndex = cursorLocation.charIndex,
  21762. widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex),
  21763. indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor),
  21764. textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex);
  21765. // return a negative offset
  21766. return -this._textLines[lineIndex - 1].length + indexOnOtherLine - textBeforeCursor.length;
  21767. },
  21768. /**
  21769. * find for a given width it founds the matching character.
  21770. * @private
  21771. */
  21772. _getIndexOnLine: function(lineIndex, width) {
  21773. var widthOfLine = this._getLineWidth(this.ctx, lineIndex),
  21774. textOnLine = this._textLines[lineIndex],
  21775. lineLeftOffset = this._getLineLeftOffset(widthOfLine),
  21776. widthOfCharsOnLine = lineLeftOffset,
  21777. indexOnLine = 0,
  21778. foundMatch;
  21779. for (var j = 0, jlen = textOnLine.length; j < jlen; j++) {
  21780. var _char = textOnLine[j],
  21781. widthOfChar = this._getWidthOfChar(this.ctx, _char, lineIndex, j);
  21782. widthOfCharsOnLine += widthOfChar;
  21783. if (widthOfCharsOnLine > width) {
  21784. foundMatch = true;
  21785. var leftEdge = widthOfCharsOnLine - widthOfChar,
  21786. rightEdge = widthOfCharsOnLine,
  21787. offsetFromLeftEdge = Math.abs(leftEdge - width),
  21788. offsetFromRightEdge = Math.abs(rightEdge - width);
  21789. indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1);
  21790. break;
  21791. }
  21792. }
  21793. // reached end
  21794. if (!foundMatch) {
  21795. indexOnLine = textOnLine.length - 1;
  21796. }
  21797. return indexOnLine;
  21798. },
  21799. /**
  21800. * Moves cursor down
  21801. * @param {Event} e Event object
  21802. */
  21803. moveCursorDown: function(e) {
  21804. if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
  21805. return;
  21806. }
  21807. this._moveCursorUpOrDown('Down', e);
  21808. },
  21809. /**
  21810. * Moves cursor up
  21811. * @param {Event} e Event object
  21812. */
  21813. moveCursorUp: function(e) {
  21814. if (this.selectionStart === 0 && this.selectionEnd === 0) {
  21815. return;
  21816. }
  21817. this._moveCursorUpOrDown('Up', e);
  21818. },
  21819. /**
  21820. * Moves cursor up or down, fires the events
  21821. * @param {String} direction 'Up' or 'Down'
  21822. * @param {Event} e Event object
  21823. */
  21824. _moveCursorUpOrDown: function(direction, e) {
  21825. // getUpCursorOffset
  21826. // getDownCursorOffset
  21827. var action = 'get' + direction + 'CursorOffset',
  21828. offset = this[action](e, this._selectionDirection === 'right');
  21829. if (e.shiftKey) {
  21830. this.moveCursorWithShift(offset);
  21831. }
  21832. else {
  21833. this.moveCursorWithoutShift(offset);
  21834. }
  21835. if (offset !== 0) {
  21836. this.setSelectionInBoundaries();
  21837. this.abortCursorAnimation();
  21838. this._currentCursorOpacity = 1;
  21839. this.initDelayedCursor();
  21840. this._fireSelectionChanged();
  21841. this._updateTextarea();
  21842. }
  21843. },
  21844. /**
  21845. * Moves cursor with shift
  21846. * @param {Number} offset
  21847. */
  21848. moveCursorWithShift: function(offset) {
  21849. var newSelection = this._selectionDirection === 'left'
  21850. ? this.selectionStart + offset
  21851. : this.selectionEnd + offset;
  21852. this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection);
  21853. return offset !== 0;
  21854. },
  21855. /**
  21856. * Moves cursor up without shift
  21857. * @param {Number} offset
  21858. */
  21859. moveCursorWithoutShift: function(offset) {
  21860. if (offset < 0) {
  21861. this.selectionStart += offset;
  21862. this.selectionEnd = this.selectionStart;
  21863. }
  21864. else {
  21865. this.selectionEnd += offset;
  21866. this.selectionStart = this.selectionEnd;
  21867. }
  21868. return offset !== 0;
  21869. },
  21870. /**
  21871. * Moves cursor left
  21872. * @param {Event} e Event object
  21873. */
  21874. moveCursorLeft: function(e) {
  21875. if (this.selectionStart === 0 && this.selectionEnd === 0) {
  21876. return;
  21877. }
  21878. this._moveCursorLeftOrRight('Left', e);
  21879. },
  21880. /**
  21881. * @private
  21882. * @return {Boolean} true if a change happened
  21883. */
  21884. _move: function(e, prop, direction) {
  21885. var newValue;
  21886. if (e.altKey) {
  21887. newValue = this['findWordBoundary' + direction](this[prop]);
  21888. }
  21889. else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) {
  21890. newValue = this['findLineBoundary' + direction](this[prop]);
  21891. }
  21892. else {
  21893. this[prop] += direction === 'Left' ? -1 : 1;
  21894. return true;
  21895. }
  21896. if (typeof newValue !== undefined && this[prop] !== newValue) {
  21897. this[prop] = newValue;
  21898. return true;
  21899. }
  21900. },
  21901. /**
  21902. * @private
  21903. */
  21904. _moveLeft: function(e, prop) {
  21905. return this._move(e, prop, 'Left');
  21906. },
  21907. /**
  21908. * @private
  21909. */
  21910. _moveRight: function(e, prop) {
  21911. return this._move(e, prop, 'Right');
  21912. },
  21913. /**
  21914. * Moves cursor left without keeping selection
  21915. * @param {Event} e
  21916. */
  21917. moveCursorLeftWithoutShift: function(e) {
  21918. var change = true;
  21919. this._selectionDirection = 'left';
  21920. // only move cursor when there is no selection,
  21921. // otherwise we discard it, and leave cursor on same place
  21922. if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) {
  21923. change = this._moveLeft(e, 'selectionStart');
  21924. }
  21925. this.selectionEnd = this.selectionStart;
  21926. return change;
  21927. },
  21928. /**
  21929. * Moves cursor left while keeping selection
  21930. * @param {Event} e
  21931. */
  21932. moveCursorLeftWithShift: function(e) {
  21933. if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) {
  21934. return this._moveLeft(e, 'selectionEnd');
  21935. }
  21936. else if (this.selectionStart !== 0){
  21937. this._selectionDirection = 'left';
  21938. return this._moveLeft(e, 'selectionStart');
  21939. }
  21940. },
  21941. /**
  21942. * Moves cursor right
  21943. * @param {Event} e Event object
  21944. */
  21945. moveCursorRight: function(e) {
  21946. if (this.selectionStart >= this.text.length && this.selectionEnd >= this.text.length) {
  21947. return;
  21948. }
  21949. this._moveCursorLeftOrRight('Right', e);
  21950. },
  21951. /**
  21952. * Moves cursor right or Left, fires event
  21953. * @param {String} direction 'Left', 'Right'
  21954. * @param {Event} e Event object
  21955. */
  21956. _moveCursorLeftOrRight: function(direction, e) {
  21957. var actionName = 'moveCursor' + direction + 'With';
  21958. this._currentCursorOpacity = 1;
  21959. if (e.shiftKey) {
  21960. actionName += 'Shift';
  21961. }
  21962. else {
  21963. actionName += 'outShift';
  21964. }
  21965. if (this[actionName](e)) {
  21966. this.abortCursorAnimation();
  21967. this.initDelayedCursor();
  21968. this._fireSelectionChanged();
  21969. this._updateTextarea();
  21970. }
  21971. },
  21972. /**
  21973. * Moves cursor right while keeping selection
  21974. * @param {Event} e
  21975. */
  21976. moveCursorRightWithShift: function(e) {
  21977. if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) {
  21978. return this._moveRight(e, 'selectionStart');
  21979. }
  21980. else if (this.selectionEnd !== this.text.length) {
  21981. this._selectionDirection = 'right';
  21982. return this._moveRight(e, 'selectionEnd');
  21983. }
  21984. },
  21985. /**
  21986. * Moves cursor right without keeping selection
  21987. * @param {Event} e Event object
  21988. */
  21989. moveCursorRightWithoutShift: function(e) {
  21990. var changed = true;
  21991. this._selectionDirection = 'right';
  21992. if (this.selectionStart === this.selectionEnd) {
  21993. changed = this._moveRight(e, 'selectionStart');
  21994. this.selectionEnd = this.selectionStart;
  21995. }
  21996. else {
  21997. this.selectionStart = this.selectionEnd;
  21998. }
  21999. return changed;
  22000. },
  22001. /**
  22002. * Removes characters selected by selection
  22003. * @param {Event} e Event object
  22004. */
  22005. removeChars: function(e) {
  22006. if (this.selectionStart === this.selectionEnd) {
  22007. this._removeCharsNearCursor(e);
  22008. }
  22009. else {
  22010. this._removeCharsFromTo(this.selectionStart, this.selectionEnd);
  22011. }
  22012. this.setSelectionEnd(this.selectionStart);
  22013. this._removeExtraneousStyles();
  22014. this.canvas && this.canvas.renderAll();
  22015. this.setCoords();
  22016. this.fire('changed');
  22017. this.canvas && this.canvas.fire('text:changed', { target: this });
  22018. },
  22019. /**
  22020. * @private
  22021. * @param {Event} e Event object
  22022. */
  22023. _removeCharsNearCursor: function(e) {
  22024. if (this.selectionStart === 0) {
  22025. return;
  22026. }
  22027. if (e.metaKey) {
  22028. // remove all till the start of current line
  22029. var leftLineBoundary = this.findLineBoundaryLeft(this.selectionStart);
  22030. this._removeCharsFromTo(leftLineBoundary, this.selectionStart);
  22031. this.setSelectionStart(leftLineBoundary);
  22032. }
  22033. else if (e.altKey) {
  22034. // remove all till the start of current word
  22035. var leftWordBoundary = this.findWordBoundaryLeft(this.selectionStart);
  22036. this._removeCharsFromTo(leftWordBoundary, this.selectionStart);
  22037. this.setSelectionStart(leftWordBoundary);
  22038. }
  22039. else {
  22040. this._removeSingleCharAndStyle(this.selectionStart);
  22041. this.setSelectionStart(this.selectionStart - 1);
  22042. }
  22043. }
  22044. });
  22045. /* _TO_SVG_START_ */
  22046. (function() {
  22047. var toFixed = fabric.util.toFixed,
  22048. NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
  22049. fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ {
  22050. /**
  22051. * @private
  22052. */
  22053. _setSVGTextLineText: function(lineIndex, textSpans, height, textLeftOffset, textTopOffset, textBgRects) {
  22054. if (!this._getLineStyle(lineIndex)) {
  22055. fabric.Text.prototype._setSVGTextLineText.call(this,
  22056. lineIndex, textSpans, height, textLeftOffset, textTopOffset);
  22057. }
  22058. else {
  22059. this._setSVGTextLineChars(
  22060. lineIndex, textSpans, height, textLeftOffset, textBgRects);
  22061. }
  22062. },
  22063. /**
  22064. * @private
  22065. */
  22066. _setSVGTextLineChars: function(lineIndex, textSpans, height, textLeftOffset, textBgRects) {
  22067. var chars = this._textLines[lineIndex],
  22068. charOffset = 0,
  22069. lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, lineIndex)) - this.width / 2,
  22070. lineOffset = this._getSVGLineTopOffset(lineIndex),
  22071. heightOfLine = this._getHeightOfLine(this.ctx, lineIndex);
  22072. for (var i = 0, len = chars.length; i < len; i++) {
  22073. var styleDecl = this._getStyleDeclaration(lineIndex, i) || { };
  22074. textSpans.push(
  22075. this._createTextCharSpan(
  22076. chars[i], styleDecl, lineLeftOffset, lineOffset.lineTop + lineOffset.offset, charOffset));
  22077. var charWidth = this._getWidthOfChar(this.ctx, chars[i], lineIndex, i);
  22078. if (styleDecl.textBackgroundColor) {
  22079. textBgRects.push(
  22080. this._createTextCharBg(
  22081. styleDecl, lineLeftOffset, lineOffset.lineTop, heightOfLine, charWidth, charOffset));
  22082. }
  22083. charOffset += charWidth;
  22084. }
  22085. },
  22086. /**
  22087. * @private
  22088. */
  22089. _getSVGLineTopOffset: function(lineIndex) {
  22090. var lineTopOffset = 0, lastHeight = 0;
  22091. for (var j = 0; j < lineIndex; j++) {
  22092. lineTopOffset += this._getHeightOfLine(this.ctx, j);
  22093. }
  22094. lastHeight = this._getHeightOfLine(this.ctx, j);
  22095. return {
  22096. lineTop: lineTopOffset,
  22097. offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult)
  22098. };
  22099. },
  22100. /**
  22101. * @private
  22102. */
  22103. _createTextCharBg: function(styleDecl, lineLeftOffset, lineTopOffset, heightOfLine, charWidth, charOffset) {
  22104. return [
  22105. '\t\t<rect fill="', styleDecl.textBackgroundColor,
  22106. '" x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS),
  22107. '" y="', toFixed(lineTopOffset - this.height / 2, NUM_FRACTION_DIGITS),
  22108. '" width="', toFixed(charWidth, NUM_FRACTION_DIGITS),
  22109. '" height="', toFixed(heightOfLine / this.lineHeight, NUM_FRACTION_DIGITS),
  22110. '"></rect>\n'
  22111. ].join('');
  22112. },
  22113. /**
  22114. * @private
  22115. */
  22116. _createTextCharSpan: function(_char, styleDecl, lineLeftOffset, lineTopOffset, charOffset) {
  22117. var fillStyles = this.getSvgStyles.call(fabric.util.object.extend({
  22118. visible: true,
  22119. fill: this.fill,
  22120. stroke: this.stroke,
  22121. type: 'text',
  22122. getSvgFilter: fabric.Object.prototype.getSvgFilter
  22123. }, styleDecl));
  22124. return [
  22125. '\t\t\t<tspan x="', toFixed(lineLeftOffset + charOffset, NUM_FRACTION_DIGITS), '" y="',
  22126. toFixed(lineTopOffset - this.height / 2, NUM_FRACTION_DIGITS), '" ',
  22127. (styleDecl.fontFamily ? 'font-family="' + styleDecl.fontFamily.replace(/"/g, '\'') + '" ' : ''),
  22128. (styleDecl.fontSize ? 'font-size="' + styleDecl.fontSize + '" ' : ''),
  22129. (styleDecl.fontStyle ? 'font-style="' + styleDecl.fontStyle + '" ' : ''),
  22130. (styleDecl.fontWeight ? 'font-weight="' + styleDecl.fontWeight + '" ' : ''),
  22131. (styleDecl.textDecoration ? 'text-decoration="' + styleDecl.textDecoration + '" ' : ''),
  22132. 'style="', fillStyles, '">',
  22133. fabric.util.string.escapeXml(_char),
  22134. '</tspan>\n'
  22135. ].join('');
  22136. }
  22137. });
  22138. })();
  22139. /* _TO_SVG_END_ */
  22140. (function(global) {
  22141. 'use strict';
  22142. var fabric = global.fabric || (global.fabric = {}),
  22143. clone = fabric.util.object.clone;
  22144. /**
  22145. * Textbox class, based on IText, allows the user to resize the text rectangle
  22146. * and wraps lines automatically. Textboxes have their Y scaling locked, the
  22147. * user can only change width. Height is adjusted automatically based on the
  22148. * wrapping of lines.
  22149. * @class fabric.Textbox
  22150. * @extends fabric.IText
  22151. * @mixes fabric.Observable
  22152. * @return {fabric.Textbox} thisArg
  22153. * @see {@link fabric.Textbox#initialize} for constructor definition
  22154. */
  22155. fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, {
  22156. /**
  22157. * Type of an object
  22158. * @type String
  22159. * @default
  22160. */
  22161. type: 'textbox',
  22162. /**
  22163. * Minimum width of textbox, in pixels.
  22164. * @type Number
  22165. * @default
  22166. */
  22167. minWidth: 20,
  22168. /**
  22169. * Minimum calculated width of a textbox, in pixels.
  22170. * fixed to 2 so that an empty textbox cannot go to 0
  22171. * and is still selectable without text.
  22172. * @type Number
  22173. * @default
  22174. */
  22175. dynamicMinWidth: 2,
  22176. /**
  22177. * Cached array of text wrapping.
  22178. * @type Array
  22179. */
  22180. __cachedLines: null,
  22181. /**
  22182. * Override standard Object class values
  22183. */
  22184. lockScalingY: true,
  22185. /**
  22186. * Override standard Object class values
  22187. */
  22188. lockScalingFlip: true,
  22189. /**
  22190. * Constructor. Some scaling related property values are forced. Visibility
  22191. * of controls is also fixed; only the rotation and width controls are
  22192. * made available.
  22193. * @param {String} text Text string
  22194. * @param {Object} [options] Options object
  22195. * @return {fabric.Textbox} thisArg
  22196. */
  22197. initialize: function(text, options) {
  22198. this.ctx = fabric.util.createCanvasElement().getContext('2d');
  22199. this.callSuper('initialize', text, options);
  22200. this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
  22201. // add width to this list of props that effect line wrapping.
  22202. this._dimensionAffectingProps.width = true;
  22203. },
  22204. /**
  22205. * Unlike superclass's version of this function, Textbox does not update
  22206. * its width.
  22207. * @param {CanvasRenderingContext2D} ctx Context to use for measurements
  22208. * @private
  22209. * @override
  22210. */
  22211. _initDimensions: function(ctx) {
  22212. if (this.__skipDimension) {
  22213. return;
  22214. }
  22215. if (!ctx) {
  22216. ctx = fabric.util.createCanvasElement().getContext('2d');
  22217. this._setTextStyles(ctx);
  22218. }
  22219. // clear dynamicMinWidth as it will be different after we re-wrap line
  22220. this.dynamicMinWidth = 0;
  22221. // wrap lines
  22222. this._textLines = this._splitTextIntoLines();
  22223. // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap
  22224. if (this.dynamicMinWidth > this.width) {
  22225. this._set('width', this.dynamicMinWidth);
  22226. }
  22227. // clear cache and re-calculate height
  22228. this._clearCache();
  22229. this.height = this._getTextHeight(ctx);
  22230. },
  22231. /**
  22232. * Generate an object that translates the style object so that it is
  22233. * broken up by visual lines (new lines and automatic wrapping).
  22234. * The original text styles object is broken up by actual lines (new lines only),
  22235. * which is only sufficient for Text / IText
  22236. * @private
  22237. */
  22238. _generateStyleMap: function() {
  22239. var realLineCount = 0,
  22240. realLineCharCount = 0,
  22241. charCount = 0,
  22242. map = {};
  22243. for (var i = 0; i < this._textLines.length; i++) {
  22244. if (this.text[charCount] === '\n' && i > 0) {
  22245. realLineCharCount = 0;
  22246. charCount++;
  22247. realLineCount++;
  22248. }
  22249. else if (this.text[charCount] === ' ' && i > 0) {
  22250. // this case deals with space's that are removed from end of lines when wrapping
  22251. realLineCharCount++;
  22252. charCount++;
  22253. }
  22254. map[i] = { line: realLineCount, offset: realLineCharCount };
  22255. charCount += this._textLines[i].length;
  22256. realLineCharCount += this._textLines[i].length;
  22257. }
  22258. return map;
  22259. },
  22260. /**
  22261. * @param {Number} lineIndex
  22262. * @param {Number} charIndex
  22263. * @param {Boolean} [returnCloneOrEmpty=false]
  22264. * @private
  22265. */
  22266. _getStyleDeclaration: function(lineIndex, charIndex, returnCloneOrEmpty) {
  22267. if (this._styleMap) {
  22268. var map = this._styleMap[lineIndex];
  22269. if (!map) {
  22270. return returnCloneOrEmpty ? { } : null;
  22271. }
  22272. lineIndex = map.line;
  22273. charIndex = map.offset + charIndex;
  22274. }
  22275. return this.callSuper('_getStyleDeclaration', lineIndex, charIndex, returnCloneOrEmpty);
  22276. },
  22277. /**
  22278. * @param {Number} lineIndex
  22279. * @param {Number} charIndex
  22280. * @param {Object} style
  22281. * @private
  22282. */
  22283. _setStyleDeclaration: function(lineIndex, charIndex, style) {
  22284. var map = this._styleMap[lineIndex];
  22285. lineIndex = map.line;
  22286. charIndex = map.offset + charIndex;
  22287. this.styles[lineIndex][charIndex] = style;
  22288. },
  22289. /**
  22290. * @param {Number} lineIndex
  22291. * @param {Number} charIndex
  22292. * @private
  22293. */
  22294. _deleteStyleDeclaration: function(lineIndex, charIndex) {
  22295. var map = this._styleMap[lineIndex];
  22296. lineIndex = map.line;
  22297. charIndex = map.offset + charIndex;
  22298. delete this.styles[lineIndex][charIndex];
  22299. },
  22300. /**
  22301. * @param {Number} lineIndex
  22302. * @private
  22303. */
  22304. _getLineStyle: function(lineIndex) {
  22305. var map = this._styleMap[lineIndex];
  22306. return this.styles[map.line];
  22307. },
  22308. /**
  22309. * @param {Number} lineIndex
  22310. * @param {Object} style
  22311. * @private
  22312. */
  22313. _setLineStyle: function(lineIndex, style) {
  22314. var map = this._styleMap[lineIndex];
  22315. this.styles[map.line] = style;
  22316. },
  22317. /**
  22318. * @param {Number} lineIndex
  22319. * @private
  22320. */
  22321. _deleteLineStyle: function(lineIndex) {
  22322. var map = this._styleMap[lineIndex];
  22323. delete this.styles[map.line];
  22324. },
  22325. /**
  22326. * Wraps text using the 'width' property of Textbox. First this function
  22327. * splits text on newlines, so we preserve newlines entered by the user.
  22328. * Then it wraps each line using the width of the Textbox by calling
  22329. * _wrapLine().
  22330. * @param {CanvasRenderingContext2D} ctx Context to use for measurements
  22331. * @param {String} text The string of text that is split into lines
  22332. * @returns {Array} Array of lines
  22333. */
  22334. _wrapText: function(ctx, text) {
  22335. var lines = text.split(this._reNewline), wrapped = [], i;
  22336. for (i = 0; i < lines.length; i++) {
  22337. wrapped = wrapped.concat(this._wrapLine(ctx, lines[i], i));
  22338. }
  22339. return wrapped;
  22340. },
  22341. /**
  22342. * Helper function to measure a string of text, given its lineIndex and charIndex offset
  22343. *
  22344. * @param {CanvasRenderingContext2D} ctx
  22345. * @param {String} text
  22346. * @param {number} lineIndex
  22347. * @param {number} charOffset
  22348. * @returns {number}
  22349. * @private
  22350. */
  22351. _measureText: function(ctx, text, lineIndex, charOffset) {
  22352. var width = 0;
  22353. charOffset = charOffset || 0;
  22354. for (var i = 0, len = text.length; i < len; i++) {
  22355. width += this._getWidthOfChar(ctx, text[i], lineIndex, i + charOffset);
  22356. }
  22357. return width;
  22358. },
  22359. /**
  22360. * Wraps a line of text using the width of the Textbox and a context.
  22361. * @param {CanvasRenderingContext2D} ctx Context to use for measurements
  22362. * @param {String} text The string of text to split into lines
  22363. * @param {Number} lineIndex
  22364. * @returns {Array} Array of line(s) into which the given text is wrapped
  22365. * to.
  22366. */
  22367. _wrapLine: function(ctx, text, lineIndex) {
  22368. var lineWidth = 0,
  22369. lines = [],
  22370. line = '',
  22371. words = text.split(' '),
  22372. word = '',
  22373. offset = 0,
  22374. infix = ' ',
  22375. wordWidth = 0,
  22376. infixWidth = 0,
  22377. largestWordWidth = 0,
  22378. lineJustStarted = true,
  22379. additionalSpace = this._getWidthOfCharSpacing();
  22380. for (var i = 0; i < words.length; i++) {
  22381. word = words[i];
  22382. wordWidth = this._measureText(ctx, word, lineIndex, offset);
  22383. offset += word.length;
  22384. lineWidth += infixWidth + wordWidth - additionalSpace;
  22385. if (lineWidth >= this.width && !lineJustStarted) {
  22386. lines.push(line);
  22387. line = '';
  22388. lineWidth = wordWidth;
  22389. lineJustStarted = true;
  22390. }
  22391. else {
  22392. lineWidth += additionalSpace;
  22393. }
  22394. if (!lineJustStarted) {
  22395. line += infix;
  22396. }
  22397. line += word;
  22398. infixWidth = this._measureText(ctx, infix, lineIndex, offset);
  22399. offset++;
  22400. lineJustStarted = false;
  22401. // keep track of largest word
  22402. if (wordWidth > largestWordWidth) {
  22403. largestWordWidth = wordWidth;
  22404. }
  22405. }
  22406. i && lines.push(line);
  22407. if (largestWordWidth > this.dynamicMinWidth) {
  22408. this.dynamicMinWidth = largestWordWidth - additionalSpace;
  22409. }
  22410. return lines;
  22411. },
  22412. /**
  22413. * Gets lines of text to render in the Textbox. This function calculates
  22414. * text wrapping on the fly everytime it is called.
  22415. * @returns {Array} Array of lines in the Textbox.
  22416. * @override
  22417. */
  22418. _splitTextIntoLines: function() {
  22419. var originalAlign = this.textAlign;
  22420. this.ctx.save();
  22421. this._setTextStyles(this.ctx);
  22422. this.textAlign = 'left';
  22423. var lines = this._wrapText(this.ctx, this.text);
  22424. this.textAlign = originalAlign;
  22425. this.ctx.restore();
  22426. this._textLines = lines;
  22427. this._styleMap = this._generateStyleMap();
  22428. return lines;
  22429. },
  22430. /**
  22431. * When part of a group, we don't want the Textbox's scale to increase if
  22432. * the group's increases. That's why we reduce the scale of the Textbox by
  22433. * the amount that the group's increases. This is to maintain the effective
  22434. * scale of the Textbox at 1, so that font-size values make sense. Otherwise
  22435. * the same font-size value would result in different actual size depending
  22436. * on the value of the scale.
  22437. * @param {String} key
  22438. * @param {*} value
  22439. */
  22440. setOnGroup: function(key, value) {
  22441. if (key === 'scaleX') {
  22442. this.set('scaleX', Math.abs(1 / value));
  22443. this.set('width', (this.get('width') * value) /
  22444. (typeof this.__oldScaleX === 'undefined' ? 1 : this.__oldScaleX));
  22445. this.__oldScaleX = value;
  22446. }
  22447. },
  22448. /**
  22449. * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start).
  22450. * Overrides the superclass function to take into account text wrapping.
  22451. *
  22452. * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used.
  22453. */
  22454. get2DCursorLocation: function(selectionStart) {
  22455. if (typeof selectionStart === 'undefined') {
  22456. selectionStart = this.selectionStart;
  22457. }
  22458. var numLines = this._textLines.length,
  22459. removed = 0;
  22460. for (var i = 0; i < numLines; i++) {
  22461. var line = this._textLines[i],
  22462. lineLen = line.length;
  22463. if (selectionStart <= removed + lineLen) {
  22464. return {
  22465. lineIndex: i,
  22466. charIndex: selectionStart - removed
  22467. };
  22468. }
  22469. removed += lineLen;
  22470. if (this.text[removed] === '\n' || this.text[removed] === ' ') {
  22471. removed++;
  22472. }
  22473. }
  22474. return {
  22475. lineIndex: numLines - 1,
  22476. charIndex: this._textLines[numLines - 1].length
  22477. };
  22478. },
  22479. /**
  22480. * Overrides superclass function and uses text wrapping data to get cursor
  22481. * boundary offsets instead of the array of chars.
  22482. * @param {Array} chars Unused
  22483. * @param {String} typeOfBoundaries Can be 'cursor' or 'selection'
  22484. * @returns {Object} Object with 'top', 'left', and 'lineLeft' properties set.
  22485. */
  22486. _getCursorBoundariesOffsets: function(chars, typeOfBoundaries) {
  22487. var topOffset = 0,
  22488. leftOffset = 0,
  22489. cursorLocation = this.get2DCursorLocation(),
  22490. lineChars = this._textLines[cursorLocation.lineIndex].split(''),
  22491. lineLeftOffset = this._getLineLeftOffset(this._getLineWidth(this.ctx, cursorLocation.lineIndex));
  22492. for (var i = 0; i < cursorLocation.charIndex; i++) {
  22493. leftOffset += this._getWidthOfChar(this.ctx, lineChars[i], cursorLocation.lineIndex, i);
  22494. }
  22495. for (i = 0; i < cursorLocation.lineIndex; i++) {
  22496. topOffset += this._getHeightOfLine(this.ctx, i);
  22497. }
  22498. if (typeOfBoundaries === 'cursor') {
  22499. topOffset += (1 - this._fontSizeFraction) * this._getHeightOfLine(this.ctx, cursorLocation.lineIndex)
  22500. / this.lineHeight - this.getCurrentCharFontSize(cursorLocation.lineIndex, cursorLocation.charIndex)
  22501. * (1 - this._fontSizeFraction);
  22502. }
  22503. return {
  22504. top: topOffset,
  22505. left: leftOffset,
  22506. lineLeft: lineLeftOffset
  22507. };
  22508. },
  22509. getMinWidth: function() {
  22510. return Math.max(this.minWidth, this.dynamicMinWidth);
  22511. },
  22512. /**
  22513. * Returns object representation of an instance
  22514. * @method toObject
  22515. * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
  22516. * @return {Object} object representation of an instance
  22517. */
  22518. toObject: function(propertiesToInclude) {
  22519. return this.callSuper('toObject', ['minWidth'].concat(propertiesToInclude));
  22520. }
  22521. });
  22522. /**
  22523. * Returns fabric.Textbox instance from an object representation
  22524. * @static
  22525. * @memberOf fabric.Textbox
  22526. * @param {Object} object Object to create an instance from
  22527. * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created
  22528. * @return {fabric.Textbox} instance of fabric.Textbox
  22529. */
  22530. fabric.Textbox.fromObject = function(object, callback) {
  22531. var textbox = new fabric.Textbox(object.text, clone(object));
  22532. callback && callback(textbox);
  22533. return textbox;
  22534. };
  22535. /**
  22536. * Returns the default controls visibility required for Textboxes.
  22537. * @returns {Object}
  22538. */
  22539. fabric.Textbox.getTextboxControlVisibility = function() {
  22540. return {
  22541. tl: false,
  22542. tr: false,
  22543. br: false,
  22544. bl: false,
  22545. ml: true,
  22546. mt: false,
  22547. mr: true,
  22548. mb: false,
  22549. mtr: true
  22550. };
  22551. };
  22552. })(typeof exports !== 'undefined' ? exports : this);
  22553. (function() {
  22554. /**
  22555. * Override _setObjectScale and add Textbox specific resizing behavior. Resizing
  22556. * a Textbox doesn't scale text, it only changes width and makes text wrap automatically.
  22557. */
  22558. var setObjectScaleOverridden = fabric.Canvas.prototype._setObjectScale;
  22559. fabric.Canvas.prototype._setObjectScale = function(localMouse, transform,
  22560. lockScalingX, lockScalingY, by, lockScalingFlip, _dim) {
  22561. var t = transform.target;
  22562. if (t instanceof fabric.Textbox) {
  22563. var w = t.width * ((localMouse.x / transform.scaleX) / (t.width + t.strokeWidth));
  22564. if (w >= t.getMinWidth()) {
  22565. t.set('width', w);
  22566. return true;
  22567. }
  22568. }
  22569. else {
  22570. return setObjectScaleOverridden.call(fabric.Canvas.prototype, localMouse, transform,
  22571. lockScalingX, lockScalingY, by, lockScalingFlip, _dim);
  22572. }
  22573. };
  22574. /**
  22575. * Sets controls of this group to the Textbox's special configuration if
  22576. * one is present in the group. Deletes _controlsVisibility otherwise, so that
  22577. * it gets initialized to default value at runtime.
  22578. */
  22579. fabric.Group.prototype._refreshControlsVisibility = function() {
  22580. if (typeof fabric.Textbox === 'undefined') {
  22581. return;
  22582. }
  22583. for (var i = this._objects.length; i--;) {
  22584. if (this._objects[i] instanceof fabric.Textbox) {
  22585. this.setControlsVisibility(fabric.Textbox.getTextboxControlVisibility());
  22586. return;
  22587. }
  22588. }
  22589. };
  22590. var clone = fabric.util.object.clone;
  22591. fabric.util.object.extend(fabric.Textbox.prototype, /** @lends fabric.IText.prototype */ {
  22592. /**
  22593. * @private
  22594. */
  22595. _removeExtraneousStyles: function() {
  22596. for (var prop in this._styleMap) {
  22597. if (!this._textLines[prop]) {
  22598. delete this.styles[this._styleMap[prop].line];
  22599. }
  22600. }
  22601. },
  22602. /**
  22603. * Inserts style object for a given line/char index
  22604. * @param {Number} lineIndex Index of a line
  22605. * @param {Number} charIndex Index of a char
  22606. * @param {Object} [style] Style object to insert, if given
  22607. */
  22608. insertCharStyleObject: function(lineIndex, charIndex, style) {
  22609. // adjust lineIndex and charIndex
  22610. var map = this._styleMap[lineIndex];
  22611. lineIndex = map.line;
  22612. charIndex = map.offset + charIndex;
  22613. fabric.IText.prototype.insertCharStyleObject.apply(this, [lineIndex, charIndex, style]);
  22614. },
  22615. /**
  22616. * Inserts new style object
  22617. * @param {Number} lineIndex Index of a line
  22618. * @param {Number} charIndex Index of a char
  22619. * @param {Boolean} isEndOfLine True if it's end of line
  22620. */
  22621. insertNewlineStyleObject: function(lineIndex, charIndex, isEndOfLine) {
  22622. // adjust lineIndex and charIndex
  22623. var map = this._styleMap[lineIndex];
  22624. lineIndex = map.line;
  22625. charIndex = map.offset + charIndex;
  22626. fabric.IText.prototype.insertNewlineStyleObject.apply(this, [lineIndex, charIndex, isEndOfLine]);
  22627. },
  22628. /**
  22629. * Shifts line styles up or down. This function is slightly different than the one in
  22630. * itext_behaviour as it takes into account the styleMap.
  22631. *
  22632. * @param {Number} lineIndex Index of a line
  22633. * @param {Number} offset Can be -1 or +1
  22634. */
  22635. shiftLineStyles: function(lineIndex, offset) {
  22636. // shift all line styles by 1 upward
  22637. var clonedStyles = clone(this.styles),
  22638. map = this._styleMap[lineIndex];
  22639. // adjust line index
  22640. lineIndex = map.line;
  22641. for (var line in this.styles) {
  22642. var numericLine = parseInt(line, 10);
  22643. if (numericLine > lineIndex) {
  22644. this.styles[numericLine + offset] = clonedStyles[numericLine];
  22645. if (!clonedStyles[numericLine - offset]) {
  22646. delete this.styles[numericLine];
  22647. }
  22648. }
  22649. }
  22650. //TODO: evaluate if delete old style lines with offset -1
  22651. },
  22652. /**
  22653. * Figure out programatically the text on previous actual line (actual = separated by \n);
  22654. *
  22655. * @param {Number} lIndex
  22656. * @returns {String}
  22657. * @private
  22658. */
  22659. _getTextOnPreviousLine: function(lIndex) {
  22660. var textOnPreviousLine = this._textLines[lIndex - 1];
  22661. while (this._styleMap[lIndex - 2] && this._styleMap[lIndex - 2].line === this._styleMap[lIndex - 1].line) {
  22662. textOnPreviousLine = this._textLines[lIndex - 2] + textOnPreviousLine;
  22663. lIndex--;
  22664. }
  22665. return textOnPreviousLine;
  22666. },
  22667. /**
  22668. * Removes style object
  22669. * @param {Boolean} isBeginningOfLine True if cursor is at the beginning of line
  22670. * @param {Number} [index] Optional index. When not given, current selectionStart is used.
  22671. */
  22672. removeStyleObject: function(isBeginningOfLine, index) {
  22673. var cursorLocation = this.get2DCursorLocation(index),
  22674. map = this._styleMap[cursorLocation.lineIndex],
  22675. lineIndex = map.line,
  22676. charIndex = map.offset + cursorLocation.charIndex;
  22677. this._removeStyleObject(isBeginningOfLine, cursorLocation, lineIndex, charIndex);
  22678. }
  22679. });
  22680. })();
  22681. (function() {
  22682. var override = fabric.IText.prototype._getNewSelectionStartFromOffset;
  22683. /**
  22684. * Overrides the IText implementation and adjusts character index as there is not always a linebreak
  22685. *
  22686. * @param {Number} mouseOffset
  22687. * @param {Number} prevWidth
  22688. * @param {Number} width
  22689. * @param {Number} index
  22690. * @param {Number} jlen
  22691. * @returns {Number}
  22692. */
  22693. fabric.IText.prototype._getNewSelectionStartFromOffset = function(mouseOffset, prevWidth, width, index, jlen) {
  22694. index = override.call(this, mouseOffset, prevWidth, width, index, jlen);
  22695. // the index passed into the function is padded by the amount of lines from _textLines (to account for \n)
  22696. // we need to remove this padding, and pad it by actual lines, and / or spaces that are meant to be there
  22697. var tmp = 0,
  22698. removed = 0;
  22699. // account for removed characters
  22700. for (var i = 0; i < this._textLines.length; i++) {
  22701. tmp += this._textLines[i].length;
  22702. if (tmp + removed >= index) {
  22703. break;
  22704. }
  22705. if (this.text[tmp + removed] === '\n' || this.text[tmp + removed] === ' ') {
  22706. removed++;
  22707. }
  22708. }
  22709. return index - i + removed;
  22710. };
  22711. })();
  22712. (function() {
  22713. if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  22714. return;
  22715. }
  22716. var DOMParser = require('xmldom').DOMParser,
  22717. URL = require('url'),
  22718. HTTP = require('http'),
  22719. HTTPS = require('https'),
  22720. Canvas = require('canvas'),
  22721. Image = require('canvas').Image;
  22722. /** @private */
  22723. function request(url, encoding, callback) {
  22724. var oURL = URL.parse(url);
  22725. // detect if http or https is used
  22726. if ( !oURL.port ) {
  22727. oURL.port = ( oURL.protocol.indexOf('https:') === 0 ) ? 443 : 80;
  22728. }
  22729. // assign request handler based on protocol
  22730. var reqHandler = (oURL.protocol.indexOf('https:') === 0 ) ? HTTPS : HTTP,
  22731. req = reqHandler.request({
  22732. hostname: oURL.hostname,
  22733. port: oURL.port,
  22734. path: oURL.path,
  22735. method: 'GET'
  22736. }, function(response) {
  22737. var body = '';
  22738. if (encoding) {
  22739. response.setEncoding(encoding);
  22740. }
  22741. response.on('end', function () {
  22742. callback(body);
  22743. });
  22744. response.on('data', function (chunk) {
  22745. if (response.statusCode === 200) {
  22746. body += chunk;
  22747. }
  22748. });
  22749. });
  22750. req.on('error', function(err) {
  22751. if (err.errno === process.ECONNREFUSED) {
  22752. fabric.log('ECONNREFUSED: connection refused to ' + oURL.hostname + ':' + oURL.port);
  22753. }
  22754. else {
  22755. fabric.log(err.message);
  22756. }
  22757. callback(null);
  22758. });
  22759. req.end();
  22760. }
  22761. /** @private */
  22762. function requestFs(path, callback) {
  22763. var fs = require('fs');
  22764. fs.readFile(path, function (err, data) {
  22765. if (err) {
  22766. fabric.log(err);
  22767. throw err;
  22768. }
  22769. else {
  22770. callback(data);
  22771. }
  22772. });
  22773. }
  22774. fabric.util.loadImage = function(url, callback, context) {
  22775. function createImageAndCallBack(data) {
  22776. if (data) {
  22777. img.src = new Buffer(data, 'binary');
  22778. // preserving original url, which seems to be lost in node-canvas
  22779. img._src = url;
  22780. callback && callback.call(context, img);
  22781. }
  22782. else {
  22783. img = null;
  22784. callback && callback.call(context, null, true);
  22785. }
  22786. }
  22787. var img = new Image();
  22788. if (url && (url instanceof Buffer || url.indexOf('data') === 0)) {
  22789. img.src = img._src = url;
  22790. callback && callback.call(context, img);
  22791. }
  22792. else if (url && url.indexOf('http') !== 0) {
  22793. requestFs(url, createImageAndCallBack);
  22794. }
  22795. else if (url) {
  22796. request(url, 'binary', createImageAndCallBack);
  22797. }
  22798. else {
  22799. callback && callback.call(context, url);
  22800. }
  22801. };
  22802. fabric.loadSVGFromURL = function(url, callback, reviver) {
  22803. url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
  22804. if (url.indexOf('http') !== 0) {
  22805. requestFs(url, function(body) {
  22806. fabric.loadSVGFromString(body.toString(), callback, reviver);
  22807. });
  22808. }
  22809. else {
  22810. request(url, '', function(body) {
  22811. fabric.loadSVGFromString(body, callback, reviver);
  22812. });
  22813. }
  22814. };
  22815. fabric.loadSVGFromString = function(string, callback, reviver) {
  22816. var doc = new DOMParser().parseFromString(string);
  22817. fabric.parseSVGDocument(doc.documentElement, function(results, options) {
  22818. callback && callback(results, options);
  22819. }, reviver);
  22820. };
  22821. fabric.util.getScript = function(url, callback) {
  22822. request(url, '', function(body) {
  22823. // eslint-disable-next-line no-eval
  22824. eval(body);
  22825. callback && callback();
  22826. });
  22827. };
  22828. // fabric.util.createCanvasElement = function(_, width, height) {
  22829. // return new Canvas(width, height);
  22830. // }
  22831. /**
  22832. * Only available when running fabric on node.js
  22833. * @param {Number} width Canvas width
  22834. * @param {Number} height Canvas height
  22835. * @param {Object} [options] Options to pass to FabricCanvas.
  22836. * @param {Object} [nodeCanvasOptions] Options to pass to NodeCanvas.
  22837. * @return {Object} wrapped canvas instance
  22838. */
  22839. fabric.createCanvasForNode = function(width, height, options, nodeCanvasOptions) {
  22840. nodeCanvasOptions = nodeCanvasOptions || options;
  22841. var canvasEl = fabric.document.createElement('canvas'),
  22842. nodeCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions),
  22843. nodeCacheCanvas = new Canvas(width || 600, height || 600, nodeCanvasOptions);
  22844. // jsdom doesn't create style on canvas element, so here be temp. workaround
  22845. canvasEl.style = { };
  22846. canvasEl.width = nodeCanvas.width;
  22847. canvasEl.height = nodeCanvas.height;
  22848. options = options || { };
  22849. options.nodeCanvas = nodeCanvas;
  22850. options.nodeCacheCanvas = nodeCacheCanvas;
  22851. var FabricCanvas = fabric.Canvas || fabric.StaticCanvas,
  22852. fabricCanvas = new FabricCanvas(canvasEl, options);
  22853. fabricCanvas.nodeCanvas = nodeCanvas;
  22854. fabricCanvas.nodeCacheCanvas = nodeCacheCanvas;
  22855. fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
  22856. fabricCanvas.contextCache = nodeCacheCanvas.getContext('2d');
  22857. fabricCanvas.Font = Canvas.Font;
  22858. return fabricCanvas;
  22859. };
  22860. var originaInitStatic = fabric.StaticCanvas.prototype._initStatic;
  22861. fabric.StaticCanvas.prototype._initStatic = function(el, options) {
  22862. el = el || fabric.document.createElement('canvas');
  22863. this.nodeCanvas = new Canvas(el.width, el.height);
  22864. this.nodeCacheCanvas = new Canvas(el.width, el.height);
  22865. originaInitStatic.call(this, el, options);
  22866. this.contextContainer = this.nodeCanvas.getContext('2d');
  22867. this.contextCache = this.nodeCacheCanvas.getContext('2d');
  22868. this.Font = Canvas.Font;
  22869. }
  22870. /** @ignore */
  22871. fabric.StaticCanvas.prototype.createPNGStream = function() {
  22872. return this.nodeCanvas.createPNGStream();
  22873. };
  22874. fabric.StaticCanvas.prototype.createJPEGStream = function(opts) {
  22875. return this.nodeCanvas.createJPEGStream(opts);
  22876. };
  22877. fabric.StaticCanvas.prototype._initRetinaScaling = function() {
  22878. if (!this._isRetinaScaling()) {
  22879. return;
  22880. }
  22881. this.lowerCanvasEl.setAttribute('width', this.width * fabric.devicePixelRatio);
  22882. this.lowerCanvasEl.setAttribute('height', this.height * fabric.devicePixelRatio);
  22883. this.nodeCanvas.width = this.width * fabric.devicePixelRatio;
  22884. this.nodeCanvas.height = this.height * fabric.devicePixelRatio;
  22885. this.contextContainer.scale(fabric.devicePixelRatio, fabric.devicePixelRatio);
  22886. return this;
  22887. };
  22888. if (fabric.Canvas) {
  22889. fabric.Canvas.prototype._initRetinaScaling = fabric.StaticCanvas.prototype._initRetinaScaling;
  22890. }
  22891. var origSetBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension;
  22892. fabric.StaticCanvas.prototype._setBackstoreDimension = function(prop, value) {
  22893. origSetBackstoreDimension.call(this, prop, value);
  22894. this.nodeCanvas[prop] = value;
  22895. return this;
  22896. };
  22897. if (fabric.Canvas) {
  22898. fabric.Canvas.prototype._setBackstoreDimension = fabric.StaticCanvas.prototype._setBackstoreDimension;
  22899. }
  22900. })();