From 4c8de9f0258c283bce1cc35a3094b1eaeabfa5e5 Mon Sep 17 00:00:00 2001 From: be5invis Date: Sat, 17 Apr 2021 15:42:07 -0700 Subject: [PATCH] Cache the entire geometry flattener --- font-src/caching/index.js | 31 ++++++++--------- font-src/gen/finalize/glyphs.js | 44 ++++++++++------------- font-src/support/curve-util.js | 14 -------- font-src/support/geometry.js | 62 ++++++++++++++++++++++++++++++++- 4 files changed, 95 insertions(+), 56 deletions(-) diff --git a/font-src/caching/index.js b/font-src/caching/index.js index 7c2209125..bbf936ba7 100644 --- a/font-src/caching/index.js +++ b/font-src/caching/index.js @@ -3,39 +3,38 @@ const fs = require("fs-extra"); const zlib = require("zlib"); -const EDITION = "Iosevka.PTCache.1"; - class Cache { constructor() { - this.ptSource = {}; - this.ptSink = {}; + this.gfSource = {}; + this.gfSink = {}; } - loadRep(rep) { - if (!rep || rep.edition !== EDITION) return; - this.ptSource = rep.pt; + loadRep(version, rep) { + if (!rep || rep.version !== version) return; + this.gfSource = rep.gf || {}; } - toRep() { - return { edition: EDITION, pt: this.ptSink }; + toRep(version) { + return { version, gf: this.gfSink }; } - // Path conversion cache - getPT(ck) { - return this.ptSource[ck]; + + // Geometry flattening conversion cache + getGF(ck) { + return this.gfSource[ck]; } - savePT(ck, val) { - this.ptSink[ck] = val; + saveGF(ck, val) { + this.gfSink[ck] = val; } } exports.load = async function (argv) { let cache = new Cache(); if (argv.oCache && fs.existsSync(argv.oCache)) { - cache.loadRep(unzip(await fs.readFile(argv.oCache))); + cache.loadRep(argv.menu.version, unzip(await fs.readFile(argv.oCache))); } return cache; }; exports.save = async function savePTCache(argv, cache) { - if (argv.oCache) await fs.writeFile(argv.oCache, zip(cache.toRep())); + if (argv.oCache) await fs.writeFile(argv.oCache, zip(cache.toRep(argv.menu.version))); }; function unzip(buf) { diff --git a/font-src/gen/finalize/glyphs.js b/font-src/gen/finalize/glyphs.js index d5a9bce33..ab6f44f53 100644 --- a/font-src/gen/finalize/glyphs.js +++ b/font-src/gen/finalize/glyphs.js @@ -1,6 +1,7 @@ "use strict"; const TypoGeom = require("typo-geom"); +const Geom = require("../../support/geometry"); const Point = require("../../support/point"); const CurveUtil = require("../../support/curve-util"); const util = require("util"); @@ -17,14 +18,7 @@ function finalizeGlyphs(cache, para, glyphStore) { function regulateGlyphStore(cache, skew, glyphStore) { for (const g of glyphStore.glyphs()) { if (g.geometry.isEmpty()) continue; - if (!regulateCompositeGlyph(glyphStore, g)) { - const cs = g.geometry.asContours(); - g.clearGeometry(); - g.includeContours(cs, 0, 0); - } - } - for (const g of glyphStore.glyphs()) { - if (!g.geometry.asReferences()) regulateSimpleGlyph(cache, skew, g); + if (!regulateCompositeGlyph(glyphStore, g)) flattenSimpleGlyph(cache, skew, g); } } @@ -40,26 +34,28 @@ function regulateCompositeGlyph(glyphStore, g) { return true; } -function regulateSimpleGlyph(cache, skew, g) { - let cs = g.geometry.asContours(); - for (const contour of cs) for (const z of contour) z.x -= z.y * skew; - cs = simplifyContours(cache, cs); - for (const contour of cs) for (const z of contour) z.x += z.y * skew; +function flattenSimpleGlyph(cache, skew, g) { + const ck = Geom.hashGeometry(g.geometry); + const cached = cache.getGF(ck); + if (ck && cached) { + g.clearGeometry(); + g.includeContours(CurveUtil.repToShape(cached), 0, 0); + cache.saveGF(ck, cached); + } else { + let cs = g.geometry.asContours(); + for (const contour of cs) for (const z of contour) z.x -= z.y * skew; + cs = simplifyContours(cs); + for (const contour of cs) for (const z of contour) z.x += z.y * skew; - g.clearGeometry(); - g.includeContours(cs, 0, 0); + g.clearGeometry(); + g.includeContours(cs, 0, 0); + if (ck) cache.saveGF(ck, CurveUtil.shapeToRep(cs)); + } } /////////////////////////////////////////////////////////////////////////////////////////////////// -function simplifyContours(cache, source) { - const ck = CurveUtil.hashShape(source); - const cached = cache.getPT(ck); - if (cached) { - cache.savePT(ck, cached); - return CurveUtil.repToShape(cached); - } - +function simplifyContours(source) { const sink = new FairizedShapeSink(); TypoGeom.ShapeConv.transferGenericShape( TypoGeom.Fairize.fairizeBezierShape( @@ -72,8 +68,6 @@ function simplifyContours(cache, source) { sink, CurveUtil.GEOMETRY_PRECISION ); - - cache.savePT(ck, CurveUtil.shapeToRep(sink.contours)); return sink.contours; } diff --git a/font-src/support/curve-util.js b/font-src/support/curve-util.js index 04042b1d8..495324bc3 100644 --- a/font-src/support/curve-util.js +++ b/font-src/support/curve-util.js @@ -1,7 +1,5 @@ "use strict"; -const crypto = require("crypto"); - const TypoGeom = require("typo-geom"); const Point = require("./point"); const Transform = require("./transform"); @@ -53,18 +51,6 @@ exports.convertShapeToArcs = function convertShapeToArcs(shape) { return shape.map(convertContourToArcs); }; -exports.hashShape = function (shape) { - let s = ""; - for (const c of shape) { - s += "["; - for (const z of c) { - s += `[${z.type};${Math.round(z.x * 0x10000)};${Math.round(z.y * 0x10000)}]`; - } - s += "]"; - } - return crypto.createHash("sha256").update(s).digest(); -}; - function contourToRep(contour) { let c = []; for (const z of contour) c.push({ type: z.type, x: z.x, y: z.y }); diff --git a/font-src/support/geometry.js b/font-src/support/geometry.js index 0431c4e9d..eb2984211 100644 --- a/font-src/support/geometry.js +++ b/font-src/support/geometry.js @@ -1,9 +1,11 @@ "use strict"; +const crypto = require("crypto"); +const TypoGeom = require("typo-geom"); + const Point = require("./point"); const Transform = require("./transform"); const CurveUtil = require("./curve-util"); -const TypoGeom = require("typo-geom"); class GeometryBase { asContours() { @@ -21,6 +23,9 @@ class GeometryBase { measureComplexity() { return 0; } + toShapeStringOrNull() { + return null; + } } class ContourGeometry extends GeometryBase { @@ -47,6 +52,14 @@ class ContourGeometry extends GeometryBase { measureComplexity() { return this.m_points.length; } + toShapeStringOrNull() { + let s = "ContourGeometry{"; + for (const z of this.m_points) { + s += `(${z.type};${formatN(z.x)};${formatN(z.y)})`; + } + s += "}"; + return s; + } } class ReferenceGeometry extends GeometryBase { @@ -82,6 +95,11 @@ class ReferenceGeometry extends GeometryBase { measureComplexity() { return this.m_glyph.geometry.measureComplexity(); } + toShapeStringOrNull() { + let sTarget = this.m_glyph.geometry.toShapeStringOrNull(); + if (!sTarget) return null; + return `ReferenceGeometry{${sTarget};${formatN(this.m_x)};${formatN(this.m_y)}}`; + } } class TaggedGeometry extends GeometryBase { @@ -106,6 +124,9 @@ class TaggedGeometry extends GeometryBase { measureComplexity() { return this.m_geom.measureComplexity(); } + toShapeStringOrNull() { + return this.m_geom.toShapeStringOrNull(); + } } class TransformedGeometry extends GeometryBase { @@ -144,6 +165,17 @@ class TransformedGeometry extends GeometryBase { measureComplexity() { return this.m_geom.measureComplexity(); } + toShapeStringOrNull() { + const sTarget = this.m_geom.toShapeStringOrNull(); + if (!sTarget) return null; + return ( + `TransformedGeometry{${sTarget};` + + `${formatN(this.m_transform.xx)},${formatN(this.m_transform.xy)},` + + `${formatN(this.m_transform.yx)},${formatN(this.m_transform.yy)},` + + `${formatN(this.m_transform.x)},${formatN(this.m_transform.y)}` + + `}` + ); + } } class CombineGeometry extends GeometryBase { @@ -194,6 +226,15 @@ class CombineGeometry extends GeometryBase { let s = 0; for (const part of this.m_parts) s += part.measureComplexity(); } + toShapeStringOrNull() { + let sParts = []; + for (const item of this.m_parts) { + const sPart = item.toShapeStringOrNull(); + if (!sPart) return null; + sParts.push(sPart); + } + return `CombineGeometry{${sParts.join(",")}}`; + } } class BooleanGeometry extends GeometryBase { @@ -250,8 +291,23 @@ class BooleanGeometry extends GeometryBase { let s = 0; for (const operand of this.m_operands) s += operand.measureComplexity(); } + toShapeStringOrNull() { + let sParts = []; + for (const item of this.m_operands) { + const sPart = item.toShapeStringOrNull(); + if (!sPart) return null; + sParts.push(sPart); + } + return `BooleanGeometry{${this.m_operator};${sParts.join(",")}}`; + } } +exports.hashGeometry = function (geom) { + const s = geom.toShapeStringOrNull(); + if (!s) return null; + return crypto.createHash("sha256").update(s).digest(); +}; + function combineWith(a, b) { if (a instanceof CombineGeometry) { return a.with(b); @@ -260,6 +316,10 @@ function combineWith(a, b) { } } +function formatN(x) { + return `${Math.round(x * 0x10000)}`; +} + exports.GeometryBase = GeometryBase; exports.ContourGeometry = ContourGeometry; exports.ReferenceGeometry = ReferenceGeometry;