More outline cleanup changes (#1562)
This commit is contained in:
parent
880ff0deee
commit
c5d322709a
4 changed files with 126 additions and 28 deletions
|
@ -6,4 +6,4 @@
|
||||||
- MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE (`U+1E06D`).
|
- MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE (`U+1E06D`).
|
||||||
- COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I (`U+1E08F`).
|
- COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I (`U+1E08F`).
|
||||||
* Add variants for partial differential symbol (#1503).
|
* Add variants for partial differential symbol (#1503).
|
||||||
* Prevent pathological geometry produced in phonetic ligatures (#1565, #1566).
|
* Prevent pathological geometry produced in phonetic ligatures (#1562, #1565, #1566).
|
||||||
|
|
|
@ -3,7 +3,7 @@ import zlib from "zlib";
|
||||||
|
|
||||||
import { encode, decode } from "@msgpack/msgpack";
|
import { encode, decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
const Edition = 23;
|
const Edition = 24;
|
||||||
const MAX_AGE = 16;
|
const MAX_AGE = 16;
|
||||||
class GfEntry {
|
class GfEntry {
|
||||||
constructor(age, value) {
|
constructor(age, value) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as CurveUtil from "../../support/geometry/curve-util.mjs";
|
||||||
import * as Geom from "../../support/geometry/index.mjs";
|
import * as Geom from "../../support/geometry/index.mjs";
|
||||||
import { Point } from "../../support/geometry/point.mjs";
|
import { Point } from "../../support/geometry/point.mjs";
|
||||||
import { Transform } from "../../support/geometry/transform.mjs";
|
import { Transform } from "../../support/geometry/transform.mjs";
|
||||||
|
import { mix } from "../../support/utils.mjs";
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -144,8 +145,12 @@ class QuadifySink {
|
||||||
if (this.lastContour.length > 2) {
|
if (this.lastContour.length > 2) {
|
||||||
let c = this.lastContour;
|
let c = this.lastContour;
|
||||||
c = this.alignHVKnots(c);
|
c = this.alignHVKnots(c);
|
||||||
|
c = this.dropDuplicateFirstLast(c);
|
||||||
|
c = this.cleanupOccurrentKnots2(c);
|
||||||
|
c = this.cleanupOccurrentKnots1(c);
|
||||||
|
c = this.removeColinearArc(c);
|
||||||
|
c = this.removeColinearCorners(c);
|
||||||
c = this.cleanupOccurrentKnots1(c);
|
c = this.cleanupOccurrentKnots1(c);
|
||||||
c = this.removeColinearKnots(c);
|
|
||||||
if (c.length > 2) this.contours.push(c);
|
if (c.length > 2) this.contours.push(c);
|
||||||
}
|
}
|
||||||
this.lastContour = [];
|
this.lastContour = [];
|
||||||
|
@ -170,25 +175,43 @@ class QuadifySink {
|
||||||
const c = c0.slice(0);
|
const c = c0.slice(0);
|
||||||
const alignX = new CoordinateAligner(c, GetX, SetX);
|
const alignX = new CoordinateAligner(c, GetX, SetX);
|
||||||
const alignY = new CoordinateAligner(c, GetY, SetY);
|
const alignY = new CoordinateAligner(c, GetY, SetY);
|
||||||
|
|
||||||
for (let i = 0; i < c.length; i++) {
|
for (let i = 0; i < c.length; i++) {
|
||||||
if (c[i].type === Point.Type.Corner) {
|
const iNext = (i + 1) % c.length,
|
||||||
alignX.tryAlign(i, (i + 1) % c.length);
|
zCurr = c[i],
|
||||||
alignY.tryAlign(i, (i + 1) % c.length);
|
zNext = c[iNext];
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < c.length; i++) {
|
|
||||||
const zCurr = c[i],
|
|
||||||
zNext = c[(i + 1) % c.length];
|
|
||||||
if (zCurr.type === Point.Type.Quadratic && zNext.type === Point.Type.Corner) {
|
if (zCurr.type === Point.Type.Quadratic && zNext.type === Point.Type.Corner) {
|
||||||
alignX.tryAlign(i, (i + 1) % c.length);
|
alignX.tryAlign(i, iNext);
|
||||||
alignY.tryAlign(i, (i + 1) % c.length);
|
alignY.tryAlign(i, iNext);
|
||||||
|
} else {
|
||||||
|
alignX.tryAlign(iNext, i);
|
||||||
|
alignY.tryAlign(iNext, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alignX.apply();
|
alignX.apply();
|
||||||
alignY.apply();
|
alignY.apply();
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop the duplicate point (first-last)
|
||||||
|
dropDuplicateFirstLast(c) {
|
||||||
|
while (c.length > 1) {
|
||||||
|
const first = c[0],
|
||||||
|
last = c[c.length - 1];
|
||||||
|
if (
|
||||||
|
first.type === Point.Type.Corner &&
|
||||||
|
last.type === Point.Type.Corner &&
|
||||||
|
isOccurrent(first, last)
|
||||||
|
) {
|
||||||
|
c.pop();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
// Occurrent cleanup -- corner-corner
|
// Occurrent cleanup -- corner-corner
|
||||||
cleanupOccurrentKnots1(c0) {
|
cleanupOccurrentKnots1(c0) {
|
||||||
let drops = [];
|
let drops = [];
|
||||||
|
@ -198,6 +221,7 @@ class QuadifySink {
|
||||||
const pre = c0[i],
|
const pre = c0[i],
|
||||||
post = c0[iPost];
|
post = c0[iPost];
|
||||||
if (
|
if (
|
||||||
|
iPost > 0 &&
|
||||||
pre.type === Point.Type.Corner &&
|
pre.type === Point.Type.Corner &&
|
||||||
post.type === Point.Type.Corner &&
|
post.type === Point.Type.Corner &&
|
||||||
isOccurrent(pre, post)
|
isOccurrent(pre, post)
|
||||||
|
@ -209,7 +233,43 @@ class QuadifySink {
|
||||||
return dropBy(c0, drops);
|
return dropBy(c0, drops);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeColinearKnots(c0) {
|
// Occurrent cleanup -- off points
|
||||||
|
// This function actually **INSERTS** points for occurrent off knots.
|
||||||
|
cleanupOccurrentKnots2(c0) {
|
||||||
|
let insertAfter = [];
|
||||||
|
for (let i = 0; i < c0.length; i++) insertAfter[i] = false;
|
||||||
|
for (let i = 0; i < c0.length; i++) {
|
||||||
|
const cur = c0[i];
|
||||||
|
if (cur.type !== Point.Type.Quadratic) continue;
|
||||||
|
|
||||||
|
const iPre = (i - 1 + c0.length) % c0.length;
|
||||||
|
const iPost = (i + 1) % c0.length;
|
||||||
|
const pre = c0[iPre];
|
||||||
|
const post = c0[iPost];
|
||||||
|
|
||||||
|
if (isOccurrent(pre, cur) && post.type === Point.Type.Quadratic) {
|
||||||
|
insertAfter[i] = true;
|
||||||
|
}
|
||||||
|
if (isOccurrent(cur, post) && pre.type === Point.Type.Quadratic) {
|
||||||
|
insertAfter[iPre] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let c1 = [];
|
||||||
|
for (let i = 0; i < c0.length; i++) {
|
||||||
|
const cur = c0[i];
|
||||||
|
c1.push(cur);
|
||||||
|
if (insertAfter[i]) {
|
||||||
|
const iPost = (i + 1) % c0.length;
|
||||||
|
const post = c0[iPost];
|
||||||
|
c1.push(Point.mix(Point.Type.Corner, cur, post, 0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeColinearCorners(c0) {
|
||||||
const c = c0.slice(0);
|
const c = c0.slice(0);
|
||||||
let lengthBefore = c.length,
|
let lengthBefore = c.length,
|
||||||
lengthAfter = c.length;
|
lengthAfter = c.length;
|
||||||
|
@ -220,22 +280,47 @@ class QuadifySink {
|
||||||
const zPrev = c[(i - 1 + c.length) % c.length],
|
const zPrev = c[(i - 1 + c.length) % c.length],
|
||||||
zCurr = c[i],
|
zCurr = c[i],
|
||||||
zNext = c[(i + 1) % c.length];
|
zNext = c[(i + 1) % c.length];
|
||||||
if (zPrev.type === Point.Type.Corner && zNext.type === Point.Type.Corner) {
|
if (
|
||||||
if (aligned(zPrev.x, zCurr.x, zNext.x) && between(zPrev.y, zCurr.y, zNext.y))
|
zPrev.type === Point.Type.Corner &&
|
||||||
shouldRemove[i] = true;
|
zNext.type === Point.Type.Corner &&
|
||||||
if (aligned(zPrev.y, zCurr.y, zNext.y) && between(zPrev.x, zCurr.x, zNext.x))
|
pointsColinear(zPrev, zCurr, zNext)
|
||||||
shouldRemove[i] = true;
|
) {
|
||||||
|
shouldRemove[i] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let n = 0;
|
|
||||||
for (let i = 0; i < c.length; i++) {
|
dropBy(c, shouldRemove);
|
||||||
if (!shouldRemove[i]) c[n++] = c[i];
|
|
||||||
}
|
|
||||||
c.length = n;
|
|
||||||
lengthAfter = c.length;
|
lengthAfter = c.length;
|
||||||
} while (lengthAfter < lengthBefore);
|
} while (lengthAfter < lengthBefore);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeColinearArc(c) {
|
||||||
|
if (c[0].type !== Point.Type.Corner) throw new Error("Unreachable");
|
||||||
|
|
||||||
|
let front = 0,
|
||||||
|
shouldRemove = [],
|
||||||
|
middlePoints = [];
|
||||||
|
for (let rear = 1; rear <= c.length; rear++) {
|
||||||
|
let zFront = c[front],
|
||||||
|
zRear = c[rear % c.length];
|
||||||
|
if (zRear.type === Point.Type.Corner) {
|
||||||
|
let allColinear = true;
|
||||||
|
for (const z of middlePoints) {
|
||||||
|
if (!pointsColinear(zFront, z, zRear)) allColinear = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allColinear) for (let i = front + 1; i < rear; i++) shouldRemove[i] = true;
|
||||||
|
|
||||||
|
front = rear;
|
||||||
|
middlePoints.length = 0;
|
||||||
|
} else {
|
||||||
|
middlePoints.push(zRear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dropBy(c, shouldRemove);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disjoint set for coordinate alignment
|
// Disjoint set for coordinate alignment
|
||||||
|
@ -297,10 +382,18 @@ function aligned(a, b, c) {
|
||||||
function between(a, b, c) {
|
function between(a, b, c) {
|
||||||
return (a <= b && b <= c) || (a >= b && b >= c);
|
return (a <= b && b <= c) || (a >= b && b >= c);
|
||||||
}
|
}
|
||||||
|
function pointsColinear(zPrev, zCurr, zNext) {
|
||||||
|
if (aligned(zPrev.x, zCurr.x, zNext.x) && between(zPrev.y, zCurr.y, zNext.y)) return true;
|
||||||
|
if (aligned(zPrev.y, zCurr.y, zNext.y) && between(zPrev.x, zCurr.x, zNext.x)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Dropping helper
|
// Dropping helper
|
||||||
function dropBy(a, d) {
|
function dropBy(c, shouldRemove) {
|
||||||
let r = [];
|
let n = 0;
|
||||||
for (let i = 0; i < a.length; i++) if (!d[i]) r.push(a[i]);
|
for (let i = 0; i < c.length; i++) {
|
||||||
return r;
|
if (!shouldRemove[i]) c[n++] = c[i];
|
||||||
|
}
|
||||||
|
c.length = n;
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { mix } from "../utils.mjs";
|
||||||
|
|
||||||
export class Point {
|
export class Point {
|
||||||
constructor(type, x, y) {
|
constructor(type, x, y) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
@ -57,6 +59,9 @@ export class Point {
|
||||||
static translated(z, dx, dy) {
|
static translated(z, dx, dy) {
|
||||||
return new Point(z.type, z.x + dx || 0, z.y + dy || 0);
|
return new Point(z.type, z.x + dx || 0, z.y + dy || 0);
|
||||||
}
|
}
|
||||||
|
static mix(type, a, b, p) {
|
||||||
|
return new Point(type, mix(a.x, b.x, p), mix(a.y, b.y, p));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Point.Type = {
|
Point.Type = {
|
||||||
Corner: 0,
|
Corner: 0,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue