1/* 2 * fabric.js Controls Extension 3 * for fabric.js current build 4 * Simon Kunz 09.02.2016 for pixolith 5 * Licensed under the MIT license. 6 */ 7( function( global ) { 8 'use strict'; 9 var fabric = global.fabric || ( global.fabric = {} ), 10 extCompat = '1.6', 11 isVML = function() { 12 return typeof G_vmlCanvasManager !== 'undefined'; 13 }, 14 degreesToRadians = fabric.util.degreesToRadians, 15 cursorOffset = { 16 mt: 0, // n 17 tr: 1, // ne 18 mr: 2, // e 19 br: 3, // se 20 mb: 4, // s 21 bl: 5, // sw 22 ml: 6, // w 23 tl: 7 // nw 24 }; 25 26 if ( global.fabric.version.indexOf( extCompat ) === -1 ) { 27 console.warn( 'this extension might not be fully compatible with your version ' + 28 'of fabric.js (' + global.fabric.version + ').' + 29 'Consider using the latest compatible build of fabric.js' + extCompat 30 ); 31 } 32 33 fabric.util.object.extend( fabric.Object.prototype, { 34 35 /** 36 * When true, image icons are loaded via the drawImage method 37 * @type Boolean 38 * @default false 39 */ 40 41 useCustomIcons: false, 42 43 /** 44 * Sets a background-color for drawImage operations with transparency 45 * @type string 46 * @default transparent 47 */ 48 49 cornerBackgroundColor: 'transparent', 50 51 /** 52 * Sets the shape of the background for drawImage operations with transparency 53 * @type string 54 * @default rect 55 */ 56 57 cornerShape: '', 58 59 /** 60 * Inner Padding between Shape Background and drawn Image 61 * @type int 62 * @default rect 63 */ 64 65 cornerPadding: 0, 66 67 /** 68 * Set a custom corner icon 69 * @param {Object} obj settings and icon url. 70 * @param callback function 71 */ 72 73 customiseCornerIcons: function( obj, callback ) { 74 var setting; 75 76 for ( setting in obj ) { 77 if ( obj.hasOwnProperty( setting ) ) { 78 79 if ( obj[ setting ].cornerShape !== undefined ) { 80 this.cornerShape = obj[ setting ].cornerShape; 81 } 82 83 if ( obj[ setting ].cornerBackgroundColor !== undefined ) { 84 this.cornerBackgroundColor = obj[ setting ].cornerBackgroundColor; 85 } 86 87 if ( obj[ setting ].borderColor !== undefined ) { 88 this.borderColor = obj[ setting ].borderColor; 89 } 90 91 if ( obj[ setting ].cornerSize !== undefined ) { 92 this.cornerSize = obj[ setting ].cornerSize; 93 } 94 95 if ( obj[ setting ].cornerPadding !== undefined ) { 96 this.cornerPadding = obj[ setting ].cornerPadding; 97 } 98 99 if ( obj[ setting ].icon !== undefined ) { 100 this.useCustomIcons = true; 101 102 this.loadIcon( setting, obj[ setting ].icon, function() { 103 if ( callback && typeof( callback ) === 'function' ) { 104 callback(); 105 } 106 } ); 107 } 108 } 109 } 110 }, 111 112 /** 113 * loads the icon image as an image src. 114 * @param {Object} corner to load an icon. 115 * @param iconUrl as a string. 116 * @param callback function. 117 */ 118 119 loadIcon: function( corner, iconUrl, callback ) { 120 var self = this, 121 icon = new Image(); 122 123 icon.onload = function() { 124 self[ corner + 'Icon' ] = this; 125 if ( callback && typeof( callback ) === 'function' ) { 126 callback(); 127 } 128 }; 129 130 icon.onerror = function() { 131 fabric.warn( this.src + ' icon is not an image' ); 132 }; 133 134 if ( iconUrl.indexOf( 'http' ) > -1 ) { 135 icon.crossOrigin = 'Anonymous'; 136 } 137 138 icon.src = iconUrl; 139 }, 140 141 /** 142 * copy of the setter method for our american friends. 143 * @param {Object} obj containing corner icon urls and settings. 144 */ 145 146 customizeCornerIcons: function( obj ) { 147 this.customiseCornerIcons( obj ); 148 }, 149 150 /** 151 * Draws corners of an object's bounding box. 152 * Requires public properties: width, height 153 * Requires public options: cornerSize, padding 154 * @param {CanvasRenderingContext2D} ctx Context to draw on 155 * @return {fabric.Object} thisArg 156 * @chainable 157 */ 158 159 drawControls: function( ctx ) { 160 161 if ( !this.hasControls ) { 162 return this; 163 } 164 165 var wh = this._calculateCurrentDimensions( true ), 166 width = wh.x, 167 height = wh.y, 168 scaleOffset = this.cornerSize, 169 left = -( width + scaleOffset ) / 2, 170 top = -( height + scaleOffset ) / 2, 171 methodName; 172 173 if ( !this.useCustomIcons ) { 174 175 ctx.lineWidth = 1; 176 ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; 177 ctx.strokeStyle = ctx.fillStyle = this.cornerColor; 178 179 methodName = this.transparentCorners ? 'strokeRect' : 'fillRect'; 180 } else { 181 methodName = 'drawImage'; 182 } 183 184 ctx.save(); 185 186 // top-left 187 this._drawControl( 'tl', ctx, methodName, 188 left, 189 top, 190 this.tlIcon 191 ); 192 193 // top-right 194 this._drawControl( 'tr', ctx, methodName, 195 left + width, 196 top, 197 this.trIcon 198 ); 199 200 // bottom-left 201 this._drawControl( 'bl', ctx, methodName, 202 left, 203 top + height, 204 this.blIcon 205 ); 206 207 // bottom-right 208 this._drawControl( 'br', ctx, methodName, 209 left + width, 210 top + height, 211 this.brIcon 212 ); 213 214 if ( !this.get( 'lockUniScaling' ) ) { 215 216 // middle-top 217 this._drawControl( 'mt', ctx, methodName, 218 left + width / 2, 219 top, 220 this.mtIcon 221 ); 222 223 // middle-bottom 224 this._drawControl( 'mb', ctx, methodName, 225 left + width / 2, 226 top + height, 227 this.mbIcon 228 ); 229 230 // middle-right 231 this._drawControl( 'mr', ctx, methodName, 232 left + width, 233 top + height / 2, 234 this.mrIcon 235 ); 236 237 // middle-left 238 this._drawControl( 'ml', ctx, methodName, 239 left, 240 top + height / 2, 241 this.mlIcon 242 ); 243 } 244 245 // middle-top-rotate 246 if ( this.hasRotatingPoint ) { 247 this._drawControl( 'mtr', ctx, methodName, 248 left + width / 2, 249 top - this.rotatingPointOffset, 250 this.mtrIcon 251 ); 252 } 253
254 ctx.restore(); 255 256 return this; 257 }, 258 259 /** Draw controls either with background-shape and color (transparency) or plain image (modified core method) 260 * @private 261 * {string} icon url of the control 262 */ 263 264 _drawControl: function( control, ctx, methodName, left, top, icon ) { 265 if ( !this.isControlVisible( control ) ) { 266 return; 267 } 268 269 var size = this.cornerSize, 270 cornerBG = this.cornerBackgroundColor || 'black', 271 cornerShape = this.cornerShape || 'rect', 272 cornerPadding = this.cornerPadding || 10 ; 273 274 if ( this.useCustomIcons ) { 275 if ( cornerShape ) { 276 ctx.globalAlpha = 1; 277 ctx.fillStyle = cornerBG; 278 switch ( cornerShape ) { 279 case 'rect': 280 ctx.fillRect( left, top, size, size ); 281 if ( icon !== undefined ) { 282 ctx[ methodName ]( 283 icon, 284 left + cornerPadding / 2, 285 top + cornerPadding / 2, 286 size - cornerPadding, 287 size - cornerPadding 288 ); 289 } 290 break; 291 case 'circle': 292 ctx.beginPath(); 293 ctx.arc( left + size / 2, top + size / 2, size / 2, 0, 2 * Math.PI ); 294 ctx.fill(); 295 ctx.closePath(); 296 if ( icon !== undefined ) { 297 ctx[ methodName ]( 298 icon, 299 left + cornerPadding / 2, 300 top + cornerPadding / 2, 301 size - cornerPadding, 302 size - cornerPadding 303 ); 304 } 305 break; 306 } 307 } else { 308 if ( icon !== undefined ) { 309 ctx[ methodName ]( 310 icon, 311 left, 312 top, 313 size, 314 size 315 ); 316 } 317 } 318 } else { 319 isVML() || this.transparentCorners || ctx.clearRect( left, top, size, size ); 320 ctx[ methodName ]( left, top, size, size ); 321 } 322 323 } 324 } ); 325 326 fabric.util.object.extend( fabric.Canvas.prototype, { 327 /** 328 * When true, actions can be overwritten 329 * @type Boolean 330 * @default false 331 */ 332 333 overwriteActions: false, 334 335 /** 336 * When true, cursors are fixed 337 * @type Boolean 338 * @default false 339 */ 340 341 fixedCursors: false, 342 343 /** 344 * setter Method for actions and cursors. 345 * @param {Object} obj containing corner action and cursor url/type. 346 */ 347 348 customiseControls: function( obj ) { 349 var setting; 350 351 for ( setting in obj ) { 352 if ( obj.hasOwnProperty( setting ) ) { 353 if ( obj[ setting ].action !== undefined ) { 354 this.overwriteActions = true; 355 this.setCustomAction( setting, obj[ setting ].action ); 356 } 357 358 if ( obj[ setting ].cursor !== undefined ) { 359 this.fixedCursors = true; 360 this.setCustomCursor( setting, obj[ setting ].cursor ); 361 } 362 } 363 } 364 }, 365 366 /** 367 * loads the icon image as an image src. 368 * @param {Object} corner to load an icon. 369 * @param action as a string. 370 */ 371 372 setCustomAction: function( corner, action ) { 373 this[ corner + 'Action' ] = action ; 374 }, 375 376 /** 377 * loads the icon image as an image src. 378 * @param {Object} corner to load an icon. 379 * @param cursorUrl as a string. 380 */ 381 382 setCustomCursor: function( corner, cursorUrl ) { 383 this[ corner + 'cursorIcon' ] = cursorUrl ; 384 }, 385 386 /** 387 * copy of the setter method for our american friends. 388 * @param {Object}
388 obj containing corner action and cursor url/type. 389 */ 390 391 customizeControls: function( obj ) { 392 this.customiseControls( obj ); 393 }, 394 395 /** 396 * @private 397 */ 398 399 _getActionFromCorner: function( target, corner, e ) { 400 if ( !corner ) { 401 return 'drag'; 402 } 403 404 if ( corner ) { 405 if ( this[ corner + 'Action' ] && this.overwriteActions ) { 406 switch ( corner ) { 407 case 'mtr': 408 return this[ corner + 'Action' ] || 'rotate'; 409 case 'ml': 410 case 'mr': 411 if ( e.shiftKey ) { 412 return e.shiftKey ? 'skewY' : 'scaleX'; 413 } 414 return this[ corner + 'Action' ]; 415 case 'mt': 416 case 'mb': 417 if ( e.shiftKey ) { 418 return e.shiftKey ? 'skewY' : 'scaleY'; 419 } 420 return this[ corner + 'Action' ]; 421 default: 422 return this[ corner + 'Action' ] || 'scale'; 423 } 424 } else { 425 switch ( corner ) { 426 case 'mtr': 427 return 'rotate'; 428 case 'ml': 429 case 'mr': 430 return e.shiftKey ? 'skewY' : 'scaleX'; 431 case 'mt': 432 case 'mb': 433 return e.shiftKey ? 'skewX' : 'scaleY'; 434 default: 435 return 'scale'; 436 } 437 } 438 } 439 }, 440 441 /** 442 * @private 443 * @param {Event} e Event object 444 * @param {fabric.Object} target 445 */ 446 _setupCurrentTransform: function( e, target ) { 447 if ( !target ) { 448 return; 449 } 450 451 var pointer = this.getPointer( e ), 452 corner = target._findTargetCorner( this.getPointer( e, true ) ), 453 action = this._getActionFromCorner( target, corner, e ), 454 origin = this._getOriginFromCorner( target, corner ), 455 _this = this; 456 457 if ( typeof action === 'function' ) { 458 action.call( _this, e, target ); 459 } 460 461 this._currentTransform = { 462 target: target, 463 action: action, 464 corner: corner, 465 scaleX: target.scaleX, 466 scaleY: target.scaleY, 467 skewX: target.skewX, 468 skewY: target.skewY, 469 offsetX: pointer.x - target.left, 470 offsetY: pointer.y - target.top, 471 originX: origin.x, 472 originY: origin.y, 473 ex: pointer.x, 474 ey: pointer.y, 475 lastX: pointer.x, 476 lastY: pointer.y, 477 left: target.left, 478 top: target.top, 479 theta: degreesToRadians( target.angle ), 480 width: target.width * target.scaleX, 481 mouseXSign: 1, 482 mouseYSign: 1, 483 shiftKey: e.shiftKey, 484 altKey: e.altKey 485 }; 486 487 this._currentTransform.original = { 488 left: target.left, 489 top: target.top, 490 scaleX: target.scaleX, 491 scaleY: target.scaleY, 492 skewX: target.skewX, 493 skewY: target.skewY, 494 originX: origin.x, 495 originY: origin.y 496 }; 497 498 if ( action === 'remove' ) { 499 this._removeAction( e, target ); 500 } 501 502 if ( action === 'moveUp' ) { 503 this._moveLayerUpAction( e, target ); 504 } 505 506 if ( action === 'moveDown' ) { 507 this._moveLayerDownAction( e, target ); 508 } 509 510 if ( typeof action === 'object' ) { 511 if ( Object.keys( action )[ 0 ] === 'rotateByDegrees' ) { 512 this._rotateByDegrees( e, target, action.rotateByDegrees ); 513 } 514 } 515 516 this._resetCurrentTransform( e ); 517 }, 518 519 /** 520 * Custom remove object action 521 * @private 522 * @param {Event} e Event object 523 * @param {fabric.Object} target 524 */ 525 526 _removeAction: function( e, target ) { 527 var _this = this; 528 if ( this.getActiveGroup() && this.getActiveGroup() !== 'undefined' ) {
529 this.getActiveGroup().forEachObject( function( o ) { 530 o.remove(); 531 } ); 532 this.discardActiveGroup(); 533 534 // as of fabric 1.6.3 necessary for reasons.. 535 setTimeout( function() { 536 _this.deactivateAll(); 537 }, 0 ); 538 539 } else { 540 target.remove(); 541 542 setTimeout( function() { 543 _this.deactivateAll(); 544 }, 0 ); 545 } 546 }, 547 548 /** 549 * Custom move up object action 550 * @private 551 * @param {Event} e Event object 552 * @param {fabric.Object} target 553 */ 554 555 _moveLayerUpAction: function( e, target ) { 556 if ( this.getActiveGroup() && this.getActiveGroup() !== 'undefined' ) { 557 this.getActiveGroup().forEachObject( function( o ) { 558 o.bringForward(); 559 } ); 560 } else { 561 target.bringForward(); 562 } 563 }, 564 565 /** 566 * Custom move down object action 567 * @private 568 * @param {Event} e Event object 569 * @param {fabric.Object} target 570 */ 571 572 _moveLayerDownAction: function( e, target ) { 573 if ( this.getActiveGroup() && this.getActiveGroup() !== 'undefined' ) { 574 this.getActiveGroup().forEachObject( function( o ) { 575 o.sendBackwards(); 576 } ); 577 } else { 578 target.sendBackwards(); 579 } 580 }, 581 582 /** 583 * Custom move down object action 584 * @private 585 * @param {Event} e Event object 586 * @param {fabric.Object} target 587 * @param {Integer} value of rotation 588 */ 589 590 _rotateByDegrees: function( e, target, value ) { 591 var angle = parseInt( target.getAngle() ) + value, 592 needsOriginRestore = false; 593 594 if ( ( target.originX !== 'center' || target.originY !== 'center' ) && target.centeredRotation ) { 595 this._setOriginToCenter( target ); 596 needsOriginRestore = true; 597 } 598 599 angle = angle > 360 ? angle - 360 : angle; 600 601 if ( this.getActiveGroup() && this.getActiveGroup() !== 'undefined' ) { 602 this.getActiveGroup().forEachObject( function( obj ) { 603 obj 604 .setAngle( angle ) 605 .setCoords(); 606 } ); 607 } else { 608 target 609 .setAngle( angle ) 610 .setCoords(); 611 } 612 613 if ( needsOriginRestore ) { 614 this._setCenterToOrigin( target ); 615 } 616 617 this.renderAll(); 618 }, 619 620 _setOriginToCenter: function( target ) { 621 target._originalOriginX = target.originX; 622 target._originalOriginY = target.originY; 623 624 var center = target.getCenterPoint(); 625 626 target.set( { 627 originX: 'center', 628 originY: 'center', 629 left: center.x, 630 top: center.y 631 } ); 632 }, 633 634 _setCenterToOrigin: function( target ) { 635 var originPoint = target.translateToOriginPoint( 636 target.getCenterPoint(), 637 target._originalOriginX, 638 target._originalOriginY ); 639 640 target.set( { 641 originX: target._originalOriginX, 642 originY: target._originalOriginY, 643 left: originPoint.x, 644 top: originPoint.y 645 } ); 646 }, 647 648 /** 649 * Sets either the standard behaviour cursors or if fixedCursors is true, tries to set a custom cursor 650 * either by using an icon or a build-in cursor. Cursor icon extensions are matched with a regular expression. 651 * @private 652 * {string} corner name 653 * {target} event handler of the hovered corner 654 */ 655 _setCornerCursor: function( corner, target, e ) { 656 var iconUrlPattern = /\.(?:jpe?g|png|gif|jpg|jpeg|svg)$/; 657 658 if ( this.fixedCursors && this[ corner + 'cursorIcon' ] ) { 659 if ( iconUrlPattern.test( this[ corner + 'cursorIcon' ] ) ) { 660 this.setCursor( 'url(' + this[ corner + 'cursorIcon' ] + '), auto' ); 661 } else { 662 if ( this[ corner + 'cursorIcon' ] === 'resize' ) { 663 this.setCursor( this._getRotatedCornerCursor( corner, target, e ) ); 664 } else { 665 this.setCursor( this[ corner + 'cursorIcon' ] ); 666 } 667 } 668 } else { 669 if ( corner in cursorOffset ) { 670 this.setCursor( this._getRotatedCornerCursor( corner, target, e ) ); 671 } else if ( corner === 'mtr' && target.hasRotatingPoint ) { 672 this.setCursor( this.rotationCursor ); 673 } else { 674 this.setCursor( this.defaultCursor ); 675 return false;
676 } 677 } 678 } 679 } ); 680 681} )( typeof exports !== 'undefined' ? exports : this );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.