fabricJs custom object does not render from JSON - fabricjs

fabricJS Version 2.2.3
Test jsFiddle
I'm trying to use LabeledRect subclass but my problem is that whenever I try to load it from JSON, it does not render, and I got no error in console. See the fiddle below.
How can I render it properly ? I think my problem is in the fromObject func but I got no idea where.
/**
* fabric.js template for bug reports
*
* Please update the name of the jsfiddle (see Fiddle Options).
* This templates uses latest dev verison of fabric.js (https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js).
*/
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
// ADD YOUR CODE HERE
var json = '{"version":"2.2.3","objects":[{"type":"labeledRect","version":"2.2.3","originX":"left","originY":"top","left":0,"top":0,"width":100,"height":50,"fill":"#faa","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0,"label":"1"}]}';
fabric.LabeledRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = {});
this.callSuper('initialize', options);
this.set('label', options.label || '');
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width / 2, -this.height / 2 + 20);
}
});
fabric.LabeledRect.fromObject = function(object, callback) {
fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
delete object.objects;
callback && callback(new fabric.LabeledRect(enlivenedObjects, object));
});
};
fabric.LabeledRect.async = true;
canvas.loadFromJSON(json);
canvas.renderAll();
canvas {
border: 1px solid #999;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="1000" height="600"></canvas>

fabric.LabeledRect.fromObject = function(object, callback) {
return fabric.Object._fromObject('LabeledRect', object, callback);
};
call fabric.Object._fromObject inside fromObject
DEMO
var canvas = window._canvas = new fabric.Canvas('c');
var json = '{"version":"2.2.3","objects":[{"type":"labeledRect","version":"2.2.3","originX":"left","originY":"top","left":0,"top":0,"width":100,"height":50,"fill":"#faa","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0,"label":"1"}]}';
fabric.LabeledRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = {});
this.callSuper('initialize', options);
this.set('label', options.label || '');
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width / 2, -this.height / 2 + 20);
}
});
fabric.LabeledRect.fromObject = function(object, callback) {
return fabric.Object._fromObject('LabeledRect', object, callback);
};
canvas.loadFromJSON(json,canvas.renderAll.bind(canvas));
canvas {
border: 1px solid #999;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="1000" height="600"></canvas>

Related

Clip object to path drawn using free drawing brush

Fabric.js 2.3.6
I'm trying to clip an object to a path drawn with the free drawing bush. The code fails to show the image inside the path and I'm not sure what I'm doing wrong.
Multiple objects could be clipped, so I can't apply the path to the canvas itself.
let image = new Image();
let object;
let canvas;
// canvas
canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
// clip
function clip(path) {
object.set({
clipTo: function(ctx) {
path.render(ctx);
}
});
canvas.requestRenderAll();
}
// image
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
top: 50,
left: 50
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
https://jsfiddle.net/o91rv38q/7/
In order to clip on the same spot when is path has been drawn, you need to reset pathOffset for a SVG path, and setTransform to the ctx. Your clip function will look like:
function clip(path) {
path.set({pathOffset: {x: 0, y: 0}});
object.set({
clipTo: function(ctx) {
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
ctx.beginPath();
path._renderPathCommands(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
_renderPathCommands - renders a path.
Updated fiddle
To clip multiple objects you'll need to have some sort of an array of objects and then combine then into single path:
function combinePaths (paths) {
if (!paths.length) {
return null;
}
let singlePath = paths[0].path;
for (let i = 1; i < paths.length; i++){
singlePath = [...singlePath, ...paths[i].path];
}
return new fabric.Path(singlePath, {
top: 0,
left: 0,
pathOffset: {
x: 0,
y: 0
}
});
}
Here is an example with multiple paths to clip:
// canvas
let canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false
});
let paths = [];
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function (options) {
paths.push(options.path);
clip(combinePaths(paths));
});
function combinePaths(paths) {
if (!paths.length) {
return null;
}
let singlePath = paths[0].path;
for (let i = 1; i < paths.length; i++) {
singlePath = [...singlePath, ...paths[i].path];
}
return new fabric.Path(singlePath, {
top: 0,
left: 0,
pathOffset: {
x: 0,
y: 0
}
});
}
function clip(path) {
if (!path) {
return;
}
object.set({
clipTo: function (ctx) {
var retina = this.canvas.getRetinaScaling();
ctx.save();
ctx.setTransform(retina, 0, 0, retina, 0, 0);
ctx.beginPath();
path._renderPathCommands(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
let object;
image.onload = function () {
object = new fabric.Image(image, {
width: 500,
height: 500,
top: 50,
left: 50
});
object.globalCompositeOperation = 'source-atop';
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<canvas id="canvas" width="1280" height="720"></canvas>

File Input fabric js adds images

I have a canvas and have 2 images on it one is not evented and other we can change. Now I'm trying to change/replace image using file input, everything is fine just while changing the image it replaces the one I want but also adds one on top. I want to change the Image with id='changeimg'
//Add Image to Canvas
var imgObj = new Image();
//imgObj.id='changeimg';
imgObj.src = "blank.png";
var imgmain = imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.set({
id: 'changeimg',
angle: 0,
height: 350,
width: canvas.getWidth(),
align: 'mid', //added
originX: 'center', //added
originY: 'center', //added
});
canvas.centerObject(image);
canvas.add(image);
}
var imgObj1 = new Image();
//imgObj1.id='backgroundimg';
imgObj1.src = "template1.png";
var imgmain = imgObj1.onload = function() {
var image1 = new fabric.Image(imgObj1);
image1.set({
id: 'backgroundimg',
angle: 0,
height: canvas.getHeight(),
width: canvas.getWidth(),
evented: false,
});
//canvas.centerObject(image1);
canvas.add(image1);
}
//On Image Browse and Set on Canvas
document.getElementById('uploadedImg').onchange = function handleImage(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(file) {
addImage(file.target.result);
}
reader.readAsDataURL(file);
}
function addImage(imgLink) {
fabric.Image.fromURL(imgLink, function(img) {
img.set({
'left': 50
});
img.set({
'top': 150
});
img.scaleToWidth(250);
img.scaleToHeight(250);
var cnt = 0;
var objs = canvas.getObjects();
//var objs = canvas.getActiveObject();
if (objs.length > 0) {
objs.forEach(function(e) {
if (e && e.type === 'image' && e.id === "changeimg") {
e._element.src = imgLink;;
canvas.renderAll();
cnt = 1;
}
});
}
});
}
Use imageEl.setSrc to change source of image element.
DEMO
var canvas = new fabric.Canvas('c');
fabric.Image.fromURL("https://picsum.photos/200/300/?random", function(img) {
img.set({
id: 'changeimg',
align: 'mid', //added
originX: 'center', //added
originY: 'center', //added,
scaleX: 200 / img.width,
scaleY: 200 / img.height,
});
canvas.centerObject(img);
canvas.add(img);
image = img;
})
fabric.Image.fromURL("https://picsum.photos/200/250/?random", function(img) {
img.set({
id: 'backgroundimg',
angle: 0,
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height,
});
//canvas.centerObject(image1);
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
})
//On Image Browse and Set on Canvas
document.getElementById('uploadedImg').onchange = function handleImage(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(file) {
addImage(file.target.result);
}
reader.readAsDataURL(file);
}
function addImage(imgLink) {
fabric.Image.fromURL(imgLink, function(img) {
var objects = canvas.getObjects();
for (var i = 0, l = objects.length; i < l; i++) {
if (objects[i].id == 'changeimg') {
imageEl = objects[i];
break
}
}
if (imageEl) {
imageEl.setSrc(img.getSrc(), function() {
canvas.renderAll()
imageEl.setCoords();
},{
left: 50,
top: 150,
scaleX: 150 / img.width,
scaleY: 200 / img.height,
})
}
});
}
canvas{
border:2px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.4/fabric.js"></script>
<canvas id="c" width=500 height=500></canvas><br>
<input type='file' id='uploadedImg'>

FabricJS - Custom type not renders on LoadFromJSON

After loading canvas data from JSON, an element with the custom type attribute isn't renders.
Here is jsFiddle.
Steps to reproduce are:
1) Click on SAVE button
2) Mover RED element somewhere
3) Click on RESTORE button
Nothing happens, but if the type attribute is commented out, then everything works just perfect.
Not sure if it's a bug or I forgot to add something...
Tnx
fabric.ContainerRect = fabric.util.createClass(fabric.Rect, {
type: 'container-rect',
initialize: function(options) {
this.callSuper('initialize', options);
this.on('moving', function() {
// console.log('Red is moving...');
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
}
});
fabric.ContainerRect.fromObject = function(options) {
return new fabric.ContainerRect(options);
}
//==========================================================================================
let store;
const canvas = new fabric.Canvas('paper');
const canvasOriginalSize = {
width: 600,
height: 600
};
const redBox = new fabric.ContainerRect({
left: 0,
top: 0,
width: 50,
height: 50,
fill: 'red'
});
canvas.add(redBox);
canvas.renderAll();
$("#save").on('click', function() {
store = canvas.toJSON();
console.log(store);
});
$("#restore").on('click', function() {
canvas.loadFromJSON(store, function() {
//console.log('restored:', canvas.getObjects());
//canvas.renderAll();
});
});
#paper {
border: solid 1px red;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>
<button id="save">Save to JSON</button>
<button id="restore">Restore form JSON</button>
fabric.ContainerRect.fromObject = function(object, callback) {
return fabric.Object._fromObject('ContainerRect', object, callback);
};
You need to use callback in fromObject.
If you remove type then it takes rect type for its object and while loadingfrom json it takes fabric.Rect.fromObject method not your fabric.ContainerRect.fromObject, thats why working.
DEMO
fabric.ContainerRect = fabric.util.createClass(fabric.Rect, {
type: 'container-rect',
initialize: function(options) {
this.callSuper('initialize', options);
this.on('moving', function() {
// console.log('Red is moving...');
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
}
});
fabric.ContainerRect.fromObject = function(object, callback) {
return fabric.Object._fromObject('ContainerRect', object, callback);
};
//==========================================================================================
let store;
const canvas = new fabric.Canvas('paper');
const canvasOriginalSize = {
width: 600,
height: 600
};
const redBox = new fabric.ContainerRect({
left: 0,
top: 0,
width: 50,
height: 50,
fill: 'red'
});
canvas.add(redBox);
canvas.renderAll();
$("#save").on('click', function() {
store = canvas.toJSON();
console.log(store);
});
$("#restore").on('click', function() {
canvas.loadFromJSON(store, function() {
console.log('restored:');
});
});
#paper {
border: solid 1px red;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/2.2.3/fabric.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>
<button id="save">Save to JSON</button>
<button id="restore">Restore form JSON</button>

Exporting custom properties from fabricjs objects to SVG

I have an image object where I have added a custom property.
When stringify the object, I can see that the property is there, but when I export the SVG by using toSVG, the custom property is not in the SVG source.
I would like the name property in my example to be exported as a custom property in the SVG image xml data (with the possibility to add multiple properties)
Is this possible?
Please se my fiddle: https://jsfiddle.net/Jonah/ujexg46s/
var svgImage = fabric.util.createClass(fabric.Image, {
initialize: function(element, options) {
this.callSuper("initialize", element, options);
options && this.set("name", options.name);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
name: this.name
});
}
});
var canvas = new fabric.Canvas('container');
function loadImage() {
var img = new Image();
img.src = imageSrc;
img.onload = function() {
var image = new svgImage(img, {
name: "test",
left: 0,
top: 0
});
canvas.add(image);
}
}
document.getElementById('load-btn').onclick = function() {
loadImage();
};
document.getElementById('export-btn').onclick = function() {
canvas.deactivateAll().renderAll();
console.log(JSON.stringify(canvas));
window.open('data:image/svg+xml;utf8,' + encodeURIComponent(canvas.toSVG()));
};
You can find answer here.
fabric.util.object.extend(fabric.Image.prototype, {
toObject: function(propertiesToInclude) {
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
src: this._originalElement.src || this._originalElement._src,
filters: this.filters.map(function(filterObj) {
return filterObj && filterObj.toObject();
}),
crossOrigin: this.crossOrigin,
alignX: this.alignX,
alignY: this.alignY,
meetOrSlice: this.meetOrSlice,
name: this.name
});
},
});
https://jsfiddle.net/mullainathan/ujexg46s/1/
You can override the existing toSVG function like this.
var circle = new fabric.Circle ({
radius: 40,
left: 50,
top: 50,
fill: 'rgb(0,255,0)',
opacity: 0.5,
id: 'hello'
});
circle.toSVG = (function(toSVG) {
return function(){
var svgString = toSVG.call(this);
var domParser = new DOMParser();
var doc = domParser.parseFromString(svgString, 'image/svg+xml');
var parentG = doc.querySelector('path')
parentG.setAttribute('id', this.id);
return doc.documentElement.outerHTML;
}
})(circle.toSVG)

webshot/phantom pdf, printing only second half of the page

I'm working with webshot npm module to create pdf file of my page
This is my page
and
I'm getting this as output in pdf
my settings are
var options = {
renderDelay:10000,
"paperSize": {
"format": "Letter",
"orientation": "portrait",
"border": "1cm"
},
shotSize: {
width: 'all',
height: 'all'
},
shotOffset: {
left: 0
, right: 0
, top: 0
, bottom: 0
}
};
webshot(url, fileName, options, function(err) {
fs.readFile(fileName, function (err,data) {
if (err) {
return console.log(err);
}
fs.unlinkSync(fileName);
fut.return(data);
});
});
this.response.writeHead(200, {'Content-Type': 'application/pdf',"Content-Disposition": "attachment; filename=generated.pdf"});
this.response.end(fut.wait());
For meteor guys this is my server side root
this.route('generatePDF', {
path: '/api/generatePDF',
where: 'server',
action: function() {
var webshot = Meteor.npmRequire('webshot');
var fs = Npm.require('fs');
Future = Npm.require('fibers/future');
var fut = new Future();
var fileName = "generated_"+Random.id()+".pdf";
var userid = (Meteor.isClient) ? Meteor.userId() : this.userId;
console.log(userid);
// var username = Meteor.users.findOne({_id: userid}).username;
var url = "url";
var options = {
renderDelay:10000,
"paperSize": {
"format": "Letter",
"orientation": "portrait",
"border": "1cm"
},
shotSize: {
width: 'all',
height: 'all'
},
shotOffset: {
left: 0
, right: 0
, top: 0
, bottom: 0
}
};
webshot(url, fileName, options, function(err) {
fs.readFile(fileName, function (err,data) {
if (err) {
return console.log(err);
}
fs.unlinkSync(fileName);
fut.return(data);
});
});
this.response.writeHead(200, {'Content-Type': 'application/pdf',"Content-Disposition": "attachment; filename=generated.pdf"});
this.response.end(fut.wait());
}
});
Am I missing anything here? Any help appreciated
You can try to change paperSize to:
"paperSize": {
width: '612px',
height: '792px',
margin: 'XXpx'
},
If anyone else will have trouble with that - I had the same issue. I am not able to tell you exactly why, but the problem was that I was using bootstrap and the wrapper of my page had the "container" class. After removing this class the whole page was rendered - without removing it it just rendered around the half of the page.

Resources