UNPKG

vue-fabric

Version:
721 lines (604 loc) 19.8 kB
/* * fabric.js Controls Extension * for fabric.js current build * Simon Kunz 09.02.2016 for pixolith * Licensed under the MIT license. */ 'use strict'; (function (window) { var fabric = window.fabric || (window.fabric = {}), minExtCompat = '1.6.0', isVML = function () { return typeof G_vmlCanvasManager !== 'undefined'; }, degreesToRadians = fabric.util.degreesToRadians, cursorOffset = { mt: 0, // n tr: 1, // ne mr: 2, // e br: 3, // se mb: 4, // s bl: 5, // sw ml: 6, // w tl: 7, // nw }; if (minExtCompat.localeCompare(window.fabric.version) > -1) { console.warn('this extension might not be fully compatible with your version ' + 'of fabric.js (' + window.fabric.version + ').' + 'Consider using the latest compatible build of fabric.js (> ' + minExtCompat + ')' ); } fabric.util.object.extend(fabric.Object.prototype, { /** * When true, image icons are loaded via the drawImage method * @type Boolean * @default false */ useCustomIcons: false, /** * Sets a background-color for drawImage operations with transparency * @type string * @default transparent */ cornerBackgroundColor: 'transparent', /** * Sets the shape of the background for drawImage operations with transparency * @type string * @default rect */ cornerShape: '', /** * Inner Padding between Shape Background and drawn Image * @type int * @default rect */ cornerPadding: 0, /** * Set a custom corner icon * @param {Object} obj settings and icon url. * @param callback function */ customiseCornerIcons: function (obj, callback) { var setting, cornerConfig; for (setting in obj) { if (obj.hasOwnProperty(setting)) { cornerConfig = {}; if (obj[setting].cornerShape !== undefined) { this.cornerShape = obj[setting].cornerShape; } if (obj[setting].cornerBackgroundColor !== undefined) { this.cornerBackgroundColor = obj[setting].cornerBackgroundColor; } if (obj[setting].borderColor !== undefined) { this.borderColor = obj[setting].borderColor; } if (obj[setting].cornerSize !== undefined) { this.cornerSize = obj[setting].cornerSize; } if (obj[setting].cornerPadding !== undefined) { this.cornerPadding = obj[setting].cornerPadding; } if (obj[setting].icon !== undefined || Object.keys(obj)[0] === 'settings') { this.useCustomIcons = true; if (obj[setting].settings !== undefined) { cornerConfig.settings = obj[setting].settings; } if (obj[setting].icon !== undefined) { cornerConfig.icon = obj[setting].icon; this.loadIcon(setting, cornerConfig, function () { if (callback && typeof (callback) === 'function') { callback(); } }); } } } } }, /** * loads the icon image as an image src. * @param {Object} corner to load an icon. * @param cornerConfig as object containing icon url and corner specific settings * @param callback function. */ loadIcon: function (corner, cornerConfig, callback) { var self = this, icon = new Image(); icon.onload = function () { self[corner + 'Icon'] = this; if (cornerConfig.settings) { self[corner + 'Settings'] = cornerConfig.settings; } if (callback && typeof (callback) === 'function') { callback(); } }; icon.onerror = function () { fabric.warn(this.src + ' icon is not an image'); }; if (cornerConfig.icon.match(/^http[s]?:\/\//) || cornerConfig.icon.substring(0, 2) === '//') { icon.crossOrigin = 'Anonymous'; } icon.src = cornerConfig.icon; }, /** * copy of the setter method for our american friends. * @param {Object} obj containing corner icon urls and settings. */ customizeCornerIcons: function (obj) { this.customiseCornerIcons(obj); }, /** * Draws corners of an object's bounding box. * Requires public properties: width, height * Requires public options: cornerSize, padding * @param {CanvasRenderingContext2D} ctx Context to draw on * @return {fabric.Object} thisArg * @chainable */ drawControls: function (ctx) { if (!this.hasControls) { return this; } var wh = this._calculateCurrentDimensions(), width = wh.x, height = wh.y, scaleOffset = this.cornerSize, left = -(width + scaleOffset) / 2, top = -(height + scaleOffset) / 2, methodName; if (!this.useCustomIcons) { ctx.lineWidth = 1; ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; ctx.strokeStyle = ctx.fillStyle = this.cornerColor; if (!this.transparentCorners) { ctx.strokeStyle = this.cornerStrokeColor; } methodName = this.transparentCorners ? 'stroke' : 'fill'; } else { methodName = 'drawImage'; } ctx.save(); this._setLineDash(ctx, this.cornerDashArray, null); // top-left this._drawControl('tl', ctx, methodName, left, top, this.tlIcon, this.tlSettings ); // top-right this._drawControl('tr', ctx, methodName, left + width, top, this.trIcon, this.trSettings ); // bottom-left this._drawControl('bl', ctx, methodName, left, top + height, this.blIcon, this.blSettings ); // bottom-right this._drawControl('br', ctx, methodName, left + width, top + height, this.brIcon, this.brSettings ); if (!this.get('lockUniScaling')) { // middle-top this._drawControl('mt', ctx, methodName, left + width / 2, top, this.mtIcon, this.mtSettings ); // middle-bottom this._drawControl('mb', ctx, methodName, left + width / 2, top + height, this.mbIcon, this.mbSettings ); // middle-right this._drawControl('mr', ctx, methodName, left + width, top + height / 2, this.mrIcon, this.mrSettings ); // middle-left this._drawControl('ml', ctx, methodName, left, top + height / 2, this.mlIcon, this.mlSettings ); } // middle-top-rotate if (this.hasRotatingPoint) { this._drawControl('mtr', ctx, methodName, left + width / 2, top - this.rotatingPointOffset, this.mtrIcon, this.mtrSettings ); } ctx.restore(); return this; }, /** Draw controls either with background-shape and color (transparency) or plain image (modified core method) * @private * {string} icon url of the control */ _drawControl: function (control, ctx, methodName, left, top, icon, settings) { if (!this.isControlVisible(control)) { return; } var size = this.cornerSize, cornerStroke = this.cornerStrokeColor || 'transparent', cornerBG = this.cornerBackgroundColor || 'black', cornerShape = this.cornerShape || 'rect', cornerPadding = typeof this.cornerPadding === 'number' ? this.cornerPadding : 10; if (settings) { if (settings.cornerSize) { // Set the size, and also recalc left and top left = left + size / 2 - settings.cornerSize / 2; top = top + size / 2 - settings.cornerSize / 2; size = settings.cornerSize; } cornerShape = settings.cornerShape || cornerShape; cornerBG = settings.cornerBackgroundColor || cornerBG; cornerPadding = typeof settings.cornerPadding === 'number' ? settings.cornerPadding : cornerPadding; cornerStroke = settings.cornerStrokeColor || cornerStroke; } //console.log(cornerBG); if (this.useCustomIcons) { if (cornerShape) { ctx.globalAlpha = 1; ctx.fillStyle = cornerBG; ctx.lineWidth = 1; ctx.strokeStyle = cornerStroke; switch (cornerShape) { case 'rect': break; ctx.fillRect(left, top, size, size); if (cornerStroke) { ctx.strokeRect(left, top, size, size); } break; case 'circle': break; ctx.beginPath(); ctx.arc(left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI); ctx.fill(); if (cornerStroke) { ctx.stroke(); } ctx.closePath(); break; } if (icon !== undefined) { ctx[methodName]( icon, left + cornerPadding / 2, top + cornerPadding / 2, size - cornerPadding, size - cornerPadding ); } } else { if (icon !== undefined) { ctx[methodName]( icon, left, top, size, size ); } } } else { isVML() || this.transparentCorners || ctx.clearRect(left, top, size, size); ctx[methodName + 'Rect'](left, top, size, size); if (!this.transparentCorners && cornerStroke) { ctx.strokeRect(left, top, size, size); } } }, }); fabric.util.object.extend(fabric.Canvas.prototype, { /** * When true, actions can be overwritten * @type Boolean * @default false */ overwriteActions: false, /** * When true, cursors are fixed * @type Boolean * @default false */ fixedCursors: false, /** * setter Method for actions and cursors. * @param {Object} obj containing corner action and cursor url/type. */ customiseControls: function (obj) { var setting; for (setting in obj) { if (obj.hasOwnProperty(setting)) { if (obj[setting].action !== undefined) { this.overwriteActions = true; this.setCustomAction(setting, obj[setting].action); } if (obj[setting].cursor !== undefined) { this.fixedCursors = true; this.setCustomCursor(setting, obj[setting].cursor); } } } }, /** * loads the icon image as an image src. * @param {Object} corner to load an icon. * @param action as a string. */ setCustomAction: function (corner, action) { this[corner + 'Action'] = action; }, /** * loads the icon image as an image src. * @param {Object} corner to load an icon. * @param cursorUrl as a string. */ setCustomCursor: function (corner, cursorUrl) { this[corner + 'cursorIcon'] = cursorUrl; }, /** * copy of the setter method for our american friends. * @param {Object} obj containing corner action and cursor url/type. */ customizeControls: function (obj) { this.customiseControls(obj); }, /** * @private */ _getActionFromCorner: function (target, corner, e) { if (!corner) { return 'drag'; } if (corner) { if (this[corner + 'Action'] && this.overwriteActions) { switch (corner) { case 'mtr': return this[corner + 'Action'] || 'rotate'; case 'ml': case 'mr': if (e[this.altActionKey]) { return e[this.altActionKey] ? 'skewY' : 'scaleX'; } return this[corner + 'Action']; case 'mt': case 'mb': if (e[this.altActionKey]) { return e[this.altActionKey] ? 'skewY' : 'scaleY'; } return this[corner + 'Action']; default: return this[corner + 'Action'] || 'scale'; } } else { switch (corner) { case 'mtr': return 'rotate'; case 'ml': case 'mr': return e[this.altActionKey] ? 'skewY' : 'scaleX'; case 'mt': case 'mb': return e[this.altActionKey] ? 'skewX' : 'scaleY'; default: return 'scale'; } } } return false; }, /** * @private * @param {Event} e Event object * @param {fabric.Object} target */ _setupCurrentTransform: function (e, target) { if (!target) { return; } var pointer = this.getPointer(e), corner = target._findTargetCorner(this.getPointer(e, true)), action = this._getActionFromCorner(target, corner, e), origin = this._getOriginFromCorner(target, corner); if (typeof action === 'function') { action.call(this, e, target); // as of fabric 1.7.11 object cache will try to slice the action to check for scale so we need to convert this to a string action = 'void'; } this._currentTransform = { target: target, action: action, corner: corner, scaleX: target.scaleX, scaleY: target.scaleY, skewX: target.skewX, skewY: target.skewY, offsetX: pointer.x - target.left, offsetY: pointer.y - target.top, originX: origin.x, originY: origin.y, ex: pointer.x, ey: pointer.y, lastX: pointer.x, lastY: pointer.y, left: target.left, top: target.top, theta: degreesToRadians(target.angle), width: target.width * target.scaleX, mouseXSign: 1, mouseYSign: 1, shiftKey: e.shiftKey, altKey: e[this.centeredKey], }; this._currentTransform.original = { left: target.left, top: target.top, scaleX: target.scaleX, scaleY: target.scaleY, skewX: target.skewX, skewY: target.skewY, originX: origin.x, originY: origin.y, }; if (action === 'remove') { this._removeAction(e, target); } if (action === 'moveUp') { this._moveLayerUpAction(e, target); } if (action === 'moveDown') { this._moveLayerDownAction(e, target); } if (typeof action === 'object') { if (Object.keys(action)[0] === 'rotateByDegrees') { this._rotateByDegrees(e, target, action.rotateByDegrees); } } this._resetCurrentTransform(); }, /** * Custom remove object action * @private * @param {Event} e Event object * @param {fabric.Object} target */ _removeAction: function (e, target) { var _this = this; if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') { this.getActiveGroup().forEachObject(function (o) { o.off(); o.remove(); }); this.discardActiveGroup(); // as of fabric 1.6.3 necessary for reasons.. setTimeout(function () { _this.deactivateAll(); }, 0); } else { target.off(); target.remove(); setTimeout(function () { _this.deactivateAll(); }, 0); } }, /** * Custom move up object action * @private * @param {Event} e Event object * @param {fabric.Object} target */ _moveLayerUpAction: function (e, target) { if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') { this.getActiveGroup().forEachObject(function (o) { o.bringForward(); }); } else { target.bringForward(); } }, /** * Custom move down object action * @private * @param {Event} e Event object * @param {fabric.Object} target */ _moveLayerDownAction: function (e, target) { if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') { this.getActiveGroup().forEachObject(function (o) { o.sendBackwards(); }); } else { target.sendBackwards(); } }, /** * Custom move down object action * @private * @param {Event} e Event object * @param {fabric.Object} target * @param {Integer} value of rotation */ _rotateByDegrees: function (e, target, value) { var angle = parseInt(target.getAngle()) + value, needsOriginRestore = false; if ((target.originX !== 'center' || target.originY !== 'center') && target.centeredRotation) { this._setOriginToCenter(target); needsOriginRestore = true; } angle = angle > 360 ? angle - 360 : angle; if (this.getActiveGroup() && this.getActiveGroup() !== 'undefined') { this.getActiveGroup().forEachObject(function (obj) { obj .setAngle(angle) .setCoords(); }); } else { target .setAngle(angle) .setCoords(); } if (needsOriginRestore) { this._setCenterToOrigin(target); } this.renderAll(); }, /** * Sets either the standard behaviour cursors or if fixedCursors is true, tries to set a custom cursor * either by using an icon or a build-in cursor. Cursor icon extensions are matched with a regular expression. * @private * {string} corner name * {target} event handler of the hovered corner */ _setCornerCursor: function (corner, target, e) { var iconUrlPattern = /\.(?:jpe?g|png|gif|jpg|jpeg|svg)$/; if (this.fixedCursors && this[corner + 'cursorIcon']) { if (this[corner + 'cursorIcon'].match(iconUrlPattern)) { this.setCursor('url(' + this[corner + 'cursorIcon'] + '), auto'); } else { if (this[corner + 'cursorIcon'] === 'resize') { this.setCursor(this._getRotatedCornerCursor(corner, target, e)); } else { this.setCursor(this[corner + 'cursorIcon']); } } } else { if (corner in cursorOffset) { this.setCursor(this._getRotatedCornerCursor(corner, target, e)); } else if (corner === 'mtr' && target.hasRotatingPoint) { this.setCursor(this.rotationCursor); } else { this.setCursor(this.defaultCursor); return false; } } return false; }, }); if (typeof exports !== 'undefined') { module.exports = this; } })(window);