Iosevka/font-src/support/curve-util.js

163 lines
3.9 KiB
JavaScript

"use strict";
const TypoGeom = require("typo-geom");
const Point = require("./point");
const Transform = require("./transform");
exports.GEOMETRY_PRECISION = 1 / 4;
exports.RECIP_GEOMETRY_PRECISION = 4;
exports.BOOLE_RESOLUTION = 0x4000;
exports.OffsetCurve = class OffsetCurve {
constructor(bone, offset, contrast) {
this.bone = bone;
this.offset = offset;
this.contrast = contrast;
}
eval(t) {
const c = this.bone.eval(t);
const d = this.bone.derivative(t);
const absD = Math.hypot(d.x, d.y);
return {
x: c.x - (d.y / absD) * this.offset * this.contrast,
y: c.y + (d.x / absD) * this.offset
};
}
derivative(t) {
const DELTA = 1 / 0x10000;
const forward = this.eval(t + DELTA);
const backward = this.eval(t - DELTA);
return {
x: (forward.x - backward.x) / (2 * DELTA),
y: (forward.y - backward.y) / (2 * DELTA)
};
}
};
exports.convertContourToCubic = function convertContourToCubic(contour) {
if (!contour || !contour.length) return [];
const newContour = [];
let z0 = contour[0];
newContour.push(Point.cornerFrom(z0));
for (let j = 1; j < contour.length; j++) {
const z = contour[j];
if (z.on) {
newContour.push(Point.cornerFrom(z));
z0 = z;
} else if (z.cubic) {
const z1 = z;
const z2 = contour[j + 1];
const z3 = contour[j + 2];
newContour.push(Point.cubicOffFrom(z1));
newContour.push(Point.cubicOffFrom(z2));
newContour.push(Point.cornerFrom(z3));
z0 = z3;
j += 2;
} else {
const zc = z;
let zf = contour[j + 1] || contour[0];
const zfIsCorner = zf.on;
if (!zfIsCorner) zf = Point.cornerFrom(zc).mix(0.5, zf);
newContour.push(Point.cubicOffFrom(z0).mix(2 / 3, zc));
newContour.push(Point.cubicOffFrom(zf).mix(2 / 3, zc));
newContour.push(Point.cornerFrom(zf));
z0 = zf;
if (zfIsCorner) j++;
}
}
return newContour;
};
exports.convertShapeToArcs = function convertShapeToArcs(shape) {
return shape.map(convertContourToArcs);
};
function convertContourToArcs(contour) {
if (!contour || !contour.length) return [];
const newContour = [];
let z0 = Point.cornerFrom(contour[0]);
for (let j = 1; j < contour.length; j++) {
const z = contour[j];
if (z.on) {
newContour.push(
TypoGeom.Arcs.Bez3.fromStraightSegment(
new TypoGeom.Arcs.StraightSegment(z0, Point.cornerFrom(z))
)
);
z0 = z;
} else if (z.cubic) {
const z1 = z;
const z2 = contour[j + 1];
const z3 = contour[j + 2];
newContour.push(
new TypoGeom.Arcs.Bez3(
z0,
Point.cubicOffFrom(z1),
Point.cubicOffFrom(z2),
Point.cornerFrom(z3)
)
);
z0 = z3;
j += 2;
} else {
const zc = z;
let zf = contour[j + 1] || contour[0];
const zfIsCorner = zf.on;
if (!zfIsCorner) zf = Point.cornerFrom(zc).mix(0.5, zf);
newContour.push(
new TypoGeom.Arcs.Bez3(
z0,
Point.cubicOffFrom(z0).mix(2 / 3, zc),
Point.cubicOffFrom(zf).mix(2 / 3, zc),
Point.cornerFrom(zf)
)
);
z0 = zf;
if (zfIsCorner) j++;
}
}
return newContour;
}
exports.BezToContoursSink = class BezToContoursSink {
constructor(gizmo) {
this.gizmo = gizmo || Transform.Id();
this.contours = [];
this.lastContour = [];
}
beginShape() {}
endShape() {
if (this.lastContour.length) {
this.contours.push(this.lastContour);
}
this.lastContour = [];
}
moveTo(x, y) {
this.endShape();
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
}
lineTo(x, y) {
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
}
curveTo(xc, yc, x, y) {
this.lastContour.push(Point.transformedXY(this.gizmo, xc, yc, false, false));
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
}
cubicTo(x1, y1, x2, y2, x, y) {
this.lastContour.push(Point.transformedXY(this.gizmo, x1, y1, false, true));
this.lastContour.push(Point.transformedXY(this.gizmo, x2, y2, false, true));
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
}
};