0

I am using a canvas element to generate a base64 image of text. When I hit the "Generate text" button, the canvas populates with the image, but it doesn't resize until you hit the button a second time. (And weirdly, on my machine, my fonts don't work until the second click either)

My Fiddle is below, and you can see that the function works partially on the first click, and that's what's baffling me.

I have checked answers here, here, and here already, but I am not a jQuery expert, and I don't know how to apply some of these solutions to my specific case. I have tried some of the proposed solutions in the answers above including:

  • Wrapping my code in $(document).ready(function() {}

  • Using $("#addText").click instead of $("#addText").on("click"...

Edit: As mentioned by @Barmar in the comments, it resizes to about half on the first try. That's because the lineCount() function outputs the wrong number on the first click.

$(document).ready(function() {

  $("#addText").click(txtimg);

  $("#myDownload").click(function() {
    var canvas = document.getElementById("myCanvas");
    var fullQuality = canvas.toDataURL("image/png", 1.0);
    $('#imgURL').val(fullQuality);
  });

  $("input[type='radio'][name='fontRadios']").click(function() {
    console.log($("input[type='radio'][name='fontRadios']:checked").val());
  });

  $("#colorChooser > label").on("click", function() {
    $('#colorPreview').css("background-color", $(this).attr('data-color')),
      txtimg();
  });

  $("#CaptureURL").click(function() {
    $("#imgURL").select();
    document.execCommand('copy');
  });

});

function txtimg() {
  var fontSize = parseInt($('#fontSizeValue').html());
  var fontColor = $('#colorChooser > label > input:checked').attr('id');
  var fontFam = $("input[type='radio'][name='fontRadios']:checked").val();
  var canvas = document.getElementById("myCanvas");
  var myMessage = $("#myMessage").val();

  var maxWidth = 600;
  var lineHeight = parseInt($('#lineHeightValue').html());
  var ctx = canvas.getContext("2d");
  var canvHeight = lineHeight * lineCount(ctx, myMessage, maxWidth);
  canvas.height = lineHeight * lineCount(ctx, myMessage, maxWidth);

  var x = canvas.width / 2;
  var y = canvas.height;

  //ctx.clearRect(0, 0, canvas.width, canvas.height);


  //ctx.beginPath();
  //ctx.moveTo(x, 0);
  ctx.font = fontSize + "px " + fontFam;
  ctx.fillStyle = fontColor;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'bottom';
  ctx.imageSmoothingEnabled = true;
  ctx.imageSmoothingQuality = 'high';
  //ctx.fillText(myMessage, x, y);
  wrapText(ctx, myMessage, x, 0 + lineHeight, maxWidth, lineHeight);
}

function lineCount(context, text, maxWidth) {
  var words = text.split(' ');
  var line = '';
  var lines = 1;



  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) {
      line = words[n] + ' ';
      lines++;

    } else {
      line = testLine;
    }
  }
  return lines;
}

function wrapText(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 fontVal(elem) {
  var fv = document.getElementById('fontSizeValue');
  if (fv.innerHTML != elem.value) {
    fv.innerHTML = elem.value;
    console.log(elem.value);
  }
}

function lineVal(elem) {
  var lv = document.getElementById('lineHeightValue');
  if (lv.innerHTML != elem.value) {
    lv.innerHTML = elem.value;
    console.log(elem.value);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Helloworld!</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.0/css/all.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
  <link href="https://fonts.googleapis.com/css?family=Oswald|Roboto|Sofia" rel="stylesheet">
</head>

<body>
  <div class="container">
    <div class="d-flex flex-row align-content-center mb-5">
      <div class="d-flex align-self-center mx-auto">
        <canvas id="myCanvas" width="600" height="100" style="border: 1px solid #cecece;"></canvas>
      </div>
    </div>

    <form id="myForm">
      <div class="form-group">
        <label for="myMessage">Message</label>
        <input type="text" name="myMessage" id="myMessage" class="form-control" value="Did you ever hear the tragedy of Darth Plagueis the Wise? I thought not. It's not a story the Jedi would tell you. It's a Sith legend. Darth Plagueis was a Dark Lord of the Sith, so powerful and so wise he could use the Force to influence the midichlorians to create life... He had such a knowledge of the dark side that he could even keep the ones he cared about from dying. The dark side of the Force is a pathway to many abilities some consider to be unnatural. He became so powerful... the only thing he was afraid of was losing his power, which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew, then his apprentice killed him in his sleep. It's ironic he could save others from death, but not himself."
        />
      </div>
      <hr/>
      <div class="row">
        <div class="col-lg-6">
          <div class="form-group">
            <label for="fontRange">Font Size:&nbsp;</label><span id="fontSizeValue">32</span><span>px</span>
            <input type="range" class="form-control-range" id="fontRange" min="8" max="128" value="32" onChange="fontVal(this)" onMouseMove="fontVal(this)">
          </div>
        </div>
        <div class="col-lg-6">
          <div class="form-group">
            <label for="lineRange">Line Height</label><span id="lineHeightValue">32</span><span>px</span>
            <input type="range" class="form-control-range" id="lineRange" min="0" max="128" value="32" onChange="lineVal(this)" onMouseMove="lineVal(this)">
          </div>
        </div>
      </div>
      <hr/>
      <div class="row">
        <div class="col-lg-6">
          <div class="form-group">
            <label for="fontSizeSelect">Typeface</label>
            <div class="form-check">
              <input class="form-check-input" type="radio" name="fontRadios" id="Oswald" value="Oswald" checked>
              <label class="form-check-label" for="WP">
         Oswald
        </label>
            </div>
            <div class="form-check">
              <input class="form-check-input" type="radio" name="fontRadios" id="Roboto" value="Roboto">
              <label class="form-check-label" for="TG">
         Roboto
        </label>
            </div>
            <div class="form-check">
              <input class="form-check-input" type="radio" name="fontRadios" id="Sofia" value="Sofia">
              <label class="form-check-label" for="TGB">
         Sofia
        </label>
            </div>
          </div>
        </div>
        <div class="col-lg-6">
          <div class="btn-group btn-group-toggle w-100" data-toggle="buttons" id="colorChooser">
            <label class="btn rush-red active" data-color="#ed1c24">
       <input type="radio" name="colorOptions" id="#ed1c24" autocomplete="off" checked>Red
       </label>
            <label class="btn rush-gold" data-color="#eeb111">
       <input type="radio" name="colorOptions" id="#eeb111" autocomplete="off">Gold
       </label>
            <label class="btn rush-grey" data-color="#505051">
       <input type="radio" name="colorOptions" id="#505051" autocomplete="off">Grey
       </label>
            <label class="btn rush-black" data-color="#111111">
       <input type="radio" name="colorOptions" id="#111111" autocomplete="off">Black
       </label>
          </div>
          <div class="card">
            <div id="colorPreview" class="card-body" style="background-color: #ed1c24;">

            </div>
          </div>
        </div>

      </div>

      <button id="addText" class="btn btn-primary" type="button">
     Add Text
    </button>
    </form>
    <hr/>
    <button class="btn btn-primary" type="button" id="myDownload">
    <i class="fa fa-code pr-2"></i>Generate Image URL
   </button>
    <button class="btn btn-primary" type="button" id="CaptureURL">
    <i class="fa fa-copy pr-2"></i>Copy
   </button>
    <!--a class="btn disabled float-right" id="linkURL" href="#" target="_blank">
    <i class="fa fa-external-link-alt pr-2"></i>Test in Browser
   </a-->

    <textarea id="imgURL" class="form-control"></textarea>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</body>

</html>
WushuDrew
  • 168
  • 11
  • 1
    It resizes, but only about half the correct size. – Barmar May 10 '19 at 18:38
  • Yes, I should have mentioned that my ````lineCount()```` function outputs the wrong number on the first click. – WushuDrew May 10 '19 at 18:52
  • So I think the whole problem is just in that function. – Barmar May 10 '19 at 18:55
  • 1
    I suggest you single-step through the function, and notice how the calculations differ the first and second time. – Barmar May 10 '19 at 18:57
  • Actually, if you select the 'sofia' font in the radio buttons, that doesn't work on the first try either. One of the other answers mentioned that the first click was binding the click? not 100% sure what that means, so I don't know how to apply it. – WushuDrew May 10 '19 at 18:58
  • 1
    @WushuDrew A common cause of "something only works after the first click" is that you add another event handler when processing the click. That doesn't seem to be the problem here, so it's not relevant. – Barmar May 10 '19 at 19:06

2 Answers2

1

Set the canvas font properties before the first time you call lineCount(). The inner call to measureText() is using whatever defaults there are before you assign them.

Steve Wakeford
  • 331
  • 1
  • 8
0

I found the answer here: Drawing text to <canvas> with @font-face does not work at the first time

Browsers load the font in the background, asynchronously, so I put them in the document elsewhere to be loaded. this was throwing off the measureText() function, and therefore throwing off the line spacing

WushuDrew
  • 168
  • 11