Iosevka/support/curve-util.js
2020-03-21 13:48:45 -07:00

276 lines
6.6 KiB
JavaScript

"use strict";
const typoGeom = require("typo-geom");
const Point = require("./point");
const { mix } = require("./utils");
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.curveToContour = function(curve, err) {
let exitPoints = [];
const z0 = curve.eval(0);
const z1 = curve.eval(1);
exitPoints.push({
x: z0.x,
y: z0.y,
cubic: false,
on: true
});
const offPoints = typoGeom.Quadify.auto(curve, err || 1);
for (let k = 0; k < offPoints.length; k++) {
const z = offPoints[k];
if (k > 0) {
const z0 = offPoints[k - 1];
exitPoints.push({
x: (z.x + z0.x) / 2,
y: (z.y + z0.y) / 2,
on: true
});
}
exitPoints.push({
x: z.x,
y: z.y,
cubic: false,
on: false
});
}
exitPoints.push({
x: z1.x,
y: z1.y,
cubic: false,
on: true
});
return exitPoints;
};
function removeMids(contour) {
for (let rounds = 0; rounds < 255; rounds++) {
const n0 = contour.length;
let last = contour.length - 1;
for (let j = 0; j < contour.length - 1; j++) {
if (
Math.abs(contour[j].x - contour[j + 1].x) < 1 &&
Math.abs(contour[j].y - contour[j + 1].y) < 1
) {
contour[j + 1].rem = true;
contour[j].on = true;
}
}
while (
last > 0 &&
Math.abs(contour[0].x - contour[last].x) < 1 &&
Math.abs(contour[0].y - contour[last].y) < 1
) {
contour[last].rem = true;
contour[0].on = true;
last -= 1;
}
contour = contour.filter(x => !x.rem);
last = contour.length - 1;
for (let j = 1; j < contour.length - 1; j++) {
if (!contour[j - 1].on && contour[j].on && !contour[j + 1].on) {
const mx = contour[j - 1].x + contour[j + 1].x;
const my = contour[j - 1].y + contour[j + 1].y;
const dy = contour[j - 1].y - contour[j + 1].y;
if (
Math.abs(dy) >= 1 &&
Math.abs(contour[j].x * 2 - mx) < 1 &&
Math.abs(contour[j].y * 2 - my) < 1
) {
contour[j].rem = true;
}
}
}
if (!contour[last].rem && !contour[last].on && contour[0].on && !contour[1].on) {
const mx = contour[last].x + contour[1].x;
const my = contour[last].y + contour[1].y;
if (Math.abs(contour[0].x * 2 - mx) < 1 && Math.abs(contour[0].y * 2 - my) < 1) {
contour[0].rem = true;
}
}
contour = contour.filter(x => !x.rem);
const n = contour.length;
if (n >= n0) break;
}
return contour;
}
function extPrior(a, b) {
return a.y < b.y || (a.y === b.y && ((a.on && !b.on) || (a.on === b.on && a.x < b.x)));
}
function canonicalStart(_points) {
const points = _points.reverse().map(z => {
z.x = Math.round(z.x * 1024) / 1024;
z.y = Math.round(z.y * 1024) / 1024;
return z;
});
let jm = 0;
for (var j = 0; j < points.length * 2; j++) {
if (extPrior(points[j % points.length], points[jm])) {
jm = j % points.length;
}
}
return points.slice(jm).concat(points.slice(0, jm));
}
function colinear(x1, y1, x2, y2, x3, y3, err) {
const det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
return det <= err && det >= -err;
}
function inspan(a, b, c) {
if (a > c) return inspan(c, b, a);
return a <= b && b <= c;
}
function handle(z1, z2, z3, z4, err) {
if (
colinear(z1.x, z1.y, z2.x, z2.y, z4.x, z4.y, err) &&
colinear(z1.x, z1.y, z3.x, z3.y, z4.x, z4.y, err) &&
inspan(z1.x, z2.x, z4.x) &&
inspan(z1.y, z2.y, z4.y) &&
inspan(z1.x, z3.x, z4.x) &&
inspan(z1.y, z3.y, z4.y)
) {
return [];
}
const curve = new typoGeom.Curve.Bez3(z1, z2, z3, z4);
const offPoints = typoGeom.Quadify.auto(curve, err);
const ans = [];
for (const z of offPoints) {
ans.push(Point.offFrom(z));
}
return ans;
}
function convertContourToTt(contour, err) {
if (contour.length === 0) return [];
if (contour.length === 1) return [contour[0]];
err = err || 1 / 4;
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];
const quadZs = handle(z0, z1, z2, z3, err);
for (const z of quadZs) newContour.push(z);
newContour.push(Point.cornerFrom(z3));
z0 = z3;
j += 2;
} else {
const zc = z;
let zf = contour[j + 1] ? contour[j + 1] : contour[0];
if (!zf.on) {
zf = Point.cornerFromXY(mix(zc.x, zf.x, 0.5), mix(zc.y, zf.y, 0.5));
}
newContour.push(Point.offFrom(zc));
newContour.push(Point.cornerFrom(zf));
z0 = zf;
j++;
}
}
return newContour;
}
function byFirstPointCoord(a, b) {
if (!a.length) return -1;
if (!b.length) return 1;
let z1 = a[0];
let z2 = b[0];
return z1.y !== z2.y
? z1.y - z2.y
: z1.x !== z2.x
? z1.x - z2.x
: byFirstPointCoord(a.slice(1), b.slice(1));
}
function convertContourListToTt(contours, err) {
err = err || 1 / 4;
let ans = [];
for (let c of contours) {
ans.push(canonicalStart(removeMids(convertContourToTt(c, err))));
}
return ans.sort(byFirstPointCoord);
}
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[j + 1] : contour[0];
if (!zf.on) {
zf = Point.cornerFromXY(mix(zc.x, zf.x, 0.5), mix(zc.y, zf.y, 0.5));
}
const x1 = mix(z0.x, zc.x, 2 / 3);
const y1 = mix(z0.y, zc.y, 2 / 3);
const x2 = mix(zf.x, zc.x, 2 / 3);
const y2 = mix(zf.y, zc.y, 2 / 3);
newContour.push(Point.cubicOffFromXY(x1, y1));
newContour.push(Point.cubicOffFromXY(x2, y2));
newContour.push(Point.cornerFrom(zf));
z0 = zf;
j++;
}
}
return newContour;
}
exports.convertContourToTt = convertContourToTt;
exports.convertContourToCubic = convertContourToCubic;
exports.convertContourListToTt = convertContourListToTt;