Wednesday, 18 August 2021

FabricJS automatically adding new lines to textboxes causes text cursor to move backwards

I have a function that wraps textboxes in FabricJS so that they don't become too wide, (it is automatically line breaking when the the maximum width is reached). However it is causing the text cursor to get pushed behind by 1 character every time it adds a new line.

Look at this gif to fully see the problem in play a gif

I am using the following function to automatically line break the text box. To give some context, it checks if the length of a textLine exceeds the maxWidth, and if it is the case with the last word included but doesn't exceed if the last word is not included, then it adds a new line by entering \n and somewhere here it causes the problem.

    function wrapCanvasText(t, canvas, maxW, maxH) {
    
    let initialFormatted = t.text

    if (typeof maxH === "undefined") {
        maxH = 0;
    }

  var words = t.text.split(" ")
    var formatted = '';

    // clear newlines
     var sansBreaks = t.text.replace(/(\r\n|\n|\r)/gm, "");  

    // calc line height
    var lineHeight = new fabric.Text(sansBreaks, {
        fontFamily: t.fontFamily,
        fontSize: t.fontSize
    }).height;

    // adjust for vertical offset
    var maxHAdjusted = maxH > 0 ? maxH - lineHeight : 0;
    var context = canvas.getContext("2d");


    context.font = t.fontSize + "px " + t.fontFamily;
    var currentLine = "";
  var breakLineCount = 0;
  

  for (var n = 0; n < words.length; n++) {
        console.log(words[n])
        var isNewLine = currentLine == " ";
        var testOverlap = currentLine + ' ' + words[n] + ' ';

        // are we over width?
      var w = context.measureText(testOverlap).width;

      if (w < maxW) { // if not, keep adding words

            currentLine += words[n] + ' ';
        formatted += words[n] += ' ';

      } else {

            // if this hits, we got a word that need to be hypenated
            if (isNewLine) {
                var wordOverlap = "";

                // test word length until its over maxW
                for (var i = 0; i < words[n].length; ++i) {

                    wordOverlap += words[n].charAt(i);
                    var withHypeh = wordOverlap + "-";

                    if (context.measureText(withHypeh).width >= maxW) {
                        // add hyphen when splitting a word
                        withHypeh = wordOverlap.substr(0, wordOverlap.length - 2) + "-";
                        // update current word with remainder
                        words[n] = words[n].substr(wordOverlap.length - 1, words[n].length);
                        formatted += withHypeh; // add hypenated word
                        break;
                    }
                }
          }
          n--; // restart cycle
          if (words[n+1] !== '') {
              formatted += '\n';
              breakLineCount++;
              
          }
          
            currentLine = "";
        }
        if (maxHAdjusted > 0 && (breakLineCount * lineHeight) > maxHAdjusted) {
            // add ... at the end indicating text was cutoff
            formatted = formatted.substr(0, formatted.length - 3) + "...\n";
            break;
      }
    }
    // get rid of empy newline at the end
    formatted = formatted.substr(0, formatted.length - 1);
    
    

    return formatted;
}

You can try out this snippet it is an approximate version of what I have, most importantly, it does have the same cursor problem. To try out the problem, edit the text directly in the canvas after initialization.

var canvas = new fabric.Canvas('c');
canvas.backgroundColor = "#F5F5F5";
var textArea = document.getElementById('addNote');

function checkForChange() {
var activeObject = canvas.getActiveObject();
let formatted1 = wrapCanvasText(activeObject, canvas, 400, 2000);
activeObject.text = formatted1;
}



function wrapCanvasText(t, canvas, maxW, maxH) {

  let initialFormatted = t.text

  if (typeof maxH === "undefined") {
    maxH = 0;
  }

  var words = t.text.split(" ")
  var formatted = '';

  // clear newlines
  var sansBreaks = t.text.replace(/(\r\n|\n|\r)/gm, "");

  // calc line height
  var lineHeight = new fabric.Textbox(sansBreaks, {
    fontFamily: t.fontFamily,
    fontSize: t.fontSize
  }).height;

  // adjust for vertical offset
  var maxHAdjusted = maxH > 0 ? maxH - lineHeight : 0;
  var context = canvas.getContext("2d");


  context.font = t.fontSize + "px " + t.fontFamily;
  var currentLine = "";
  var breakLineCount = 0;


  for (var n = 0; n < words.length; n++) {
    console.log(words[n])
    var isNewLine = currentLine == " ";
    var testOverlap = currentLine + ' ' + words[n] + ' ';

    // are we over width?
    var w = context.measureText(testOverlap).width;

    if (w < maxW) { // if not, keep adding words

      currentLine += words[n] + ' ';
      formatted += words[n] += ' ';

    } else {

      // if this hits, we got a word that need to be hypenated
      if (isNewLine) {
        var wordOverlap = "";

        // test word length until its over maxW
        for (var i = 0; i < words[n].length; ++i) {

          wordOverlap += words[n].charAt(i);
          var withHypeh = wordOverlap + "-";

          if (context.measureText(withHypeh).width >= maxW) {
            // add hyphen when splitting a word
            withHypeh = wordOverlap.substr(0, wordOverlap.length - 2) + "-";
            // update current word with remainder
            words[n] = words[n].substr(wordOverlap.length - 1, words[n].length);
            formatted += withHypeh; // add hypenated word
            break;
          }
        }
      }
      n--; // restart cycle
      if (words[n + 1] !== '') {
        formatted += '\n';
        breakLineCount++;

      }

      currentLine = "";
    }
    if (maxHAdjusted > 0 && (breakLineCount * lineHeight) > maxHAdjusted) {
      // add ... at the end indicating text was cutoff
      formatted = formatted.substr(0, formatted.length - 3) + "...\n";
      break;
    }
  }
  // get rid of empy newline at the end
  formatted = formatted.substr(0, formatted.length - 1);



  return formatted;
}

$("#addNote").keyup(function(e) {


  var activeObject = canvas.getActiveObject();
  if (activeObject && activeObject.type == 'textbox') {
    activeObject.text = textArea.value
    let formatted1 = wrapCanvasText(activeObject, canvas, 400, 2000);

    activeObject.text = formatted1;
        while (activeObject.textLines.length > 1 && canvas.getWidth() * 0.8 >= activeObject.width) {
      activeObject.set({
        width: activeObject.getScaledWidth() + 1
      })
    }
    canvas.renderAll();
  } else {
    var textSample = new fabric.Textbox(textArea.value, {});
    textSample.left = 0
    textSample.splitByGrapheme = true
    textSample.lockRotation = true
    textSample.editable = true
    textSample.perPixelTargetFind = false
    textSample.hasControls = true
    textSample.width = canvas.getWidth() * 0.8
    textSample.height = canvas.getHeight() * 0.8
    textSample.maxWidth = canvas.getWidth() * 0.8
    textSample.maxHeight = canvas.getHeight() * 3
    canvas.add(textSample);
    canvas.setActiveObject(textSample);
    canvas.renderAll();
  }
  canvas.on('text:changed', checkForChange)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js"></script>
<textarea id="addNote"></textarea>
<canvas id="c" width="400" height="400"></canvas>


from FabricJS automatically adding new lines to textboxes causes text cursor to move backwards

No comments:

Post a Comment