Fabric.js: How to keep text from blurring as group grows larger - javascript

In fabricjs, version 1.7.20, I noticed that whenever a group grows beyond 1152 pixels (12in) then the text objects in the group begin to blur. I'm programmatically setting the group size using the set() method when I need to grow the group.
Is there a way to keep them crisp/normal? Should I be calling some other method on the text object or group so that the text remains readable? I realize that the fonts are vectors but I'm not sure how to keep the font size constant regardless of the group size.
In this particular case I can't scale the group as I need the text object to stay the same size regardless of the group size.
Here is an example of the blurring effect:
https://jsfiddle.net/c7fn1e08/
Here is some example code as well:
var c = new fabric.Canvas('c');
var lbl = new fabric.Text('RESZING GROUP', { fontSize: 12 });
var g = new fabric.Group([lbl],{ top: 0, left: 0});
c.add(g);
c.renderAll();
var resizeGroup = function(g, sz) {
g.set({ height: fabric.util.parseUnit(sz), width: fabric.util.parseUnit(sz)});
var l = g._objects[0];
l.set({ top: -(g.height/2), left: -(g.width/2), text: 'GROUP SIZE '+sz}).setCoords();
g.set({ dirty: true });
g.canvas.renderAll();
}
//resizeGroup(g, '12in'); // looks good
//resizeGroup(g, '24in'); // looks bad
resizeGroup(g, '36in'); // looks really bad

Related

How can I prevent both vertical and horizontal stretching of contained text while resizing text-based objects in Fabric.js?

I want to be able to scale text-based objects (for example with classes and subclasses of IText, Textbox) without stretching the text inside of the object. The interaction of scaling and moving is on user-side (UI), not programmatically (I am working on a drawing editor).
The behaviour I am looking for is similar to the one in the sticky notes apps: You can scale the paper but this action does not scale your text too.
I have already checked this, but this is only for horizontal prevention: Fabric.js How to resize IText horizontally without stretching the text
This is neither what I want/mean: Fabric.js : How to set a custom size to Text or IText?
I know that a scaling factor different than 1 implies the stretching of the inner text, and that by changing the width and height I can resize the object while keeping the text unscaled, so I tried updating these during scaling using events listeners.
I tried many combinations of the IText,Textbox with some scaling events, like this one:
fbtext.on('scaling', function() {
this.set({
width : this.width * this.scaleX,
height: this.height * this.scaleY,
scaleX: 1,
scaleY: 1,
});
});
I also tried this with Textbox:
fbtext.on('scaling', function() {
this.set({
height: this.height * this.scaleY,
});
});
But nothing worked so far. I am out of ideas.
var canvas = new fabric.Canvas('mycanvas');
let textbox = new fabric.Textbox("Sample text", {
left: 100,
top: 100
});
let lastHeight;
const updateTextSize = () => {
const controlPoint = textbox.__corner;
//mr and ml are the only controlPoints that dont modify textbox height
if( controlPoint && controlPoint != "mr" && controlPoint != "ml"){
lastHeight = textbox.height * textbox.scaleY;
}
textbox.set({
height: lastHeight || textbox.height,
scaleY:1
});
canvas.renderAll();
};
textbox.on('scaling', updateTextSize );
textbox.on('editing:entered', updateTextSize);
canvas.on('text:changed', updateTextSize);
canvas.add(textbox);
canvas {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.3.2/fabric.min.js"></script>
<canvas id="mycanvas" width="400" height="400"></canvas>
Here is a code sample that will let you modify the "sticky note size" without streching out the text
Thanks #Ivan, your solution is working for me, with a small glitch.
When increasing the size of the text box using corners the text is getting stretched and compressed. I haven't found the solution of this yet and have currently implemented a workaround to hide all the corner scaling points.
box.setControlsVisibility({
bl: false,
br: false,
tl: false,
tr: false
});
http://jsfiddle.net/bkgvewh6/
Just to add I am posting this as an answer and not as a comment to your answer as I don't have stack overflow reputation to do so.

FabricJS adding Image to Group causes odd control behavior

I'm attempting to add a loaded image into a fabric Group object. Everything looks ok, but the selection controls aren't selectable and I can't drag the object around. The top left control works though and after clicking it everything is fine.
Here is a jsfiddle that demonstrates the behavior.
var canvas = new fabric.Canvas('canvas', {
width: 200,
height: 200
});
var group = new fabric.Group();
canvas.add(group);
fabric.Image.fromURL('https://placehold.it/100x100', function(img) {
group.addWithUpdate(img);
canvas.setActiveObject(group);
canvas.renderAll();
});
Is this a bug or am I doing something wrong?
For some performance reason, fabricjs does not call setCoords automatically after adding objects to a group ( in case of many object added you can call setCoords just once ).
So after doing addWithUpdate, just call group.setCoords();
var canvas = new fabric.Canvas('canvas', {
width: 200,
height: 200
});
var group = new fabric.Group();
canvas.add(group);
fabric.Image.fromURL('https://placehold.it/100x100', function(img) {
group.addWithUpdate(img);
group.setCoords();
canvas.setActiveObject(group);
canvas.renderAll();
});
I came across this post because I was having trouble getting the positioning of images to work properly. I ended up finding that it's easier to just create create an image element using document.createElement and set the src, then feed that into fabric.Image with all the options you need (instead of using fabric.Image.fromURL which was too much of a headache to use), before adding it to the group.
var oImg = document.createElement("img");
oImg.setAttribute('src', 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Cog_font_awesome.svg/512px-Cog_font_awesome.svg.png');
var i = new fabric.Image(oImg, {
originX: 'center',
originY: 'center',
left: left+35,
top: top-30,
scaleX:.05,
scaleY:.05,
});
g = new fabric.Group([r, t, i]); // where r and t are other fabric objects

Paper.js Subraster Selecting Wrong Area

I'm working in a Paper.js project where we're essentially doing image editing. There is one large Raster. I'm attempting to use the getSubRaster method to copy a section of the image (raster) that the user can then move around.
After the raster to edit is loaded, selectArea is called to register these listeners:
var selectArea = function() {
if(paper.project != null) {
var startDragPoint;
paper.project.layers[0].on('mousedown', function(event) { // TODO should be layer 0 in long run? // Capture start of drag selection
if(event.event.ctrlKey && event.event.altKey) {
startDragPoint = new paper.Point(event.point.x + imageWidth/2, (event.point.y + imageHeight/2));
//topLeftPointOfSelectionRectangleCanvasCoordinates = new paper.Point(event.point.x, event.point.y);
}
});
paper.project.layers[0].on('mouseup', function(event) { // TODO should be layer 0 in long run? // Capture end of drag selection
if(event.event.ctrlKey && event.event.altKey) {
var endDragPoint = new paper.Point(event.point.x + imageWidth/2, event.point.y + imageHeight/2);
// Don't know which corner user started dragging from, aggregate the data we have into the leftmost and topmost points for constructing a rectangle
var leftmostX;
if(startDragPoint.x < endDragPoint.x) {
leftmostX = startDragPoint.x;
} else {
leftmostX = endDragPoint.x;
}
var width = Math.abs(startDragPoint.x - endDragPoint.x);
var topmostY;
if(startDragPoint.y < endDragPoint.y) {
topmostY = startDragPoint.y;
} else {
topmostY = endDragPoint.y;
}
var height = Math.abs(startDragPoint.y - endDragPoint.y);
var boundingRectangle = new paper.Rectangle(leftmostX, topmostY, width, height);
console.log(boundingRectangle);
console.log(paper.view.center);
var selectedArea = raster.getSubRaster(boundingRectangle);
var selectedAreaAsDataUrl = selectedArea.toDataURL();
var subImage = new Image(width, height);
subImage.src = selectedAreaAsDataUrl;
subImage.onload = function(event) {
var subRaster = new paper.Raster(subImage);
// Make movable
subRaster.onMouseEnter = movableEvents.showSelected;
subRaster.onMouseDrag = movableEvents.dragItem;
subRaster.onMouseLeave = movableEvents.hideSelected;
};
}
});
}
};
The methods are triggered at the right time and the selection box seems to be the right size. It does indeed render a new raster for me that I can move around, but the contents of the raster are not what I selected. They are close to what I selected but not what I selected. Selecting different areas does not seem to yield different results. The content of the generated subraster always seems to be down and to the right of the actual selection.
Note that as I build the points for the bounding selection rectangle I do some translations. This is because of differences in coordinate systems. The coordinate system where I've drawn the rectangle selection has (0,0) in the center of the image and x increases rightward and y increases downward. But for getSubRaster, we are required to provide the pixel coordinates, per the documentation, which start at (0,0) at the top left of the image and increase going rightward and downward. Consequently, as I build the points, I translate the points to the raster/pixel coordinates by adding imageWidth/2 and imageHeight/2`.
So why does this code select the wrong area? Thanks in advance.
EDIT:
Unfortunately I can't share the image I'm working with because it is sensitive company data. But here is some metadata:
Image Width: 4250 pixels
Image Height: 5500 pixels
Canvas Width: 591 pixels
Canvas Height: 766 pixels
My canvas size varies by the size of the browser window, but those are the parameters I've been testing in. I don't think the canvas dimensions are particularly relevant because I'm doing everything in terms of image pixels. When I capture the event.point.x and event.point.y to the best of my knowledge these are image scaled coordinates, but from a different origin - the center rather than the top left. Unfortunately I can't find any documentation on this. Observe how the coordinates work in this sketch.
I've also been working on a sketch to illustrate the problem of this question. To use it, hold Ctrl + Alt and drag a box on the image. This should trigger some logging data and attempt to get a subraster, but I get an operation insecure error, which I think is because of security settings in the image request header. Using the base 64 string instead of the URL doesn't give the security error, but doesn't do anything. Using that string in the sketch produces a super long URL I can't paste here. But to get that you can download the image (or any image) and convert it here, and put that as the img.src.
The problem is that the mouse events all return points relative to 0, 0 of the canvas. And getSubRaster expects the coordinates to be relative to the 0, 0 of the raster item it is extracting from.
The adjustment needs to be eventpoint - raster.bounds.topLeft. It doesn't really have anything to do with the image width or height. You want to adjust the event points so they are relative to 0, 0 of the raster, and 0, 0 is raster.bounds.topLeft.
When you adjust the event points by 1/2 the image size that causes event points to be offset incorrectly. For the Mona Lisa example, the raster size (image size) is w: 320, h: 491; divided by two they are w: 160, h: 245.5. But bounds.topLeft of the image (when I ran my sketch) was x: 252.5, y: 155.5.
Here's a sketch that shows it working. I've added a little red square highlighting the selected area just to make it easier to compare when it's done. I also didn't include the toDataURL logic as that creates the security issues you mentioned.
Here you go: Sketch
Here's code I put into an HTML file; I noticed that the sketch I put together links to a previous version of the code that doesn't completely work.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rasters</title>
<script src="./vendor/jquery-2.1.3.js"></script>
<script src="./vendor/paper-0.9.25.js"></script>
</head>
<body>
<main>
<h3>Raster Bug</h3>
<div>
<canvas id="canvas"></canvas>
</div>
<div id="position">
</div>
</main>
<script>
// initialization code
$(function() {
// setup paper
$("#canvas").attr({width: 600, height: 600});
var canvas = document.getElementById("canvas");
paper.setup(canvas);
// show a border to make range of canvas clear
var border = new paper.Path.Rectangle({
rectangle: {point: [0, 0], size: paper.view.size},
strokeColor: 'black',
strokeWidth: 2
});
var tool = new paper.Tool();
// setup mouse position tracking
tool.on('mousemove', function(e) {
$("#position").text("mouse: " + e.point);
});
// load the image from a dataURL to avoid CORS issues
var raster = new paper.Raster(dataURL);
raster.position = paper.view.center;
var lt = raster.bounds.topLeft;
var startDrag, endDrag;
console.log('rb', raster.bounds);
console.log('lt', lt);
// setup mouse handling
tool.on('mousedown', function(e) {
startDrag = new paper.Point(e.point);
console.log('sd', startDrag);
});
tool.on('mousedrag', function(e) {
var show = new paper.Path.Rectangle({
from: startDrag,
to: e.point,
strokeColor: 'red',
strokeWidth: 1
});
show.removeOn({
drag: true,
up: true
});
});
tool.on('mouseup', function(e) {
endDrag = new paper.Point(e.point);
console.log('ed', endDrag);
var bounds = new paper.Rectangle({
from: startDrag.subtract(lt),
to: endDrag.subtract(lt)
});
console.log('bounds', bounds);
var sub = raster.getSubRaster(bounds);
sub.bringToFront();
var subData = sub.toDataURL();
sub.remove();
var subRaster = new paper.Raster(subData);
subRaster.position = paper.view.center;
});
});
var dataURL = ; // insert data or real URL here
</script>
</body>
</html>

fabric js - Resize complete canvas according to a rectangle added in a canvas in order to achieve cropping

I am using fabric js for generating templates, I want user to set height and width of a canvas for which I am using following code
canvas.setHeight(height);
canvas.setWidth(width);
However on doing this, the full width canvas stretches itself and the object disappears from the resized canvas as it was lets say 500 px wide before and user resized it to 100px then the items disappear.
I want to achieve a functionality so that if a user wants to change the size of canvas then instead of resizing the canvas I will show a rectangle and user can move rectangle accordingly to get the visible area and once user clicks on save then I will resize the canvas according to that rectangle, so that I can process the SVG later for further conversions but by doing so I am not sure how to get the visible area in that rectangle to the resized canvas, in short I want functionality like Darkroom js but for fabric js canvas object.
The current functionality is added to this JS fiddle http://jsfiddle.net/tbqrn/102/
Finally after doing some research I am able to do that, instead of resizing the canvas I am adding a rectangle object with "cropper" type as mentioned below
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'transparent',
width: 400,
height: 400,
strokeDashArray: [5, 5],
stroke: 'black',
type: 'cropper',
lockScalingX: true,
lockScalingY: true,
lockRotation: true
});
canvas.add(rect);
This rectangle will serve as the cropper, when a user submits the form then I am getting that cropper's dimensions as follow along with changing canvas size
var i;
var croppedLeft = 0;
var croppedTop = 0;
var canvasJson = canvas.getObjects();
// Cropping canvas according to cropper rectangle
if (canvas.getObjects().length > 0) {
var i;
for (i = 0; i < canvas.getObjects().length; i++) {
if (canvas.getObjects()[i].type == 'cropper') {
croppedLeft = canvas.getObjects()[i].left;
croppedTop = canvas.getObjects()[i].top;
canvas.setHeight(canvas.getObjects()[i].height);
canvas.setWidth(canvas.getObjects()[i].width);
canvas.getObjects()[i].remove();
}
}
}
And after that I am shifting all the other canvas objects accordingly as follow.
for (i = 0; i < canvasJson.length; i++) {
canvas.getObjects()[i].left = canvas.getObjects()[i].left - croppedLeft
canvas.getObjects()[i].top = canvas.getObjects()[i].top - croppedTop
canvas.renderAll();
}
And after doing that I am able to achieve the desired functionality, hope this helps someone.
Here is my fiddle http://jsfiddle.net/tbqrn/115/

Word wrapping in JointJS

I am working on JointJS. I have various elements with text in it. However the element's width increases with increase in text. I want to dynamically set the size of element such that there is a maximum height and width that the box can attain and expands accordingly by text wrapping. If the text os unable to fit in the maximum height and width element, then the fontsize may be reduced dynamically.
I hav tried using style="word-wrap: break-word;" in my div id. However there is no effect.
<div id="myholder" style="word-wrap: break-word;"> </div>
My holder is defined in the JS file as follows:
var paper = new joint.dia.Paper({
el: $('#myholder'),
width: 1200,
height: 700,
model: graph
});
What strategy may I follow?
It is also possible (if you don't want to bother with extra shapes) to use the
joint.util.breakText()
utility. It works like this:
var wraptext = joint.util.breakText('My text here', {
width: holderElement.size.width,
height: optionalHeight
});
After that you can add wraptext to your holderElement as into attrs when creating it. Like this:
var holder = joint.shapes.basic.Rect({
//position, size blablabla
attrs: {
text: {
text: wraptext,
//text styling
}
}
});
I have to say it's a bit strange that your holder is an entire paper, but you can probably use it the same way, just put the attrs when you define it.
To get word wrap working you can use joint.shapes.basic.TextBlock.
Now, to work with TextBlock you are going to set a top level map entry for "content" (instead of including "text" inside of "attrs" => "text" map entry)
graph.addCell (
new joint.shapes.basic.TextBlock({
position: { x:100, y:100 },
size: { width: 100, height: 100 },
attrs: { rect: { fill: 'green' }},
content: "<p style='color:white;'>asdf asdf asdf asdf this needs to word wrap</p>"
})
);
As you can see, the "content" entry can be raw html and will be rendered as such.
For this to work your browser needs to have SVG ForeignObject support, which most browsers these days have. To first check that this is the case you can run this in your console:
document.implementation.hasFeature("w3.org/TR/SVG11/feature#Extensibility","1.1")
I made a javascript function to wrap words based on the Line size and Max size of the shape you want the sentence wrap in.
If the sentence is very long then the function trim it and put 3 duts instead of the rest of the sentence.
Every Line size of the sentence the function put a '\n' (newline ASCII).
var sentenceWrapped = function (sentence, lineSize, maxSize) {
var descriptionTrim = "";
if (sentence.length + 3 > maxSize) {
descriptionTrim = sentence.substring(0, maxSize - 3);
descriptionTrim = descriptionTrim + '...';
}
else {
descriptionTrim = sentence
}
var splitSentence = descriptionTrim.match(new RegExp('.{1,' + lineSize + '}', 'g'));
var sentenceWrapped = "";
for (i = 0; i < splitSentence.length; i++)
{
sentenceWrapped = sentenceWrapped + splitSentence[i] + '\n';
}
return sentenceWrapped;
}
LineSize = the max size of characters for every line you want inside
your shape
MaxSize = the max size of characters you want inside your
shape
sentence = description you want to put in your shape
If you are interested in creating custom element you can create like this
joint.shapes.devs.Model = joint.shapes.basic.TextBlock.extend( {
markup: ['>',
joint.env.test('svgforeignobject') ? '' : '',
''].join(''),
defaults: joint.util.deepSupplement({
content: 'A content of the Activity'
}});

Categories

Resources