Search code examples
javascriptcanvasfabricjs

fabric.js - Text object min width problem - [Solved: Textbox - minWidth]


So I'm trying to replace the text objects on the template with the data.(This is not achieved dynamically, I take the data from server (array of objects) and replace 'name' with dataObj[name]) I am adjusting the fontSize so that text fits its container. The problem I have is when text falls short, text BOX also shrinks even though I don't alter its width and height

I'm adjusting the fontSize with a helper function to make sure content fits. Is there any way to LOCK the height and width of the text 'CONTAINER' (To preserve the look on the design) Please observe the output below. Dimensions are not 'standardized'.

This is how I adjust the font size. Basically providing textObjWidth, textObjHeight, fontSize and text value

I can provide additional info on request.


function getTextWidth(text, fSize, font = 'Helvetica') {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');

  context.font = `${fSize}px ${font}`

  return context.measureText(text).width;

}

export function adjustFontSize(maxTextWidth, maxTextHeight, fontSize, text){

  let fSize = fontSize;
  let currentTextWidth = getTextWidth(text, fSize);

  //decrease font size until the text fits  
  while (currentTextWidth > maxTextWidth || fSize > maxTextHeight) {
    fSize -= 1;
    currentTextWidth = getTextWidth(text, fSize);
  }

  return fSize;
}

Text object properties - (used to create the template [image-1])

    const textObj = new fabric.Text(formParameterText, {
      textBackgroundColor: '#fff',
      fill: '#000',
      visible,
      // scaleToWidth: 1,
      textAlign: 'center',
      fontFamily: 'Helvetica'
    });

How I map values - No alteration on text object width and height whatsoever

    for(let i = 0; i < dataToBePrinted.length; i++) {
      const userData = dataToBePrinted[i];
      const updatedSingleCanvas = {
        ...eventCard,
        objects: eventCard.objects.map(obj => {
          if(obj.type === 'image') {
            return {...obj, visible: true}
          }
          if(obj.type === 'text' && userData[obj.text]) {
            return { ...obj, selectAble: false, evented: false, text: userData[obj.text], fontSize: adjustFontSize(obj.width, obj.height, obj.fontSize, userData[obj.text]), width: obj.width, height: obj.height}
          }
          return obj
        })
      }

Here is the template - designed by the user enter image description here

Here is the output after mapping data enter image description here

enter image description here

If I remove fontSize: adjustFontSize(obj.width, obj.height, obj.fontSize, userData[obj.text]) I obtain this output. Which is unacceptable. enter image description here


Solution

  • OK So I resolved the issue. First off big thanks to @ShaMan123 @fabricjs community on github for providing the solution.

    https://github.com/fabricjs/fabric.js/discussions/8367#discussioncomment-3884600 https://github.com/fabricjs/fabric.js/pull/7981

    1. Replacing 'Text' with 'Textbox'
    2. Textbox objects can utilize a property called --> minWidth: number(px)

    Imposing minWidth helped me achieve the standardized output. If the content falls short, text is centered and min width is applied, If fontSize is too large, fontSize adjuster function does the work and content fits.In either cases, text fits and design persists.

    However, height of the textbox cannot be locked like minWidth. I assume fontSize alters this and dictates its own height. A minHeight property could be useful.

    Here are the changes in my code

    const textObj = new fabric.Textbox(formParameterText, {
      textBackgroundColor: '#fff',
      fill: '#000',
      visible,
      backgroundColor: '#fff',
      textAlign: 'center',
      fontFamily: 'Helvetica',
      editable: false,
    });
    

    Mapping - replacing keys with values

          const updatedSingleCanvas = {
            ...eventCard,
            objects: eventCard.objects.map(obj => {
              if (obj.type === 'image') {
                return { ...obj, visible: true }
              }
              if (obj.type === 'textbox' && userData[obj.text]) {
                return {
                  ...obj,
                  selectAble: false,
                  evented: false,
                  text: userData[obj.text],
                  fontSize: adjustFontSize(obj.width, obj.height, obj.fontSize, userData[obj.text]),
                  minWidth: obj.width,
                  //minHeight: obj.height,
                  //height: obj.height
                }
              }
              return obj
            })
          }
    

    --Results--

    -Template enter image description here

    -Result1 enter image description here

    -Result2

    enter image description here