Text Wrap in a <Canvas> Element

wrap text within rect without overflowing it, fiddle canvas html5

Define how many words do you have, after that draw rect based on length of words.

Example

const words =  text.split(' ');
const incrementFactor = 4; // it adds 4 pixels to rect for each line
const paragraphCount = words.length // Define the paragraph count
ctx.fillRect(20,20,250,paragraphCount*incrementFactor);
ctx.fillStyle = 'black';
drawWords(ctx, text, x, y, maxWidth, lineHeight,rectHeight,words)

You need to redraw canvas in each update.
I suggest first define all elements to be drawn and after that draw it. Canvas does not have real-time updates, you need to redraw in each update.

working code https://jsfiddle.net/vf8gvq7m/91/

Wrapping a text around an image in a Canvas

You can wrap text around an image (rectangle) using canvas transforms (translate+rotate)

Sample Image

For example, this is how you rotate the canvas and draw text down the right side of your image:

// save the untransformed state of the context
ctx.save();

// translate to the top-right corner of the image
// ( x/y properties have been added to the standard img element )
ctx.translate(image.x+image.width,image.y);

// rotate the canvas 90 degrees (PI/2 radians)
ctx.rotate(Math.PI/2);

// the canvas is now properly moved and rotated
// so we can just draw the text at [0,0]
ctx.fillText(subtext,0,0);

// restore the context to its untransformed state
ctx.restore();

This calculates how many words fit on a side using context.measureText:

var end=0;

var subtext="";

while(ctx.measureText(text.split(" ",end+1).join(" ")).width<=length)
{
subtext=text.split(" ",end+1).join(" ");
end++;
}

An interesting enhancement would be to draw text around a rectangle with rounded corners.

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/U2hE3/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid red;}
</style>

<script>
$(function(){

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

var rect={x:40,y:40,width:150,height:120};

var text="This is some text that wraps on the outside of a rectangle.";

var font="14px Verdana";

drawTextAroundRect(rect,text,7,font);

function drawTextAtAngle(text,length,x,y,radians){

// if text is empty then return
if(text.length==0){return;}

var end=0;
var subtext="";

if(ctx.measureText(text).width<=length){

// all the text fits
subtext=text;

}else{

// calc how many words will fit on this length
while(ctx.measureText(text.split(" ",end+1).join(" ")).width<=length)
{
subtext=text.split(" ",end+1).join(" ");
end++;
}

}

// draw the text at the appropriate angle
ctx.save();
ctx.translate(x,y);
ctx.rotate(radians);
ctx.fillText(subtext,0,0);
ctx.restore();

// return any text that didn't fit for further processing
if(end==text.length){
return("");
}else{
return(text.substr(subtext.length));
}

}

function drawTextAroundRect(rect,text,textPadding,font){

// set the font
ctx.font=font;

// top
text=drawTextAtAngle(text,rect.width,rect.x,rect.y-textPadding,0);

// right
text=drawTextAtAngle(text,rect.height,rect.x+rect.width+textPadding,rect.y,Math.PI/2);

// bottom
text=drawTextAtAngle(text,rect.width,rect.x+rect.width,rect.y+rect.height+textPadding,Math.PI);

// left
text=drawTextAtAngle(text,rect.height,rect.x-textPadding,rect.y+rect.height,Math.PI*1.5);

// draw the rect (just for illustration)
ctx.beginPath();
ctx.rect(rect.x,rect.y,rect.width,rect.height);
ctx.stroke();

}

}); // end $(function(){});
</script>

</head>

<body>
<canvas id="canvas" width=300 height=250></canvas>
</body>
</html>

[ Add more word-wrap code given questioners clarification ]

You can use context.measureText to get the width of text and use that to wrap text around an image.

Sample Image

Given text width you can build a word-wrap by advancing to the next line when text exceeds your desired width.

In the case of wrapping around a picture, you will have 2 desired widths—shorter while the text might run into the image and longer after that.

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/XM5Yp/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>

<script>
$(function(){

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

var maxWidth = 350;
var lineHeight = 25;
var x = (canvas.width - maxWidth) / 2;
var y = 60;
var text = "'Twas the night before Christmas, when all through the house. Not a creature was stirring, not even a mouse. The stockings were hung by the chimney with care in hopes that St. Nicholas soon would be there.";

ctx.font = '16pt Calibri';
ctx.fillStyle = '#333';

var imgWidth;
var imgHeight;

var img=new Image();
img.onload=function(){

imgWidth=img.width;
imgHeight=img.height;

ctx.drawImage(img,canvas.width-img.width,0)

wrapText(text, x, y, maxWidth, lineHeight);

}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house204-1.jpg";

function wrapText(text, x, y, maxWidth, lineHeight) {

var words = text.split(' ');
var line = '';

var maxLineWidth=y>imgHeight+10?maxWidth:maxWidth-imgWidth;

for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if(testWidth > maxLineWidth) {
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
maxLineWidth= y>imgHeight+10?maxWidth:maxWidth-imgWidth;

}
else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}

}); // end $(function(){});
</script>

</head>

<body>
<canvas id="canvas" width=400 height=325></canvas>
</body>
</html>

How can I implement word wrap and carriage returns in canvas fillText?

The best at rendering text in a browser are definitively HTML and CSS.

Canvas 2D API is still far below, so when you need to render complex text on a canvas, the best is to use the power of HTML and CSS to take all the measures needed for your canvas.

I already made a few answers that deal with similar issues, so this one is just an adaptation of these previous codes to your needs:

// see https://stackoverflow.com/questions/55604798// added x outputfunction getLineBreaks(node, contTop = 0, contLeft = 0) {  if(!node) return [];  const range = document.createRange();  const lines = [];  range.setStart(node, 0);  let prevBottom = range.getBoundingClientRect().bottom;  let str = node.textContent;  let current = 1;  let lastFound = 0;  let bottom = 0;  let left = range.getBoundingClientRect().left;  while(current <= str.length) {    range.setStart(node, current);    if(current < str.length -1) {      range.setEnd(node, current + 1);    }    const range_rect = range.getBoundingClientRect();    bottom = range_rect.bottom;    if(bottom > prevBottom) {      lines.push({        x: left - contLeft,        y: prevBottom - contTop,        text: str.substr(lastFound , current - lastFound)      });      prevBottom = bottom;      lastFound = current;      left = range_rect.left;    }    current++;  }  // push the last line  lines.push({    x: left - contLeft,    y: bottom - contTop,    text: str.substr(lastFound)  });
return lines;}
function getRenderedTextLinesFromElement(elem) { elem.normalize(); // first grab all TextNodes const nodes = []; const walker = document.createTreeWalker( elem, NodeFilter.SHOW_TEXT ); while(walker.nextNode()) { nodes.push(walker.currentNode); } // now get all their positions, with line breaks const elem_rect = elem.getBoundingClientRect(); const top = elem_rect.top; const left = elem_rect.left; return nodes.reduce((lines, node) => lines.concat(getLineBreaks(node, top, left)), []);}
const ctx = canvas.getContext('2d');ctx.textBaseline = 'bottom';txt_area.oninput = e => { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); const lines = getRenderedTextLinesFromElement(txt_area); // apply the div's style to our canvas const node_style = getComputedStyle(txt_area); const nodeFont = (prop) => node_style.getPropertyValue('font-' + prop); ctx.font = nodeFont('weight') + ' ' + nodeFont('size') + ' ' + nodeFont('family'); ctx.textAlign = node_style.getPropertyValue('text-align'); ctx.textBaseline = "bottom"; // draw each line of text lines.forEach(({text, x, y}) => ctx.fillText(text, x, y));};txt_area.oninput();
#txt_area, canvas {  width: 300px;  height: 150px;  resize: none;  border: 1px solid;  max-width: 300px;  max-height: 150px;  overflow: hidden;}canvas {  border-color: green;}
<div contenteditable id="txt_area">This is an example text<br>that should get rendered as is in the nearby canvas</div><canvas id="canvas"></canvas>

How to wrap text on image on canvas

Try adding a width property in the constructor for the Text shape, in this case since the image has a width of 300 it would be the following:

label.add(new Kinetic.Text({
text: $("#newtext").val(),
fontFamily: 'Verdana',
width: 300,
fontSize: 18,
padding: 10,
fill: 'white'
}));

And a working example: http://jsfiddle.net/CLk3Y/1/

How to show user input over canvas with multiple text fields with text wrapping

Few comments:

You use var text = document.getElementById('input-text1').value; with the id input-text1 whereas in your HTML your id is input-text.

You define the function drawImage(text) with a text parameter that is never used. In this function, you create the canvas and a context variables again.

You insert in the DOM an image that you hide to draw it later on the canvas. I think that it's easier to create a new Image object.

The function wrapText() is a bit ambiguous. Does it format text? Does it display anything? You could rename it like displayWrapText() or getWrappedText().

Then, about your issues:

  1. The text is not updated when the user deletes characters because, except for the input #1, when the user types, it doesn't clear the bubble and it draws the new text over the previous one (it's getting darker). That's why you see no difference. Deletion works for the first input because you clear the bubble and draw the full text again.

  2. Tbh, I forgot about this issue when I tried, and now it's working well... In any case, provide an appropriate maxWidth to the wrapText() function: there are small and big bubbles. However, it doesn't wrap if the string doesn't contain any space.

  3. When the user types in the first input, it clears the canvas but display text again only inside the first bubble, so the others are displayed empty. You need to clear the canvas and to redraw the texts in all the corresponding bubbles.

What I did was to apply what I told you before, plus improve the event handling (see bubbling) since it was pretty much the same code for each input. To be able to redraw all the bubbles after every update, I store their value each time they are updated.

const canvas = document.getElementById('canvas');const context = canvas.getContext('2d');const lineHeight = 30;let backgroundImg;let text1;let text2;let text3;let text4;
init();
function init() { text1 = ''; text2 = ''; text3 = ''; text4 = ''; backgroundImg = new Image(); backgroundImg.src = 'https://cdn.glitch.com/4ed5f9d8-97ad-4c53-b855-3e8d508ba2f3%2FDVSN-FUTURE-NO-CRYIN-FINAL-NoText.jpg?v=1572122142300';
context.drawImage(backgroundImg, 0, 0, canvas.width, canvas.height);
document.querySelector('#input-container').addEventListener('keyup', function(e) { const num = parseInt(e.target.getAttribute('data-bubble'), 10); const text = e.target.value; saveText(num, text); draw(canvas, context, backgroundImg); }, false);}
function draw(canvas, context, backgroundImg) { context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(backgroundImg, 0, 0, canvas.width, canvas.height); drawBubble(canvas, context, getTextFrom(1), 39, 315, '26px Bubblegum', '#000000', 110, lineHeight); // bubble 1 drawBubble(canvas, context, getTextFrom(2), 45, 370, '22px Bubblegum', '#000000', 70, lineHeight); // bubble 2 drawBubble(canvas, context, getTextFrom(3), 20, 425, '26px Bubblegum', '#000000', 120, lineHeight); // bubble 3 drawBubble(canvas, context, getTextFrom(4), 20, 515, '24px Bubblegum', '#000000', 120, lineHeight); // bubble 4}
function getTextFrom(num) { switch(num) { case 1: return text1; case 2: return text2; case 3: return text3; case 4: return text4; default: ''; }}
function saveText(num, text) { switch(num) { case 1: text1 = text; break; case 2: text2 = text; break; case 3: text3 = text; break; case 4: text4 = text; break; }}
function displayWrappedText(context, text, x, y, maxWidth, lineHeight) { var words = text.split(' '); var line = '';
for (var n = 0; n < words.length; n++) { var testLine = line + words[n] + ' '; var metrics = context.measureText(testLine); var testWidth = metrics.width; if (testWidth > maxWidth && n > 0) { context.fillText(line, x, y); line = words[n] + ' '; y += lineHeight; } else { line = testLine; } } context.fillText(line, x, y);}
function drawBubble(canvas, context, text, x, y, font, color, maxWidth, lineHeight) { context.font = font; context.fillStyle = color; //context.fillText(text, x, y); displayWrappedText(context, text, x, y, maxWidth, lineHeight);}
#input-container > input {  width: 90%;  font-size: 18px;  height: 24px;  text-transform: uppercase;  padding: 0 8px;  background-color: transparent;  color: red;  border: 2px solid black;  outline: none;  border-radius: 4px;  margin-bottom: 12px;  margin-left: auto;  margin-right: auto;  font-weight: 500;  font-family: bubblegum;}
<div id="input-container">  <input class="js-input-text" data-bubble="1" type="text" maxlength="6" />  <input class="js-input-text" data-bubble="2" type="text" maxlength="5" />  <input class="js-input-text" data-bubble="3" type="text" maxlength="12" />  <input class="js-input-text" data-bubble="4" type="text" maxlength="18" /></div><div class="art-container">  <canvas id="canvas" width="576" height="576">    Canvas requires a browser that supports HTML5.  </canvas></div>

Word-wrap: break-down not working. Text overflow issue in draggable element

Solution:

You need to use the following properties in your .parent-canvas class:

.parent-canvas {
display: inline-block; /* Display inline but retain the block-level characteristics */
overflow: hidden; /* Hide the text if it overflows the container */
position: relative; /* Text is removed from the normal flow with absolute position, use this to contain it */
}

After this you have two options, using word-break or max-width in your .text-canvas class:

.text-canvas {
word-break: break-all;
}

Code Snippet:

function submit_button() {  /* ....Image upload function.. */}$(".text-canvas").draggable({  containment: ".imageupload",  create: function() {    $("#text-canvas ").css("width ", 'auto');  },  drag: function() {    $("#text-canvas ").css("width ", 'auto');  },  start: function() {    $("#text-canvas ").css("width ", 'auto');  },  stop: function() {    $("#text-canvas ").css("width ", 'auto');  }});
$("#fontsize").on("change", function() { var v = $(this).val(); $('.text-canvas').css('font-size', v + 'px');});
.text-canvas {  z-index: 1;  position: absolute;}.imageupload {  z-index: -1;}.parent-canvas {  display: inline-block;  overflow: hidden;  position: relative;}.text-canvas {  word-break: break-all;}.image-canvas img {  vertical-align: middle;}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" /><script src="//code.jquery.com/jquery-1.10.2.js"></script><script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<div class="col-sm-4"> <div name="anotherdiv"> <input type="range" min="12" max="54" id="fontsize"> </div></div><div class="col-sm-8"> <div class="parent-canvas"> <div class="text-canvas" id="text-canvas" contenteditable="true"> my text </div> <div class="image-canvas"> <div class="imageupload" onclick="submit_button()"> <img src="http://placehold.it/100x100"> </div>
</div> </div></div>

How can I wrap text around a moveable image?

TL;DR

This is a common and desirable feature that has been discussed numerous times in the history of CSS, and there is a specification under development to make it possible. Unfortunately, implementations are few and far-between, and some implementers are reluctant to work on it. Doing it with JavaScript is a complex problem for any non-trivial layout; such a solution is unlikely to be fast enough for your purposes and you will fast approach the sort of markup you'd expect from a PDF-to-HTML converter.

Background

There are two questions here: irregular text flow and excluding text and other inline elements from an arbitrary region of the page.

This is not the first time either feature has been discussed for CSS. In particular, flowing text around irregular floated shapes was mentioned in a CSS level 1 working draft back in 1996, and Eric Meyer's ragged float demo dates from at least 2002. This is a long overdue feature!

In June 2007, James Elmore suggested adding position values to the float property, enabling elements to be positioned arbitrarily on the page while excluding other elements from flowing underneath.

SVG 1.2 initially specified a model for flowed text regions, and goes into some detail on how this would be implemented. Unfortunately, the latest version of the spec (which is still in development) blows this out of the water by noting that previous work will be replaced with "a superset of the SVG 1.2 Tiny textArea feature".

Current status (revised August 2012)

More recently, we have the CSS Exclusions specification, a proposal from Adobe and what you see being shown off in that video. As of August 2012, these have been implemented in IE 10 RTM and are slowly being rolled out in WebKit, but developers working for other vendors have expressed mixed feelings about the proposal.*

  • Trident (IE): implemented in IE10 platform preview and available in the RTM. (Referred to as "positioned floats" in IE Test Drive, similar to James Elmore's proposal.)
  • WebKit (Chrome, Safari): partially implemented, with patches gradually being approved for landing in the WebKit trunk, meaning we should start seeing this soon. (Bug 57311 - CSSRegions: add exclusions support in WebKit.)
  • Gecko (Firefox): unlikely to be implemented soon; bug currently resolved as WONTFIX. (Bug 672053 - Add support for CSS3 Positioned Floats—note David Baron's objections concerning interoperability.)
  • Presto (Opera): not yet implemented. (Bug tracker is private; I tried asking the ever gregarious Bruce Lawson if there was an open bug, but he is constrained from commenting on their roadmap.)

Adobe maintain a handy support matrix for easy reference.

Hackcough "Polyfilling"?

It would be difficult achieve a similar effect using JavaScript, and even more difficult to do it efficiently. I can think of two very naive approaches to make room for an absolutely positioned element in a region:

  1. "block out" space for element using strategically-inserted inline spans; or
  2. surround each word with a span element, and style each word individually to make room for the excluded element using padding.

I've hacked up a very broken demo of how the second approach might work. It's horrible, buggy and easy to break. I actually spent a few weeks after answering this question working on a polyfill for the Exclusions spec, but gave up because there were too many bugs and performance issues.

You will have myriad issues with either approach: columns, text alignment, errant child elements (especially floated or positioned elements!), various edge conditions, horrible things if you change the HTML, hyphenation—merciful heavens, I don't even want to think about hyphenation—and, of course, potentially magnificent performance issues after taking account of these things.

Performance issues can be ameliorated somewhat; for example, I've used elementFromPoint to try and get the span containing the first overlapping word directly, and some browsers even support caretPositionFromPoint, which may also help. I think that with a lot of work, you could make something that works pretty well for static content; but making it fast enough that you can drag it around with the mouse? My demo page has precious little content and doesn't address any of the mind-bendingly complex issues you'd have to deal with to make this work on real web pages. Even if you can get around all of those issues, making it fast enough to drag around smoothly would be very challenging.


* I strongly hope vendors will implement CSS Exclusions. People have been asking for these features since the earliest days of CSS, and it is a common and legitimate visual design objective both on screen and in print.



Related Topics



Leave a reply



Submit