Search code examples
pdfpdf-generationjspdfmeasure

How to calculate the length in mm of a string in a PDF document created with jsPDF library?


I use jsPDF library to create and print a PDF document. This library exposes low level methods which are ok, but i have tons of fields to create, many of which are similar, and i need to create higher level abstractions.

For example i have a createLabel function that i want to call instead of this low level stuff.

var doc = new jsPDF('portrait', 'mm', 'a4');
doc.addFont('Arial', "sans-serif", "normal");

// name
doc.setFontSize(14);
doc.text(10, 19, "name:");
doc.setLineWidth(0.1);
doc.line(25, 19, 100, 19); // meaning x1, y1, x2, y2

// CUI
doc.setFontSize(14);
doc.text(10, 29, "CUI:");
doc.setLineWidth(0.1);
doc.line(21, 29, 100, 29);

// same stuff but use functions instead.
createLabel("name: ", 10,50, 100); // meaning (labelName, x, y, totalWidth) 
createLabel("CUI: ", 10,60, 100);

how to calculate the length of a string in mm, inside a pdf document

As you can see, the lines for the second group of labels are not placed in the right position. They are too much on the left. Their starting postion is generated based on the length of the labelName, and this length calculation fails. How can i make this work properly? The code so far is:

function createLabel(name, x, y, totalWidth) {
    //draw name
    doc.setFontSize(14);
    doc.text(x, y, name);

    // draw line
    const nameLength = (measureLength(name)) + 2; 
    doc.setLineWidth(0.1);

        // i want to start the line after the name ends + 2 mm.
        // and end the line in such a way that nameLength + lineLength == totalWidth of the compoenent.

    doc.line(x + nameLength, y, x + totalWidth, y);
}

function measureLength(str) {
    let canvas = document.createElement('canvas'); // in memory canvas.. not rendered anywere..
    let ctx = canvas.getContext("2d")
    ctx.font = "14px Arial";

    let width = ctx.measureText(str).width;

    let mm = ( width * 25.4 ) / 149 // meaning (px * 25.4) / screen DPI
    console.log (mm); 
    return mm; // of course, this calculation turns out wrong..
}

How to make this measureLength function work correctly? Most solutions i found involve DOM but this is PDF.

Notice: I use the same font ('14px Arial') for the PDF document and for the canvas. jsPDF live demo. Any insight is appreciated, thanks :)


Solution

  • This might resolve your problem:

    createLabel(name, x, y, totalWidth) {
       doc.setFontSize(14);
       doc.text(x, y, name);
    
       // draw line
       const nameLength = (doc.getTextDimensions(name).w / (72 / 25.6) ) + 2;
       console.log('nameLength', nameLength); // todo remove
    
       doc.setLineWidth(0.1);
    
       // i want to start the line after the name ends + 2 mm.
       // and end the line in such a way that nameLength + lineLength == totalWidth of the compoenent.
    
       doc.line(x + nameLength, y, x + totalWidth, y);
    }
    

    Check how I calculate nameLength - using build in jsPDF function and converting to mm. Helpful links:

    how to calculate text size

    why sometimes calculation might be wrong by few pixels

    This is the result:

    Result of new code

    Remember that you use x + totalWidth for line width, so lines are longer by x compared to manual example at the top