Skip to content

Instantly share code, notes, and snippets.

@clouddueling
Created January 18, 2014 15:32
Show Gist options
  • Save clouddueling/8492029 to your computer and use it in GitHub Desktop.
Save clouddueling/8492029 to your computer and use it in GitHub Desktop.
common.fabric
/**
* http://fabricjs.com/js/kitchensink/controller.js
* http://fabricjs.com/js/kitchensink/utils.js
* http://fabricjs.com/js/kitchensink/app_config.js
* http://fabricjs.com/events/
* view-source:http://fabricjs.com/kitchensink/
*/
(function() {
'use strict';
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
angular.module('common.fabric', [])
.factory('FabricService', [function() {
var multiplier = 2;
var self = {};
self.canvas = undefined;
self.rotatingPointOffset = 20;
self.element = {};
self.canvasId = 'fabric-' + Math.floor(Math.random() * 10000000);
self.defaultShapeOptions = {};
self.defaultITextOptions = {};
self.defaultTextOptions = {};
self.defaultImageOptions = {};
self.jsonExportProperties = [];
function update() {
if (!self.canvas) {
return;
}
self.canvas.fire('canvas:modified');
self.canvas.renderAll();
}
function getActiveStyle(styleName, object) {
if (!self.canvas) {
return;
}
object = object || self.canvas.getActiveObject();
if (!object) {
return;
}
return (object.getSelectionStyles && object.isEditing)
? (object.getSelectionStyles()[styleName] || '')
: (object[styleName] || '');
}
function setActiveStyle(styleName, value, object) {
if (!self.canvas) {
return;
}
object = object || self.canvas.getActiveObject();
if (!object) {
return;
}
if (object.setSelectionStyles && object.isEditing) {
var style = { };
style[styleName] = value;
object.setSelectionStyles(style);
object.setCoords();
}
else {
object[styleName] = value;
}
object.setCoords();
update();
self.canvas.renderAll();
}
function getActiveProp(name) {
if (!self.canvas) {
return;
}
var object = self.canvas.getActiveObject();
if (!object) {
return;
}
return object[name] || '';
}
function setActiveProp(name, value) {
var object = self.canvas.getActiveObject();
if (!object) {
return;
}
object.set(name, value).setCoords();
update();
self.canvas.renderAll();
}
self.getActiveProp = function(name) {
return getActiveProp(name);
};
self.setActiveProp = function(name, value) {
setActiveProp(name, value);
};
self.toggleActiveProp = function(name) {
var value = getActiveProp(name);
var object = self.canvas.getActiveObject();
if (!object) {
return;
}
object[name] = !value;
update();
self.canvas.renderAll();
};
self.setJsonExportProperties = function(arr) {
self.jsonExportProperties = arr;
};
self.setFabric = function(newFabric) {
fabric = newFabric;
};
self.loadJson = function(json) {
self.canvas.loadFromJSON(json);
var obj = angular.fromJson(json);
self.canvas.setWidth(obj.width);
self.canvas.setHeight(obj.height);
};
self.setShapeOptions = function(options) {
self.defaultShapeOptions = angular.extend(self.defaultShapeOptions, options);
};
self.getShapeOptions = function() {
return self.defaultShapeOptions;
};
self.addShape = function(shapePath) {
fabric.loadSVGFromURL(shapePath, function(objects, options) {
var object = fabric.util.groupSVGElements(objects, options);
for (var p in self.defaultShapeOptions) {
object[p] = self.defaultShapeOptions[p];
}
if (object.isSameColor && object.isSameColor() || !object.paths) {
object.setFill(self.defaultShapeOptions.fill);
} else if (object.paths) {
for (var i = 0; i < object.paths.length; i++) {
object.paths[i].setFill(self.defaultShapeOptions.fill);
}
}
self.canvas.add(object);
self.canvas.deactivateAll();
self.canvas.setActiveObject(object);
object.bringToFront();
});
};
self.setImageOptions = function(options) {
self.defaultImageOptions = angular.extend(self.defaultImageOptions, options);
};
self.getImageOptions = function() {
return self.defaultImageOptions;
};
self.addImage = function(image) {
fabric.Image.fromURL(image, function (object) {
for (var p in self.defaultImageOptions) {
object[p] = self.defaultImageOptions[p];
}
self.canvas.add(object);
self.canvas.deactivateAll();
self.canvas.setActiveObject(object);
object.bringToFront();
});
};
self.setITextOptions = function(options) {
self.defaultITextOptions = angular.extend(self.defaultITextOptions, options);
};
self.getITextOptions = function() {
return self.defaultITextOptions;
};
self.addIText = function(str) {
str = str || "New Text";
var text = new fabric.IText(str, self.defaultITextOptions);
self.canvas.add(text);
self.canvas.deactivateAll();
self.canvas.setActiveObject(text);
text.bringToFront();
};
self.setTextOptions = function(options) {
self.defaultTextOptions = angular.extend(self.defaultTextOptions, options);
};
self.getTextOptions = function() {
return self.defaultTextOptions;
};
self.addText = function(str) {
str = str || "New Text";
var text = new fabric.Text(str, self.defaultTextOptions);
self.canvas.add(text);
self.canvas.deactivateAll();
self.canvas.setActiveObject(text);
text.bringToFront();
};
self.download = function() {
self.deactivateAll();
// get the data to be added to an anchor element
var data = self.getCanvasData().replace("image/png", "image/octet-stream");
var filename = self.downloadFilename + '.png';
var link = document.createElement('a');
// force click the link to download the image. Not sure if works in all browsers.
link.download = filename;
link.href = data;
link.click();
};
self.sendBackwards = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
self.canvas.sendBackwards(activeObject);
}
};
self.sendToBack = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
self.canvas.sendToBack(activeObject);
}
};
self.bringForward = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
self.canvas.bringForward(activeObject);
}
};
self.bringToFront = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
self.canvas.bringToFront(activeObject);
}
};
self.hasActiveObject = function() {
if (self.canvas.getActiveObject()) {
return true;
}
return false;
};
self.center = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
activeObject.center();
}
};
self.centerH = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
activeObject.centerH();
}
};
self.centerV = function() {
var activeObject = self.canvas.getActiveObject();
if (activeObject) {
activeObject.centerV();
}
};
self.init = function(json) {
self.element.attr('id', self.canvasId);
self.canvas = new fabric.Canvas(self.canvasId);
};
self.setBackgroundColor = function(color) {
self.canvas.setBackgroundColor(color);
update();
};
self.setWidth = function(width) {
self.canvas.setWidth(width);
update();
};
self.setHeight = function(height) {
self.canvas.setHeight(height);
update();
};
self.clearCanvas = function() {
self.canvas.clear();
self.canvas.setBackgroundColor('ffffff');
update();
};
self.setDownloadFilename = function(str) {
self.downloadFilename = str;
};
self.deselectActiveObject = function() {
var object = self.canvas.getActiveObject();
if (object) {
object.active = false;
}
};
self.deactivateAll = function() {
self.canvas.deactivateAll().renderAll();
};
self.getCanvasData = function() {
var data = self.canvas.toDataURL({
width: self.canvas.getWidth(),
height: self.canvas.getHeight(),
multiplier: multiplier
});
return data;
};
self.removeSelected = function() {
var activeObject = self.canvas.getActiveObject(),
activeGroup = self.canvas.getActiveGroup();
if (activeGroup) {
var objectsInGroup = activeGroup.getObjects();
self.canvas.discardActiveGroup();
objectsInGroup.forEach(function(object) {
self.canvas.remove(object);
});
} else if (activeObject) {
self.canvas.remove(activeObject);
}
};
self.getActiveType = function() {
return getActiveProp('type');
};
self.getOpacity = function() {
return getActiveStyle('opacity') * 100;
};
self.setOpacity = function(value) {
setActiveStyle('opacity', parseInt(value, 10) / 100);
};
self.getFill = function() {
return getActiveStyle('fill');
};
self.setFill = function(value) {
if (! self.canvas) {
return;
}
// Yes this looks like shit but it works for text and uploaded shapes who both use the same prop.
var object = self.canvas.getActiveObject();
if (object) {
if (object.type === 'text') {
setActiveStyle('fill', value);
} else {
if (object.isSameColor && object.isSameColor() || !object.paths) {
object.setFill(value);
} else if (object.paths) {
for (var i = 0; i < object.paths.length; i++) {
object.paths[i].setFill(value);
}
}
self.canvas.renderAll();
}
}
};
self.isBold = function() {
return getActiveStyle('fontWeight') === 'bold';
};
self.toggleBold = function() {
setActiveStyle('fontWeight',
getActiveStyle('fontWeight') === 'bold' ? '' : 'bold');
};
self.isItalic = function() {
return getActiveStyle('fontStyle') === 'italic';
};
self.toggleItalic = function() {
setActiveStyle('fontStyle',
getActiveStyle('fontStyle') === 'italic' ? '' : 'italic');
};
self.isUnderline = function() {
return getActiveStyle('textDecoration').indexOf('underline') > -1;
};
self.toggleUnderline = function() {
var value = self.isUnderline()
? getActiveStyle('textDecoration').replace('underline', '')
: (getActiveStyle('textDecoration') + ' underline');
setActiveStyle('textDecoration', value);
};
self.isLinethrough = function() {
return getActiveStyle('textDecoration').indexOf('line-through') > -1;
};
self.toggleLinethrough = function() {
var value = self.isLinethrough()
? getActiveStyle('textDecoration').replace('line-through', '')
: (getActiveStyle('textDecoration') + ' line-through');
setActiveStyle('textDecoration', value);
};
self.isOverline = function() {
return getActiveStyle('textDecoration').indexOf('overline') > -1;
};
self.toggleOverline = function() {
var value = self.isOverline()
? getActiveStyle('textDecoration').replace('overline', '')
: (getActiveStyle('textDecoration') + ' overline');
setActiveStyle('textDecoration', value);
};
self.getText = function() {
return getActiveProp('text');
};
self.setText = function(value) {
setActiveProp('text', value);
};
self.getTextAlign = function() {
return capitalize(getActiveProp('textAlign'));
};
self.setTextAlign = function(value) {
setActiveProp('textAlign', value.toLowerCase());
};
self.getFontFamilygetStrokeColor = function() {
return getActiveProp('fontFamily').toLowerCase();
};
self.setFontFamily = function(value) {
setActiveProp('fontFamily', value.toLowerCase());
};
self.getBgColor = function() {
return getActiveProp('backgroundColor');
};
self.setBgColor = function(value) {
setActiveProp('backgroundColor', value);
};
self.getFlipX = function() {
return getActiveProp('flipX');
};
self.setFlipX = function(value) {
setActiveProp('flipX', value);
};
self.toggleFlipX = function() {
var value = self.getFlipX() ? false : true;
self.setFlipX(value);
};
self.getTextBgColor = function() {
return getActiveProp('textBackgroundColor');
};
self.setTextBgColor = function(value) {
setActiveProp('textBackgroundColor', value);
};
self.getStrokeColor = function() {
return getActiveStyle('stroke');
};
self.setStrokeColor = function(value) {
setActiveStyle('stroke', value);
};
self.getStrokeWidth = function() {
return getActiveStyle('strokeWidth');
};
self.setStrokeWidth = function(value) {
setActiveStyle('strokeWidth', parseInt(value, 10));
};
self.getFontSize = function() {
return getActiveStyle('fontSize');
};
self.setFontSize = function(value) {
setActiveStyle('fontSize', parseInt(value, 10));
};
self.getLineHeight = function() {
return getActiveStyle('lineHeight');
};
self.setLineHeight = function(value) {
setActiveStyle('lineHeight', parseFloat(value, 10));
};
self.getBold = function() {
return getActiveStyle('fontWeight');
};
self.setBold = function(value) {
setActiveStyle('fontWeight', value ? 'bold' : '');
};
self.getCanvasBgColor = function() {
return self.canvas.backgroundColor;
};
self.setCanvasBgColor = function(value) {
self.canvas.backgroundColor = value;
self.canvas.renderAll();
};
return self;
}])
.directive('fabric', ['FabricService', '$timeout', function(FabricService, $timeout) {
return {
scope: {
ngModel: '=',
ngChange: '&'
},
link: function(scope, element) {
fabric.Object.prototype.transparentCorners = false;
function update() {
$timeout(function() {
scope.ngModel = FabricService.canvas.toJSON(FabricService.jsonExportProperties);
scope.ngChange();
});
}
function initCanvas() {
FabricService.element = element;
FabricService.init(scope.ngModel);
}
function initListeners() {
FabricService.canvas.on('canvas:modified', function(options) {
update();
});
FabricService.canvas.on('object:modified', function(options) {
FabricService.canvas.renderAll();
update();
});
FabricService.canvas.on("selection:cleared", function(){
$timeout(function() {
scope.fabricActiveObject = undefined;
});
});
FabricService.canvas.on("object:selected", function(){
$timeout(function() {
});
});
FabricService.canvas.on("text:changed", function(){
$timeout(function() {
scope.fabricActiveObject = undefined;
});
});
FabricService.canvas.on("after:render", function(){
FabricService.canvas.calcOffset();
});
}
initCanvas();
initListeners();
}
};
}])
.directive('ngFabric', ['FabricService', function(FabricService) {
return {
restrict: 'A',
link: function ($scope, $element, $attrs) {
var prop = capitalize($attrs.ngFabric),
getter = 'get' + prop,
setter = 'set' + prop;
$element.on('change keyup select', function() {
FabricService[setter] && FabricService[setter](this.value);
FabricService.canvas.fire('canvas:modified');
});
$scope.$watch(FabricService[getter], function(newVal) {
if (!FabricService.canvas) {
return;
}
if ($element[0].type === 'radio') {
var radioGroup = document.getElementsByName($element[0].name);
for (var i = 0, len = radioGroup.length; i < len; i++) {
radioGroup[i].checked = radioGroup[i].value === newVal;
}
} else {
$element.val(newVal);
}
FabricService.canvas.fire('canvas:modified');
});
}
};
}])
// Use to deactivate all selected elements when you click off canvas
.directive('parentClick', [function() {
return {
scope: {
parentClick: '&'
},
link: function (scope, element) {
element.click(function() {
scope.parentClick();
}).children().click(function(e) {
e.stopPropagation();
});
}
};
}]);
})();
describe('Fabric |', function() {
var fabricService, scope, spy;
var borderColor = 'EEF6FC';
var cornerColor = 'rgba(64, 159, 221, .3)';
var cornerSize = 8;
var padding = 5;
var rotatingPointOffset = 10;
var transparentCorners = false;
var fill = '0088CC';
var fabric = {
Image: {
fromURL: function(path, cb) {
return function() {
return {}
};
}
},
IText: function() {
return {
setFill: function() {},
bringToFront: function() {},
center: function() {}
};
},
Text: function() {
return {
setFill: function() {},
bringToFront: function() {},
center: function() {}
};
},
util: {
groupSVGElements: function() {
return {
setFill: function() {},
bringToFront: function() {},
center: function() {}
};
}
},
loadSVGFromURL: function(path, cb) {
return cb();
}
};
var canvas = {
add: function() {},
loadFromJSON: function() {},
renderAll: function() {},
getActiveObject: function() { return {}; },
sendBackwards: function() {},
sendToBack: function() {},
bringForward: function() {},
bringToFront: function() {},
};
var getActiveObject = sinon.spy(canvas, 'getActiveObject');
beforeEach(module('common.fabric'));
beforeEach(inject(function(FabricService, $rootScope) {
fabricService = FabricService;
fabricService.setFabric(fabric);
fabricService.canvas = canvas;
scope = $rootScope.$new();
}));
describe('FabricService |', function() {
it('should accept default options for shapes', function() {
fabricService.setShapeOptions({
angle: 100,
opacity: 50,
borderColor: borderColor,
cornerColor: cornerColor,
cornerSize: cornerSize,
padding: padding,
rotatingPointOffset: rotatingPointOffset,
transparentCorners: transparentCorners,
fill: fill
});
var shapeOptions = fabricService.getShapeOptions();
expect(shapeOptions.angle).to.equal(100);
expect(shapeOptions.opacity).to.equal(50);
expect(shapeOptions.rotatingPointOffset).to.equal(10);
});
it('should accept default options for images', function() {
fabricService.setImageOptions({
angle: 100,
opacity: 50,
borderColor: borderColor,
cornerColor: cornerColor,
cornerSize: cornerSize,
padding: padding,
rotatingPointOffset: rotatingPointOffset,
transparentCorners: transparentCorners,
fill: fill
});
var imageOptions = fabricService.getImageOptions();
expect(imageOptions.angle).to.equal(100);
expect(imageOptions.opacity).to.equal(50);
expect(imageOptions.rotatingPointOffset).to.equal(10);
});
it('should accept default options for itext', function() {
fabricService.setITextOptions({
color: '8800cc',
fontFamily: 'Open Sans',
fontWeight: 'bold',
textDecoration: 'underline',
fontStyle: 'italics',
textAlign: 'center',
fontSize: '40',
angle: 100,
opacity: 50,
rotatingPointOffset: 10,
borderColor: borderColor,
cornerColor: cornerColor,
cornerSize: cornerSize,
padding: padding,
rotatingPointOffset: rotatingPointOffset,
transparentCorners: transparentCorners,
fill: fill
});
var iTextOptions = fabricService.getITextOptions();
expect(iTextOptions.color).to.equal('8800cc');
expect(iTextOptions.fontFamily).to.equal('Open Sans');
expect(iTextOptions.fontWeight).to.equal('bold');
expect(iTextOptions.textDecoration).to.equal('underline');
expect(iTextOptions.fontStyle).to.equal('italics');
expect(iTextOptions.textAlign).to.equal('center');
expect(iTextOptions.fontSize).to.equal('40');
expect(iTextOptions.opacity).to.equal(50);
expect(iTextOptions.rotatingPointOffset).to.equal(10);
});
it('should accept default options for text', function() {
fabricService.setTextOptions({
color: '8800cc',
fontFamily: 'Open Sans',
fontWeight: 'bold',
textDecoration: 'underline',
fontStyle: 'italics',
textAlign: 'center',
fontSize: '40',
angle: 100,
opacity: 50,
rotatingPointOffset: 10,
borderColor: borderColor,
cornerColor: cornerColor,
cornerSize: cornerSize,
padding: padding,
rotatingPointOffset: rotatingPointOffset,
transparentCorners: transparentCorners,
fill: fill
});
var textOptions = fabricService.getTextOptions();
expect(textOptions.color).to.equal('8800cc');
expect(textOptions.fontFamily).to.equal('Open Sans');
expect(textOptions.fontWeight).to.equal('bold');
expect(textOptions.textDecoration).to.equal('underline');
expect(textOptions.fontStyle).to.equal('italics');
expect(textOptions.textAlign).to.equal('center');
expect(textOptions.fontSize).to.equal('40');
expect(textOptions.opacity).to.equal(50);
expect(textOptions.rotatingPointOffset).to.equal(10);
});
it('should add an image to canvas', function() {
spy = sinon.spy(fabric.Image, 'fromURL');
fabricService.addImage('test.png');
expect(spy.calledOnce).to.be.true;
});
it('should add a shape to canvas', function() {
spy = sinon.spy(fabric, 'loadSVGFromURL');
fabricService.addShape('test.svg');
expect(spy.calledOnce).to.be.true;
});
it('should add an itext to canvas', function() {
spy = sinon.spy(fabric, 'IText');
fabricService.addIText();
expect(spy.calledOnce).to.be.true;
});
it('should add an text to canvas', function() {
spy = sinon.spy(fabric, 'Text');
fabricService.addText();
expect(spy.calledOnce).to.be.true;
});
it('should call loadJson', function() {
spy = sinon.spy(canvas, 'loadFromJSON');
fabricService.loadJson("{}");
expect(spy.calledOnce).to.be.true;
});
it('should call sendBackwards', function() {
var sendBackwards = sinon.spy(canvas, 'sendBackwards');
fabricService.sendBackwards();
expect(sendBackwards.calledOnce).to.be.true;
});
it('should call sendToBack', function() {
getActiveObject.reset();
var sendToBack = sinon.spy(canvas, 'sendToBack');
fabricService.sendToBack();
expect(getActiveObject.calledOnce).to.be.true;
expect(sendToBack.calledOnce).to.be.true;
});
it('should call bringForward', function() {
getActiveObject.reset();
var bringForward = sinon.spy(canvas, 'bringForward');
fabricService.bringForward();
expect(getActiveObject.calledOnce).to.be.true;
expect(bringForward.calledOnce).to.be.true;
});
it('should call bringToFront', function() {
getActiveObject.reset();
var bringToFront = sinon.spy(canvas, 'bringToFront');
fabricService.bringToFront();
expect(getActiveObject.calledOnce).to.be.true;
expect(bringToFront.calledOnce).to.be.true;
});
it('should return true if has object', function() {
expect(fabricService.hasActiveObject()).to.be.true;
});
it('should')
});
describe('fabricValue |', function() {
});
describe('fabric |', function() {
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment