Use primitive-quadify-off-curves to do curve conversion.
This commit is contained in:
parent
b4ba453ad8
commit
bae8c1c6c0
9 changed files with 462 additions and 468 deletions
24
.eslintrc.json
Normal file
24
.eslintrc.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"commonjs": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018,
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"rules": {
|
||||||
|
"indent": ["error", "tab", { "SwitchCase": 1 }],
|
||||||
|
"linebreak-style": ["error", "windows"],
|
||||||
|
"quotes": ["error", "double", { "allowTemplateLiterals": true }],
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"no-var":"error",
|
||||||
|
"no-console": 0,
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }]
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,61 +152,63 @@ if (argv.charmap) {
|
||||||
fs.writeFileSync(argv.charmap, JSON.stringify(charmap), "utf8");
|
fs.writeFileSync(argv.charmap, JSON.stringify(charmap), "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.o) {
|
function regulateGlyph(g, skew) {
|
||||||
function regulateGlyph(g, skew) {
|
if (!g.contours) return;
|
||||||
if (!g.contours) return;
|
|
||||||
|
|
||||||
// Regulate
|
// Regulate
|
||||||
for (let k = 0; k < g.contours.length; k++) {
|
for (let k = 0; k < g.contours.length; k++) {
|
||||||
const contour = g.contours[k];
|
const contour = g.contours[k];
|
||||||
for (let p = 0; p < contour.length; p++) {
|
for (let p = 0; p < contour.length; p++) {
|
||||||
contour[p].x += contour[p].y * skew;
|
contour[p].x += contour[p].y * skew;
|
||||||
if (!contour[p].on) continue;
|
if (!contour[p].on) continue;
|
||||||
contour[p].x = Math.round(contour[p].x);
|
contour[p].x = Math.round(contour[p].x);
|
||||||
}
|
}
|
||||||
let offJ = null,
|
let offJ = null,
|
||||||
mx = null;
|
mx = null;
|
||||||
for (let p = 0; p < contour.length; p++) {
|
for (let p = 0; p < contour.length; p++) {
|
||||||
if (!contour[p].on) continue;
|
if (!contour[p].on) continue;
|
||||||
if (offJ) {
|
if (offJ) {
|
||||||
const origx = contour[p].x;
|
const origx = contour[p].x;
|
||||||
const rx = Math.round(contour[p].x * 4) / 4;
|
const rx = Math.round(contour[p].x * 4) / 4;
|
||||||
const origx0 = mx;
|
const origx0 = mx;
|
||||||
const rx0 = contour[offJ - 1].x;
|
const rx0 = contour[offJ - 1].x;
|
||||||
if (origx === origx0) continue;
|
if (origx === origx0) continue;
|
||||||
for (let poff = offJ; poff < p; poff++) {
|
for (let poff = offJ; poff < p; poff++) {
|
||||||
contour[poff].x =
|
contour[poff].x =
|
||||||
((contour[poff].x - origx0) / (origx - origx0)) * (rx - rx0) + rx0;
|
((contour[poff].x - origx0) / (origx - origx0)) * (rx - rx0) + rx0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
mx = contour[p].x;
|
|
||||||
contour[p].x = Math.round(contour[p].x * 4) / 4;
|
|
||||||
offJ = p + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const c1 = [];
|
|
||||||
for (let k = 0; k < g.contours.length; k++) {
|
|
||||||
c1.push(Glyph.contourToStandardCubic(g.contours[k]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// De-overlap
|
|
||||||
g.contours = caryllShapeOps.removeOverlap(c1, 1, 256, true);
|
|
||||||
|
|
||||||
// Finalize
|
|
||||||
Glyph.prototype.cleanup.call(g, 0.1);
|
|
||||||
g.contours = c2q.contours(g.contours);
|
|
||||||
for (let k = 0; k < g.contours.length; k++) {
|
|
||||||
const contour = g.contours[k];
|
|
||||||
for (let p = 0; p < contour.length; p++) {
|
|
||||||
contour[p].x -= contour[p].y * skew;
|
|
||||||
}
|
}
|
||||||
|
mx = contour[p].x;
|
||||||
|
contour[p].x = Math.round(contour[p].x * 4) / 4;
|
||||||
|
offJ = p + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const c1 = [];
|
||||||
|
for (let k = 0; k < g.contours.length; k++) {
|
||||||
|
c1.push(Glyph.contourToStandardCubic(g.contours[k]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-overlap
|
||||||
|
g.contours = caryllShapeOps.removeOverlap(c1, 1, 256, true);
|
||||||
|
|
||||||
|
// Finalize
|
||||||
|
g.contours = c2q.contours(g.contours);
|
||||||
|
for (let k = 0; k < g.contours.length; k++) {
|
||||||
|
const contour = g.contours[k];
|
||||||
|
for (let p = 0; p < contour.length; p++) {
|
||||||
|
contour[p].x -= contour[p].y * skew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.o) {
|
||||||
const skew =
|
const skew =
|
||||||
(argv.uprightify ? 1 : 0) * Math.tan(((font.post.italicAngle || 0) / 180) * Math.PI);
|
(argv.uprightify ? 1 : 0) * Math.tan(((font.post.italicAngle || 0) / 180) * Math.PI);
|
||||||
|
const excludeUnicodes = new Set();
|
||||||
|
excludeUnicodes.add(0x80);
|
||||||
|
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicodes.add(c);
|
||||||
// autoref
|
// autoref
|
||||||
autoref(font.glyf);
|
autoref(font.glyf, excludeUnicodes);
|
||||||
// regulate
|
// regulate
|
||||||
for (let g of font.glyf) regulateGlyph(g, skew);
|
for (let g of font.glyf) regulateGlyph(g, skew);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@
|
||||||
"ttf2woff2": "^2.0.3",
|
"ttf2woff2": "^2.0.3",
|
||||||
"unorm": "^1.4.1",
|
"unorm": "^1.4.1",
|
||||||
"verda": "^0.1.4",
|
"verda": "^0.1.4",
|
||||||
"yargs": "^12.0.0"
|
"yargs": "^12.0.0",
|
||||||
|
"primitive-quadify-off-curves": "^0.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^5.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
var Point = require("./point");
|
"use strict";
|
||||||
|
|
||||||
|
const Point = require("./point");
|
||||||
|
|
||||||
function delta(a, b) {
|
function delta(a, b) {
|
||||||
return Math.round((a - b) * 32);
|
return Math.round((a - b) * 32);
|
||||||
|
@ -6,11 +8,11 @@ function delta(a, b) {
|
||||||
|
|
||||||
function contourHash(c) {
|
function contourHash(c) {
|
||||||
if (!c || c.length < 2) return ".";
|
if (!c || c.length < 2) return ".";
|
||||||
var lx = c[0].x,
|
let lx = c[0].x,
|
||||||
ly = c[0].y;
|
ly = c[0].y;
|
||||||
var buf = "";
|
let buf = "";
|
||||||
for (var j = 1; j < c.length; j++) {
|
for (let j = 1; j < c.length; j++) {
|
||||||
var z = c[j];
|
const z = c[j];
|
||||||
buf += `${z.on ? "o" : "f"}${z.cubic ? "c" : "q"}${delta(z.x, lx)},${delta(z.y, ly)};`;
|
buf += `${z.on ? "o" : "f"}${z.cubic ? "c" : "q"}${delta(z.x, lx)},${delta(z.y, ly)};`;
|
||||||
(lx = z.x), (ly = z.y);
|
(lx = z.x), (ly = z.y);
|
||||||
}
|
}
|
||||||
|
@ -19,8 +21,8 @@ function contourHash(c) {
|
||||||
|
|
||||||
function match(g1, g2, _n) {
|
function match(g1, g2, _n) {
|
||||||
for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) {
|
for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) {
|
||||||
var found = true;
|
let found = true;
|
||||||
for (var k = j; k < g2.contours.length && k - j < g1.contours.length; k++) {
|
for (let k = j; k < g2.contours.length && k - j < g1.contours.length; k++) {
|
||||||
if (
|
if (
|
||||||
g1.contours[k - j].hash !== g2.contours[k].hash ||
|
g1.contours[k - j].hash !== g2.contours[k].hash ||
|
||||||
!(
|
!(
|
||||||
|
@ -46,7 +48,8 @@ function match(g1, g2, _n) {
|
||||||
glyph: g1.name,
|
glyph: g1.name,
|
||||||
_n: _n,
|
_n: _n,
|
||||||
x: refX,
|
x: refX,
|
||||||
y: refY
|
y: refY,
|
||||||
|
roundToGrid: false
|
||||||
});
|
});
|
||||||
g2.contours.splice(j, g1.contours.length);
|
g2.contours.splice(j, g1.contours.length);
|
||||||
return true;
|
return true;
|
||||||
|
@ -56,31 +59,31 @@ function match(g1, g2, _n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlinkRef(g, dx, dy, glyf) {
|
function unlinkRef(g, dx, dy, glyf) {
|
||||||
var cntrs = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic)));
|
let contours = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic)));
|
||||||
if (g.references)
|
if (g.references)
|
||||||
for (let r of g.references) {
|
for (let r of g.references) {
|
||||||
cntrs = cntrs.concat(unlinkRef(glyf[r._n], r.x + dx, r.y + dy, glyf));
|
contours = contours.concat(unlinkRef(glyf[r._n], r.x + dx, r.y + dy, glyf));
|
||||||
}
|
}
|
||||||
return cntrs;
|
return contours;
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoref(glyf) {
|
function autoref(glyf, excludeUnicodes) {
|
||||||
supporessNaN(glyf);
|
suppressNaN(glyf);
|
||||||
|
|
||||||
for (var j = 0; j < glyf.length; j++) {
|
for (let j = 0; j < glyf.length; j++) {
|
||||||
var g = glyf[j];
|
const g = glyf[j];
|
||||||
if (g.contours) {
|
if (g.contours) {
|
||||||
for (var k = 0; k < g.contours.length; k++) {
|
for (let k = 0; k < g.contours.length; k++) {
|
||||||
var contour = g.contours[k];
|
const contour = g.contours[k];
|
||||||
contour.hash = contourHash(contour);
|
contour.hash = contourHash(contour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refl-referencify, forward.
|
// Refl-referencify, forward.
|
||||||
for (var j = 0; j < glyf.length; j++) {
|
for (let j = 0; j < glyf.length; j++) {
|
||||||
if (!glyf[j].contours.length || (glyf[j].references && glyf[j].references.length)) continue;
|
if (!glyf[j].contours.length || (glyf[j].references && glyf[j].references.length)) continue;
|
||||||
for (var k = j + 1; k < glyf.length; k++) {
|
for (let k = j + 1; k < glyf.length; k++) {
|
||||||
if (glyf[j].contours.length === glyf[k].contours.length) {
|
if (glyf[j].contours.length === glyf[k].contours.length) {
|
||||||
match(glyf[j], glyf[k], j);
|
match(glyf[j], glyf[k], j);
|
||||||
}
|
}
|
||||||
|
@ -88,44 +91,42 @@ function autoref(glyf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// referencify, backward
|
// referencify, backward
|
||||||
for (var j = 0; j < glyf.length; j++) {
|
for (let j = 0; j < glyf.length; j++) {
|
||||||
if (glyf[j].cmpPriority < 0) continue;
|
if (glyf[j].cmpPriority < 0) continue;
|
||||||
if (!glyf[j].contours.length) continue;
|
if (!glyf[j].contours.length) continue;
|
||||||
if (glyf[j].references && glyf[j].references.length) continue;
|
if (glyf[j].references && glyf[j].references.length) continue;
|
||||||
for (var k = glyf.length - 1; k >= 0; k--) {
|
for (let k = glyf.length - 1; k >= 0; k--) {
|
||||||
|
if (glyf[j].contours.length > glyf[k].contours.length) continue;
|
||||||
if (
|
if (
|
||||||
glyf[j].contours.length > glyf[k].contours.length ||
|
glyf[j].contours.length === glyf[k].contours.length &&
|
||||||
(glyf[j].contours.length === glyf[k].contours.length &&
|
!(glyf[k].references && glyf[k].references.length)
|
||||||
!(glyf[k].references && glyf[k].references.length))
|
) {
|
||||||
)
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
while (match(glyf[j], glyf[k], j)) "pass";
|
while (match(glyf[j], glyf[k], j)) "pass";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlink composite
|
// unlink composite
|
||||||
for (var j = 0; j < glyf.length; j++) {
|
for (let j = 0; j < glyf.length; j++) {
|
||||||
if (!glyf[j].flatten) {
|
if (!glyf[j].references || glyf[j].references.length === 0) continue;
|
||||||
if (!glyf[j].references || glyf[j].references.length === 0) continue;
|
if (
|
||||||
if (
|
glyf[j].contours.length === 0 &&
|
||||||
glyf[j].contours.length === 0 &&
|
!(glyf[j].unicode && excludeUnicodes.has(glyf[j].unicode[0]))
|
||||||
!(glyf[j].unicode && glyf[j].unicode[0] < 0x80) &&
|
) {
|
||||||
!(glyf[j].unicode && glyf[j].unicode[0] >= 0x2500 && glyf[j].unicode[0] <= 0x259f)
|
continue;
|
||||||
)
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
var cs = unlinkRef(glyf[j], 0, 0, glyf);
|
glyf[j].contours = unlinkRef(glyf[j], 0, 0, glyf);
|
||||||
glyf[j].contours = g.contours.concat(cs);
|
|
||||||
glyf[j].references = [];
|
glyf[j].references = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function supporessNaN(glyf) {
|
function suppressNaN(glyf) {
|
||||||
for (var j = 0; j < glyf.length; j++) {
|
for (let j = 0; j < glyf.length; j++) {
|
||||||
var g = glyf[j];
|
let g = glyf[j];
|
||||||
if (!g.contours) continue;
|
if (!g.contours) continue;
|
||||||
for (var k = 0; k < g.contours.length; k++) {
|
for (let k = 0; k < g.contours.length; k++) {
|
||||||
var contour = g.contours[k];
|
let contour = g.contours[k];
|
||||||
for (let z of contour) {
|
for (let z of contour) {
|
||||||
if (!isFinite(z.x)) z.x = 0;
|
if (!isFinite(z.x)) z.x = 0;
|
||||||
if (!isFinite(z.y)) z.y = 0;
|
if (!isFinite(z.y)) z.y = 0;
|
||||||
|
|
|
@ -1,24 +1,15 @@
|
||||||
var Transform = require('./transform.js');
|
"use strict";
|
||||||
|
|
||||||
var ANGLES = 12;
|
const Transform = require("./transform.js");
|
||||||
var VERYCROWD = 2;
|
const quadify = require("primitive-quadify-off-curves");
|
||||||
var SMALLANGLE = 0.05;
|
|
||||||
var SMALLANGLE_CLEANMORE = 0.075;
|
const ANGLES = 12;
|
||||||
var CROWD = 4;
|
const SMALL = 1e-4;
|
||||||
var LOOSE = 6;
|
|
||||||
var SMALL = 1e-4;
|
|
||||||
var CLOSE = 1;
|
|
||||||
function cross(z1, z2, z3) {
|
|
||||||
return (z2.x - z1.x) * (z3.y - z1.y) - (z3.x - z1.x) * (z2.y - z1.y);
|
|
||||||
}
|
|
||||||
function dot(z1, z2, z3) {
|
|
||||||
return (z2.x - z1.x) * (z3.x - z1.x) + (z3.y - z1.y) * (z2.y - z1.y);
|
|
||||||
}
|
|
||||||
function solveTS(a, b, c, out, flag) {
|
function solveTS(a, b, c, out, flag) {
|
||||||
var delta = b * b - 4 * a * c;
|
const delta = b * b - 4 * a * c;
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
var t1 = (Math.sqrt(delta) - b) / (2 * a);
|
const t1 = (Math.sqrt(delta) - b) / (2 * a);
|
||||||
var t2 = (-Math.sqrt(delta) - b) / (2 * a);
|
const t2 = (-Math.sqrt(delta) - b) / (2 * a);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
if (t1 >= 0 && t1 <= 1) out.push(t1);
|
if (t1 >= 0 && t1 <= 1) out.push(t1);
|
||||||
if (t2 >= 0 && t2 <= 1) out.push(t2);
|
if (t2 >= 0 && t2 <= 1) out.push(t2);
|
||||||
|
@ -27,7 +18,7 @@ function solveTS(a, b, c, out, flag) {
|
||||||
if (t2 > 0 && t2 < 1) out.push(t2);
|
if (t2 > 0 && t2 < 1) out.push(t2);
|
||||||
}
|
}
|
||||||
} else if (delta === 0) {
|
} else if (delta === 0) {
|
||||||
var t = -b / (2 * a);
|
const t = -b / (2 * a);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
if (t >= 0 && t <= 1) out.push(t);
|
if (t >= 0 && t <= 1) out.push(t);
|
||||||
} else {
|
} else {
|
||||||
|
@ -36,22 +27,22 @@ function solveTS(a, b, c, out, flag) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function findExtrema(z1, z2, z3, z4, out) {
|
function findExtrema(z1, z2, z3, z4, out) {
|
||||||
var a = 3 * (-z1.y + 3 * z2.y - 3 * z3.y + z4.y);
|
const a = 3 * (-z1.y + 3 * z2.y - 3 * z3.y + z4.y);
|
||||||
var b = 6 * (z1.y - 2 * z2.y + z3.y);
|
const b = 6 * (z1.y - 2 * z2.y + z3.y);
|
||||||
var c = 3 * (z2.y - z1.y);
|
const c = 3 * (z2.y - z1.y);
|
||||||
solveTS(a, b, c, out);
|
solveTS(a, b, c, out);
|
||||||
}
|
}
|
||||||
function findInflections(z1, z2, z3, z4, out) {
|
// function findInflections(z1, z2, z3, z4, out) {
|
||||||
var ax = z2.x - z1.x;
|
// const ax = z2.x - z1.x;
|
||||||
var ay = z2.y - z1.y;
|
// const ay = z2.y - z1.y;
|
||||||
var bx = z3.x - z2.x - ax;
|
// const bx = z3.x - z2.x - ax;
|
||||||
var by = z3.y - z2.y - ay;
|
// const by = z3.y - z2.y - ay;
|
||||||
var cx = z4.x - z3.x - ax - 2 * bx;
|
// const cx = z4.x - z3.x - ax - 2 * bx;
|
||||||
var cy = z4.y - z3.y - ay - 2 * by;
|
// const cy = z4.y - z3.y - ay - 2 * by;
|
||||||
solveTS(bx * cy - by * cx, ax * cy - ay * cx, ax * by - ay * bx, out, true);
|
// solveTS(bx * cy - by * cx, ax * cy - ay * cx, ax * by - ay * bx, out, true);
|
||||||
}
|
// }
|
||||||
function rotate(z, angle) {
|
function rotate(z, angle) {
|
||||||
var c = Math.cos(angle),
|
const c = Math.cos(angle),
|
||||||
s = Math.sin(angle);
|
s = Math.sin(angle);
|
||||||
return {
|
return {
|
||||||
x: c * z.x + s * z.y,
|
x: c * z.x + s * z.y,
|
||||||
|
@ -62,9 +53,9 @@ function ASCEND(a, b) {
|
||||||
return a - b;
|
return a - b;
|
||||||
}
|
}
|
||||||
function fineAllExtrema(z1, z2, z3, z4, angles) {
|
function fineAllExtrema(z1, z2, z3, z4, angles) {
|
||||||
var exs = [];
|
let exs = [];
|
||||||
findInflections(z1, z2, z3, z4, exs);
|
// findInflections(z1, z2, z3, z4, exs);
|
||||||
for (var a = 0; a < angles; a += 1) {
|
for (let a = 0; a < angles; a += 1) {
|
||||||
findExtrema(z1, z2, z3, z4, exs);
|
findExtrema(z1, z2, z3, z4, exs);
|
||||||
z1 = rotate(z1, Math.PI / angles);
|
z1 = rotate(z1, Math.PI / angles);
|
||||||
z2 = rotate(z2, Math.PI / angles);
|
z2 = rotate(z2, Math.PI / angles);
|
||||||
|
@ -76,13 +67,16 @@ function fineAllExtrema(z1, z2, z3, z4, angles) {
|
||||||
function mix(z1, z2, t) {
|
function mix(z1, z2, t) {
|
||||||
if (t <= 0) return z1;
|
if (t <= 0) return z1;
|
||||||
if (t >= 1) return z2;
|
if (t >= 1) return z2;
|
||||||
var x = (1 - t) * z1.x + t * z2.x, y = (1 - t) * z1.y + t * z2.y
|
let x = (1 - t) * z1.x + t * z2.x,
|
||||||
|
y = (1 - t) * z1.y + t * z2.y;
|
||||||
return { x: x, y: y };
|
return { x: x, y: y };
|
||||||
}
|
}
|
||||||
function bez2(z1, z2, z3, t) {
|
function bez2(z1, z2, z3, t) {
|
||||||
if (t <= 0) return z1;
|
if (t <= 0) return z1;
|
||||||
if (t >= 1) return z3;
|
if (t >= 1) return z3;
|
||||||
var c1 = (1 - t) * (1 - t), c2 = 2 * (1 - t) * t, c3 = t * t;
|
let c1 = (1 - t) * (1 - t),
|
||||||
|
c2 = 2 * (1 - t) * t,
|
||||||
|
c3 = t * t;
|
||||||
return {
|
return {
|
||||||
x: c1 * z1.x + c2 * z2.x + c3 * z3.x,
|
x: c1 * z1.x + c2 * z2.x + c3 * z3.x,
|
||||||
y: c1 * z1.y + c2 * z2.y + c3 * z3.y
|
y: c1 * z1.y + c2 * z2.y + c3 * z3.y
|
||||||
|
@ -91,8 +85,10 @@ function bez2(z1, z2, z3, t) {
|
||||||
function bez3(z1, z2, z3, z4, t) {
|
function bez3(z1, z2, z3, z4, t) {
|
||||||
if (t <= 0) return z1;
|
if (t <= 0) return z1;
|
||||||
if (t >= 1) return z4;
|
if (t >= 1) return z4;
|
||||||
var c1 = (1 - t) * (1 - t) * (1 - t), c2 = 3 * t * (1 - t) * (1 - t),
|
let c1 = (1 - t) * (1 - t) * (1 - t),
|
||||||
c3 = 3 * t * t * (1 - t), c4 = t * t * t;
|
c2 = 3 * t * (1 - t) * (1 - t),
|
||||||
|
c3 = 3 * t * t * (1 - t),
|
||||||
|
c4 = t * t * t;
|
||||||
return {
|
return {
|
||||||
x: c1 * z1.x + c2 * z2.x + c3 * z3.x + c4 * z4.x,
|
x: c1 * z1.x + c2 * z2.x + c3 * z3.x + c4 * z4.x,
|
||||||
y: c1 * z1.y + c2 * z2.y + c3 * z3.y + c4 * z4.y
|
y: c1 * z1.y + c2 * z2.y + c3 * z3.y + c4 * z4.y
|
||||||
|
@ -104,8 +100,8 @@ function splitBefore(z1, z2, z3, z4, t) {
|
||||||
function splitAfter(z1, z2, z3, z4, t) {
|
function splitAfter(z1, z2, z3, z4, t) {
|
||||||
return [bez3(z1, z2, z3, z4, t), bez2(z2, z3, z4, t), mix(z3, z4, t), z4];
|
return [bez3(z1, z2, z3, z4, t), bez2(z2, z3, z4, t), mix(z3, z4, t), z4];
|
||||||
}
|
}
|
||||||
function splitAtExtrema(z1, z2, z3, z4, angles, splitpoints) {
|
function splitAtExtrema(z1, z2, z3, z4, angles, curve) {
|
||||||
var ts = fineAllExtrema(z1, z2, z3, z4, angles);
|
const ts = fineAllExtrema(z1, z2, z3, z4, angles);
|
||||||
if (ts[0] < SMALL) {
|
if (ts[0] < SMALL) {
|
||||||
ts[0] = 0;
|
ts[0] = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,236 +112,262 @@ function splitAtExtrema(z1, z2, z3, z4, angles, splitpoints) {
|
||||||
} else {
|
} else {
|
||||||
ts.push(1);
|
ts.push(1);
|
||||||
}
|
}
|
||||||
for (var k = 0; k < ts.length; k++) {
|
for (let k = 0; k < ts.length; k++) {
|
||||||
if (k > 0) {
|
if (k > 0) {
|
||||||
var t1 = ts[k - 1];
|
const t1 = ts[k - 1];
|
||||||
var t2 = ts[k];
|
const t2 = ts[k];
|
||||||
var bef = splitBefore(z1, z2, z3, z4, t2);
|
const bef = splitBefore(z1, z2, z3, z4, t2);
|
||||||
var seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2);
|
const seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2);
|
||||||
seg[1].on = seg[2].on = false;
|
seg[1].on = seg[2].on = false;
|
||||||
seg[1].cubic = seg[2].cubic = true;
|
seg[1].cubic = seg[2].cubic = true;
|
||||||
seg[3].on = true;
|
seg[3].on = true;
|
||||||
splitpoints.push(seg[1], seg[2], seg[3]);
|
curve.push(seg[1], seg[2], seg[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function splitSegment(z1, z2, z3, z4, angles, splitpoints) {
|
// function splitSegment(z1, z2, z3, z4, angles, curve) {
|
||||||
var ts = [];
|
// let ts = [];
|
||||||
var inflectAtEnd = false;
|
// let inflectAtEnd = false;
|
||||||
findInflections(z1, z2, z3, z4, ts);
|
// // findInflections(z1, z2, z3, z4, ts);
|
||||||
ts = ts.sort(ASCEND);
|
// // ts = ts.sort(ASCEND);
|
||||||
if (ts[0] < SMALL) {
|
// if (ts[0] < SMALL) {
|
||||||
ts[0] = 0;
|
// ts[0] = 0;
|
||||||
splitpoints[splitpoints.length - 1].inflect = true;
|
// curve[curve.length - 1].inflect = true;
|
||||||
} else {
|
// } else {
|
||||||
ts.unshift(0);
|
// ts.unshift(0);
|
||||||
}
|
// }
|
||||||
if (ts[ts.length - 1] > 1 - SMALL) {
|
// if (ts[ts.length - 1] > 1 - SMALL) {
|
||||||
inflectAtEnd = true;
|
// inflectAtEnd = true;
|
||||||
ts[ts.length - 1] = 1;
|
// ts[ts.length - 1] = 1;
|
||||||
} else {
|
// } else {
|
||||||
ts.push(1);
|
// ts.push(1);
|
||||||
}
|
// }
|
||||||
for (var k = 0; k < ts.length; k++) {
|
// for (let k = 0; k < ts.length; k++) {
|
||||||
if (k > 0) {
|
// if (k > 0) {
|
||||||
var t1 = ts[k - 1];
|
// const t1 = ts[k - 1];
|
||||||
var t2 = ts[k];
|
// const t2 = ts[k];
|
||||||
var bef = splitBefore(z1, z2, z3, z4, t2);
|
// const bef = splitBefore(z1, z2, z3, z4, t2);
|
||||||
var seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2);
|
// const seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2);
|
||||||
splitAtExtrema(seg[0], seg[1], seg[2], seg[3], angles, splitpoints);
|
// splitAtExtrema(seg[0], seg[1], seg[2], seg[3], angles, curve);
|
||||||
if (t2 < 1 || inflectAtEnd)
|
// if (t2 < 1 || inflectAtEnd) curve[curve.length - 1].inflect = true;
|
||||||
splitpoints[splitpoints.length - 1].inflect = true;
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
function veryClose(z1, z2) {
|
||||||
function fitpts(p1, c1, c2, p2) {
|
|
||||||
var d1 = {
|
|
||||||
x: c1.x - p1.x,
|
|
||||||
y: c1.y - p1.y
|
|
||||||
};
|
|
||||||
var d2 = {
|
|
||||||
x: c2.x - p2.x,
|
|
||||||
y: c2.y - p2.y
|
|
||||||
};
|
|
||||||
|
|
||||||
var det = d2.x * d1.y - d2.y * d1.x;
|
|
||||||
if (Math.abs(det) < 1e-6) return null;
|
|
||||||
var u = ((p2.y - p1.y) * d2.x - (p2.x - p1.x) * d2.y) / det;
|
|
||||||
var v = ((p2.y - p1.y) * d1.x - (p2.x - p1.x) * d1.y) / det;
|
|
||||||
if (u <= 0 || v <= 0) return null;
|
|
||||||
var mid = {
|
|
||||||
x: p1.x + d1.x * u,
|
|
||||||
y: p1.y + d1.y * u
|
|
||||||
};
|
|
||||||
return [mix(mid, p1, 1 / 3), mix(mid, p2, 1 / 3)];
|
|
||||||
}
|
|
||||||
function distance(z1, z2) {
|
|
||||||
return Math.sqrt((z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y));
|
|
||||||
}
|
|
||||||
function veryclose(z1, z2) {
|
|
||||||
return (z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y) <= SMALL;
|
return (z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y) <= SMALL;
|
||||||
}
|
}
|
||||||
function angleBetween(z1, z2, z3, z4) {
|
|
||||||
return (Math.atan2(z2.y - z1.y, z2.x - z1.x) - Math.atan2(z4.y - z3.y, z4.x - z3.x)) % Math.PI;
|
function splitCurve(sourceCurve) {
|
||||||
}
|
const curve = [sourceCurve[0]];
|
||||||
function pldistance(z1, z2, z) {
|
let last = sourceCurve[0];
|
||||||
return Math.abs((z2.y - z1.y) * z.x - (z2.x - z1.x) * z.y + z2.x * z1.y - z2.y * z1.x) / Math.sqrt((z2.x - z1.x) * (z2.x - z1.x) + (z2.y - z1.y) * (z2.y - z1.y));
|
for (let j = 1; j < sourceCurve.length; j++) {
|
||||||
}
|
if (sourceCurve[j].on) {
|
||||||
function estimateSegments(z1, z2) {
|
const z1 = last,
|
||||||
var hspan = Math.abs(z1.x - z2.x);
|
z4 = sourceCurve[j];
|
||||||
var vspan = Math.abs(z1.y - z2.y);
|
// const z2 = mix(z1, z4, 1 / 3);
|
||||||
return hspan <= 5 * CROWD || vspan <= 5 * CROWD ? VERYCROWD : hspan <= 10 * LOOSE || vspan <= 10 * LOOSE ? CROWD : LOOSE;
|
//const z3 = mix(z1, z4, 2 / 3);
|
||||||
}
|
if (!veryClose(z1, z4)) {
|
||||||
function enoughRotate(bef, z0, z1, z2, aft, cleanMore, flagl, flagr) {
|
curve.push(z4);
|
||||||
var angleRotatedBefore = Math.abs(angleBetween(bef.next || z1, bef, z1, z0));
|
// splitAtExtrema(z1, z2, z3, z4, ANGLES, curve);
|
||||||
var angleRotatedAfter = Math.abs(angleBetween(aft.prev || z1, aft, z1, z2));
|
last = z4;
|
||||||
var smallanglel = (cleanMore ? SMALLANGLE_CLEANMORE : SMALLANGLE) * (flagl && cleanMore ? 1 : 2);
|
}
|
||||||
var smallangler = (cleanMore ? SMALLANGLE_CLEANMORE : SMALLANGLE) * (flagr && cleanMore ? 1 : 2);
|
} else if (sourceCurve[j].cubic) {
|
||||||
return !((angleRotatedBefore < smallanglel || angleRotatedBefore > Math.PI - smallanglel) && pldistance(z1, z0, bef) <= CLOSE && distance(z1, bef) <= 5 * CROWD
|
const z1 = last,
|
||||||
|| (flagr ? false : (angleRotatedAfter < smallangler || angleRotatedAfter > Math.PI - smallangler) && pldistance(z1, z2, aft) <= CLOSE && distance(z1, aft) <= 5 * CROWD));
|
z2 = sourceCurve[j],
|
||||||
}
|
z3 = sourceCurve[j + 1],
|
||||||
function fairify(scurve, gizmo, denseQ, cleanMore) {
|
z4 = sourceCurve[j + 2];
|
||||||
for (var j = 0; j < scurve.length; j++) {
|
if (!(veryClose(z1, z2) && veryClose(z2, z3) && veryClose(z3, z4))) {
|
||||||
if (!isFinite(scurve[j].x)) scurve[j].x = 0;
|
splitAtExtrema(z1, z2, z3, z4, ANGLES, curve);
|
||||||
if (!isFinite(scurve[j].y)) scurve[j].y = 0;
|
|
||||||
scurve[j] = Transform.untransform(gizmo, scurve[j]);
|
|
||||||
}
|
|
||||||
var splitpoints = [scurve[0]];
|
|
||||||
var last = scurve[0];
|
|
||||||
for (var j = 1; j < scurve.length; j++) {
|
|
||||||
if (scurve[j].on) {
|
|
||||||
splitpoints.push(last = scurve[j]);
|
|
||||||
} else if (scurve[j].cubic) {
|
|
||||||
var z1 = last,
|
|
||||||
z2 = scurve[j],
|
|
||||||
z3 = scurve[j + 1],
|
|
||||||
z4 = scurve[j + 2];
|
|
||||||
if (!(veryclose(z1, z2) && veryclose(z2, z3) && veryclose(z3, z4))) {
|
|
||||||
splitSegment(z1, z2, z3, z4, ANGLES, splitpoints);
|
|
||||||
last = z4;
|
last = z4;
|
||||||
}
|
}
|
||||||
j += 2;
|
j += 2;
|
||||||
} else {
|
} else {
|
||||||
var z1 = last,
|
const z1 = last,
|
||||||
zm = scurve[j],
|
zm = sourceCurve[j],
|
||||||
z4 = scurve[j + 1];
|
z4 = sourceCurve[j + 1];
|
||||||
if (!(veryclose(z1, zm) && veryclose(zm, z4))) {
|
if (!(veryClose(z1, zm) && veryClose(zm, z4))) {
|
||||||
var z2 = mix(zm, z1, 1 / 3);
|
const z2 = mix(zm, z1, 1 / 3);
|
||||||
var z3 = mix(zm, z4, 1 / 3);
|
const z3 = mix(zm, z4, 1 / 3);
|
||||||
splitSegment(z1, z2, z3, z4, ANGLES, splitpoints);
|
splitAtExtrema(z1, z2, z3, z4, ANGLES, curve);
|
||||||
last = z4;
|
last = z4;
|
||||||
}
|
}
|
||||||
j += 1;
|
j += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mark corners and extrema
|
return curve;
|
||||||
for (var j = 1; j < splitpoints.length - 1; j++) {
|
}
|
||||||
if (splitpoints[j].on && !splitpoints[j - 1].on) {
|
|
||||||
splitpoints[j].prev = splitpoints[j - 1];
|
function cross(z1, z2, z3) {
|
||||||
}
|
return (z2.x - z1.x) * (z3.y - z1.y) - (z3.x - z1.x) * (z2.y - z1.y);
|
||||||
if (splitpoints[j].on && !splitpoints[j + 1].on) {
|
}
|
||||||
splitpoints[j].next = splitpoints[j + 1];
|
function dot(z1, z2, z3) {
|
||||||
}
|
return (z2.x - z1.x) * (z3.x - z1.x) + (z3.y - z1.y) * (z2.y - z1.y);
|
||||||
if (splitpoints[j].on && !splitpoints[j - 1].on && !splitpoints[j + 1].on) {
|
}
|
||||||
var z1 = splitpoints[j],
|
|
||||||
z0 = splitpoints[j - 1],
|
function markCorners(curve) {
|
||||||
z2 = splitpoints[j + 1];
|
for (let j = 0; j < curve.length; j++) {
|
||||||
if (cross(z1, z0, z2) < 1e-6 && dot(z1, z0, z2) < 0) {
|
if (!curve[j].on) continue;
|
||||||
var angle0 = Math.atan2(z2.y - z0.y, z2.x - z0.x);
|
const z1 = curve[j],
|
||||||
var angle = Math.abs(angle0 / Math.PI * 2 % 1);
|
z0 = curve[(j - 1 + curve.length) % curve.length],
|
||||||
if (Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL) {
|
z2 = curve[(j + 1) % curve.length];
|
||||||
z1.mark = true; // extremum
|
if (Math.abs(cross(z1, z0, z2)) < 1e-6) {
|
||||||
z1.inflect = false;
|
// Z0 -- Z1 -- Z2 are linear
|
||||||
} else {
|
if (!z0.on && !z2.on && dot(z1, z0, z2) < 0) {
|
||||||
var isInflection = false;
|
const angle0 = Math.atan2(z2.y - z0.y, z2.x - z0.x);
|
||||||
if (j > 2 && j < splitpoints.length - 2) {
|
const angle = Math.abs(((angle0 / Math.PI) * 2) % 1);
|
||||||
var za = bez3(z1, z0, splitpoints[j - 2], splitpoints[j - 3], SMALL);
|
if (
|
||||||
var zb = bez3(z1, z2, splitpoints[j + 2], splitpoints[j + 3], SMALL);
|
Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL ||
|
||||||
var inflect = ((z0.y - z2.y) * (za.x - z0.x) + (z2.x - z0.x) * (za.y - z0.y))
|
angle <= SMALL ||
|
||||||
* ((z0.y - z2.y) * (zb.x - z0.x) + (z2.x - z0.x) * (zb.y - z0.y));
|
angle >= 1 - SMALL
|
||||||
if (inflect < 0)
|
) {
|
||||||
isInflection = true;
|
z1.mark = true; // curve extremum
|
||||||
}
|
|
||||||
if (z1.inflect || isInflection) {
|
|
||||||
z1.mark = true;
|
|
||||||
z1.asinflect = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if (z0.on && z2.on && dot(z1, z0, z2) < 0) {
|
||||||
|
// Colinear on-knots
|
||||||
|
// Remove
|
||||||
} else {
|
} else {
|
||||||
z1.mark = true; // also corner
|
z1.mark = true; // also corner
|
||||||
}
|
}
|
||||||
} else if (splitpoints[j].on) {
|
} else {
|
||||||
splitpoints[j].mark = true; // corner
|
z1.mark = true; // corner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splitpoints[0].mark = splitpoints[splitpoints.length - 1].mark = true;
|
}
|
||||||
// Mark cleanup inflections
|
|
||||||
for (var pass = 0; pass < 2; pass++) {
|
class BezierCurveCluster {
|
||||||
for (var j = 1; j < splitpoints.length - 1; j++) {
|
constructor(zs) {
|
||||||
if (splitpoints[j].mark) {
|
let segments = [];
|
||||||
for (var k = j - 1; k >= 0 && !splitpoints[k].mark; k--);
|
let lengths = [];
|
||||||
lastmark = splitpoints[k];
|
let last = zs[0];
|
||||||
for (var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
for (let j = 1; j < zs.length; j++) {
|
||||||
nextmark = splitpoints[k];
|
if (zs[j].on) {
|
||||||
|
const z1 = last,
|
||||||
|
z4 = zs[j];
|
||||||
|
const z2 = mix(z1, z4, 1 / 3);
|
||||||
|
const z3 = mix(z1, z4, 2 / 3);
|
||||||
|
segments.push(new quadify.CubicBezierCurve(z1, z2, z3, z4));
|
||||||
|
lengths.push(Math.hypot(z4.x - z1.x, z4.y - z1.y));
|
||||||
|
last = z4;
|
||||||
|
} else if (zs[j].cubic) {
|
||||||
|
const z1 = last,
|
||||||
|
z2 = zs[j],
|
||||||
|
z3 = zs[j + 1],
|
||||||
|
z4 = zs[j + 2];
|
||||||
|
segments.push(new quadify.CubicBezierCurve(z1, z2, z3, z4));
|
||||||
|
lengths.push(Math.hypot(z4.x - z1.x, z4.y - z1.y));
|
||||||
|
last = z4;
|
||||||
|
j += 2;
|
||||||
|
} else {
|
||||||
|
const z1 = last,
|
||||||
|
zm = zs[j],
|
||||||
|
z4 = zs[j + 1];
|
||||||
|
const z2 = mix(zm, z1, 1 / 3);
|
||||||
|
const z3 = mix(zm, z4, 1 / 3);
|
||||||
|
segments.push(new quadify.CubicBezierCurve(z1, z2, z3, z4));
|
||||||
|
lengths.push(Math.hypot(z4.x - z1.x, z4.y - z1.y));
|
||||||
|
last = z4;
|
||||||
|
j += 1;
|
||||||
}
|
}
|
||||||
if (splitpoints[j].mark && splitpoints[j].asinflect) {
|
}
|
||||||
var z1 = splitpoints[j],
|
|
||||||
z0 = splitpoints[j - 1],
|
let totalLength = 0;
|
||||||
z2 = splitpoints[j + 1];
|
for (let j = 0; j < lengths.length; j++) totalLength += lengths[j];
|
||||||
if (!(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark, cleanMore, lastmark.asinflect, nextmark.asinflect))) {
|
let lengthSofar = 0;
|
||||||
//z1.mark = false;
|
for (let j = 0; j < lengths.length; j++) {
|
||||||
z0.remove = z1.remove = z2.remove = true;
|
let segLen = lengths[j];
|
||||||
|
lengths[j] = lengthSofar / totalLength;
|
||||||
|
lengthSofar += segLen;
|
||||||
|
}
|
||||||
|
this.segments = segments;
|
||||||
|
this.lengths = lengths;
|
||||||
|
// console.log(this.eval(0), this.eval(1 / 2), this.eval(1));
|
||||||
|
// console.log(this.derivative(0), this.derivative(1 / 2), this.derivative(1));
|
||||||
|
}
|
||||||
|
getIndex(t) {
|
||||||
|
let j = this.lengths.length - 1;
|
||||||
|
while (j > 0 && this.lengths[j] > t) j--;
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
eval(t) {
|
||||||
|
const j = this.getIndex(t);
|
||||||
|
const tBefore = this.lengths[j];
|
||||||
|
const tNext = j < this.lengths.length - 1 ? this.lengths[j + 1] : 1;
|
||||||
|
const tRelative = (t - tBefore) / (tNext - tBefore);
|
||||||
|
return this.segments[j].eval(tRelative);
|
||||||
|
}
|
||||||
|
derivative(t) {
|
||||||
|
const j = this.getIndex(t);
|
||||||
|
const tBefore = this.lengths[j];
|
||||||
|
const tNext = j < this.lengths.length - 1 ? this.lengths[j + 1] : 1;
|
||||||
|
const tRelative = (t - tBefore) / (tNext - tBefore);
|
||||||
|
// console.log(
|
||||||
|
// t,
|
||||||
|
// tRelative,
|
||||||
|
// tNext,
|
||||||
|
// tBefore,
|
||||||
|
// tNext - tBefore,
|
||||||
|
// this.segments[j].derivative(tRelative)
|
||||||
|
// );
|
||||||
|
const d = this.segments[j].derivative(tRelative);
|
||||||
|
d.x /= tNext - tBefore;
|
||||||
|
d.y /= tNext - tBefore;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCurve(curve) {
|
||||||
|
let exitPoints = [];
|
||||||
|
for (let j = 0; j < curve.length; j++) {
|
||||||
|
if (!curve[j].mark) continue;
|
||||||
|
let k = j;
|
||||||
|
for (; k < curve.length && (k === j || !curve[k].mark); k++);
|
||||||
|
exitPoints.push(curve[j]);
|
||||||
|
const pts = curve.slice(j, k + 1);
|
||||||
|
let nPtsOffPoints = 0;
|
||||||
|
for (const z of pts) {
|
||||||
|
if (!z.on) nPtsOffPoints += 1;
|
||||||
|
}
|
||||||
|
if (nPtsOffPoints > 0) {
|
||||||
|
const curve = new BezierCurveCluster(pts);
|
||||||
|
const offPoints = quadify.autoQuadify(curve, 1 / 4);
|
||||||
|
if (!offPoints) continue;
|
||||||
|
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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j].remove) {
|
j = k - 1;
|
||||||
splitpoints[j].mark = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Mark diagonals
|
return exitPoints;
|
||||||
var lastmark = splitpoints[0];
|
}
|
||||||
for (var k = 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
|
||||||
var nextmark = splitpoints[k];
|
module.exports = function(sourceCurve, gizmo) {
|
||||||
var segments = estimateSegments(lastmark, nextmark);
|
for (let j = 0; j < sourceCurve.length; j++) {
|
||||||
for (var j = 1; j < splitpoints.length - 1; j++) {
|
if (!isFinite(sourceCurve[j].x)) sourceCurve[j].x = 0;
|
||||||
if (splitpoints[j].mark) {
|
if (!isFinite(sourceCurve[j].y)) sourceCurve[j].y = 0;
|
||||||
lastmark = splitpoints[j];
|
sourceCurve[j] = Transform.untransform(gizmo, sourceCurve[j]);
|
||||||
for (var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
|
||||||
nextmark = splitpoints[k];
|
|
||||||
segments = estimateSegments(lastmark, nextmark);
|
|
||||||
}
|
|
||||||
if (splitpoints[j].on && !splitpoints[j].mark) {
|
|
||||||
var z1 = splitpoints[j],
|
|
||||||
z0 = splitpoints[j - 1],
|
|
||||||
z2 = splitpoints[j + 1];
|
|
||||||
var angle0 = Math.atan2(z2.y - z0.y, z2.x - z0.x);
|
|
||||||
var angle = Math.abs(angle0 / Math.PI * segments % 1);
|
|
||||||
var angleRotatedBefore = Math.abs(angleBetween(z1, lastmark, z1, z0));
|
|
||||||
var angleRotatedAfter = Math.abs(angleBetween(z1, nextmark, z1, z2));
|
|
||||||
if (!(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL)
|
|
||||||
|| !(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark))) {
|
|
||||||
z1.remove = z0.remove = z2.remove = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Rebuild curve
|
const curve = splitCurve(sourceCurve);
|
||||||
for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j].on && !splitpoints[j].remove && splitpoints[j + 1] && !splitpoints[j + 1].on) {
|
markCorners(curve);
|
||||||
for (var k = j + 2; k < splitpoints.length && splitpoints[k].remove; k++);
|
const builtCurve = buildCurve(curve);
|
||||||
if (k - j > 2) {
|
const ans = [];
|
||||||
var zs = fitpts(splitpoints[j], splitpoints[j + 1], splitpoints[k], splitpoints[k + 1]);
|
for (let j = 0; j < builtCurve.length; j++) {
|
||||||
if (zs) {
|
if (builtCurve[j] && !builtCurve[j].remove) {
|
||||||
zs[0].on = zs[1].on = false;
|
ans.push(Transform.transformPoint(gizmo, builtCurve[j]));
|
||||||
zs[0].cubic = zs[1].cubic = true;
|
|
||||||
splitpoints[j + 1] = zs[0];
|
|
||||||
splitpoints[k] = zs[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
j = k;
|
|
||||||
}
|
|
||||||
var ans = [];
|
|
||||||
for (var j = 0; j < splitpoints.length; j++)if (splitpoints[j] && !splitpoints[j].remove) {
|
|
||||||
ans.push(Transform.transformPoint(gizmo, splitpoints[j]));
|
|
||||||
}
|
}
|
||||||
return ans;
|
return ans;
|
||||||
}
|
};
|
||||||
module.exports = fairify;
|
|
||||||
|
|
|
@ -251,102 +251,3 @@ export all : class Glyph
|
||||||
set {.x lx .y ly} zf
|
set {.x lx .y ly} zf
|
||||||
inc j
|
inc j
|
||||||
return c
|
return c
|
||||||
|
|
||||||
public [cleanup t] : begin
|
|
||||||
foreach c [range 0 this.contours.length] : begin
|
|
||||||
local ocontour this.contours.(c)
|
|
||||||
# add infections
|
|
||||||
local contour {[new Point ocontour.0.x ocontour.0.y ocontour.0.on]}
|
|
||||||
local flag 0
|
|
||||||
foreach [j : range 1 (ocontour.length - 1)] : piecewise
|
|
||||||
flag : dec flag
|
|
||||||
ocontour.(j).on : contour.push ocontour.(j)
|
|
||||||
ocontour.(j).cubic : begin
|
|
||||||
local p0 contour.(contour.length - 1)
|
|
||||||
local p1 ocontour.(j)
|
|
||||||
local p2 ocontour.(j + 1)
|
|
||||||
local p3 ocontour.(j + 2)
|
|
||||||
local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y p3.x p3.y
|
|
||||||
local ts [strand.extrema].y
|
|
||||||
piecewise
|
|
||||||
(!ts || ts.length < 1) : contour.push p1 p2 p3
|
|
||||||
true : begin
|
|
||||||
set ts [ts.sort byx]
|
|
||||||
ts.unshift 0
|
|
||||||
ts.push 1
|
|
||||||
foreach [k : range 0 (ts.length - 1)] : begin
|
|
||||||
local s : strand.split ts.(k) ts.(k + 1)
|
|
||||||
if s.points : begin
|
|
||||||
contour.push : new Point s.points.1.x s.points.1.y false true
|
|
||||||
contour.push : new Point s.points.2.x s.points.2.y false true
|
|
||||||
contour.push : new Point s.points.3.x s.points.3.y true
|
|
||||||
set flag 2
|
|
||||||
true : begin
|
|
||||||
local p0 contour.(contour.length - 1)
|
|
||||||
local p1 ocontour.(j)
|
|
||||||
local p2 ocontour.(j + 1)
|
|
||||||
if [not p2.on] : set p2 : new Point [mix p1.x p2.x 0.5] [mix p1.y p2.y 0.5] true
|
|
||||||
local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y
|
|
||||||
local ts [strand.extrema].y
|
|
||||||
piecewise
|
|
||||||
(!ts || ts.length < 1) : contour.push p1 p2
|
|
||||||
true : begin
|
|
||||||
set ts [ts.sort byx]
|
|
||||||
ts.unshift 0
|
|
||||||
ts.push 1
|
|
||||||
foreach [k : range 0 (ts.length - 1)] : begin
|
|
||||||
local s : strand.split ts.(k) ts.(k + 1)
|
|
||||||
if s.points : contour.push
|
|
||||||
new Point s.points.1.x s.points.1.y false
|
|
||||||
new Point s.points.2.x s.points.2.y true
|
|
||||||
set flag 1
|
|
||||||
|
|
||||||
contour.push [new Point ocontour.(ocontour.length - 1).x ocontour.(ocontour.length - 1).y ocontour.(ocontour.length - 1).on]
|
|
||||||
|
|
||||||
# cleanup
|
|
||||||
local cleanedContour {}
|
|
||||||
foreach j [range 1 : contour.length - 1] : begin
|
|
||||||
local p0 contour.(j - 1)
|
|
||||||
local p1 contour.(j)
|
|
||||||
local p2 contour.(j + 1)
|
|
||||||
if [oncurveRemovable p0 p1 p2 t] : set p1.unimportant true
|
|
||||||
foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point
|
|
||||||
set contour cleanedContour
|
|
||||||
|
|
||||||
set cleanedContour {}
|
|
||||||
foreach j [range 0 contour.length] : if ([not contour.(j).cubic] && [not contour.(j).unimportant]) : begin
|
|
||||||
local found false
|
|
||||||
for [local k : j + 1] ((k < contour.length) && (
|
|
||||||
[not contour.(k).cubic] && [closepoint contour.(j) contour.(k) t]
|
|
||||||
|| contour.(k).cubic && contour.(k + 1).cubic
|
|
||||||
&& [closepoint contour.(j) contour.(k) t]
|
|
||||||
&& [closepoint contour.(j) contour.(k) t]
|
|
||||||
&& [closepoint contour.(j) contour.(k + 2) t])) [inc k] : begin
|
|
||||||
set contour.(k).unimportant true
|
|
||||||
set found true
|
|
||||||
if (contour.(k).cubic && contour.(k + 1).cubic
|
|
||||||
&& [closepoint contour.(j) contour.(k) t]
|
|
||||||
&& [closepoint contour.(j) contour.(k) t]
|
|
||||||
&& [closepoint contour.(j) contour.(k + 2) t]) : begin
|
|
||||||
set contour.(k + 1).unimportant true
|
|
||||||
set contour.(k + 2).unimportant true
|
|
||||||
set k : k + 2
|
|
||||||
if found : begin
|
|
||||||
set contour.(j).on true
|
|
||||||
set j (k - 1)
|
|
||||||
foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point
|
|
||||||
this.contours.(c) = cleanedContour
|
|
||||||
set this.contours : this.contours.filter : lambda [c] : begin
|
|
||||||
if [not c.length] : return false
|
|
||||||
local xmin c.(0).x
|
|
||||||
local xmax c.(0).x
|
|
||||||
local ymin c.(0).y
|
|
||||||
local ymax c.(0).y
|
|
||||||
foreach z [items-of c] : begin
|
|
||||||
if (z.x < xmin) : set xmin z.x
|
|
||||||
if (z.x > xmax) : set xmax z.x
|
|
||||||
if (z.y < ymin) : set ymin z.y
|
|
||||||
if (z.y > ymax) : set ymax z.y
|
|
||||||
return : ymax - ymin > 1 && xmax - xmin > 1
|
|
||||||
return this
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function maskBit(x, y) {
|
function maskBit(x, y) {
|
||||||
return (x & (1 << y));
|
return x & (1 << y);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = maskBit;
|
module.exports = maskBit;
|
||||||
|
|
|
@ -1,72 +1,112 @@
|
||||||
module.exports = function(xs, ys) {
|
module.exports = function(xs, ys) {
|
||||||
var i, length = xs.length;
|
let i,
|
||||||
|
length = xs.length;
|
||||||
|
|
||||||
// Deal with length issues
|
// Deal with length issues
|
||||||
if (length != ys.length) { throw 'Need an equal count of xs and ys.'; }
|
if (length != ys.length) {
|
||||||
if (length === 0) { return function(x) { return 0; }; }
|
throw "Need an equal count of xs and ys.";
|
||||||
|
}
|
||||||
|
if (length === 0) {
|
||||||
|
return function() {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
if (length === 1) {
|
if (length === 1) {
|
||||||
// Impl: Precomputing the result prevents problems if ys is mutated later and allows garbage collection of ys
|
// Impl: Precomputing the result prevents problems if ys is mutated later and allows garbage collection of ys
|
||||||
// Impl: Unary plus properly converts values to numbers
|
// Impl: Unary plus properly converts values to numbers
|
||||||
var result = +ys[0];
|
let result = +ys[0];
|
||||||
return function(x) { return result; };
|
return function() {
|
||||||
|
return result;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rearrange xs and ys so that xs is sorted
|
// Rearrange xs and ys so that xs is sorted
|
||||||
var indexes = [];
|
let indexes = [];
|
||||||
for (i = 0; i < length; i++) { indexes.push(i); }
|
for (i = 0; i < length; i++) {
|
||||||
indexes.sort(function(a, b) { return xs[a] < xs[b] ? -1 : 1; });
|
indexes.push(i);
|
||||||
var oldXs = xs, oldYs = ys;
|
}
|
||||||
|
indexes.sort(function(a, b) {
|
||||||
|
return xs[a] < xs[b] ? -1 : 1;
|
||||||
|
});
|
||||||
|
let oldXs = xs,
|
||||||
|
oldYs = ys;
|
||||||
// Impl: Creating new arrays also prevents problems if the input arrays are mutated later
|
// Impl: Creating new arrays also prevents problems if the input arrays are mutated later
|
||||||
xs = []; ys = [];
|
xs = [];
|
||||||
|
ys = [];
|
||||||
// Impl: Unary plus properly converts values to numbers
|
// Impl: Unary plus properly converts values to numbers
|
||||||
for (i = 0; i < length; i++) { xs.push(+oldXs[indexes[i]]); ys.push(+oldYs[indexes[i]]); }
|
for (i = 0; i < length; i++) {
|
||||||
|
xs.push(+oldXs[indexes[i]]);
|
||||||
|
ys.push(+oldYs[indexes[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
// Get consecutive differences and slopes
|
// Get consecutive differences and slopes
|
||||||
var dys = [], dxs = [], ms = [];
|
let dys = [],
|
||||||
|
dxs = [],
|
||||||
|
ms = [];
|
||||||
for (i = 0; i < length - 1; i++) {
|
for (i = 0; i < length - 1; i++) {
|
||||||
var dx = xs[i + 1] - xs[i], dy = ys[i + 1] - ys[i];
|
const dx = xs[i + 1] - xs[i],
|
||||||
dxs.push(dx); dys.push(dy); ms.push(dy / dx);
|
dy = ys[i + 1] - ys[i];
|
||||||
|
dxs.push(dx);
|
||||||
|
dys.push(dy);
|
||||||
|
ms.push(dy / dx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get degree-1 coefficients
|
// Get degree-1 coefficients
|
||||||
var c1s = [ms[0]];
|
let c1s = [ms[0]];
|
||||||
for (i = 0; i < dxs.length - 1; i++) {
|
for (i = 0; i < dxs.length - 1; i++) {
|
||||||
var m = ms[i], mNext = ms[i + 1];
|
const m = ms[i],
|
||||||
|
mNext = ms[i + 1];
|
||||||
if (m * mNext <= 0) {
|
if (m * mNext <= 0) {
|
||||||
c1s.push(0);
|
c1s.push(0);
|
||||||
} else {
|
} else {
|
||||||
var dx = dxs[i], dxNext = dxs[i + 1], common = dx + dxNext;
|
const dx = dxs[i],
|
||||||
c1s.push(3 * common / ((common + dxNext) / m + (common + dx) / mNext));
|
dxNext = dxs[i + 1],
|
||||||
|
common = dx + dxNext;
|
||||||
|
c1s.push((3 * common) / ((common + dxNext) / m + (common + dx) / mNext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c1s.push(ms[ms.length - 1]);
|
c1s.push(ms[ms.length - 1]);
|
||||||
|
|
||||||
// Get degree-2 and degree-3 coefficients
|
// Get degree-2 and degree-3 coefficients
|
||||||
var c2s = [], c3s = [];
|
let c2s = [],
|
||||||
|
c3s = [];
|
||||||
for (i = 0; i < c1s.length - 1; i++) {
|
for (i = 0; i < c1s.length - 1; i++) {
|
||||||
var c1 = c1s[i], m = ms[i], invDx = 1 / dxs[i], common = c1 + c1s[i + 1] - m - m;
|
const c1 = c1s[i],
|
||||||
c2s.push((m - c1 - common) * invDx); c3s.push(common * invDx * invDx);
|
m = ms[i],
|
||||||
|
invDx = 1 / dxs[i],
|
||||||
|
common = c1 + c1s[i + 1] - m - m;
|
||||||
|
c2s.push((m - c1 - common) * invDx);
|
||||||
|
c3s.push(common * invDx * invDx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return interpolant function
|
// Return interpolant function
|
||||||
return function(x) {
|
return function(x) {
|
||||||
// The rightmost point in the dataset should give an exact result
|
// The rightmost point in the dataset should give an exact result
|
||||||
var i = xs.length - 1;
|
let i = xs.length - 1;
|
||||||
if (x == xs[i]) { return ys[i]; }
|
if (x == xs[i]) {
|
||||||
|
return ys[i];
|
||||||
|
}
|
||||||
|
|
||||||
// Search for the interval x is in, returning the corresponding y if x is one of the original xs
|
// Search for the interval x is in, returning the corresponding y if x is one of the original xs
|
||||||
var low = 0, mid, high = c3s.length - 1;
|
let low = 0,
|
||||||
|
mid,
|
||||||
|
high = c3s.length - 1;
|
||||||
while (low <= high) {
|
while (low <= high) {
|
||||||
mid = Math.floor(0.5 * (low + high));
|
mid = Math.floor(0.5 * (low + high));
|
||||||
var xHere = xs[mid];
|
let xHere = xs[mid];
|
||||||
if (xHere < x) { low = mid + 1; }
|
if (xHere < x) {
|
||||||
else if (xHere > x) { high = mid - 1; }
|
low = mid + 1;
|
||||||
else { return ys[mid]; }
|
} else if (xHere > x) {
|
||||||
|
high = mid - 1;
|
||||||
|
} else {
|
||||||
|
return ys[mid];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i = Math.max(0, high);
|
i = Math.max(0, high);
|
||||||
|
|
||||||
// Interpolate
|
// Interpolate
|
||||||
var diff = x - xs[i], diffSq = diff * diff;
|
let diff = x - xs[i],
|
||||||
|
diffSq = diff * diff;
|
||||||
return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq;
|
return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq;
|
||||||
};
|
};
|
||||||
};
|
};
|
10
verdafile.js
10
verdafile.js
|
@ -27,12 +27,12 @@ want(...argv._);
|
||||||
////// Oracles //////
|
////// Oracles //////
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
|
|
||||||
oracle(`o:version`).def(async target => {
|
oracle(`o:version`).def(async () => {
|
||||||
const package_json = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json")));
|
const package_json = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json")));
|
||||||
return package_json.version;
|
return package_json.version;
|
||||||
});
|
});
|
||||||
|
|
||||||
oracle(`o:raw-plans`).def(async target => {
|
oracle(`o:raw-plans`).def(async () => {
|
||||||
const t = toml.parse(fs.readFileSync(path.resolve(__dirname, "build-plans.toml")));
|
const t = toml.parse(fs.readFileSync(path.resolve(__dirname, "build-plans.toml")));
|
||||||
for (const prefix in t.buildPlans) {
|
for (const prefix in t.buildPlans) {
|
||||||
const plan = t.buildPlans[prefix];
|
const plan = t.buildPlans[prefix];
|
||||||
|
@ -287,7 +287,7 @@ task("contents:***").def(async (target, gid) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Archive
|
// Archive
|
||||||
task(`${ARCHIVE_DIR}/*-*.zip`).def(async (target, gid, version) => {
|
task(`${ARCHIVE_DIR}/*-*.zip`).def(async (target, gid) => {
|
||||||
// Note: this target does NOT depend on the font files.
|
// Note: this target does NOT depend on the font files.
|
||||||
const [exportPlans] = await target.need(`o:export-plans`, `dir:${target.path.dir}`);
|
const [exportPlans] = await target.need(`o:export-plans`, `dir:${target.path.dir}`);
|
||||||
await target.need(`contents:${exportPlans[gid]}`);
|
await target.need(`contents:${exportPlans[gid]}`);
|
||||||
|
@ -308,7 +308,7 @@ task("collection-fonts:***").def(async (target, cid) => {
|
||||||
const [{ groups }] = await target.need("o:collect-plans");
|
const [{ groups }] = await target.need("o:collect-plans");
|
||||||
await target.need(groups[cid].map(file => `${DIST}/collections/${cid}/${file}.ttc`));
|
await target.need(groups[cid].map(file => `${DIST}/collections/${cid}/${file}.ttc`));
|
||||||
});
|
});
|
||||||
task(`${ARCHIVE_DIR}/ttc-*-*.zip`).def(async (target, cid, version) => {
|
task(`${ARCHIVE_DIR}/ttc-*-*.zip`).def(async (target, cid) => {
|
||||||
// Note: this target does NOT depend on the font files.
|
// Note: this target does NOT depend on the font files.
|
||||||
await target.need(`dir:${target.path.dir}`);
|
await target.need(`dir:${target.path.dir}`);
|
||||||
await target.need(`collection-fonts:${cid}`);
|
await target.need(`collection-fonts:${cid}`);
|
||||||
|
@ -375,7 +375,7 @@ task(`all:archives`).def(async target => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
phony(`clean`).def(async target => {
|
phony(`clean`).def(async () => {
|
||||||
await rm(`build`);
|
await rm(`build`);
|
||||||
await rm(`dist`);
|
await rm(`dist`);
|
||||||
await rm(`release-archives`);
|
await rm(`release-archives`);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue