diff --git a/doc/custom-build.md b/doc/custom-build.md index 3105b3d6a..3fe5fb877 100644 --- a/doc/custom-build.md +++ b/doc/custom-build.md @@ -38,13 +38,15 @@ To create a custom build, you need: 3. Run `npm run build -- contents::` and the built fonts would be available in `dist/`. Aside from `contents::`, other options are: - 1. `contents::` : TTF (Hinted and Unhinted), WOFF(2) and Web font CSS; - 2. `ttf::` : TTF; + 1. `contents::` : Everything (TTF + webfont, hinted + unhinted); + 2. `ttf::` : TTF only; 3. `ttf-unhinted::` : Unhinted TTF only; 4. `webfont::` : Web fonts only (CSS + WOFF2); + 4. `webfont-unhinted::` : Unhinted web fonts only (CSS + WOFF2); 5. `woff2::` : WOFF2 only. + 5. `woff2-unhinted::` : Unhinted WOFF2 only. -⚠️ **Important:** By default, the build system will schedule a number of concurrently running jobs equal to the number of threads available on the CPU, which *will* push CPU usage and also likely RAM usage, if you do not have very much to work with, to the ceiling (each job consumes more than 1 GB of RAM at its peak). If this is an issue for you, pass an additional argument `--jCmd=`. +⚠️ **Important**: By default, the build system will schedule a number of concurrently running jobs equal to the number of threads available on the CPU, which *will* push CPU usage and also likely RAM usage, if you do not have very much to work with, to the ceiling (each job consumes more than 1 GB of RAM at its peak). If this is an issue for you, pass an additional argument `--jCmd=`. ### Configuring Custom Build diff --git a/utility/make-webfont-css.js b/utility/make-webfont-css.js index 491ed0652..c1b2e7688 100644 --- a/utility/make-webfont-css.js +++ b/utility/make-webfont-css.js @@ -6,7 +6,7 @@ const WebfontFormatMap = new Map([ ["woff2", "woff2"], ["ttf", "truetype"] ]); -module.exports = function (output, family, hs, formats) { +module.exports = function (output, family, hs, formats, unhinted) { if (!formats) { fs.writeFileSync(output, ""); return; @@ -17,8 +17,13 @@ module.exports = function (output, family, hs, formats) { if (!WebfontFormatMap.get(ext)) throw new TypeError("Invalid webfont file format " + ext); } for (const term of hs) { + const dirSuffix = unhinted ? "-unhinted" : ""; const src = formats - .map(ext => `url('${ext}/${term.name}.${ext}') format('${WebfontFormatMap.get(ext)}')`) + .map( + ext => + `url('${ext}${dirSuffix}/${term.name}.${ext}') ` + + `format('${WebfontFormatMap.get(ext)}')` + ) .join(", "); ans += ` @font-face { diff --git a/verdafile.js b/verdafile.js index b4d4dc96e..a744a8710 100644 --- a/verdafile.js +++ b/verdafile.js @@ -352,7 +352,6 @@ const DistUnhintedTTF = file.make( } } ); - const BuildCM = file.make( (gr, f) => `${BUILD}/ttf/${gr}/${f}.charmap.mpz`, async (target, output, gr, f) => { @@ -360,65 +359,10 @@ const BuildCM = file.make( } ); -/////////////////////////////////////////////////////////// -////// Font Distribution ////// -/////////////////////////////////////////////////////////// - -// Group-level -const GroupContents = task.group("contents", async (target, gr) => { - await target.need(GroupFonts(gr), DistWebFontCSS(gr)); - return gr; -}); - -// Webfont CSS -const DistWebFontCSS = file.make( - gr => `${DIST}/${gr}/${gr}.css`, - async (target, out, gr) => { - const [plan] = await target.need(BuildPlanOf(gr)); - await target.need(de(out.dir)); - await createWebFontCssImpl(target, out.full, gr, plan.webfontFormats); - } -); -async function createWebFontCssImpl(target, output, gr, formats) { - const [bp, ts] = await target.need(BuildPlanOf(gr), GroupFontsOf(gr)); - const hs = await target.need(...ts.map(FontInfoOf)); - echo.action(echo.hl.command(`Create WebFont CSS`), gr, echo.hl.operator("->"), output); - await silently.node("utility/make-webfont-css.js", output, bp.family, hs, formats); +function formatSuffix(fmt, unhinted) { + return fmt + (unhinted ? "-unhinted" : ""); } -// Content files -const GroupTTFs = task.group("ttf", async (target, gr) => { - const [ts] = await target.need(GroupFontsOf(gr)); - await target.need(ts.map(tn => DistHintedTTF(gr, tn))); -}); -const GroupUnhintedTTFs = task.group("ttf-unhinted", async (target, gr) => { - const [ts] = await target.need(GroupFontsOf(gr)); - await target.need(ts.map(tn => DistUnhintedTTF(gr, tn))); -}); -const GroupWebFonts = task.group("webfont", async (target, gr) => { - const [bp] = await target.need(BuildPlanOf(gr)); - const groupsNeeded = []; - for (const ext of bp.webfontFormats) { - switch (ext) { - case "ttf": - groupsNeeded.push(GroupTTFs(gr)); - break; - case "woff2": - groupsNeeded.push(GroupWoff2s(gr)); - break; - } - } - await target.need(groupsNeeded, DistWebFontCSS(gr)); -}); -const GroupWoff2s = task.group("woff2", async (target, gr) => { - const [ts] = await target.need(GroupFontsOf(gr)); - await target.need(ts.map(tn => DistWoff2(gr, tn))); -}); -const GroupFonts = task.group("fonts", async (target, gr) => { - await target.need(GroupTTFs(gr), GroupUnhintedTTFs(gr), GroupWoff2s(gr)); -}); - -// Per group file const DistHintedTTF = file.make( (gr, fn) => `${DIST}/${gr}/ttf/${fn}.ttf`, async (target, out, gr, fn) => { @@ -428,15 +372,103 @@ const DistHintedTTF = file.make( await silently.run(hint, hintParams, from.full, out.full); } ); + const DistWoff2 = file.make( - (gr, fn) => `${DIST}/${gr}/woff2/${fn}.woff2`, - async (target, out, group, f) => { - const [from] = await target.need(DistHintedTTF(group, f), de`${out.dir}`); + (gr, fn, unhinted) => `${DIST}/${gr}/${formatSuffix("woff2", unhinted)}/${fn}.woff2`, + async (target, out, group, f, unhinted) => { + const Ctor = unhinted ? DistUnhintedTTF : DistHintedTTF; + + const [from] = await target.need(Ctor(group, f), de`${out.dir}`); echo.action(echo.hl.command("Create WOFF2"), from.full, echo.hl.operator("->"), out.full); await silently.node(`utility/ttf-to-woff2.js`, from.full, out.full); } ); +/////////////////////////////////////////////////////////// +////// Font Distribution ////// +/////////////////////////////////////////////////////////// + +// Group-level entry points +const Entry_GroupContents = task.group("contents", async (target, gr) => { + await target.need(Entry_GroupFonts(gr), Entry_GroupUnhintedFonts(gr)); + return gr; +}); +const Entry_GroupTTFs = task.group("ttf", async (target, gr) => { + await target.need(GroupTtfsImpl(gr, false)); +}); +const Entry_GroupUnhintedTTFs = task.group("ttf-unhinted", async (target, gr) => { + await target.need(GroupTtfsImpl(gr, true)); +}); +const Entry_GroupWoff2s = task.group("woff2", async (target, gr) => { + await target.need(GroupWoff2Impl(gr, false)); +}); +const Entry_GroupUnhintedWoff2s = task.group("woff2-unhinted", async (target, gr) => { + await target.need(GroupWoff2Impl(gr, true)); +}); +const Entry_GroupWebFonts = task.group("webfont", async (target, gr) => { + await target.need(GroupWebFontsImpl(gr, false)); +}); +const Entry_GroupUnhintedWebFonts = task.group("webfont-unhinted", async (target, gr) => { + await target.need(GroupWebFontsImpl(gr, true)); +}); +const Entry_GroupFonts = task.group("fonts", async (target, gr) => { + await target.need(GroupTtfsImpl(gr, false), GroupWebFontsImpl(gr, false)); +}); +const Entry_GroupUnhintedFonts = task.group("fonts-unhinted", async (target, gr) => { + await target.need(GroupTtfsImpl(gr, true), GroupWebFontsImpl(gr, true)); +}); + +// Webfont CSS +const DistWebFontCSS = file.make( + (gr, unhinted) => `${DIST}/${gr}/${formatSuffix(gr, unhinted)}.css`, + async (target, out, gr, unhinted) => { + const [plan] = await target.need(BuildPlanOf(gr)); + await target.need(de(out.dir)); + await createWebFontCssImpl(target, out.full, gr, plan.webfontFormats, unhinted); + } +); +async function createWebFontCssImpl(target, output, gr, formats, unhinted) { + const [bp, ts] = await target.need(BuildPlanOf(gr), GroupFontsOf(gr)); + const hs = await target.need(...ts.map(FontInfoOf)); + echo.action(echo.hl.command(`Create WebFont CSS`), gr, echo.hl.operator("->"), output); + await silently.node("utility/make-webfont-css.js", output, bp.family, hs, formats, unhinted); +} + +// Content files +const GroupTtfsImpl = task.make( + (gr, unhinted) => `group-${formatSuffix("ttf-impl", unhinted)}::${gr}`, + async (target, gr, unhinted) => { + const Ctor = unhinted ? DistUnhintedTTF : DistHintedTTF; + const [ts] = await target.need(GroupFontsOf(gr)); + await target.need(ts.map(tn => Ctor(gr, tn))); + } +); +const GroupWoff2Impl = task.make( + (gr, unhinted) => `group-${formatSuffix("woff2-impl", unhinted)}::${gr}`, + async (target, gr, unhinted) => { + const [ts] = await target.need(GroupFontsOf(gr)); + await target.need(ts.map(tn => DistWoff2(gr, tn, unhinted))); + } +); +const GroupWebFontsImpl = task.make( + (gr, unhinted) => `group-${formatSuffix("webfont-impl", unhinted)}::${gr}`, + async (target, gr, unhinted) => { + const [bp] = await target.need(BuildPlanOf(gr)); + const groupsNeeded = []; + for (const ext of bp.webfontFormats) { + switch (ext) { + case "ttf": + groupsNeeded.push(GroupTtfsImpl(gr, unhinted)); + break; + case "woff2": + groupsNeeded.push(GroupWoff2Impl(gr, unhinted)); + break; + } + } + await target.need(groupsNeeded, DistWebFontCSS(gr, unhinted)); + } +); + /////////////////////////////////////////////////////////// ////// Font Collection Plans ////// /////////////////////////////////////////////////////////// @@ -582,7 +614,7 @@ async function buildGlyphSharingTtc(target, parts, out) { /////////////////////////////////////////////////////////// // Collection Archives -const TtcArchiveFile = file.make( +const TtcZip = file.make( (cgr, version) => `${ARCHIVE_DIR}/ttc-${cgr}-${version}.zip`, async (target, out, cgr) => { const [cPlan] = await target.need(CollectPlans, de`${out.dir}`); @@ -591,7 +623,7 @@ const TtcArchiveFile = file.make( await CreateGroupArchiveFile(`${BUILD}/ttc-collect/${cgr}`, out, `*.ttc`); } ); -const SuperTtcArchiveFile = file.make( +const SuperTtcZip = file.make( (cgr, version) => `${ARCHIVE_DIR}/super-ttc-${cgr}-${version}.zip`, async (target, out, cgr) => { await target.need(de`${out.dir}`, CollectedSuperTtcFile(cgr)); @@ -600,29 +632,32 @@ const SuperTtcArchiveFile = file.make( ); // Single-group Archives -const GroupTtfArchiveFile = file.make( - (gr, version) => `${ARCHIVE_DIR}/ttf-${gr}-${version}.zip`, - async (target, out, gr) => { +const GroupTtfZip = file.make( + (gr, version, unhinted) => + `${ARCHIVE_DIR}/${formatSuffix("ttf", unhinted)}-${gr}-${version}.zip`, + async (target, out, gr, _version_, unhinted) => { await target.need(de`${out.dir}`); - await target.need(GroupContents(gr)); - await CreateGroupArchiveFile(`${DIST}/${gr}/ttf`, out, "*.ttf"); + await target.need(GroupTtfsImpl(gr, unhinted)); + await CreateGroupArchiveFile( + `${DIST}/${gr}/${formatSuffix("ttf", unhinted)}`, + out, + "*.ttf" + ); } ); -const GroupTtfUnhintedArchiveFile = file.make( - (gr, version) => `${ARCHIVE_DIR}/ttf-unhinted-${gr}-${version}.zip`, - async (target, out, gr) => { - await target.need(de`${out.dir}`); - await target.need(GroupContents(gr)); - await CreateGroupArchiveFile(`${DIST}/${gr}/ttf-unhinted`, out, "*.ttf"); - } -); -const GroupWebArchiveFile = file.make( - (gr, version) => `${ARCHIVE_DIR}/webfont-${gr}-${version}.zip`, - async (target, out, gr) => { +const GroupWebZip = file.make( + (gr, version, unhinted) => + `${ARCHIVE_DIR}/${formatSuffix("webfont", unhinted)}-${gr}-${version}.zip`, + async (target, out, gr, _version_, unhinted) => { const [plan] = await target.need(BuildPlanOf(gr)); await target.need(de`${out.dir}`); - await target.need(GroupContents(gr)); - await CreateGroupArchiveFile(`${DIST}/${gr}`, out, "*.css", ...plan.webfontFormats); + await target.need(GroupWebFontsImpl(gr, unhinted)); + await CreateGroupArchiveFile( + `${DIST}/${gr}`, + out, + `${formatSuffix(gr, unhinted)}.css`, + ...plan.webfontFormats.map(format => formatSuffix(format, unhinted)) + ); } ); @@ -681,7 +716,7 @@ const PagesFontExport = task.group(`pages:font-export`, async (target, gr) => { const [pagesDir] = await target.need(PagesDir); if (!pagesDir) return; const outDir = Path.resolve(pagesDir, "shared/fonts", gr); - await target.need(GroupWebFonts(gr), de(outDir)); + await target.need(GroupWebFontsImpl(gr, false), de(outDir)); await cp(`${DIST}/${gr}/woff2`, Path.resolve(outDir, "woff2")); await createWebFontCssImpl(target, Path.resolve(outDir, `${gr}.css`), gr, webfontFormatsPages); await rm(Path.resolve(outDir, "ttf")); @@ -692,7 +727,7 @@ const PagesFastFontExport = task.group(`pages:fast-font-export`, async (target, const [pagesDir] = await target.need(PagesDir); if (!pagesDir) return; const outDir = Path.resolve(pagesDir, "shared/fonts", gr); - await target.need(GroupUnhintedTTFs(gr), de(outDir)); + await target.need(GroupTtfsImpl(gr, true), de(outDir)); // Next.js 12 has some problem about refreshing fonts, so write an empty CSS first await createWebFontCssImpl(target, Path.resolve(outDir, `${gr}.css`), gr, null); @@ -723,10 +758,10 @@ const SampleImages = task(`sample-images`, async target => { const SampleImagesPre = task(`sample-images:pre`, async target => { const [sans, slab, aile, etoile] = await target.need( - GroupContents`iosevka`, - GroupContents`iosevka-slab`, - GroupContents`iosevka-aile`, - GroupContents`iosevka-etoile`, + GroupWebFontsImpl(`iosevka`, false), + GroupWebFontsImpl(`iosevka-slab`, false), + GroupWebFontsImpl(`iosevka-aile`, false), + GroupWebFontsImpl(`iosevka-etoile`, false), SnapShotStatic("index.js"), SnapShotStatic("get-snap.js"), SnapShotJson, @@ -928,12 +963,12 @@ phony(`release`, async target => { for (const [cgr, plan] of Object.entries(collectPlans)) { if (!plan.inRelease) continue; const subGroups = collectPlans[cgr].groupDecomposition; - goals.push(TtcArchiveFile(cgr, version)); - goals.push(SuperTtcArchiveFile(cgr, version)); + goals.push(TtcZip(cgr, version)); + goals.push(SuperTtcZip(cgr, version)); for (const gr of subGroups) { - goals.push(GroupTtfArchiveFile(gr, version)); - goals.push(GroupTtfUnhintedArchiveFile(gr, version)); - goals.push(GroupWebArchiveFile(gr, version)); + goals.push(GroupTtfZip(gr, version, false)); + goals.push(GroupTtfZip(gr, version, true)); + goals.push(GroupWebZip(gr, version, false)); } } const [archiveFiles] = await target.need(goals); @@ -943,6 +978,15 @@ phony(`release`, async target => { // Images and release notes await target.need(SampleImages, Pages, AmendReadme, ReleaseNotes, ChangeLog); }); +phony.group(`release-test`, async (target, gr) => { + const [version] = await target.need(Version); + await target.need( + GroupTtfZip(gr, version, false), + GroupTtfZip(gr, version, true), + GroupWebZip(gr, version, false), + GroupWebZip(gr, version, true) + ); +}); /////////////////////////////////////////////////////////// ////// Script Building //////