Search code examples
canvaskineticjs

Kinteticjs text inside circle


I'm looking to have text inside a circle and have the text be contained by it like this:

enter image description here

All I've been able to do is position the text inside but it is keeping a square container, not a circle.

Demo


Solution

  • Wrapping text inside a circle

    A circle chord is a line segment with its endpoints on the circle.

    enter image description here

    To fit text horizontally inside a circle you need to calculate widths of horizontal chords in that circle.

    // r == the circle radius
    // h == the vertical distance of the horizontal chord from the top of the circle
    
    var chordWidth = 2*Math.sqrt(h*(2*r-h));
    

    Calculate the width used by any text using context.measureText:

    var textWidth = context.measureText("Measure this text").width;
    

    With these calculations you can "word wrap" your text onto each chord.

    You do that by adding words to a line of text until the accumulated text phrase is wider than the chord width. Then start the next line of text with the word that exceeded the chord width.

    Here's code and a Demo: http://jsfiddle.net/m1erickson/jcb3D/

    enter image description here

    <!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 text = "'Twas the night before Christmas, when all through the house,  Not a creature was stirring, not even a mouse.";
        var font = "12pt verdana";
        var textHeight = 15;
        var lineHeight = textHeight + 5;
        var lines = [];
    
        var cx = 150;
        var cy = 150;
        var r = 100;
    
        initLines();
    
        wrapText();
    
        ctx.beginPath();
        ctx.arc(cx, cy, r, 0, Math.PI * 2, false);
        ctx.closePath();
        ctx.strokeStyle = "skyblue";
        ctx.lineWidth = 2;
        ctx.stroke();
    
    
        // pre-calculate width of each horizontal chord of the circle
        // This is the max width allowed for text
    
        function initLines() {
    
            for (var y = r * .90; y > -r; y -= lineHeight) {
    
                var h = Math.abs(r - y);
    
                if (y - lineHeight < 0) {
                    h += 20;
                }
    
                var length = 2 * Math.sqrt(h * (2 * r - h));
    
                if (length && length > 10) {
                    lines.push({
                        y: y,
                        maxLength: length
                    });
                }
    
            }
        }
    
    
      // draw text on each line of the circle
    
      function wrapText(){
    
          var i=0;
          var words=text.split(" ");
    
          while(i<lines.length && words.length>0){
    
              line=lines[i++];
    
              var lineData=calcAllowableWords(line.maxLength,words);
    
              ctx.fillText(lineData.text, cx-lineData.width/2, cy-line.y+textHeight);
    
              words.splice(0,lineData.count);
          };
    
      }
    
    
      // calculate how many words will fit on a line
    
      function calcAllowableWords(maxWidth,words){
    
          var wordCount=0;
          var testLine="";
          var spacer="";
          var fittedWidth=0;
          var fittedText="";
    
          ctx.font=font;
    
          for(var i=0;i<words.length; i++){
    
              testLine+=spacer+words[i];
              spacer=" ";
    
              var width=ctx.measureText(testLine).width;
    
              if(width>maxWidth){ 
                  return({
                      count:i, 
                      width:fittedWidth, 
                      text:fittedText
                  }); 
              }
    
              fittedWidth=width;
              fittedText=testLine;
          }
    
          // always return the last set of words
          return({
              count:words.length,
              width:width,
              text:testLine,
          });
      }
    
    }); // end $(function(){});
    </script>
    </head>
    <body>
        <canvas id="canvas" width=300 height=300></canvas>
    </body>
    </html>