Further improve shape cleanup code
This commit is contained in:
parent
35fa24274d
commit
229d624ebf
4 changed files with 90 additions and 69 deletions
|
@ -20,9 +20,12 @@ function regulateGlyphStore(cache, skew, glyphStore) {
|
||||||
for (const g of glyphStore.glyphs()) {
|
for (const g of glyphStore.glyphs()) {
|
||||||
if (g.geometry.isEmpty()) continue;
|
if (g.geometry.isEmpty()) continue;
|
||||||
if (!regulateCompositeGlyph(glyphStore, compositeMemo, g)) {
|
if (!regulateCompositeGlyph(glyphStore, compositeMemo, g)) {
|
||||||
flattenSimpleGlyph(cache, skew, g);
|
g.geometry = g.geometry.unlinkReferences();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const g of glyphStore.glyphs()) {
|
||||||
|
if (!compositeMemo.get(g)) flattenSimpleGlyph(cache, skew, g);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -113,7 +116,7 @@ class SimplifyGeometry extends Geom.GeometryBase {
|
||||||
return this.m_geom.measureComplexity();
|
return this.m_geom.measureComplexity();
|
||||||
}
|
}
|
||||||
toShapeStringOrNull() {
|
toShapeStringOrNull() {
|
||||||
const sTarget = this.m_geom.unwrapShapeIdentity().toShapeStringOrNull();
|
const sTarget = this.m_geom.unlinkReferences().toShapeStringOrNull();
|
||||||
if (!sTarget) return null;
|
if (!sTarget) return null;
|
||||||
return `SimplifyGeometry{${sTarget}}`;
|
return `SimplifyGeometry{${sTarget}}`;
|
||||||
}
|
}
|
||||||
|
@ -121,83 +124,104 @@ class SimplifyGeometry extends Geom.GeometryBase {
|
||||||
|
|
||||||
class FairizedShapeSink {
|
class FairizedShapeSink {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.lastReferenceZ = null;
|
|
||||||
this.contours = [];
|
this.contours = [];
|
||||||
this.lastContour = [];
|
this.lastContour = [];
|
||||||
}
|
}
|
||||||
beginShape() {}
|
beginShape() {}
|
||||||
endShape() {
|
endShape() {
|
||||||
this.lastReferenceZ = null;
|
|
||||||
if (this.lastContour.length > 2) {
|
if (this.lastContour.length > 2) {
|
||||||
// TT use CW for outline, being different from Clipper
|
// TT use CW for outline, being different from Clipper
|
||||||
const c = this.lastContour.reverse();
|
let c = this.lastContour.reverse();
|
||||||
const zFirst = c[0],
|
c = this.alignHVKnots(c);
|
||||||
zLast = c[c.length - 1];
|
c = this.cleanupOccurrentKnots1(c);
|
||||||
if (isOccurrent(zFirst, zLast)) c.pop();
|
c = this.cleanupOccurrentKnots2(c);
|
||||||
|
c = this.removeColinearKnots(c);
|
||||||
this.contours.push(c);
|
this.contours.push(c);
|
||||||
}
|
}
|
||||||
this.lastContour = [];
|
this.lastContour = [];
|
||||||
}
|
}
|
||||||
tryAlignWithPreviousKnot(z) {
|
|
||||||
if (!this.lastReferenceZ) return z;
|
|
||||||
let x1 = z.x,
|
|
||||||
y1 = z.y;
|
|
||||||
if (geometryPrecisionEqual(x1, this.lastReferenceZ.x)) x1 = this.lastReferenceZ.x;
|
|
||||||
if (geometryPrecisionEqual(y1, this.lastReferenceZ.y)) y1 = this.lastReferenceZ.y;
|
|
||||||
return Point.fromXY(z.type, x1, y1).round(CurveUtil.GEOMETRY_PRECISION);
|
|
||||||
}
|
|
||||||
moveTo(x, y) {
|
moveTo(x, y) {
|
||||||
this.endShape();
|
this.endShape();
|
||||||
this.lineTo(x, y);
|
this.lineTo(x, y);
|
||||||
}
|
}
|
||||||
lineTo(x, y) {
|
lineTo(x, y) {
|
||||||
const z0 = Point.fromXY(Point.Type.Corner, x, y);
|
this.lastContour.push(Point.fromXY(Point.Type.Corner, x, y));
|
||||||
const z = this.tryAlignWithPreviousKnot(z0);
|
|
||||||
this.popOccurrentKnots(z);
|
|
||||||
this.popColinearKnots(z);
|
|
||||||
this.lastContour.push(z);
|
|
||||||
this.lastReferenceZ = z0;
|
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
const offPoints = TypoGeom.Quadify.auto(arc, 1, 8);
|
const offPoints = TypoGeom.Quadify.auto(arc, 1, 8);
|
||||||
for (const z of offPoints) {
|
for (const z of offPoints) {
|
||||||
this.lastContour.push(
|
this.lastContour.push(Point.from(Point.Type.Quadratic, z));
|
||||||
this.tryAlignWithPreviousKnot(Point.from(Point.Type.Quadratic, z))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.lineTo(x, y);
|
this.lineTo(x, y);
|
||||||
}
|
}
|
||||||
popOccurrentKnots(z) {
|
|
||||||
if (this.lastContour.length <= 0) return;
|
// Contour cleaning code
|
||||||
const last = this.lastContour[this.lastContour.length - 1];
|
alignHVKnots(c0) {
|
||||||
if (last.type === Point.Type.Corner && last.x === z.x && last.y === z.y) {
|
const c = c0.slice(0);
|
||||||
this.lastContour.pop();
|
for (let i = 0; i < c.length; i++) {
|
||||||
|
const zPrev = c[i],
|
||||||
|
zCurr = c[(i + 1) % c.length];
|
||||||
|
if (zPrev.type === Point.Type.Corner) {
|
||||||
|
if (occurrentPrecisionEqual(zPrev.x, zCurr.x)) zCurr.x = zPrev.x;
|
||||||
|
if (occurrentPrecisionEqual(zPrev.y, zCurr.y)) zCurr.y = zPrev.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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 (occurrentPrecisionEqual(zCurr.x, zNext.x)) zCurr.x = zNext.x;
|
||||||
|
if (occurrentPrecisionEqual(zCurr.y, zNext.y)) zCurr.y = zNext.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
popColinearKnots(z) {
|
cleanupOccurrentKnots1(c0) {
|
||||||
let kArcStart = this.lastContour.length - 2;
|
const c = [c0[0]];
|
||||||
if (kArcStart >= 0) {
|
for (let i = 1; i < c0.length; i++) {
|
||||||
const kLast = kArcStart + 1;
|
|
||||||
if (
|
if (
|
||||||
this.lastContour[kArcStart].type !== Point.Type.Corner &&
|
!(
|
||||||
this.lastContour[kLast].type === Point.Type.Corner
|
c0[i].type === Point.Type.Corner &&
|
||||||
|
c0[i - 1].type === Point.Type.Corner &&
|
||||||
|
isOccurrent(c0[i], c0[i - 1])
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
c.push(c0[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (kArcStart >= 0 && this.lastContour[kArcStart].type !== Point.Type.Corner)
|
return c;
|
||||||
kArcStart--;
|
}
|
||||||
if (kArcStart >= 0) {
|
cleanupOccurrentKnots2(c0) {
|
||||||
const a = this.lastContour[kArcStart];
|
const c = c0.slice(0);
|
||||||
let fColinearH = true;
|
const zFirst = c[0],
|
||||||
let fColinearV = true;
|
zLast = c[c.length - 1];
|
||||||
for (let m = kArcStart + 1; m < this.lastContour.length; m++) {
|
if (isOccurrent(zFirst, zLast)) c.pop();
|
||||||
const b = this.lastContour[m];
|
return c;
|
||||||
if (!(aligned(a.y, b.y, z.y) && between(a.x, b.x, z.x))) fColinearH = false;
|
}
|
||||||
if (!(aligned(a.x, b.x, z.x) && between(a.y, b.y, z.y))) fColinearV = false;
|
removeColinearKnots(c0) {
|
||||||
|
const c = c0.slice(0),
|
||||||
|
shouldRemove = [];
|
||||||
|
for (let i = 0; i < c.length; i++) {
|
||||||
|
const zPrev = c[(i - 1 + c.length) % c.length],
|
||||||
|
zCurr = c[i],
|
||||||
|
zNext = c[(i + 1) % c.length];
|
||||||
|
if (
|
||||||
|
zPrev.type === Point.Type.Corner &&
|
||||||
|
zCurr.type === Point.Type.Corner &&
|
||||||
|
zNext.type === Point.Type.Corner
|
||||||
|
) {
|
||||||
|
if (aligned(zPrev.x, zCurr.x, zNext.x) && between(zPrev.y, zCurr.y, zNext.y))
|
||||||
|
shouldRemove[i] = true;
|
||||||
|
if (aligned(zPrev.y, zCurr.y, zNext.y) && between(zPrev.x, zCurr.x, zNext.x))
|
||||||
|
shouldRemove[i] = true;
|
||||||
}
|
}
|
||||||
if (fColinearH || fColinearV) this.lastContour.length = kArcStart + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const c2 = [];
|
||||||
|
for (let i = 0; i < c.length; i++) {
|
||||||
|
if (!shouldRemove[i]) c2.push(c[i]);
|
||||||
|
}
|
||||||
|
return c2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function isOccurrent(zFirst, zLast) {
|
function isOccurrent(zFirst, zLast) {
|
||||||
|
@ -208,14 +232,11 @@ function isOccurrent(zFirst, zLast) {
|
||||||
zFirst.y === zLast.y
|
zFirst.y === zLast.y
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function geometryPrecisionEqual(a, b) {
|
function occurrentPrecisionEqual(a, b) {
|
||||||
return (
|
return Math.abs(a - b) < CurveUtil.OCCURRENT_PRECISION;
|
||||||
Math.round(a * CurveUtil.RECIP_GEOMETRY_PRECISION) ===
|
|
||||||
Math.round(b * CurveUtil.RECIP_GEOMETRY_PRECISION)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
function aligned(a, b, c) {
|
function aligned(a, b, c) {
|
||||||
return geometryPrecisionEqual(a, b) && geometryPrecisionEqual(b, c);
|
return occurrentPrecisionEqual(a, b) && occurrentPrecisionEqual(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);
|
||||||
|
|
|
@ -5,8 +5,8 @@ const Point = require("./point");
|
||||||
const Transform = require("./transform");
|
const Transform = require("./transform");
|
||||||
|
|
||||||
exports.SPIRO_PRECISION = 1 / 2;
|
exports.SPIRO_PRECISION = 1 / 2;
|
||||||
|
exports.OCCURRENT_PRECISION = 1 / 16;
|
||||||
exports.GEOMETRY_PRECISION = 1 / 4;
|
exports.GEOMETRY_PRECISION = 1 / 4;
|
||||||
exports.RECIP_GEOMETRY_PRECISION = 4;
|
|
||||||
exports.BOOLE_RESOLUTION = 0x4000;
|
exports.BOOLE_RESOLUTION = 0x4000;
|
||||||
|
|
||||||
exports.OffsetCurve = class OffsetCurve {
|
exports.OffsetCurve = class OffsetCurve {
|
||||||
|
|
|
@ -14,7 +14,7 @@ class GeometryBase {
|
||||||
asReferences() {
|
asReferences() {
|
||||||
throw new Error("Unimplemented");
|
throw new Error("Unimplemented");
|
||||||
}
|
}
|
||||||
unwrapShapeIdentity() {
|
unlinkReferences() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
filterTag(fn) {
|
filterTag(fn) {
|
||||||
|
@ -103,8 +103,8 @@ class ReferenceGeometry extends GeometryBase {
|
||||||
measureComplexity() {
|
measureComplexity() {
|
||||||
return this.m_glyph.geometry.measureComplexity();
|
return this.m_glyph.geometry.measureComplexity();
|
||||||
}
|
}
|
||||||
unwrapShapeIdentity() {
|
unlinkReferences() {
|
||||||
return this.unwrap().unwrapShapeIdentity();
|
return this.unwrap().unlinkReferences();
|
||||||
}
|
}
|
||||||
toShapeStringOrNull() {
|
toShapeStringOrNull() {
|
||||||
let sTarget = this.m_glyph.geometry.toShapeStringOrNull();
|
let sTarget = this.m_glyph.geometry.toShapeStringOrNull();
|
||||||
|
@ -135,8 +135,8 @@ class TaggedGeometry extends GeometryBase {
|
||||||
measureComplexity() {
|
measureComplexity() {
|
||||||
return this.m_geom.measureComplexity();
|
return this.m_geom.measureComplexity();
|
||||||
}
|
}
|
||||||
unwrapShapeIdentity() {
|
unlinkReferences() {
|
||||||
return this.m_geom.unwrapShapeIdentity();
|
return this.m_geom.unlinkReferences();
|
||||||
}
|
}
|
||||||
toShapeStringOrNull() {
|
toShapeStringOrNull() {
|
||||||
return this.m_geom.toShapeStringOrNull();
|
return this.m_geom.toShapeStringOrNull();
|
||||||
|
@ -179,8 +179,8 @@ class TransformedGeometry extends GeometryBase {
|
||||||
measureComplexity() {
|
measureComplexity() {
|
||||||
return this.m_geom.measureComplexity();
|
return this.m_geom.measureComplexity();
|
||||||
}
|
}
|
||||||
unwrapShapeIdentity() {
|
unlinkReferences() {
|
||||||
const unwrapped = this.m_geom.unwrapShapeIdentity();
|
const unwrapped = this.m_geom.unlinkReferences();
|
||||||
if (Transform.isIdentity(this.m_transform)) {
|
if (Transform.isIdentity(this.m_transform)) {
|
||||||
return unwrapped;
|
return unwrapped;
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -261,10 +261,10 @@ class CombineGeometry extends GeometryBase {
|
||||||
for (const part of this.m_parts) s += part.measureComplexity();
|
for (const part of this.m_parts) s += part.measureComplexity();
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapShapeIdentity() {
|
unlinkReferences() {
|
||||||
let parts = [];
|
let parts = [];
|
||||||
for (const part of this.m_parts) {
|
for (const part of this.m_parts) {
|
||||||
const unwrapped = part.unwrapShapeIdentity();
|
const unwrapped = part.unlinkReferences();
|
||||||
if (unwrapped instanceof CombineGeometry) {
|
if (unwrapped instanceof CombineGeometry) {
|
||||||
for (const p of unwrapped.m_parts) parts.push(p);
|
for (const p of unwrapped.m_parts) parts.push(p);
|
||||||
} else {
|
} else {
|
||||||
|
@ -340,13 +340,13 @@ class BooleanGeometry extends GeometryBase {
|
||||||
let s = 0;
|
let s = 0;
|
||||||
for (const operand of this.m_operands) s += operand.measureComplexity();
|
for (const operand of this.m_operands) s += operand.measureComplexity();
|
||||||
}
|
}
|
||||||
unwrapShapeIdentity() {
|
unlinkReferences() {
|
||||||
if (this.m_operands.length === 0) return new CombineGeometry([]);
|
if (this.m_operands.length === 0) return new CombineGeometry([]);
|
||||||
if (this.m_operands.length === 1) return this.m_operands[0].unwrapShapeIdentity();
|
if (this.m_operands.length === 1) return this.m_operands[0].unlinkReferences();
|
||||||
|
|
||||||
let operands = [];
|
let operands = [];
|
||||||
for (const operand of this.m_operands) {
|
for (const operand of this.m_operands) {
|
||||||
operands.push(operand.unwrapShapeIdentity());
|
operands.push(operand.unlinkReferences());
|
||||||
}
|
}
|
||||||
return new BooleanGeometry(this.m_operator, operands);
|
return new BooleanGeometry(this.m_operator, operands);
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,8 +131,8 @@ module.exports = class Glyph {
|
||||||
|
|
||||||
tryBecomeMirrorOf(dst, rankSet) {
|
tryBecomeMirrorOf(dst, rankSet) {
|
||||||
if (rankSet.has(this) || rankSet.has(dst)) return;
|
if (rankSet.has(this) || rankSet.has(dst)) return;
|
||||||
const csThis = this.geometry.unwrapShapeIdentity().toShapeStringOrNull();
|
const csThis = this.geometry.unlinkReferences().toShapeStringOrNull();
|
||||||
const csDst = dst.geometry.unwrapShapeIdentity().toShapeStringOrNull();
|
const csDst = dst.geometry.unlinkReferences().toShapeStringOrNull();
|
||||||
if (csThis && csDst && csThis === csDst) {
|
if (csThis && csDst && csThis === csDst) {
|
||||||
this.geometry = new Geom.CombineGeometry([new Geom.ReferenceGeometry(dst, 0, 0)]);
|
this.geometry = new Geom.CombineGeometry([new Geom.ReferenceGeometry(dst, 0, 0)]);
|
||||||
rankSet.add(this);
|
rankSet.add(this);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue