Convert text to path on canvas using opentype.js and fabric and promises

Viewed 609

I want to write a function that, when called, returns an SVG of the fabric canvas where all text elements are replaced by paths.

This is what I came up with so far

const toPath = async () => {

    const myCanvas = new fabric.Canvas("render-canvas"); //create a canvas for converting
    
    myCanvas.loadFromJSON(canvas.toJSON()); //copy the orignal canvas into it


    const objects = myCanvas.getObjects();

    objects.forEach((object, index) => { //iterate over all objects
        if(object.type === "i-text") {   //if i-text, get font and use opentype.js to generate path
            if(object.fontFamily !== "") { //opentype.load is async returning a promise
                opentype.load(process.env.PUBLIC_URL+"/fonts/ttf/"+getFontByName(object.fontFamily)["url"])
                .then(font => {
                    const path = font.getPath(object.text, 0, 0, object.fontSize);
                    const fabricPath = new fabric.Path(path.toPathData(20), {
                        //some options, size, pos, color ect copied from original
                    });
                    console.log(index);

                    myCanvas.insertAt(fabricPath, index);
                    myCanvas.remove(object);
                    console.log(myCanvas.getObjects()); //contains correct paths
                })
                .catch(err => console.log("Error fetching font:", err));
            }
        }
    });

    //somewhere above I need to return a promise that is resolved when the loop is finished
    return myCanvas; //wrong here, returns unconverted copy always because of promises
}

I want to call the function somehow like this:

const pathCanvas = toPath();
// when all promises resolved
svg = pathCanvas.then(canvas => canvas.toSVG());

Now how can I use a promise here, to resolve it using fabric.toSVG() once the whole loop ran successfully?

1 Answers

Here's how I solved this now: I created an empty array for all the promises. In loop it gets filled with promises that then get resolved using Promise.all()

const toPath = async () => {
    
    //cut irrelevant fabric canvas initialization

    //init empty array
    let promises = []; 

    objects.forEach((object, index) => {
        //in loop fill array with promises
        promises = [...promises, new Promise((resolve, reject) => {
            if(object.type === "i-text") {
                if(object.fontFamily !== "") {
                    opentype.load(process.env.PUBLIC_URL+"/fonts/ttf/"+getFontByName(object.fontFamily)["url"])
                    .then(font => {
                        const path = font.getPath(object.text, 0, 0, object.fontSize);
                        const fabricPath = new fabric.Path(path.toPathData(20), {
                             //text options such as color, size, position ect
                        });

                        myCanvas.insertAt(fabricPath, index);
                        resolve(object); //I didnt know what to put here so I just resolve to object, its not used.
                    })
                    .catch(err => {
                        console.log("Error fetching font:", err);
                        reject(err);
                    });
                } else {
                    reject("no fontFamily set");
                }
            } else { //any object not an i-text, just gets added
                myCanvas.insertAt(object, index);
                resolve(object);
            }
        })];
    });

    //resolve all promises
    await Promise.all(promises);
    //after all promises resolved, get svg of the fabric canvas
    const svg = myCanvas.toSVG();
    //return it
    return svg;
}

It works, however I don't know if it's the correct way to do it or if there would be a better/cleaner way. If you know such, please comment. Promises.all probably could also run with .then() and .catch() but I wasn't sure.

Related