Make the geometry cache more efficient. Bump version. (#2270)

This commit is contained in:
Belleve 2024-04-02 00:00:59 -10:00 committed by GitHub
parent c9d9ee2319
commit 4f2f0d973c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 360 additions and 247 deletions

View file

@ -13,11 +13,11 @@ import { Transform } from "./transform.mjs";
export const CPLX_NON_EMPTY = 0x01; // A geometry tree that is not empty
export const CPLX_NON_SIMPLE = 0x02; // A geometry tree that contains non-simple contours
export const CPLX_BROKEN = 0x04; // A geometry tree that contains broken contours, like having points with NaN coordinates
export const CPLX_BROKEN = 0x04; // A geometry tree that contains broken contours
export const CPLX_UNKNOWN = 0xff;
export class GeometryBase {
toContours() {
toContours(ctx) {
throw new Error("Unimplemented");
}
toReferences() {
@ -35,8 +35,9 @@ export class GeometryBase {
measureComplexity() {
return CPLX_UNKNOWN;
}
toShapeStringOrNull() {
return null;
hash(h) {
return h.invalid();
}
}
@ -47,7 +48,7 @@ export class ContourSetGeometry extends GeometryBase {
super();
this.m_contours = contours;
}
toContours() {
toContours(ctx) {
return this.m_contours;
}
toReferences() {
@ -68,26 +69,52 @@ export class ContourSetGeometry extends GeometryBase {
}
return cp;
}
toShapeStringOrNull() {
return Format.struct(
`ContourSetGeometry`,
Format.list(this.m_contours.map(c => Format.list(c.map(Format.typedPoint)))),
);
hash(h) {
h.beginStruct("ContourSetGeometry");
h.beginArray(this.m_contours.length);
for (const c of this.m_contours) {
h.beginArray(c.length);
for (const z of c) h.typedPoint(z);
h.endArray();
}
h.endArray();
h.endStruct();
}
}
export class SpiroGeometry extends GeometryBase {
// Enabling geometry cache over the deep nodes of the geometry tree
export class CachedGeometry extends GeometryBase {
toContours(ctx) {
let ck = null;
if (ctx && ctx.cache) {
ck = hashGeometry(this);
const gf = ctx.cache.getGF(ck);
if (gf) {
ctx.cache.refreshGF(ck);
return gf;
}
}
const outline = this.toContoursImpl(ctx);
if (ck && ctx && ctx.cache) ctx.cache.saveGF(ck, outline);
return outline;
}
toContoursImpl() {
throw new Error("Unimplemented");
}
}
export class SpiroGeometry extends CachedGeometry {
constructor(gizmo, closed, knots) {
super();
this.m_knots = knots;
this.m_closed = closed;
this.m_gizmo = gizmo;
this.m_cachedContours = null;
}
toContours() {
if (this.m_cachedContours) return this.m_cachedContours;
this.m_cachedContours = spiroToOutline(this.m_knots, this.m_closed, this.m_gizmo);
return this.m_cachedContours;
toContoursImpl() {
return spiroToOutline(this.m_knots, this.m_closed, this.m_gizmo);
}
toReferences() {
return null;
@ -105,28 +132,28 @@ export class SpiroGeometry extends GeometryBase {
}
return cplx;
}
toShapeStringOrNull() {
return Format.struct(
"SpiroGeometry",
Format.gizmo(this.m_gizmo),
this.m_closed,
Format.list(this.m_knots.map(k => k.toShapeString())),
);
hash(h) {
h.beginStruct("SpiroGeometry");
h.gizmo(this.m_gizmo);
h.bool(this.m_closed);
h.beginArray(this.m_knots.length);
for (const knot of this.m_knots) h.embed(knot);
h.endArray();
h.endStruct();
}
}
export class DiSpiroGeometry extends GeometryBase {
export class DiSpiroGeometry extends CachedGeometry {
constructor(gizmo, contrast, closed, biKnots) {
super();
this.m_biKnots = biKnots; // untransformed
this.m_closed = closed;
this.m_gizmo = gizmo;
this.m_contrast = contrast;
this.m_cachedExpansionResults = null;
this.m_cachedContours = null;
}
toContours() {
if (this.m_cachedContours) return this.m_cachedContours;
toContoursImpl() {
const expandResult = this.expand();
const lhs = [...expandResult.lhsUntransformed];
const rhs = [...expandResult.rhsUntransformed];
@ -134,23 +161,20 @@ export class DiSpiroGeometry extends GeometryBase {
for (const k of rhs) k.reverseType();
rhs.reverse();
let outlineGeometry;
if (this.m_closed) {
outlineGeometry = new CombineGeometry([
new SpiroGeometry(this.m_gizmo, true, lhs),
new SpiroGeometry(this.m_gizmo, true, rhs),
]);
return [
...new SpiroGeometry(this.m_gizmo, true, lhs).toContoursImpl(),
...new SpiroGeometry(this.m_gizmo, true, rhs).toContoursImpl(),
];
} else {
lhs[0].type = lhs[lhs.length - 1].type = "corner";
rhs[0].type = rhs[rhs.length - 1].type = "corner";
const allKnots = lhs.concat(rhs);
outlineGeometry = new SpiroGeometry(this.m_gizmo, true, allKnots);
return new SpiroGeometry(this.m_gizmo, true, allKnots).toContoursImpl();
}
this.m_cachedContours = outlineGeometry.toContours();
return this.m_cachedContours;
}
expand() {
if (this.m_cachedExpansionResults) return this.m_cachedExpansionResults;
const expander = new SpiroExpander(
this.m_gizmo,
this.m_contrast,
@ -162,8 +186,7 @@ export class DiSpiroGeometry extends GeometryBase {
expander.iterateNormals();
expander.iterateNormals();
expander.iterateNormals();
this.m_cachedExpansionResults = expander.expand();
return this.m_cachedExpansionResults;
return expander.expand();
}
toReferences() {
return null;
@ -181,14 +204,16 @@ export class DiSpiroGeometry extends GeometryBase {
}
return cplx;
}
toShapeStringOrNull() {
return Format.struct(
"DiSpiroGeometry",
Format.gizmo(this.m_gizmo),
Format.n(this.m_contrast),
this.m_closed,
Format.list(this.m_biKnots.map(z => z.toShapeString())),
);
hash(h) {
h.beginStruct("DiSpiroGeometry");
h.gizmo(this.m_gizmo);
h.f64(this.m_contrast);
h.bool(this.m_closed);
h.beginArray(this.m_biKnots.length);
for (const knot of this.m_biKnots) h.embed(knot);
h.endArray();
h.endStruct();
}
}
@ -206,8 +231,8 @@ export class ReferenceGeometry extends GeometryBase {
this.m_glyph.geometry,
);
}
toContours() {
return this.unwrap().toContours();
toContours(ctx) {
return this.unwrap().toContours(ctx);
}
toReferences() {
if (this.m_glyph.geometry.measureComplexity() & CPLX_NON_EMPTY) {
@ -229,10 +254,13 @@ export class ReferenceGeometry extends GeometryBase {
unlinkReferences() {
return this.unwrap().unlinkReferences();
}
toShapeStringOrNull() {
let sTarget = this.m_glyph.geometry.toShapeStringOrNull();
if (!sTarget) return null;
return Format.struct("ReferenceGeometry", sTarget, Format.n(this.m_x), Format.n(this.m_y));
hash(h) {
h.beginStruct("ReferenceGeometry");
h.embed(this.m_glyph.geometry);
h.f64(this.m_x);
h.f64(this.m_y);
h.endStruct();
}
}
@ -242,8 +270,8 @@ export class TaggedGeometry extends GeometryBase {
this.m_geom = g;
this.m_tag = tag;
}
toContours() {
return this.m_geom.toContours();
toContours(ctx) {
return this.m_geom.toContours(ctx);
}
toReferences() {
return this.m_geom.toReferences();
@ -261,8 +289,9 @@ export class TaggedGeometry extends GeometryBase {
unlinkReferences() {
return this.m_geom.unlinkReferences();
}
toShapeStringOrNull() {
return this.m_geom.toShapeStringOrNull();
hash(h) {
this.m_geom.hash(h);
}
}
@ -277,9 +306,9 @@ export class TransformedGeometry extends GeometryBase {
return new TransformedGeometry(Transform.Combine(this.m_transform, tfm), this.m_geom);
}
toContours() {
toContours(ctx) {
let result = [];
for (const c of this.m_geom.toContours()) {
for (const c of this.m_geom.toContours(ctx)) {
let c1 = [];
for (const z of c) c1.push(Point.transformed(this.m_transform, z));
result.push(c1);
@ -319,10 +348,13 @@ export class TransformedGeometry extends GeometryBase {
return new TransformedGeometry(this.m_transform, unwrapped);
}
}
toShapeStringOrNull() {
const sTarget = this.m_geom.toShapeStringOrNull();
if (!sTarget) return null;
return Format.struct("TransformedGeometry", Format.gizmo(this.m_transform), sTarget);
hash(h) {
h.beginStruct("TransformedGeometry");
h.gizmo(this.m_transform);
h.embed(this.m_geom);
h.endStruct();
return h;
}
}
@ -331,8 +363,8 @@ export class RadicalGeometry extends GeometryBase {
super();
this.m_geom = g;
}
toContours() {
return this.m_geom.toContours();
toContours(ctx) {
return this.m_geom.toContours(ctx);
}
toReferences() {
return null;
@ -351,10 +383,9 @@ export class RadicalGeometry extends GeometryBase {
unlinkReferences() {
return this.m_geom.unlinkReferences();
}
toShapeStringOrNull() {
const sTarget = this.m_geom.toShapeStringOrNull();
if (!sTarget) return null;
return Format.struct("RadicalGeometry", sTarget);
hash(h) {
this.m_geom.hash(h);
}
}
@ -370,10 +401,10 @@ export class CombineGeometry extends GeometryBase {
return new CombineGeometry([...this.m_parts, g]);
}
}
toContours() {
toContours(ctx) {
let results = [];
for (const part of this.m_parts) {
for (const c of part.toContours()) {
for (const c of part.toContours(ctx)) {
results.push(c);
}
}
@ -424,30 +455,24 @@ export class CombineGeometry extends GeometryBase {
}
return new CombineGeometry(parts);
}
toShapeStringOrNull() {
let sParts = [];
for (const item of this.m_parts) {
const sPart = item.toShapeStringOrNull();
if (!sPart) return null;
sParts.push(sPart);
}
return Format.struct("CombineGeometry", Format.list(sParts));
hash(h) {
h.beginStruct("CombineGeometry");
h.beginArray(this.m_parts.length);
for (const part of this.m_parts) h.embed(part);
h.endArray();
h.endStruct();
}
}
export class BooleanGeometry extends GeometryBase {
export class BooleanGeometry extends CachedGeometry {
constructor(operator, operands) {
super();
this.m_operator = operator;
this.m_operands = operands;
this.m_resolved = null;
}
toContours() {
if (this.m_resolved) return this.m_resolved;
this.m_resolved = this.asContoursImpl();
return this.m_resolved;
}
asContoursImpl() {
toContoursImpl() {
if (this.m_operands.length === 0) return [];
const stack = [];
@ -516,18 +541,18 @@ export class BooleanGeometry extends GeometryBase {
}
return new BooleanGeometry(this.m_operator, operands);
}
toShapeStringOrNull() {
let sParts = [];
for (const item of this.m_operands) {
const sPart = item.toShapeStringOrNull();
if (!sPart) return null;
sParts.push(sPart);
}
return Format.struct("BooleanGeometry", this.m_operator, Format.list(sParts));
hash(h) {
h.beginStruct("BooleanGeometry");
h.u32(this.m_operator);
h.beginArray(this.m_operands.length);
for (const operand of this.m_operands) h.embed(operand);
h.endArray();
h.endStruct();
}
}
export class StrokeGeometry extends GeometryBase {
export class StrokeGeometry extends CachedGeometry {
constructor(geom, gizmo, radius, contrast, fInside) {
super();
this.m_geom = geom;
@ -537,11 +562,11 @@ export class StrokeGeometry extends GeometryBase {
this.m_fInside = fInside;
}
toContours() {
toContoursImpl(ctx) {
// Produce simplified arcs
const nonTransformedGeometry = new TransformedGeometry(this.m_gizmo.inverse(), this.m_geom);
let arcs = TypoGeom.Boolean.removeOverlap(
CurveUtil.convertShapeToArcs(nonTransformedGeometry.toContours()),
CurveUtil.convertShapeToArcs(nonTransformedGeometry.toContours(ctx)),
TypoGeom.Boolean.PolyFillType.pftNonZero,
CurveUtil.BOOLE_RESOLUTION,
);
@ -590,29 +615,27 @@ export class StrokeGeometry extends GeometryBase {
measureComplexity() {
return this.m_geom.measureComplexity() | CPLX_NON_SIMPLE;
}
toShapeStringOrNull() {
const sTarget = this.m_geom.unlinkReferences().toShapeStringOrNull();
if (!sTarget) return null;
return Format.struct(
`StrokeGeometry`,
sTarget,
Format.gizmo(this.m_gizmo),
Format.n(this.m_radius),
Format.n(this.m_contrast),
this.m_fInside,
);
hash(h) {
h.beginStruct("StrokeGeometry");
h.embed(this.m_geom);
h.gizmo(this.m_gizmo);
h.f64(this.m_radius);
h.f64(this.m_contrast);
h.bool(this.m_fInside);
h.endStruct();
}
}
// This special geometry type is used in the finalization phase to create TTF contours.
export class SimplifyGeometry extends GeometryBase {
export class SimplifyGeometry extends CachedGeometry {
constructor(g) {
super();
this.m_geom = g;
}
toContours() {
toContoursImpl(ctx) {
// Produce simplified arcs
let arcs = CurveUtil.convertShapeToArcs(this.m_geom.toContours());
let arcs = CurveUtil.convertShapeToArcs(this.m_geom.toContours(ctx));
if (this.m_geom.measureComplexity() & CPLX_NON_SIMPLE) {
arcs = TypoGeom.Boolean.removeOverlap(
arcs,
@ -645,10 +668,11 @@ export class SimplifyGeometry extends GeometryBase {
measureComplexity() {
return this.m_geom.measureComplexity();
}
toShapeStringOrNull() {
const sTarget = this.m_geom.unlinkReferences().toShapeStringOrNull();
if (!sTarget) return null;
return `SimplifyGeometry{${sTarget}}`;
hash(h) {
h.beginStruct("SimplifyGeometry");
h.embed(this.m_geom);
h.endStruct();
}
}
@ -662,7 +686,7 @@ export function combineWith(a, b) {
}
export function hashGeometry(geom) {
const s = geom.toShapeStringOrNull();
if (!s) return null;
return crypto.createHash("sha256").update(s).digest("hex");
const hasher = new Format.Hasher();
geom.hash(hasher);
return hasher.digest();
}

View file

@ -115,9 +115,15 @@ export class MonoKnot {
const k1 = new MonoKnot(this.type, this.x, this.y, this.unimportant);
return k1;
}
toShapeString() {
return Format.tuple(this.type, this.unimportant, Format.n(this.x), Format.n(this.y));
hash(h) {
h.beginStruct("MonoKnot");
h.str(this.type);
h.bool(this.unimportant);
h.f64(this.x);
h.f64(this.y);
h.endStruct();
}
reverseType() {
if (this.type === "left") {
this.type = "right";
@ -158,19 +164,26 @@ class BiKnot {
k1.unimportant = this.unimportant;
return k1;
}
toShapeString() {
return Format.tuple(
this.type,
this.unimportant,
Format.n(this.x),
Format.n(this.y),
this.d1 == null ? "" : Format.n(this.d1),
this.d2 == null ? "" : Format.n(this.d2),
this.proposedNormal
? Format.tuple(Format.n(this.proposedNormal.x), Format.n(this.proposedNormal.y))
: "",
);
hash(h) {
h.beginStruct("BiKnot");
h.str(this.type);
h.bool(this.unimportant);
h.f64(this.x);
h.f64(this.y);
h.bool(this.d1 != null);
if (this.d1 != null) h.f64(this.d1);
h.bool(this.d2 != null);
if (this.d2 != null) h.f64(this.d2);
h.bool(this.proposedNormal != null);
if (this.proposedNormal) {
h.f64(this.proposedNormal.x);
h.f64(this.proposedNormal.y);
}
h.endStruct();
}
toMono() {
return new MonoKnot(this.type, this.unimportant, this.x, this.y);
}