Why are my paths sometimes not showing up when calculated by my internal functions from variables like %
?
When developing simple graphic tool to create fillable templates which I can convert to images, I noticed that sometimes my drawn paths were disappearing depending on their positioning. This could be fixed by adding 0.02px
or Number.ESPSILON
to some points, but this is not really a solution. Why is that happening?
Code in question:
// Starts with setting canvas
async setCanvas() {
this.$self.get.loading.style.display = '';
this.canvas = this.$self.get.canvas;
this.canvas.setAttribute('width', this.canvas.offsetWidth * this.$.scale)
this.canvas.setAttribute('height', this.canvas.offsetHeight * this.$.scale)
this.ctx = this.canvas.getContext("2d");
this.picture.x = this.decimal(((this.canvas.offsetWidth - this.$.width) / 2)*this.$.scale);
this.picture.y = this.decimal(((this.canvas.offsetHeight - this.$.height) / 2)*this.$.scale);
this.drawCanvas();
// Then we draw on it
await this.draw();
this.loaderDebouncer();
}
[...]
async draw() {
for(let i=0; i < this.$.layout.length; i++) {
const layout = this.$.layout[i];
Object.values(layout.inputs || {}).forEach(input => {
// this should not be relevant as is just integration of Form framework
if (!input.object) {
input.object = FormBuilder.sGenerateFieldItem(input.value || null);
this.setMain(input.value, layout, input);
}
});
await this.drawEntity(layout);
}
}
[...]
async drawEntity(entity) {
const types = {
image: e => this.drawImage(e),
rect: e => this.drawRect(e),
text: e => this.drawText(e),
polygon: e => this.drawPolygon(e), // <- draws the path
default: e => console.error('Not recognized type', e.type),
};
await (types[entity.type] || types.default)(entity);
}
// !! The relevant method!
async drawPolygon(entity) {
const data = entity.data,
path = await this.retrieveValue(data.path || [], entity),
{ width, height, left, top } = this.calculateSizeAndPos(entity),
x = this.decimal(this.picture.x + left),
y = this.decimal(this.picture.y + top);
this.ctx.save();
this.ctx.beginPath();
this.ctx.moveTo(x, y);
path.forEach(point => {
this.polygonFunction(x, y, point[0])(...point.slice(1));
});
this.ctx.fillStyle = this.fill(data.fill) || this.ctx.fillStyle;
this.ctx.fill();
this.ctx.closePath();
if (entity.custom) {
entity.custom(this.ctx);
}
if (entity.outline) {
const outline = entity.outline;
this.polygonFunction(x, y, 'stroke')(this.thickness(outline.thickness || 5), this.fill(outline.fill || '#000'));
}
this.ctx.restore();
}
polygonFunction(x, y, type) {
const fns = {
line: (lineX, lineY,) => {
lineX = this.decimal(x + this.calc(lineX, 'x')), lineY = this.decimal(y + this.calc(lineY, 'y'));
this.ctx.lineTo(lineX, lineY)
},
curve: (cp1x, cp1y, cp2x, cp2y, curveX, curveY) => {
cp1x = this.decimal(x + this.calc(cp1x, 'x'));
cp1y = this.decimal(y + this.calc(cp1y, 'y'));
cp2x = this.decimal(x + this.calc(cp2x, 'x'));
cp2y = this.decimal(y + this.calc(cp2y, 'y'));
curveX = this.decimal(x + this.calc(curveX, 'x'));
curveY = this.decimal(y + this.calc(curveY, 'y'));
this.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, curveX, curveY);
},
stroke: (thickness, fill) => {
this.ctx.save();
this.ctx.strokeStyle = fill || '#000';
this.ctx.lineWidth = this.thickness(thickness, 5);
this.ctx.lineJoin = "round";
this.ctx.miterLimit = 2;
this.ctx.stroke();
this.ctx.restore();
},
default: () => fns.line(...arguments),
};
return fns[type] || fns.default
}
decimal(number, precision = 2) {
return +number.toFixed(precision);
}
retrieveValue(value, entity) {
if (typeof value == 'function') {
return value(entity, this.$.indexes, this);
}
return value;
}
calculateSizeAndPos(entity) {
return {
width: this.calc(entity.size?.w, 'x'),
height: this.calc(entity.size?.h, 'y'),
left: this.calc(entity.pos?.x, 'x'),
top: this.calc(entity.pos?.y, 'y')
}
}
Settings used for the disappearing path:
{
id: 'arrow',
label: 'Rune Position',
editable: true,
type: 'polygon',
pos: {
x: '42.5%',
y: '60%',
},
data: {
path: layer => {
if (layer.inputs.position.value == 0) {
layer.data.fill = '#FFF';
layer.outline.fill = '#000';
return [
['line', '1.5%', '2.5w%'],
['line', '-1.5%', '2.5w%'],
];
}
layer.data.fill = '#000';
layer.outline.fill = '#FFF';
return [
['line', '1.5%', '0'],
['line', '0', '2.5w%'],
['line', '-1.5%', '0'],
];
},
fill: '#FFF'
},
outline: {
fill: '#000',
thickness: .5,
},
inputs: {
position: {
type: 'select',
label: 'Rune Position',
options: [
['Top', '0'],
['Bottom', '1'],
],
value: '0',
inherited: null,
}
}
}
I'm closing this one. It looks like a bug on Firefox 114.0.1 (64-bit) Linux (Ubuntu 20) as opening the page in incognito tag or another browser (Chrome) fixes it for that session.