Recreating Canvas Cuts Off Bottom Of Text? - javascript

I am attempting to make a dynamic canvas where when the user types text, the canvas rerenders for the text so the canvas width (and height) is the same as the text. I followed some guide that essentially said to create a script like this
Codesandbox.com Live Link: https://codesandbox.io/s/tender-sound-zk5x6h?file=/src/App.js
function ResizeCanvas(textInput) {
const canvas3 = canvasRef.current;
const canvasContext = canvas3.getContext("2d");
canvasContext.font = "40 40px arial";
canvasContext.fillStyle = "red";
canvasContext.textAlign = "left";
canvasContext.fillText(textInput, 0, canvas3.height - 1);
canvas3.width = canvasContext.measureText(textInput).width;
canvas3.style.width = canvas3.width + "px";
canvasContext.font = "40 40px arial";
canvasContext.fillStyle = "red";
canvasContext.textAlign = "left";
canvasContext.fillText(textInput, 0, canvas3.height - 1);
}
However, the bottom text is cut off. I noticed when I add a paddingBottom: "20px" to the canvas element, it doesnt show the rest of the text and increasing the height does not change anything.
How come I cannot see the full p, q letters?

Related

How can I use a CSS script as a background for a Wordpress website?

I found some code in CodePen.io that produces a "matrix" effect and I want to try to integrate it on my website. This is the code: https://codepen.io/gnsp/pen/vYBQZJm .
What I am trying to do is to use this script as the background for my one-page website: I want this to be shown behind text and image blocks instead of a picture or a color background.
The page builder I am using is GoodLayers and it allows a "code" block which places a block that you can write javascript in which then gets interpreted.
This is the code:
<style> body {
margin: 0;
height: 100vh;
width: 100vw;
} </style>
<div>
<canvas id= "canv" height:window.innerHeight width:window.innerWidth style="z-index=0"> </canvas>
</div>
<script>
const canvas = document.getElementById('canv');
const ctx = canvas.getContext('2d');
const w = canvas.width = document.body.offsetWidth;
const h = canvas.height = document.body.offsetHeight;
const cols = Math.floor(w / 20) + 1;
const ypos = Array(cols).fill(0);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, w, h);
function matrix () {
ctx.fillStyle = '#0001';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#0f0';
ctx.font = '15pt monospace';
ypos.forEach((y, ind) => {
const text = String.fromCharCode(70,105,68,105) ;
const x = ind * 20;
ctx.fillText(text, x, y);
if (y > 100 + Math.random() * 10000) ypos[ind] = 0;
else ypos[ind] = y + 20;
});
}
setInterval(matrix, 50);
</script>
I have placed it inside a section wrapper.
This is how it looks in the backend.
And this is how it looks in the frontend: not centered and appears as a block that does not overlap with anything.
That's how it looks when I scroll down.
I want for it to either move with the viewport or better- to be fixed and just cover the whole page from top to bottom.
I am extremely new to this and hardly ever hardcoded anything in CSS or JS without the use of UI of various page builders so please understand that.

How can I fix the canvas text overflowing the image?

<script>
// (B) IMAGES + CANVAS
var iBack = new Image(),
iMark = "long text",
canvas = document.getElementById("demo"),
ctx = canvas.getContext("2d");
// (C) WATERMARK
function cmark () {
// (C1) ADD BACKGROUND IMAGE
canvas.width = iBack.naturalWidth;
canvas.height = iBack.naturalHeight;
ctx.drawImage(iBack, 100, 100, iBack.naturalWidth, iBack.naturalHeight);
// (C2) ADD WATERMARK
ctx.font = "bold 24px Arial";
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fillText(iMark, 10, 100);
ctx.fillText("width:" + ctx.measureText(iMark).width, 100, 50);
/* (C3) DOWNLOAD (IF YOU WANT)
let anchor = document.createElement("a");
anchor.href = canvas.toDataURL("image/png");
anchor.download = "marked.png";
anchor.click();
anchor.remove();*/
}
// (D) GO - PROCEED ONLY WHEN IMAGES ARE LOADED
iBack.onload = cmark;
iBack.src = "image.jpg";
</script>
When I write long text, it takes it out of the picture. I don't want it to spill out of the picture. How can I fix the canvas text overflowing the image? Can you help me?
You can draw only one line of text at a time with filltext(), so you need to split you text into several parts when it's too long. You can use measureText(iMark)to measure the length of you text, :
if (measureText(iMark)>100) {
let temp1 = iMark.slice(0,iMark.length/2);
let temp2 = iMark.slice(iMark.length/2,iMark.length);
ctx.fillText(temp1, 10, 100);
ctx.fillText(temp1, 10, 200);
}

html5 canvas - Image data empty after drawing an emoji with large font size on Chrome

I am drawing an emoji on a <canvas> element using the fillText method of the 2D context, and right after I am using getImageData to get the image as an array, like so :
ctx.fillText('🤖', 500, 500)
const imageData = ctx.getImageData(0, 0, 1000, 1000)
This works without any issue on firefox and iOS, but for some reason, imageData comes out empty on Chrome (Chromium 75.0.3770.90) when the font size is too big. See the following snippet :
https://codepen.io/anon/pen/OKWMBb?editors=1111
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<canvas id="c1" width="1000px" height="1000px"></canvas>
<canvas id="c2" width="1000px" height="1000px"></canvas>
<canvas id="c3" width="1000px" height="1000px"></canvas>
<script>
var c1 = document.querySelector('#c1')
var c2 = document.querySelector('#c2')
var c3 = document.querySelector('#c3')
var ctx1 = c1.getContext('2d')
var ctx2 = c2.getContext('2d')
var ctx3 = c3.getContext('2d')
ctx1.font = '500px monospace'
ctx2.font = '500px monospace'
ctx3.font = '200px monospace'
ctx1.fillText('🤖', 500, 500)
ctx2.fillText('🤖', 500, 500)
ctx3.fillText('🤖', 500, 500)
function printImageData(ctx, canvasId) {
const imageData1 = ctx.getImageData(0, 0, 1000, 1000)
console.log(`${canvasId} has data : `, !imageData1.data.every((v) => v === 0))
}
setTimeout(() => printImageData(ctx1, '#c1'), 100)
printImageData(ctx2, '#c2')
printImageData(ctx3, '#c3')
// Chrome prints :
// #c2 has data : false
// #c3 has data : true
// #c1 has data : true
</script>
</body>
</html>
I suspect this has to do with rendering time for the big emoji, but I can't find any reference of this anywhere, nor any workaround (besides the not-very robust setTimeout hack).
That's indeed a weird bug, very probably in getImageData, drawImage is not affected.
So one trick to workaround that issue is to call ctx.drawImage(ctx.canvas, 0,0); before getting the image data:
var c1 = document.querySelector('#c1');
var c2 = document.querySelector('#c2');
var ctx1 = c1.getContext('2d');
var ctx2 = c2.getContext('2d');
ctx1.font = '500px monospace';
ctx2.font = '500px monospace';
ctx1.fillText('🤖', 500, 500);
ctx2.fillText('🤖', 500, 500);
function printImageData(ctx, canvasId) {
const imageData1 = ctx.getImageData(0, 0, 1000, 1000);
console.log(`${canvasId} has data : `, !imageData1.data.every((v) => v === 0));
}
// #c1 has no workaround applied
printImageData(ctx1, '#c1');
// #c2 has the workaround applied
ctx2.globalCompositeOperation = "copy";
ctx2.drawImage(ctx2.canvas, 0, 0);
ctx2.globalCompositeOperation = "source-over";
printImageData(ctx2, '#c2');
<canvas id="c1" width="1000px" height="1000px"></canvas>
<canvas id="c2" width="1000px" height="1000px"></canvas>
After further tests, it seems the problem is that these emojis can't be drawn by software only when the font-size is bigger than 256px (at least when I disable Hardware acceleration, they're just not rendered at all). Thus I guess *getImageData* is somehow forcing software rendering, and making it fail even when HW acceleration is turned on.
I opened this issue on chromium's bug-tracker, but note that your particular case with HWA on is actually already fixed in canary version 78.
UPDATE
After some more test it seams there is a problem
This is not expected behavior and is a BUG with Chromes rendering.
The rest is the original answer before I found that bug with updates marked.
Alignment?
I dont see any problem Chrome 75.0.3770.142
However it could be that the font is just offset and thus missing the canvas.
Make sure you have set the text alignments as your example is just on the canvas on the right side.
ctx.textAlign = "center";
ctx.textBaseline = "middle";
Scale via transform
If this still does not work you can scale the font using the 2D transform
Example
// set constants
const fontSize = 500; // Size you want
const usingFontSize = 100; // size of font you are using
const scaleFontBy = fontSize / usingFontSize; // calculates scale
const [x, y] = [500, 500]; // where to draw text
// set 2D state
ctx.font = usingFontSize + "px monospace"
ctx.textAlign = "center"; // ensure rendering is centered
ctx.textBaseline = "middle";
ctx.setTransform(scaleFontBy, 0, 0, scaleFontBy, x, y);
// render content
ctx.fillText('🤖', 0, 0); // Draw at center of transformed space
// Restore transform state to default
ctx.setTransform(1,0,0,1,0,0);
Updated Demo
Update Will log error when can not get pixel of rendered font.
To test it out the following example draws font 50 to 2500pixels (or more if you want).
requestAnimationFrame(renderLoop);
const ctx = canvas.getContext("2d");
var w,h, x, y;
const usingFontSize = 64; // size of font you are using
const fontSizeMax = 2500; // Max Size you want
const fontSizeMin = 50; // Min Size you want
const text = "😀,😁,😂,😃,😄,😅,😆,😇,😉,😊,😋,😌,😍,😎,😏,😐,😑,😒,😓,😔,😕,😖,😗,😘,😙,😚,😛,😜,😝,😞,😟,😠,👹,👺,👻,👼,🚜,👾,👿,💀".split(",");
function draw(text,fontSize) {
if (innerHeight !== canvas.height) {
// resize clears state so must set font and alignment
h = canvas.height = innerHeight;
w = canvas.width = innerWidth;
ctx.font = usingFontSize + "px monospace"
ctx.textAlign = "center"; // ensure rendering is centered
ctx.textBaseline = "middle";
ctx.lineWidth = 5;
ctx.lineJoin = "round";
ctx.strokeStyle = "white";
x = w / 2;
y = h / 2;
}else{
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
}
const scaleFontBy = fontSize / usingFontSize; // calculates scale
ctx.setTransform(scaleFontBy, 0, 0, scaleFontBy, x, y);
// render content
ctx.fillText(text, 0, 0); // Draw at center of transformed space
const isRendered = ctx.getImageData(x | 0, y | 0, 1, 1).data[3];
if(!isRendered) {console.clear(); console.error("Bad font render at size " + (usingFontSize * scaleFontBy | 0) + "px") }
ctx.setTransform(1,0,0,1,x, 40);
ctx.strokeText("Font size " + (usingFontSize * scaleFontBy | 0) + "px", 0, 0);
ctx.fillText("Font size " + (usingFontSize * scaleFontBy | 0) + "px", 0, 0);
}
function renderLoop(time) {
draw(text[(time / 2000 | 0) % text.length], (Math.sin(time * Math.PI / 1000 - Math.PI / 2) * 0.5 + 0.5) ** 2 * (fontSizeMax - fontSizeMin) + fontSizeMin);
requestAnimationFrame(renderLoop);
}
body {
padding: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
Still not fixed
If this does not solve the problem then it is likely a Chrome bug related to your system. It works for me on Win 10 32 and 64 bit systems running Chrome 75.0.3770.142
BTW
You say
"I suspect this has to do with rendering time for the big emoji, ... besides the not-very robust setTimeout hack ..."
2D rendering calls are blocking. They will not execute the next line of code until they have completed rendering. You never need to use a timeout.
Hope this helps
😀
update
😕

How to measure text width with `italic` font style?

I am using convas context measureText to get the width of my text on the canvas. Below is the code:
ctx.fillStyle = color;
ctx.fontWeight = FONT_WEIGHT;
ctx.font = `bolder italic ${fontsize}px`;
const textWidth = ctx.measureText(text).width;
the problem is that if the font style is italic, the right of the text will be off boundary. That because measureText doesn't take italic into account. How can I calculate the text width for italic style?
Below are two screenshots for italic font on convas. The first one is the text without italic while the second one is with italic. You can see that the second one has a little off boundaries.
This is a problem inherent to italic rendering of fonts and their container box.
I am not a specialist and won't extend on the subject, which has already been treated in this Q/A about DOM + CSS. Simply note that 2DCanvas measureText also suffers from this same problem.
The only workaround I can think of is going through an svg element, which offers better graphical bounding box representation through its getBBox method.
// Calculate the BBox of a text through an svg Element
function getTextBox(txt, x, y) {
var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
var text = document.createElementNS("http://www.w3.org/2000/svg", 'text');
// so we don't see the svg element in page
svg.style = 'position: absolute; z-index:-1; width:0; height: 0';
text.setAttribute('x', x || 0);
text.setAttribute('y', y || 0);
text.setAttribute('text-anchor', getAlignment(this.textAlign));
text.setAttribute('alignment-baseline', this.textBaseline);
text.style.font = this.font;
text.textContent = txt;
svg.appendChild(text);
document.body.appendChild(svg);
var box = text.getBBox();
document.body.removeChild(svg);
return box;
// convert CSS text-align notation to correpsonding SVG text-anchor
function getAlignment(css) {
return {
'left': 'start',
'right': 'end',
'center': 'middle'
}[css] || '';
}
}
// attach it to the proto so it's easier to grab context's current status
Object.defineProperty(CanvasRenderingContext2D.prototype, 'getTextBox', {get: function() {return getTextBox;}});
var x = 20,
y = 100;
var ctx = canvas.getContext('2d');
ctx.font = 'bolder italic 100px serif';
var txt = 'Right';
ctx.fillText(txt, x, y);
// red => ctx.measureText
var m_width = ctx.measureText(txt).width;
ctx.strokeStyle = 'red';
ctx.strokeRect(x, 0, m_width, y);
ctx.strokeStyle = 'green';
// green => svg.getBBox
var box = ctx.getTextBox(txt, x, y);
ctx.strokeRect(box.x, box.y, box.width, box.height);
<canvas id="canvas"></canvas>
Set ctx.font first. The result of ctx.measureText is based on the context's current font, the same one you'd see if you were drawing. Do a ctx.fillText to check you have the font set correctly, I find the syntax is easy to get wrong.
You will want to load your text into a hidden HTML div and get the calculated width.

Determine width of string in HTML5 canvas

I'm drawing text in an HTML5 canvas on top of an image (a meme generator). I want to calculate a font size so that the string will span the entire width of the canvas.
So basically, is there a way in JavaScript to determine what font size I should use for some string of a certain font for a given width?
The fillText() method has an optional fourth argument, which is the max width to render the string.
MDN's documentation says...
maxWidth
Optional; the maximum width to draw. If specified, and the string is
computed to be wider than this width, the font is adjusted to use a
more horizontally condensed font (if one is available or if a
reasonably readable one can be synthesized by scaling the current font
horizontally) or a smaller font.
However, at the time of writing, this argument isn't supported well cross browser, which leads to the second solution using measureText() to determine the dimensions of a string without rendering it.
var width = ctx.measureText(text).width;
Here is how I may do it...
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
// Font sizes must be in descending order. You may need to use `sort()`
// if you can't guarantee this, e.g. user input.
var fontSizes = [72, 36, 28, 14, 12, 10, 5, 2],
text = 'Measure me!';
// Default styles.
ctx.textBaseline = 'top';
ctx.fillStyle = 'blue';
var textDimensions,
i = 0;
do {
ctx.font = fontSizes[i++] + 'px Arial';
textDimensions = ctx.measureText(text);
} while (textDimensions.width >= canvas.width);
ctx.fillText(text, (canvas.width - textDimensions.width) / 2, 10);​
jsFiddle.
I have a list of font sizes in descending order and I iterate through the list, determining if the rendered text will fit within the canvas dimensions.
If it will, I render the text center aligned. If you must have padding on the left and right of the text (which will look nicer), add the padding value to the textDimensions.width when calculating if the text will fit.
If you have a long list of font sizes to try, you'd be better off using a binary search algorithm. This will increase the complexity of your code, however.
For example, if you have 200 font sizes, the linear O(n) iteration through the array elements could be quite slow.
The binary chop should be O(log n).
Here is the guts of the function.
var textWidth = (function me(fontSizes, min, max) {
var index = Math.floor((min + max) / 2);
ctx.font = fontSizes[index] + 'px Arial';
var textWidth = ctx.measureText(text).width;
if (min > max) {
return textWidth;
}
if (textWidth > canvas.width) {
return me(fontSizes, min, index - 1);
} else {
return me(fontSizes, index + 1, max);
}
})(fontSizes, 0, fontSizes.length - 1);
jsFiddle.
Write All Things You Want For Text. ( Ex. ctx.font )
Before Writing Actual Text Use ctx.measureText("myText").width To get Width Of Text, Because We Have Called It After Making Changes On ctx, It Will Be Different Each Time When We Change ctx Properties (ex. font)
Now We Will Scale It From Middle.
ctx.translate(midPoint_X_Of_Text, midPoint_Y_Of_Text);
ctx.scale(desiredWidth/measuredWidth, desiredWidth/measuredWidth);
ctx.translate(-midPoint_X_Of_Text, -midPoint_Y_Of_Text);
Write Text ctx.fillText() or ctx.strokeText()
Reverse All Changes By ctx.save() and ctx.restore() Or Manually like setTransform()
var can = document.getElementById('myCan');
var ctx = can.getContext("2d");
var desiredWidth = can.width;
var myText = "Hello How Are You?"
function draw(){
ctx.font = can.height/2 + "px verdana";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
let measuredWidth = ctx.measureText(myText).width;
let ratio = desiredWidth/measuredWidth;
ctx.translate(can.width/2,can.height/2);
ctx.scale(ratio,ratio);
ctx.translate(-can.width/2,-can.height/2);
ctx.fillText(myText,can.width/2,can.height/2);
ctx.setTransform();
}
draw();
let myInput = document.getElementById('myInput');
myInput.addEventListener('input',(e)=>{
ctx.clearRect(0,0,can.width,can.height);
myText = myInput.value;
draw();
})
<html>
<body>
<center>
<input id="myInput" value="Hello How Are You?"/><br>
<canvas id="myCan" height="300" width=300 style="border:black solid 2px;">
</canvas>
</center>
</body>
</html>
I have the next code and it works perfectly for me. Maybe there is a little error but I don't need to have a list of font sizes. I just get the width with a font size and if it is too big I calculate a proportional size depending of the canvas width and padding (50)
ctx.font = FONT_SIZE+"pt Verdana";
var textWidth = ctx.measureText(markText).width;
if(textWidth > canvas.width - 50) {
ctx.font = parseInt(FONT_SIZE*(canvas.width-50)/textWidth)+"pt Verdana";
textWidth = ctx.measureText(markText).width;
}
ctx.textBaseline = "middle";
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.strokeStyle = "rgba(0,0,0,0.5)";
var xT = image.width / 2 - textWidth / 2;
var yT = image.height / 2;
ctx.fillText(markText, xT, yT);
ctx.strokeText(markText, xT, yT);
use second temporary canvas2D
function determineWidth(text, font) {
var textCtx = document.createElement("canvas").getContext("2d");
textCtx.font = font
let measure = textCtx.measureText(text);
let fontHei = parseInt(font.slice(0, font.indexOf("px")))
return [measure.width,fontHei]
}
use it before create real canvas
let font = `40px monospace`;
let [wid,hei]=determineWidth(text,font)
let textAlign = "center";
let textBaseline = "middle";
let fillStyle = "black";
var textCtx = document.createElement("canvas").getContext("2d");
textCtx.canvas.width = wid;
textCtx.canvas.height = hei;
textCtx.font = font
textCtx.textAlign = textAlign
textCtx.textBaseline = textBaseline
textCtx.fillStyle = fillStyle
textCtx.clearRect(0, 0, textCtx.canvas.width, textCtx.canvas.height);
textCtx.fillText(text, textCtx.canvas.width / 2, textCtx.canvas.height / 2);

Categories

Resources