简体   繁体   中英

Fabric JS: Text object's selection area is larger than the actual size of text

We are working with Fabric JS to add text and image objects to the canvas.

But when we add any text object and select that, it shows the selection area larger than the actual size of the text.

Please refer this link to see the screen-shot.

We are using following code to add text object.

var text_object = new fabric.Text('00', {
                    fontSize: 192,
                    fontFamily: 'Times New Roman',
                    padding: 0
                  });
text_object.top = parseInt((canvas.height - text_object.height) / 2);
text_object.left = parseInt((canvas.width - text_object.width) / 2);
text_object.lockUniScaling = true;
canvas.add(text_object);
canvas.renderAll();
canvas.setActiveObject(text_object);

The main problem is when we try to get the width and height of the added text object; it gives us the width and height of selection area of the text object and not the actual size of the text.

How can we make the text object's selection area same as the actual size of the text?

Or how can we get the actual size of text, without selection area?

There is no direct way achieve what you want, that would be the bounding box of the vector paths of the text.

If you are ok with an approximate solution, render the text to an empty canvas, use getImageData to check the transparency of all pixels, and figure out the max and min x and y where you find filled points. It is far from perfect, but you won't get a better solution easily.

I guess that there might be solutions server side, but I don't know of any.

And in theory it could be possible for you to create your own font rendering engine in JS, and so you would have perfect bounding boxes, but, except for simple/toy cases, a serious text layout engine takes years and years to implement, so I guess it is totally out of the scope.

If you need help with the approximation method, tell me and I provide an example.

Edited to provide an example:

 function getVisiblePixelsBoundingBox(canvas) { const context = canvas.getContext("2d"); const imageData = context.getImageData(0, 0, canvas.width, canvas.height).data; let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (let y = 0; y < canvas.height; y++) { for (let x = 0; x < canvas.width; x++) { const alpha = imageData[(y * canvas.width + x) * 4 + 3]; if (alpha > 0) { minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); } } } return { left: minX, top: minY, width: maxX - minX, height: maxY - minY }; } // you will need to create a new canvas var canvas = document.createElement("canvas"), context = canvas.getContext("2d"); // and set the width and height the same as the canvas you want to draw the text // here I'm just adding some values for the example: canvas.width = 600; canvas.height = 400; // now I'm going to draw some text on it context.font = "120px serif"; context.fillText("Lipsum", 100, 200); let boundingBox = getVisiblePixelsBoundingBox(canvas); // now this is not needed in your case, but want to see the canvas and // I will draw a rectangle around the bounding box to see how it works: document.body.appendChild(canvas); context.strokeStyle = "red"; context.strokeRect(boundingBox.left, boundingBox.top, boundingBox.width, boundingBox.height); 

Ok, so this seems good, but beware of the issues:

  • The precision is limited to pixels, so while it is totally ok for drawing the bounding box, it is not good for operations that would require a much higher precision, like perfectly aligning shapes. Also, because of the measure method, if a part of a shape is so thin that is not visible at all, then it won't be measured (probably not a big issue for you, because so extremely thin shapes doesn't happen in fonts).

  • Executing it might be slow as we are iterating all pixels to get the result (and JS is not specially fast for that). Knowing this, you might be tempted to try to reduce the canvas size and not use the same canvas of your target canvas, but I advise you not to, as it would introduce new problems related to the next point.

  • If you measure with your text getting outside of the canvas, then the bounding box will be limited to the visible area (it will stick to the borders). You might think in increasing the canvas size if this happens to get the real width and height, but if you don't limit the font size and the amount of text, you might end with a canvas so big that slows down the browser or maybe even crashes it... So I would just accept that the bounding box sticks to the borders. You might think in using the width provided by measureText might help, but unfortunately, to get the top and height you need to scan the full image.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM