From 40a0f310c02babaa31d3b8dc9318e81f189ba7a4 Mon Sep 17 00:00:00 2001 From: be5invis Date: Sun, 18 Sep 2022 21:21:59 -0700 Subject: [PATCH] Implement feature dropping in spacing derivation. --- doc/custom-build.md | 2 +- font-src/derive-spacing.mjs | 82 +++++++++++++++++-- font-src/index.mjs | 3 +- utility/export-data/index.mjs | 2 +- utility/export-data/ligation-data.mjs | 2 +- utility/export-data/variants-data.mjs | 2 +- utility/generate-release-note/change-log.mjs | 2 +- .../generate-release-note/package-list.mjs | 2 +- .../generate-release-note/release-note.mjs | 2 +- utility/update-package-json-version/index.mjs | 2 +- verdafile.mjs | 33 +++++--- 11 files changed, 107 insertions(+), 27 deletions(-) diff --git a/doc/custom-build.md b/doc/custom-build.md index 14e661dbf..d0eb8009f 100644 --- a/doc/custom-build.md +++ b/doc/custom-build.md @@ -73,7 +73,7 @@ Inside the plan, top-level properties include: - `U+27F6` LONG RIGHTWARDS ARROW - `U+27FB` LONG LEFTWARDS ARROW FROM BAR - `U+27FC` LONG RIGHTWARDS ARROW FROM BAR - - `fixed`: Apply `fontconfig-mono` changes and further remove ligations. + - `fixed`: Apply `fontconfig-mono` changes and further remove ligations, `NWID` and `WWID` typographic features. * `serifs`: Optional, String, configures style of serifs. - When set to `slab`, the font will be converted into slab-serif. - Otherwise the font will be sans-serif. diff --git a/font-src/derive-spacing.mjs b/font-src/derive-spacing.mjs index 19be992f6..2fe040976 100644 --- a/font-src/derive-spacing.mjs +++ b/font-src/derive-spacing.mjs @@ -1,11 +1,15 @@ import fs from "fs"; +import path from "path"; +import url from "url"; -import { FontIo, Ot } from "ot-builder"; +import * as Toml from "@iarna/toml"; +import { FontIo, Ot, CliProc } from "ot-builder"; import { assignFontNames, createNamingDictFromArgv } from "./gen/meta/naming.mjs"; -export default main; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); +export default main; async function main(argv) { const font = await readTTF(argv); @@ -14,11 +18,19 @@ async function main(argv) { switch (argv.shape.spacing) { case "term": - deriveTerm(font); + await deriveTerm(font); + break; + case "fontconfig-mono": + await deriveTerm(font); + await deriveFixed_DropWideChars(font); + await deriveFixed_DropFeatures(font, false); + CliProc.gcFont(font, Ot.ListGlyphStoreFactory); break; case "fixed": - deriveTerm(font); - deriveFixed(font); + await deriveTerm(font); + await deriveFixed_DropWideChars(font); + await deriveFixed_DropFeatures(font, true); + CliProc.gcFont(font, Ot.ListGlyphStoreFactory); break; } @@ -26,7 +38,7 @@ async function main(argv) { } // To derive -Term variants, simply apply NWID -function deriveTerm(font) { +async function deriveTerm(font) { const gsub = font.gsub; let nwidMap = new Map(); for (const feature of gsub.features) { @@ -52,7 +64,7 @@ function deriveTerm(font) { // In FontConfig, a font is considered "monospace" if and only if all encoded non-combining // characters (AW > 0) have the same width. We use this method to validate whether our // "Fixed" subfamilies are properly built. -function deriveFixed(font) { +async function deriveFixed_DropWideChars(font) { const unitWidth = font.os2.xAvgCharWidth; for (const [ch, g] of [...font.cmap.unicode.entries()]) { const advanceWidth = g.horizontal.end - g.horizontal.start; @@ -64,6 +76,62 @@ function deriveFixed(font) { } } +async function deriveFixed_DropFeatures(font, fFixed) { + if (!font.gsub) return; + + const dropFeatureTagSet = new Set(); + dropFeatureTagSet.add("NWID"); + dropFeatureTagSet.add("WWID"); + + if (fFixed) { + const LIGATIONS_TOML = path.resolve(__dirname, "../params/ligation-set.toml"); + const rawLigationData = Toml.parse(await fs.promises.readFile(LIGATIONS_TOML, "utf-8")); + for (const [_, comp] of Object.entries(rawLigationData.composite)) { + dropFeatureTagSet.add(comp.tag); + } + } + + for (const feature of font.gsub.features) { + if (dropFeatureTagSet.has(feature.tag)) { + feature.lookups.length = 0; + feature.params = null; + } + } + + markSweepLookups(font.gsub); +} +function markSweepLookups(table) { + let lookupSet = new Set(); + for (const feature of table.features) { + for (const lookup of feature.lookups) { + lookupSet.add(lookup); + } + } + + do { + let sizeBefore = lookupSet.size; + for (const lookup of table.lookups) { + if (lookup instanceof Ot.Gsub.Chaining || lookup instanceof Ot.Gpos.Chaining) { + for (const rule of lookup.rules) { + for (const app of rule.applications) lookupSet.add(app.apply); + } + } + } + let sizeAfter = lookupSet.size; + if (sizeBefore >= sizeAfter) break; + } while (true); + + let front = 0; + for (let rear = 0; rear < table.lookups.length; rear++) { + if (lookupSet.has(table.lookups[rear])) { + table.lookups[front++] = table.lookups[rear]; + } + } + table.lookups.length = front; + + return lookupSet; +} + async function readTTF(argv) { const buf = await fs.promises.readFile(argv.i); const sfnt = FontIo.readSfntOtf(buf); diff --git a/font-src/index.mjs b/font-src/index.mjs index 6991193da..c2e204e53 100644 --- a/font-src/index.mjs +++ b/font-src/index.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import zlib from "zlib"; import * as Toml from "@iarna/toml"; @@ -15,7 +15,6 @@ import { applyMetricOverride } from "./support/metric-override.mjs"; import * as Parameters from "./support/parameters.mjs"; import * as VariantData from "./support/variant-data.mjs"; -const __filename = url.fileURLToPath(import.meta.url); const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); // Parameter preparation diff --git a/utility/export-data/index.mjs b/utility/export-data/index.mjs index af6eb9dbf..d0b3be0c5 100644 --- a/utility/export-data/index.mjs +++ b/utility/export-data/index.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import { parseLigationData } from "./ligation-data.mjs"; import { getCharMapAndSupportedLanguageList } from "./supported-languages.mjs"; diff --git a/utility/export-data/ligation-data.mjs b/utility/export-data/ligation-data.mjs index 4c6cc365d..121682136 100644 --- a/utility/export-data/ligation-data.mjs +++ b/utility/export-data/ligation-data.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import * as toml from "@iarna/toml"; diff --git a/utility/export-data/variants-data.mjs b/utility/export-data/variants-data.mjs index bc8db6854..c83b79295 100644 --- a/utility/export-data/variants-data.mjs +++ b/utility/export-data/variants-data.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import * as toml from "@iarna/toml"; diff --git a/utility/generate-release-note/change-log.mjs b/utility/generate-release-note/change-log.mjs index 80a6ab660..655240c38 100644 --- a/utility/generate-release-note/change-log.mjs +++ b/utility/generate-release-note/change-log.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import semver from "semver"; diff --git a/utility/generate-release-note/package-list.mjs b/utility/generate-release-note/package-list.mjs index 619facd3c..f8a07a4bd 100644 --- a/utility/generate-release-note/package-list.mjs +++ b/utility/generate-release-note/package-list.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import { Output } from "./shared/index.mjs"; diff --git a/utility/generate-release-note/release-note.mjs b/utility/generate-release-note/release-note.mjs index 808f29242..5a5804458 100644 --- a/utility/generate-release-note/release-note.mjs +++ b/utility/generate-release-note/release-note.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import SemVer from "semver"; diff --git a/utility/update-package-json-version/index.mjs b/utility/update-package-json-version/index.mjs index 18d878554..e1dba3bd5 100644 --- a/utility/update-package-json-version/index.mjs +++ b/utility/update-package-json-version/index.mjs @@ -1,6 +1,6 @@ import fs from "fs"; import path from "path"; -import * as url from "url"; +import url from "url"; import semver from "semver"; diff --git a/verdafile.mjs b/verdafile.mjs index 71069c0e8..3720afaa1 100644 --- a/verdafile.mjs +++ b/verdafile.mjs @@ -25,6 +25,7 @@ const IMAGES = "images"; const IMAGE_TASKS = ".build/image-tasks"; const GLYF_TTC = ".build/glyf-ttc"; +const SHARED_CACHE = `${BUILD}/cache`; const DIST_TTC = "dist/.ttc"; const DIST_SUPER_TTC = "dist/.super-ttc"; const ARCHIVE_DIR = "release-archives"; @@ -157,10 +158,10 @@ function linkSpacingDerivableBuildPlans(bps) { for (const pfxTo in bps) { const planTo = bps[pfxTo]; const planToVal = rectifyPlanForSpacingDerivation(planTo); - if (!(planTo.spacing === "term" || planTo.spacing === "fixed")) continue; + if (!isLinkDeriveToSpacing(planTo.spacing)) continue; for (const pfxFrom in bps) { const planFrom = bps[pfxFrom]; - if (!(planFrom.spacing === "normal" || !planFrom.spacing)) continue; + if (!isLinkDeriveFromSpacing(planFrom.spacing)) continue; const planFromVal = rectifyPlanForSpacingDerivation(planFrom); if (!deepEqual(planToVal, planFromVal)) continue; planTo.spacingDeriveFrom = pfxFrom; @@ -168,6 +169,13 @@ function linkSpacingDerivableBuildPlans(bps) { } } +function isLinkDeriveToSpacing(spacing) { + return spacing === "term" || spacing === "fontconfig-mono" || spacing === "fixed"; +} +function isLinkDeriveFromSpacing(spacing) { + return !spacing || spacing === "normal"; +} + function rectifyPlanForSpacingDerivation(p) { return { ...p, @@ -362,14 +370,16 @@ const ageKey = uuid.v4(); const DistUnhintedTTF = file.make( (gr, fn) => `${DIST}/${gr}/ttf-unhinted/${fn}.ttf`, async (target, out, gr, fn) => { - await target.need(Scripts, Parameters, Dependencies); - const [fi] = await target.need(FontInfoOf(fn), de(out.dir)); + await target.need(Scripts, Parameters, Dependencies, de(out.dir)); + const [fi] = await target.need(FontInfoOf(fn)); if (fi.spacingDerive) { // The font is a spacing variant, and is derivable form an existing // normally-spaced variant. const spD = fi.spacingDerive; const [deriveFrom] = await target.need(DistUnhintedTTF(spD.prefix, spD.fileName)); + + echo.action(echo.hl.command(`Create TTF`), fn, echo.hl.operator("->"), out.full); await silently.node(`font-src/derive-spacing.mjs`, { i: deriveFrom.full, o: out.full, @@ -379,27 +389,30 @@ const DistUnhintedTTF = file.make( // Ab-initio build const charMapDir = `${BUILD}/ttf/${gr}`; const charMapPath = `${charMapDir}/${fn}.charmap.mpz`; - await target.need(de(charMapDir)); - await target.need(de(`${BUILD}/caches`)); const cacheFileName = `${Math.round(1000 * fi.shape.weight)}-${Math.round(1000 * fi.shape.width)}-` + `${Math.round(3600 * fi.shape.slopeAngle)}-${fi.shape.slope}`; - const cachePath = `${BUILD}/caches/${cacheFileName}.mpz`; + const cachePath = `${SHARED_CACHE}/${cacheFileName}.mpz`; const cacheDiffPath = `${charMapDir}/${fn}.cache.mpz`; - echo.action(echo.hl.command(`Create TTF`), fn, echo.hl.operator("->"), out.full); + const [comps] = await target.need( + CompositesFromBuildPlan, + de(charMapDir), + de(SHARED_CACHE) + ); - const [compositesFromBuildPlan] = await target.need(CompositesFromBuildPlan); + echo.action(echo.hl.command(`Create TTF`), fn, echo.hl.operator("->"), out.full); const { cacheUpdated } = await silently.node("font-src/index.mjs", { o: out.full, oCharMap: charMapPath, cacheFreshAgeKey: ageKey, iCache: cachePath, oCache: cacheDiffPath, - compositesFromBuildPlan, + compositesFromBuildPlan: comps, ...fi }); + if (cacheUpdated) { const lock = build.locks.alloc(cacheFileName); await lock.acquire();