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

80
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "@iosevka/monorepo", "name": "@iosevka/monorepo",
"version": "29.0.5", "version": "29.0.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@iosevka/monorepo", "name": "@iosevka/monorepo",
"version": "29.0.5", "version": "29.0.6",
"workspaces": [ "workspaces": [
"packages/*", "packages/*",
"tools/*" "tools/*"
@ -4310,16 +4310,16 @@
}, },
"packages/font": { "packages/font": {
"name": "@iosevka/font", "name": "@iosevka/font",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@iosevka/font-glyphs": "29.0.5", "@iosevka/font-glyphs": "29.0.6",
"@iosevka/font-otl": "29.0.5", "@iosevka/font-otl": "29.0.6",
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@iosevka/geometry-cache": "29.0.5", "@iosevka/geometry-cache": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"@iosevka/param": "29.0.5", "@iosevka/param": "29.0.6",
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"harfbuzzjs": "^0.3.4", "harfbuzzjs": "^0.3.4",
"ot-builder": "^1.7.3", "ot-builder": "^1.7.3",
"semver": "^7.6.0" "semver": "^7.6.0"
@ -4327,100 +4327,100 @@
}, },
"packages/font-glyphs": { "packages/font-glyphs": {
"name": "@iosevka/font-glyphs", "name": "@iosevka/font-glyphs",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/font-kits": "29.0.5", "@iosevka/font-kits": "29.0.6",
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@iosevka/geometry-cache": "29.0.5", "@iosevka/geometry-cache": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"typo-geom": "^0.15.1" "typo-geom": "^0.15.1"
} }
}, },
"packages/font-kits": { "packages/font-kits": {
"name": "@iosevka/font-kits", "name": "@iosevka/font-kits",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"@iosevka/util": "29.0.5" "@iosevka/util": "29.0.6"
} }
}, },
"packages/font-otl": { "packages/font-otl": {
"name": "@iosevka/font-otl", "name": "@iosevka/font-otl",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/font-glyphs": "29.0.5", "@iosevka/font-glyphs": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"toposort": "^2.0.2" "toposort": "^2.0.2"
} }
}, },
"packages/geometry": { "packages/geometry": {
"name": "@iosevka/geometry", "name": "@iosevka/geometry",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"spiro": "^3.0.0", "spiro": "^3.0.0",
"typo-geom": "^0.15.1" "typo-geom": "^0.15.1"
} }
}, },
"packages/geometry-cache": { "packages/geometry-cache": {
"name": "@iosevka/geometry-cache", "name": "@iosevka/geometry-cache",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@msgpack/msgpack": "^2.8.0" "@msgpack/msgpack": "^2.8.0"
} }
}, },
"packages/glyph": { "packages/glyph": {
"name": "@iosevka/glyph", "name": "@iosevka/glyph",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/geometry": "29.0.5" "@iosevka/geometry": "29.0.6"
} }
}, },
"packages/param": { "packages/param": {
"name": "@iosevka/param", "name": "@iosevka/param",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/util": "29.0.5" "@iosevka/util": "29.0.6"
} }
}, },
"packages/util": { "packages/util": {
"name": "@iosevka/util", "name": "@iosevka/util",
"version": "29.0.5" "version": "29.0.6"
}, },
"tools/amend-readme": { "tools/amend-readme": {
"name": "@iosevka/amend-readme", "name": "@iosevka/amend-readme",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@iosevka/param": "29.0.5", "@iosevka/param": "29.0.6",
"@unicode/unicode-15.1.0": "^1.5.2" "@unicode/unicode-15.1.0": "^1.5.2"
} }
}, },
"tools/data-export": { "tools/data-export": {
"name": "@iosevka/data-export", "name": "@iosevka/data-export",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@iosevka/param": "29.0.5", "@iosevka/param": "29.0.6",
"@unicode/unicode-15.1.0": "^1.5.2", "@unicode/unicode-15.1.0": "^1.5.2",
"cldr": "^7.5.0" "cldr": "^7.5.0"
} }
}, },
"tools/generate-samples": { "tools/generate-samples": {
"name": "@iosevka/generate-samples", "name": "@iosevka/generate-samples",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/data-export": "29.0.5" "@iosevka/data-export": "29.0.6"
} }
}, },
"tools/misc": { "tools/misc": {
"name": "@iosevka/misc", "name": "@iosevka/misc",
"version": "29.0.5", "version": "29.0.6",
"dependencies": { "dependencies": {
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"semver": "^7.6.0", "semver": "^7.6.0",
"wawoff2": "^2.0.1" "wawoff2": "^2.0.1"
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/monorepo", "name": "@iosevka/monorepo",
"version": "29.0.5", "version": "29.0.6",
"workspaces": [ "workspaces": [
"packages/*", "packages/*",
"tools/*" "tools/*"

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/font-glyphs", "name": "@iosevka/font-glyphs",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs", ".": "./src/index.mjs",
@ -8,11 +8,11 @@
"./unicode-knowledge": "./src/meta/unicode-knowledge.mjs" "./unicode-knowledge": "./src/meta/unicode-knowledge.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/font-kits": "29.0.5", "@iosevka/font-kits": "29.0.6",
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@iosevka/geometry-cache": "29.0.5", "@iosevka/geometry-cache": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"typo-geom": "^0.15.1" "typo-geom": "^0.15.1"
} }
} }

View file

@ -1,14 +1,14 @@
{ {
"name": "@iosevka/font-kits", "name": "@iosevka/font-kits",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
"./boole-kit": "./src/boole-kit.mjs", "./boole-kit": "./src/boole-kit.mjs",
"./spiro-kit": "./src/spiro-kit.mjs" "./spiro-kit": "./src/spiro-kit.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"@iosevka/util": "29.0.5" "@iosevka/util": "29.0.6"
} }
} }

View file

@ -1,13 +1,13 @@
{ {
"name": "@iosevka/font-otl", "name": "@iosevka/font-otl",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs" ".": "./src/index.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/font-glyphs": "29.0.5", "@iosevka/font-glyphs": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"toposort": "^2.0.2" "toposort": "^2.0.2"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/font", "name": "@iosevka/font",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs", ".": "./src/index.mjs",
@ -10,13 +10,13 @@
}, },
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@iosevka/font-glyphs": "29.0.5", "@iosevka/font-glyphs": "29.0.6",
"@iosevka/font-otl": "29.0.5", "@iosevka/font-otl": "29.0.6",
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@iosevka/geometry-cache": "29.0.5", "@iosevka/geometry-cache": "29.0.6",
"@iosevka/glyph": "29.0.5", "@iosevka/glyph": "29.0.6",
"@iosevka/param": "29.0.5", "@iosevka/param": "29.0.6",
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"harfbuzzjs": "^0.3.4", "harfbuzzjs": "^0.3.4",
"ot-builder": "^1.7.3", "ot-builder": "^1.7.3",
"semver": "^7.6.0" "semver": "^7.6.0"

View file

@ -19,19 +19,6 @@ function regulateGlyphStore(cache, skew, glyphStore) {
} }
function flattenSimpleGlyph(cache, skew, g) { function flattenSimpleGlyph(cache, skew, g) {
// Check if the geometry is already in the cache. If so, use the cached geometry.
const ck = Geom.hashGeometry(g.geometry);
if (ck && cache) {
const cachedGeometry = cache && cache.getGF(ck);
if (cachedGeometry) {
g.clearGeometry();
g.includeContours(cachedGeometry);
cache.refreshGF(ck);
return;
}
}
// Perform the actual simplification
try { try {
let gSimplified; let gSimplified;
if (skew) { if (skew) {
@ -45,10 +32,9 @@ function flattenSimpleGlyph(cache, skew, g) {
gSimplified = new Geom.SimplifyGeometry(g.geometry); gSimplified = new Geom.SimplifyGeometry(g.geometry);
} }
const cs = gSimplified.toContours(); const cs = gSimplified.toContours({ cache });
g.clearGeometry(); g.clearGeometry();
g.includeContours(cs); g.includeContours(cs);
if (ck && cache) cache.saveGF(ck, cs);
} catch (e) { } catch (e) {
console.error("Detected broken geometry when processing", g._m_identifier); console.error("Detected broken geometry when processing", g._m_identifier);
throw e; throw e;

View file

@ -1,12 +1,12 @@
{ {
"name": "@iosevka/geometry-cache", "name": "@iosevka/geometry-cache",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs" ".": "./src/index.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/geometry": "29.0.5", "@iosevka/geometry": "29.0.6",
"@msgpack/msgpack": "^2.8.0" "@msgpack/msgpack": "^2.8.0"
} }
} }

View file

@ -4,7 +4,7 @@ import zlib from "zlib";
import * as CurveUtil from "@iosevka/geometry/curve-util"; import * as CurveUtil from "@iosevka/geometry/curve-util";
import { encode, decode } from "@msgpack/msgpack"; import { encode, decode } from "@msgpack/msgpack";
const Edition = 37; const Edition = 40;
const MAX_AGE = 16; const MAX_AGE = 16;
class GfEntry { class GfEntry {
constructor(age, value) { constructor(age, value) {

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/geometry", "name": "@iosevka/geometry",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs", ".": "./src/index.mjs",
@ -13,7 +13,7 @@
"./spiro-control": "./src/spiro-control.mjs" "./spiro-control": "./src/spiro-control.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/util": "29.0.5", "@iosevka/util": "29.0.6",
"spiro": "^3.0.0", "spiro": "^3.0.0",
"typo-geom": "^0.15.1" "typo-geom": "^0.15.1"
} }

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

View file

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

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/glyph", "name": "@iosevka/glyph",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/glyph.mjs", ".": "./src/glyph.mjs",
@ -9,6 +9,6 @@
"./relation": "./src/relation.mjs" "./relation": "./src/relation.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/geometry": "29.0.5" "@iosevka/geometry": "29.0.6"
} }
} }

View file

@ -150,8 +150,8 @@ export class Glyph {
tryBecomeMirrorOf(dst, rankSet) { tryBecomeMirrorOf(dst, rankSet) {
if (rankSet.has(this) || rankSet.has(dst)) return; if (rankSet.has(this) || rankSet.has(dst)) return;
if (dst.hasDependency(this)) return; if (dst.hasDependency(this)) return;
const csThis = this.geometry.unlinkReferences().toShapeStringOrNull(); const csThis = Geom.hashGeometry(this.geometry.unlinkReferences());
const csDst = dst.geometry.unlinkReferences().toShapeStringOrNull(); const csDst = Geom.hashGeometry(dst.geometry.unlinkReferences());
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);

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/param", "name": "@iosevka/param",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs", ".": "./src/index.mjs",
@ -9,6 +9,6 @@
"./metric-override": "./src/metric-override.mjs" "./metric-override": "./src/metric-override.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/util": "29.0.5" "@iosevka/util": "29.0.6"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/util", "name": "@iosevka/util",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs", ".": "./src/index.mjs",

View file

@ -1,18 +1,108 @@
export function struct(leader, ...items) { import crypto from "crypto";
return "" + leader + "(" + items.join(";") + ")";
} const TAG_INVALID = 0x11000000;
export function tuple(...items) { const TAG_BEGIN_STRUCT = 0x12340001;
return "(" + items.join(";") + ")"; const TAG_END_STRUCT = 0x12340002;
} const TAG_BEGIN_ARRAY = 0x12340003;
export function list(items) { const TAG_END_ARRAY = 0x12340004;
return "{" + items.join(";") + "}"; const TAG_BEGIN_STR = 0x12340005;
} const TAG_END_STR = 0x12340006;
export function n(x) { const TAG_BEGIN_STRUCT_TYPE = 0x12340007;
return String(Math.round(x * 0x10000)); const TAG_END_STRUCT_TYPE = 0x12340008;
}
export function typedPoint(z) { const TAG_TYPED_POINT = 0x12340010;
return tuple(z.type, n(z.x), n(z.y)); const TAG_GIZMO = 0x12340011;
} const TAG_LIST_LENGTH = 0x12340012;
export function gizmo(g) {
return tuple(n(g.xx), n(g.xy), n(g.yx), n(g.yy), n(g.tx), n(g.ty)); const TAG_EMBED_BEGIN = 0x12340020;
const TAG_EMBED_END = 0x12340021;
export class Hasher {
constructor() {
this.hash = crypto.createHash("sha256");
this.buf4 = Buffer.alloc(4);
this.buf8 = Buffer.alloc(8);
}
digest() {
return this.hash.digest("hex");
}
invalid() {
this.u32(TAG_INVALID);
return this;
}
beginStruct(typeStr) {
this.u32(TAG_BEGIN_STRUCT);
if (typeStr) {
this.u32(TAG_BEGIN_STRUCT_TYPE);
this.hash.update(typeStr);
this.u32(TAG_END_STRUCT_TYPE);
}
return this;
}
endStruct() {
this.u32(TAG_END_STRUCT);
return this;
}
beginArray(n) {
this.u32(TAG_BEGIN_ARRAY);
this.u32(TAG_LIST_LENGTH);
this.u32(n);
return this;
}
endArray() {
this.u32(TAG_END_ARRAY);
return this;
}
str(s) {
this.u32(TAG_BEGIN_STR);
this.hash.update(s);
this.u32(TAG_END_STR);
return this;
}
embed(other) {
this.u32(TAG_EMBED_BEGIN);
other.hash(this);
this.u32(TAG_EMBED_END);
return this;
}
bool(x) {
this.u32(x ? 1 : 0);
return this;
}
u32(x) {
this.buf4.writeUInt32LE(x, 0);
this.hash.update(this.buf4);
return this;
}
i32(x) {
this.buf4.writeInt32LE(x, 0);
this.hash.update(this.buf4);
return this;
}
f64(x) {
this.buf8.writeDoubleLE(x, 0);
this.hash.update(this.buf8);
return this;
}
typedPoint(z) {
this.u32(TAG_TYPED_POINT);
this.u32(z.type);
this.f64(z.x);
this.f64(z.y);
return this;
}
gizmo(g) {
this.u32(TAG_GIZMO);
this.f64(g.xx);
this.f64(g.xy);
this.f64(g.yx);
this.f64(g.yy);
this.f64(g.tx);
this.f64(g.ty);
return this;
}
} }

View file

@ -1,13 +1,13 @@
{ {
"name": "@iosevka/amend-readme", "name": "@iosevka/amend-readme",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs" ".": "./src/index.mjs"
}, },
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@iosevka/param": "29.0.5", "@iosevka/param": "29.0.6",
"@unicode/unicode-15.1.0": "^1.5.2" "@unicode/unicode-15.1.0": "^1.5.2"
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@iosevka/data-export", "name": "@iosevka/data-export",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs", ".": "./src/index.mjs",
@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@iosevka/param": "29.0.5", "@iosevka/param": "29.0.6",
"@unicode/unicode-15.1.0": "^1.5.2", "@unicode/unicode-15.1.0": "^1.5.2",
"cldr": "^7.5.0" "cldr": "^7.5.0"
} }

View file

@ -1,11 +1,11 @@
{ {
"name": "@iosevka/generate-samples", "name": "@iosevka/generate-samples",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"exports": { "exports": {
".": "./src/index.mjs" ".": "./src/index.mjs"
}, },
"dependencies": { "dependencies": {
"@iosevka/data-export": "29.0.5" "@iosevka/data-export": "29.0.6"
} }
} }

View file

@ -1,10 +1,10 @@
{ {
"name": "@iosevka/misc", "name": "@iosevka/misc",
"version": "29.0.5", "version": "29.0.6",
"private": true, "private": true,
"dependencies": { "dependencies": {
"semver": "^7.6.0", "semver": "^7.6.0",
"wawoff2": "^2.0.1", "wawoff2": "^2.0.1",
"@iosevka/util": "29.0.5" "@iosevka/util": "29.0.6"
} }
} }