Implement feature dropping in spacing derivation.
This commit is contained in:
parent
3431234604
commit
40a0f310c0
11 changed files with 107 additions and 27 deletions
|
@ -73,7 +73,7 @@ Inside the plan, top-level properties include:
|
||||||
- `U+27F6` LONG RIGHTWARDS ARROW
|
- `U+27F6` LONG RIGHTWARDS ARROW
|
||||||
- `U+27FB` LONG LEFTWARDS ARROW FROM BAR
|
- `U+27FB` LONG LEFTWARDS ARROW FROM BAR
|
||||||
- `U+27FC` LONG RIGHTWARDS 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.
|
* `serifs`: Optional, String, configures style of serifs.
|
||||||
- When set to `slab`, the font will be converted into slab-serif.
|
- When set to `slab`, the font will be converted into slab-serif.
|
||||||
- Otherwise the font will be sans-serif.
|
- Otherwise the font will be sans-serif.
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import fs from "fs";
|
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";
|
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) {
|
async function main(argv) {
|
||||||
const font = await readTTF(argv);
|
const font = await readTTF(argv);
|
||||||
|
|
||||||
|
@ -14,11 +18,19 @@ async function main(argv) {
|
||||||
|
|
||||||
switch (argv.shape.spacing) {
|
switch (argv.shape.spacing) {
|
||||||
case "term":
|
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;
|
break;
|
||||||
case "fixed":
|
case "fixed":
|
||||||
deriveTerm(font);
|
await deriveTerm(font);
|
||||||
deriveFixed(font);
|
await deriveFixed_DropWideChars(font);
|
||||||
|
await deriveFixed_DropFeatures(font, true);
|
||||||
|
CliProc.gcFont(font, Ot.ListGlyphStoreFactory);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +38,7 @@ async function main(argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// To derive -Term variants, simply apply NWID
|
// To derive -Term variants, simply apply NWID
|
||||||
function deriveTerm(font) {
|
async function deriveTerm(font) {
|
||||||
const gsub = font.gsub;
|
const gsub = font.gsub;
|
||||||
let nwidMap = new Map();
|
let nwidMap = new Map();
|
||||||
for (const feature of gsub.features) {
|
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
|
// 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
|
// characters (AW > 0) have the same width. We use this method to validate whether our
|
||||||
// "Fixed" subfamilies are properly built.
|
// "Fixed" subfamilies are properly built.
|
||||||
function deriveFixed(font) {
|
async function deriveFixed_DropWideChars(font) {
|
||||||
const unitWidth = font.os2.xAvgCharWidth;
|
const unitWidth = font.os2.xAvgCharWidth;
|
||||||
for (const [ch, g] of [...font.cmap.unicode.entries()]) {
|
for (const [ch, g] of [...font.cmap.unicode.entries()]) {
|
||||||
const advanceWidth = g.horizontal.end - g.horizontal.start;
|
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) {
|
async function readTTF(argv) {
|
||||||
const buf = await fs.promises.readFile(argv.i);
|
const buf = await fs.promises.readFile(argv.i);
|
||||||
const sfnt = FontIo.readSfntOtf(buf);
|
const sfnt = FontIo.readSfntOtf(buf);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
|
|
||||||
import * as Toml from "@iarna/toml";
|
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 Parameters from "./support/parameters.mjs";
|
||||||
import * as VariantData from "./support/variant-data.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));
|
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
||||||
|
|
||||||
// Parameter preparation
|
// Parameter preparation
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import { parseLigationData } from "./ligation-data.mjs";
|
import { parseLigationData } from "./ligation-data.mjs";
|
||||||
import { getCharMapAndSupportedLanguageList } from "./supported-languages.mjs";
|
import { getCharMapAndSupportedLanguageList } from "./supported-languages.mjs";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import * as toml from "@iarna/toml";
|
import * as toml from "@iarna/toml";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import * as toml from "@iarna/toml";
|
import * as toml from "@iarna/toml";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import { Output } from "./shared/index.mjs";
|
import { Output } from "./shared/index.mjs";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import SemVer from "semver";
|
import SemVer from "semver";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as url from "url";
|
import url from "url";
|
||||||
|
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ const IMAGES = "images";
|
||||||
const IMAGE_TASKS = ".build/image-tasks";
|
const IMAGE_TASKS = ".build/image-tasks";
|
||||||
const GLYF_TTC = ".build/glyf-ttc";
|
const GLYF_TTC = ".build/glyf-ttc";
|
||||||
|
|
||||||
|
const SHARED_CACHE = `${BUILD}/cache`;
|
||||||
const DIST_TTC = "dist/.ttc";
|
const DIST_TTC = "dist/.ttc";
|
||||||
const DIST_SUPER_TTC = "dist/.super-ttc";
|
const DIST_SUPER_TTC = "dist/.super-ttc";
|
||||||
const ARCHIVE_DIR = "release-archives";
|
const ARCHIVE_DIR = "release-archives";
|
||||||
|
@ -157,10 +158,10 @@ function linkSpacingDerivableBuildPlans(bps) {
|
||||||
for (const pfxTo in bps) {
|
for (const pfxTo in bps) {
|
||||||
const planTo = bps[pfxTo];
|
const planTo = bps[pfxTo];
|
||||||
const planToVal = rectifyPlanForSpacingDerivation(planTo);
|
const planToVal = rectifyPlanForSpacingDerivation(planTo);
|
||||||
if (!(planTo.spacing === "term" || planTo.spacing === "fixed")) continue;
|
if (!isLinkDeriveToSpacing(planTo.spacing)) continue;
|
||||||
for (const pfxFrom in bps) {
|
for (const pfxFrom in bps) {
|
||||||
const planFrom = bps[pfxFrom];
|
const planFrom = bps[pfxFrom];
|
||||||
if (!(planFrom.spacing === "normal" || !planFrom.spacing)) continue;
|
if (!isLinkDeriveFromSpacing(planFrom.spacing)) continue;
|
||||||
const planFromVal = rectifyPlanForSpacingDerivation(planFrom);
|
const planFromVal = rectifyPlanForSpacingDerivation(planFrom);
|
||||||
if (!deepEqual(planToVal, planFromVal)) continue;
|
if (!deepEqual(planToVal, planFromVal)) continue;
|
||||||
planTo.spacingDeriveFrom = pfxFrom;
|
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) {
|
function rectifyPlanForSpacingDerivation(p) {
|
||||||
return {
|
return {
|
||||||
...p,
|
...p,
|
||||||
|
@ -362,14 +370,16 @@ const ageKey = uuid.v4();
|
||||||
const DistUnhintedTTF = file.make(
|
const DistUnhintedTTF = file.make(
|
||||||
(gr, fn) => `${DIST}/${gr}/ttf-unhinted/${fn}.ttf`,
|
(gr, fn) => `${DIST}/${gr}/ttf-unhinted/${fn}.ttf`,
|
||||||
async (target, out, gr, fn) => {
|
async (target, out, gr, fn) => {
|
||||||
await target.need(Scripts, Parameters, Dependencies);
|
await target.need(Scripts, Parameters, Dependencies, de(out.dir));
|
||||||
const [fi] = await target.need(FontInfoOf(fn), de(out.dir));
|
const [fi] = await target.need(FontInfoOf(fn));
|
||||||
|
|
||||||
if (fi.spacingDerive) {
|
if (fi.spacingDerive) {
|
||||||
// The font is a spacing variant, and is derivable form an existing
|
// The font is a spacing variant, and is derivable form an existing
|
||||||
// normally-spaced variant.
|
// normally-spaced variant.
|
||||||
const spD = fi.spacingDerive;
|
const spD = fi.spacingDerive;
|
||||||
const [deriveFrom] = await target.need(DistUnhintedTTF(spD.prefix, spD.fileName));
|
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`, {
|
await silently.node(`font-src/derive-spacing.mjs`, {
|
||||||
i: deriveFrom.full,
|
i: deriveFrom.full,
|
||||||
o: out.full,
|
o: out.full,
|
||||||
|
@ -379,27 +389,30 @@ const DistUnhintedTTF = file.make(
|
||||||
// Ab-initio build
|
// Ab-initio build
|
||||||
const charMapDir = `${BUILD}/ttf/${gr}`;
|
const charMapDir = `${BUILD}/ttf/${gr}`;
|
||||||
const charMapPath = `${charMapDir}/${fn}.charmap.mpz`;
|
const charMapPath = `${charMapDir}/${fn}.charmap.mpz`;
|
||||||
await target.need(de(charMapDir));
|
|
||||||
|
|
||||||
await target.need(de(`${BUILD}/caches`));
|
|
||||||
const cacheFileName =
|
const cacheFileName =
|
||||||
`${Math.round(1000 * fi.shape.weight)}-${Math.round(1000 * fi.shape.width)}-` +
|
`${Math.round(1000 * fi.shape.weight)}-${Math.round(1000 * fi.shape.width)}-` +
|
||||||
`${Math.round(3600 * fi.shape.slopeAngle)}-${fi.shape.slope}`;
|
`${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`;
|
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", {
|
const { cacheUpdated } = await silently.node("font-src/index.mjs", {
|
||||||
o: out.full,
|
o: out.full,
|
||||||
oCharMap: charMapPath,
|
oCharMap: charMapPath,
|
||||||
cacheFreshAgeKey: ageKey,
|
cacheFreshAgeKey: ageKey,
|
||||||
iCache: cachePath,
|
iCache: cachePath,
|
||||||
oCache: cacheDiffPath,
|
oCache: cacheDiffPath,
|
||||||
compositesFromBuildPlan,
|
compositesFromBuildPlan: comps,
|
||||||
...fi
|
...fi
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cacheUpdated) {
|
if (cacheUpdated) {
|
||||||
const lock = build.locks.alloc(cacheFileName);
|
const lock = build.locks.alloc(cacheFileName);
|
||||||
await lock.acquire();
|
await lock.acquire();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue