Skip to content

exporter.toSVG mutates models unexpectedly #628

Open
@xavi-

Description

@xavi-

It looks like the toSVG method mutates the models passed into it, which is expected. This came up while trying to cache submodels to improve performance.

Here's a minimal repro case:

const makerjs = require("makerjs");
const opentype = require("opentype.js");

const font = opentype.loadSync("./src/fonts/ReliefSingleLineCAD-Regular.ttf");

const text = new makerjs.models.Text(font, "Hi", 12);
const model = {
	models: {
		text1: text,
		text2: text,
	},
};

const svg = makerjs.exporter.toSVG(model);
console.log(svg);

The result is this exception:

TypeError: Cannot read properties of undefined (reading 'startT')
    at getActualBezierRange (/Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:8591:43)
    at /Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:8790:37
    at Array.forEach (<anonymous>)
    at /Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:8785:27
    at _loop_3 (/Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:6054:21)
    at Object.findChains (/Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:6058:17)
    at BezierCurve.getBezierSeeds (/Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:8773:31)
    at /Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:6170:73
    at Array.forEach (<anonymous>)
    at swapBezierPathsWithSeeds (/Users/xavi/code/maker-test/server/node_modules/makerjs/dist/index.js:6161:21)

I'm using node v22.11.0 with makerjs@0.18.1 and opentype.js@1.3.4

The issue seems to be somewhere in here:

beziers.forEach(wm => {
var b = wm.childModel as models.BezierCurve;
if (swap) {
//set layer prior to looking for seeds by layer
if (wm.layer != undefined && wm.layer !== '') {
b[tempLayerKey] = (b as IModel).layer;
(b as IModel).layer = wm.layer;
}
//use seeds as path, hide the arc paths from findChains()
var bezierPartsByLayer = models.BezierCurve.getBezierSeeds(b, { byLayers: true, pointMatchingDistance });
for (var layer in bezierPartsByLayer) {
var bezierSeeds = bezierPartsByLayer[layer];
if (bezierSeeds.length > 0) {
b[tempKey] = b.paths;
var newPaths: IPathMap = {};
bezierSeeds.forEach(function (seed, i) {
seed.layer = layer;
newPaths['seed_' + i] = seed;
});
b.paths = newPaths;
}
}
} else {
//revert the above
if (tempKey in b) {
b.paths = b[tempKey] as IPathMap;
delete b[tempKey];
}
if (tempLayerKey in b) {
if (b[tempLayerKey] == undefined) {
delete (b as IModel).layer;
} else {
(b as IModel).layer = b[tempLayerKey] as string;
}
delete b[tempLayerKey];
}
}
});

It's not obvious to me how to fix it, so I figured I'd open an issue

The exception doesn't occur when useSvgPathOnly option is enabled, which double confirms that the issue is related to the findChains function

Workaround

// Clone submodels
const model = {
	models: {
		text1: makerjs.cloneObject(text),
		text2: makerjs.cloneObject(text),
	},
};

This does works, but it add a bit of overhead. Also, it's surprising that an exporter mutates the underlying model

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions