Can fitText be used to set only height? If not, is there an alternative

PDF.js Express Version

(index) Value
UI version “7.3.0”
Core version “7.3.2”
Build “My8yMi8yMDIxfDQwM2IyMDFjNg==”
WebViewer Server false
Full API false

Detailed description of issue
I’m creating a list of freeTextannotations by using the last’s annotation bottom coordinate as the new Y coordinate for the next one. As far as I could find in the documentation, (and by a lot of experimentation), fitText seems to be the only method that can provide me with an rectangle adjusted to the size of the content. The only problem is that it adjusts the width by setting the page width as it’s X2 position. This means that the annotation occupies the rest of the width of the page, no matter where it is placed on the document.

Expected behaviour
{What I would like to do is have the annotation only adjust in height. Can that be done?}

Does your issue happen with every document, or just one?
{Every}

Link to document
{Provide a link to the document in question if possible}

Code snippet
{Provide a relevant code snippet}

Hello, I’m Ron, an automated tech support bot :robot:

While you wait for one of our customer support representatives to get back to you, please check out some of these documentation pages:

Guides:APIs:Forums:

Hi!

Can you please send me a code snippet showing how you are adding these annotations. A code snippet will help me find a solution for you much faster.

Thanks!
Logan

Hey Logan, here you go.
I adjust the line (meaning, Y position of each group of elements that compose a line), by readjusting the largest box with fitText. So, again, fitText adjusts the box, but it formats it to take the remaining width of the page. I don’t want that. I want the box to adjust given a width constraint. I would also be satisfied with a different method for building tables on a PDF. Thanks

function addIssues(annotations, pageHeight: number, docViewer, annotationList: Annotations.WidgetAnnotation[]) {
    const result: any[] = [];
    
    const doc = docViewer.getDocument();
    const pageNumber = 1;
    const pageInfo = doc.getPageInfo(pageNumber);
    const pageMatrix = doc.getPageMatrix(pageNumber);
    const pageRotation = doc.getPageRotation(pageNumber);
    const maxIter = props.state?.length ?? 0;


    // Issue item widgets
    const issueProperties = ["description",  "locationCode", "itemCode", "deficiencyCode", "statusCode", "isSiteCompleted", "completedDate"];
    const fieldNames = ["description", "location", "item", "deficiency", "status", "site-done", "complete-date"];
    const freeTextDict: { [key: string]: any } = {};
    const filteredWidgetsDict: { [key: string]: any[] } = {};
    fieldNames.forEach(prop => filteredWidgetsDict[prop] = annotationList.filter(el => el.fieldName?.includes(prop)));

    // Define bottom and page correction factor
    let bottom: number = filteredWidgetsDict["description"]?.[0].getBottom();
    let pageCorr = filteredWidgetsDict["description"]?.[0].getPageNumber() - 1;

    // Create freetext annotations for each property in each issue, 
    // MaxIter is the length of the issues list 
    for (let i = 0; i < maxIter; ++i) {
        fieldNames.forEach(prop => freeTextDict[prop] = new annotations.FreeTextAnnotation());

        for (let index = 0; index < fieldNames.length; ++index) {
            const property = issueProperties[index];
            const fieldName = fieldNames[index];
            
            // Place them on the page, items are placed using the bottom of the last description widget as the initial Y position
            if (property === "description") {
                if (i === 0) {
                    placeCustomWidgets(filteredWidgetsDict[fieldName][pageCorr], freeTextDict[fieldName])
                } else {
                    placeCustomWidgets(filteredWidgetsDict[fieldName][pageCorr], freeTextDict[fieldName], 0, bottom);
                }
            } else {
                if (i === 0) {
                    placeCustomWidgets(filteredWidgetsDict[fieldName][pageCorr], freeTextDict[fieldName], filteredWidgetsDict[fieldName][pageCorr].X, filteredWidgetsDict[fieldName][pageCorr].Y)
                } else {
                    placeCustomWidgets(filteredWidgetsDict[fieldName][pageCorr], freeTextDict[fieldName], filteredWidgetsDict[fieldName][pageCorr].X, bottom)
                }
            }
        }
        
        const pageFraction = freeTextDict["description"].getBottom() / pageHeight;

        for (let fi = 0; fi < fieldNames.length; ++fi) {

            const content = props.state?.[i]?.[issueProperties[fi]];
            const fieldName = fieldNames[fi];
            const padding = new annotations.Rect(0, 0, 0, 0);
            const textColor = new annotations.Color(0, 0, 0);
            const strokeColor = new annotations.Color(255, 255, 255);
            const fontSize = '8pt';
            formulateTextWidget(freeTextDict[fieldName], fieldName, content, padding, textColor, strokeColor, fontSize);

        }
        
        // Adjust the largest box
        freeTextDict["description"].fitText(pageInfo, pageMatrix, pageRotation);

        bottom = freeTextDict["description"].getBottom();
        if (pageFraction > 0.95) {
            // console.log("Turning page");
            pageCorr += 1;
            bottom = filteredWidgetsDict["description"]?.[pageCorr].Y;
        }
        // console.log(`The bottom is at ${bottom}`);
        Object.keys(freeTextDict).forEach(k => result.push(freeTextDict[k]));
    }
    return result;
}

export function placeCustomWidgets(templateWidget: Annotations.WidgetAnnotation, newWidget: Annotations.WidgetAnnotation, x?: number, y?: number) {
    const pageNumber = templateWidget.getPageNumber();
    const newX = x ? x : templateWidget.X;
    const newY = y ? y : templateWidget.Y;
    newWidget.setPageNumber(pageNumber);

    newWidget.X = newX;
    newWidget.Y = newY;

    // console.log("PageNumber placing issues");
    // console.log(pageNumber)
    newWidget.Width = templateWidget.Width;
    newWidget.Height = templateWidget.Height;

    return newWidget;
}

Hi there!

We have an API for this coming in version 8.0 (will be released soon), but for now I found a little trick that should work for you.

The pageInfo object you pass in contains the width and height of the document, and the fitText function uses that width to know how far over to the right it is allowed to expand before it needs to break to the next line. By altering pageInfo.width we can trick the function into breaking into a new line before it actually needs to:

const freeTextAnnot = new Annotations.FreeTextAnnotation();
freeTextAnnot.X = 100;
freeTextAnnot.Width = 100;
// ... other annot config

const pageInfo = doc.getPageInfo(pageNumber);
pageInfo.width = freeTextAnnot.X + freeTextAnnot.Width;
freeTextAnnot.fitText(pageInfo, pageMatrix, pageRotation);

This code will make sure the freeTextAnnot always maintains the width you set it to.

Let me know this goes!

Thanks,
Logan

Hi Logan,
I liked your trick, worked like a charm.

Can I suggest something for version 8. If you guys could include more examples of usage, with type annotations for typescript, in the documentation, that would be very helpful.
Other than that.
Great job.

Thanks

Glad it worked!

Yeah we plan on ramping up our documentation soon. For something like this an example probably wouldn’t have helped because this is such a unique use case, but we definitely will be adding more.

Unfortunately we do not plan on on shipping TypeScript definitions any time soon.

Thanks!
Logan