Fix compatibility ligature building (#524).

This commit is contained in:
Belleve Invis 2020-05-06 17:26:36 -07:00
parent 8d7a304b96
commit 6b319855e4
9 changed files with 354 additions and 251 deletions

View file

@ -72,12 +72,15 @@ Since version 2.0, Iosevka would no longer support building via `makefile`. To i
2. Add a build plan into `private-build-plans.toml`, following this format:
```toml
[buildPlans.iosevka-custom] # <iosevka-custom> is your plan name
family = "Iosevka Custom" # Font menu family name
design = ["leading-1500", "v-i-hooky", "v-l-hooky"] # Customize styles
hintParams = ["-a", "sss"] # Optional custom parameters for ttfautohint
[buildPlans.iosevka-custom] # <iosevka-custom> is your plan name
family = "Iosevka Custom" # Font menu family name
design = ["v-i-hooky", "v-l-hooky"] # Customize styles
# upright = ["upright-styles"] # Uncomment this line to set styles for upright only
# italic = ["italic-styles"] # Uncomment this line to set styles for italic only
# oblique = ["oblique-styles"] # Uncomment this line to set styles for oblique only
hintParams = ["-a", "sss"] # Optional custom parameters for ttfautohint
###################################################################################################
# Override default building weights
# When buildPlans.<plan name>.weights is absent, all weights would built and mapped to
# default values.
@ -99,19 +102,24 @@ Since version 2.0, Iosevka would no longer support building via `makefile`. To i
shape = 700
menu = 700
css = 700
# End weight section
###################################################################################################
###################################################################################################
# Override default building slant sets
# Format: <upright|italic|oblique> = <"normal"|"italic"|"oblique">
# When this section is absent, all slants would be built.
[buildPlans.iosevka-custom.slants]
upright = "normal"
italic = "italic"
oblique = "oblique"
# End slant section
###################################################################################################
###################################################################################################
# Override default building widths
# When buildPlans.<plan name>.widths is absent, all widths would built and mapped to
# default values.
@ -119,17 +127,62 @@ Since version 2.0, Iosevka would no longer support building via `makefile`. To i
# support 1, 2, 3, 4, 5, 6, 7, 8, 9.
# If you decide to use custom weights you have to define all the weights you
# plan to use otherwise they will not be built.
[buildPlans.iosevka-custom.widths.normal]
shape = 5 # Width of glyph shapes.
menu = 5 # Width for the font's names.
css = "normal" # "font-stretch' property of webfont CSS.
[buildPlans.iosevka-custom.widths.extended]
shape = 7
menu = 7
css = "expanded"
# End width section
###################################################################################################
###################################################################################################
# Character Exclusion
# Specify character ranges in the section below to exclude certain characters from the font being
# built. Remove this section when this feature is not needed.
[buildPlans.iosevka-custom.exclude-chars]
ranges = [[10003, 10008]]
# End character exclusion
###################################################################################################
###################################################################################################
# Compatibility Ligatures
# Certain applications like Emacs does not support proper programming liagtures provided by
# OpenType, but can support ligatures provided by PUA codepoints. Therefore you can edit the
# following section to build PUA characters that are generated from the OpenType ligatures.
# Remove this section when compatibility ligatures are not needed.
[[buildPlans.iosevka-custom.compatibility-ligatures]]
unicode = 57600 # 0xE100
featureTag = 'calt'
sequence = '<*>'
# End compatibility ligatures section
###################################################################################################
###################################################################################################
# Metric overrides
# Certain metrics like line height (leading) could be overridden in your build plan file.
# Edit the values to change the metrics. Remove this section when overriding is not needed.
[buildPlans.iosevka-custom.metric-override]
leading = 1250
winMetricAscenderPad = 0
winMetricDescenderPad = 0
powerlineScaleY = 1
powerlineScaleX = 1
powerlineShiftY = 0
powerlineShiftX = 0
# End metric override section
###################################################################################################
```
3. Run `npm run build -- contents::<your plan name>` and the built fonts would be avaliable in `dist/`. Aside from `contents::<plan>`, other options are:
@ -212,22 +265,6 @@ The current available styles for `design`/`upright`/`italic`/`oblique` options a
<!-- END Section-Cherry-Picking-Ligation-Sets -->
* Styles for changing the line space (leading):
* `leading-750`, `leading-1000`, `leading-1250`, `leading-1500`, `leading-1750`, `leading-2000`: Change the line space. Default is `leading-1250`.
* `win-metric-pad-0`, `win-metric-pad-50`, `win-metric-pad-100`, `win-metric-pad-150`, `win-metric-pad-200`, `win-metric-pad-250`, `win-metric-pad-300`: Add extra space to [OS/2 tables Win metrics](https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent) to avoid clipping in certain legacy software.
* Styles for changing Powerline symbols' position:
* `powerline-scale-y-750`, `powerline-scale-y-875`, `powerline-scale-y-1000`, `powerline-scale-y-1125`, `powerline-scale-y-1250`, `powerline-scale-y-1375`, `powerline-scale-y-1500`: Resize the Powerline symbols vertically, from 75% to 150%.
* `powerline-scale-x-750`, `powerline-scale-x-875`, `powerline-scale-x-1000`, `powerline-scale-x-1125`, `powerline-scale-x-1250`, `powerline-scale-x-1375`, `powerline-scale-x-1500`: Resize the Powerline symbols horizontally, from 75% to 150%.
* `powerline-shift-y-n500`, `powerline-shift-y-n450`, `powerline-shift-y-n400`, `powerline-shift-y-n350`, `powerline-shift-y-n300`, `powerline-shift-y-n250`, `powerline-shift-y-n200`, `powerline-shift-y-n150`, `powerline-shift-y-n100`, `powerline-shift-y-n50`, `powerline-shift-y-0`, `powerline-shift-y-p50`, `powerline-shift-y-p100`, `powerline-shift-y-p150`, `powerline-shift-y-p200`, `powerline-shift-y-p250`, `powerline-shift-y-p300`, `powerline-shift-y-p350`, `powerline-shift-y-p400`, `powerline-shift-y-p450`, `powerline-shift-y-p500`: Shift the Powerline symbols vertically, from -0.5em to +0.5em.
* `powerline-shift-x-n500`, `powerline-shift-x-n450`, `powerline-shift-x-n400`, `powerline-shift-x-n350`, `powerline-shift-x-n300`, `powerline-shift-x-n250`, `powerline-shift-x-n200`, `powerline-shift-x-n150`, `powerline-shift-x-n100`, `powerline-shift-x-n50`, `powerline-shift-x-0`, `powerline-shift-x-p50`, `powerline-shift-x-p100`, `powerline-shift-x-p150`, `powerline-shift-x-p200`, `powerline-shift-x-p250`, `powerline-shift-x-p300`, `powerline-shift-x-p350`, `powerline-shift-x-p400`, `powerline-shift-x-p450`, `powerline-shift-x-p500`: Shift the Powerline symbols horizontally, from -0.5em to +0.5em.
* Symbol exclusion:
* `exclude-check-and-cross-symbol`: Exclude `✓✔✕✖✗✘` (U+2713 U+2718) from the font.
<!-- BEGIN Section-Stylistic-Sets -->
<!-- THIS SECTION IS AUTOMATICALLY GENERATED. DO NOT EDIT. -->

View file

@ -2,3 +2,4 @@
* Fix dot removal on various derived glyphs (#513).
* Fix styling features for Bulgarian, Macedonian, or Serbian (#514).
* Fix seam on certain Cyrillic letters with descender shape (#517).
* Fix compatibility ligature building (#524). Also moved metric override configuration, compatibility ligature configuration and character removal configuration into build plans.

View file

@ -3,16 +3,16 @@
const fs = require("fs-extra");
const path = require("path");
const buildFont = require("./build-font.js");
const parameters = require("../support/parameters");
const formVariantData = require("../support/variant-data");
const formLigationData = require("../support/ligation-data");
const toml = require("toml");
const BuildFont = require("./build-font.js");
const Parameters = require("../support/parameters");
const FormVariantData = require("../support/variant-data");
const FormLigationData = require("../support/ligation-data");
const Toml = require("toml");
module.exports = async function main(argv) {
const para = await getParameters(argv);
const font = buildFont(para);
if (argv.charmap) await saveCharMap(argv, font);
const font = BuildFont(para);
if (argv.oCharMap) await saveCharMap(argv, font);
if (argv.o) await saveOtd(argv, font);
};
@ -31,23 +31,27 @@ async function getParameters(argv) {
const rawVariantsData = await tryParseToml(VARIANTS_TOML);
const rawLigationData = await tryParseToml(LIGATIONS_TOML);
const para = parameters.build(parametersData, argv.hives, { shapeWeight: argv.shapeWeight });
const para = Parameters.build(parametersData, argv.hives, { shapeWeight: argv.shape.weight });
const variantsData = formVariantData(rawVariantsData, para);
const variantsData = FormVariantData(rawVariantsData, para);
para.variants = variantsData;
para.variantSelector = parameters.build(variantsData, ["default", ...argv.hives]);
para.variantSelector = Parameters.build(variantsData, ["default", ...argv.hives]);
para.defaultVariant = variantsData.default;
const ligationData = formLigationData(rawLigationData, para);
const ligationData = FormLigationData(rawLigationData, para);
para.defaultBuildup = ligationData.defaultBuildup;
para.ligation = parameters.build(ligationData.hives, ["default", ...argv.hives]);
para.ligation = Parameters.build(ligationData.hives, ["default", ...argv.hives]);
if (argv.excludedCharRanges) para.excludedCodePointRanges = argv.excludedCharRanges;
if (argv.compatibilityLigatures) para.compLig = argv.compatibilityLigatures;
if (argv.metricOverride) Parameters.applymetricOverride(para, argv.metricOverride);
para.naming = {
family: argv.family,
version: argv.version,
weight: argv.menuWeight - 0,
width: argv.menuWidth - 0,
slant: argv.menuSlant
family: argv.menu.family,
version: argv.menu.version,
weight: argv.menu.weight - 0,
width: argv.menu.width - 0,
slant: argv.menu.slant
};
return para;
@ -55,7 +59,7 @@ async function getParameters(argv) {
async function tryParseToml(str) {
try {
return toml.parse(await fs.readFile(str, "utf-8"));
return Toml.parse(await fs.readFile(str, "utf-8"));
} catch (e) {
throw new Error(
`Failed to parse configuration file ${str}.\nPlease validate whether there's syntax error.\n${e}`
@ -101,5 +105,5 @@ async function saveCharMap(argv, font) {
glyph.featureSelector ? Object.keys(glyph.featureSelector) : []
]);
}
await fs.writeFile(argv.charmap, JSON.stringify(charMap), "utf8");
await fs.writeFile(argv.oCharMap, JSON.stringify(charMap), "utf8");
}

View file

@ -9,7 +9,7 @@ define GDEF_MARK 3
define [interpretLookups gs lutns lookups] : begin
foreach [lutn : items-of lutns] : begin
local lut lookups.(lutn)
interpretLookup gs lut lookups
if lut : interpretLookup gs lut lookups
define [interpretLookup gs lut lookups] : match lut.type
"gsub_chaining" : begin
@ -59,15 +59,20 @@ export : define [BuildCompatLigatures glyphs glyphList unicodeGlyphs GSUB GDEF c
foreach [cldef : items-of config] : do
if [not cldef.unicode] : break nothing
if [not cldef.featureTag] : break nothing
if [not GSUB.features.(cldef.featureTag)] : break nothing
if [not cldef.sequence] : break nothing
local feature null
foreach [fn : items-of GSUB.languages.'DFLT_DFLT'.features]
if (cldef.featureTag === [fn.slice 0 4]) : set feature GSUB.features.(fn)
if [not feature] : break nothing
local gnames {}
for [local j 0] [j < cldef.sequence.length] [inc j] : begin
if [not unicodeGlyphs.[cldef.sequence.charCodeAt j]] : break nothing
gnames.push unicodeGlyphs.[cldef.sequence.charCodeAt j].name
interpretLookups gnames GSUB.features.(cldef.featureTag) GSUB.lookups
interpretLookups gnames feature GSUB.lookups
local g1 : new Glyph ('$clig.' + cldef.unicode)
set g1.advanceWidth 0
@ -84,4 +89,3 @@ export : define [BuildCompatLigatures glyphs glyphList unicodeGlyphs GSUB GDEF c
set unicodeGlyphs.(cldef.unicode) g1
glyphList.push g1
set GDEF.glyphClassDef.(g1.name) GDEF_LIGATURE

View file

@ -307,6 +307,11 @@ enableLigation = false
[no-cv-ss]
enableCvSs = false
###################################################################################################
### Metric-override hives
### These hives are now discouraged in favor of 'metric-override' in build plans but they are
### still supported in version 3.x.
###### Leading
[leading-750]
leading = 750

View file

@ -1,9 +1,12 @@
[buildPlans.iosevka-custom] # <iosevka-custom> is your plan name
family = "Iosevka Custom" # Font menu family name
design = ["leading-1500", "v-i-hooky", "v-l-hooky"] # Customize styles
hintParams = ["-a", "sss"] # Optional custom parameters for ttfautohint
[buildPlans.iosevka-custom] # <iosevka-custom> is your plan name
family = "Iosevka Custom" # Font menu family name
design = ["v-i-hooky", "v-l-hooky"] # Customize styles
# upright = ["upright-styles"] # Uncomment this line to set styles for upright only
# italic = ["italic-styles"] # Uncomment this line to set styles for italic only
# oblique = ["oblique-styles"] # Uncomment this line to set styles for oblique only
hintParams = ["-a", "sss"] # Optional custom parameters for ttfautohint
###################################################################################################
# Override default building weights
# When buildPlans.<plan name>.weights is absent, all weights would built and mapped to
# default values.
@ -25,19 +28,24 @@ css = 450
shape = 700
menu = 700
css = 700
# End weight section
###################################################################################################
###################################################################################################
# Override default building slant sets
# Format: <upright|italic|oblique> = <"normal"|"italic"|"oblique">
# When this section is absent, all slants would be built.
[buildPlans.iosevka-custom.slants]
upright = "normal"
italic = "italic"
oblique = "oblique"
# End slant section
###################################################################################################
###################################################################################################
# Override default building widths
# When buildPlans.<plan name>.widths is absent, all widths would built and mapped to
# default values.
@ -45,6 +53,7 @@ oblique = "oblique"
# support 1, 2, 3, 4, 5, 6, 7, 8, 9.
# If you decide to use custom weights you have to define all the weights you
# plan to use otherwise they will not be built.
[buildPlans.iosevka-custom.widths.normal]
shape = 5 # Width of glyph shapes.
menu = 5 # Width for the font's names.
@ -54,4 +63,49 @@ css = "normal" # "font-stretch' property of webfont CSS.
shape = 7
menu = 7
css = "expanded"
# End width section
###################################################################################################
###################################################################################################
# Character Exclusion
# Specify character ranges in the section below to exclude certain characters from the font being
# built. Remove this section when this feature is not needed.
[buildPlans.iosevka-custom.exclude-chars]
ranges = [[10003, 10008]]
# End character exclusion
###################################################################################################
###################################################################################################
# Compatibility Ligatures
# Certain applications like Emacs does not support proper programming liagtures provided by
# OpenType, but can support ligatures provided by PUA codepoints. Therefore you can edit the
# following section to build PUA characters that are generated from the OpenType ligatures.
# Remove this section when compatibility ligatures are not needed.
[[buildPlans.iosevka-custom.compatibility-ligatures]]
unicode = 57600 # 0xE100
featureTag = 'calt'
sequence = '<*>'
# End compatibility ligatures section
###################################################################################################
###################################################################################################
# Metric overrides
# Certain metrics like line height (leading) could be overridden in your build plan file.
# Edit the values to change the metrics. Remove this section when overriding is not needed.
[buildPlans.iosevka-custom.metric-override]
leading = 1250
winMetricAscenderPad = 0
winMetricDescenderPad = 0
powerlineScaleY = 1
powerlineScaleX = 1
powerlineShiftY = 0
powerlineShiftX = 0
# End metric override section
###################################################################################################

View file

@ -22,3 +22,22 @@ export : define [build parametersData styles blendParams] : begin
foreach [style : items-of styles] : introStyle style
return param
extern isFinite
define [numericConfigExists x] : [isFinite x] && (x != null)
export : define [applymetricOverride para mo] : begin
if [numericConfigExists mo.leading]
set para.leading mo.leading
if [numericConfigExists mo.winMetricAscenderPad]
set para.winMetricAscenderPad mo.winMetricAscenderPad
if [numericConfigExists mo.winMetricDescenderPad]
set para.winMetricDescenderPad mo.winMetricDescenderPad
if [numericConfigExists mo.powerlineScaleY]
set para.powerlineScaleY mo.powerlineScaleY
if [numericConfigExists mo.powerlineScaleX]
set para.powerlineScaleX mo.powerlineScaleX
if [numericConfigExists mo.powerlineShiftY]
set para.powerlineShiftY mo.powerlineShiftY
if [numericConfigExists mo.powerlineShiftX]
set para.powerlineShiftX mo.powerlineShiftX

View file

@ -10,20 +10,20 @@ module.exports = function (output, family, hs, formats) {
@font-face {
font-family: '${family + " Web"}';
font-display: swap;
font-weight: ${term.cssWeight};
font-stretch: ${term.cssStretch};
font-style: ${term.cssStyle};
font-weight: ${term.css.weight};
font-stretch: ${term.css.stretch};
font-style: ${term.css.style};
src: ${src};
}
`;
if (term.cssStyle === "oblique") {
if (term.css.style === "oblique") {
// CHROME hates a family with both Italic and Oblique
ans += `
@font-face {
font-family: '${family + " Web Oblique"}';
font-display: swap;
font-weight: ${term.cssWeight};
font-stretch: ${term.cssStretch};
font-weight: ${term.css.weight};
font-stretch: ${term.css.stretch};
src: ${src};
}
`;

View file

@ -51,7 +51,7 @@ const Version = oracle(`metadata:version`, async () => {
async function tryParseToml(str) {
try {
return toml.parse(fs.readFileSync(str, "utf-8"));
return JSON.parse(JSON.stringify(toml.parse(fs.readFileSync(str, "utf-8"))));
} catch (e) {
throw new Error(
`Failed to parse configuration file ${str}.\n` +
@ -69,33 +69,16 @@ const RawPlans = oracle(`metadata:raw-plans`, async target => {
const privateBP = await tryParseToml(PRIVATE_BUILD_PLANS);
Object.assign(bp.buildPlans, privateBP.buildPlans);
}
for (const prefix in bp.buildPlans) {
const plan = bp.buildPlans[prefix];
plan.prefix = prefix;
// Style groups
if (!plan.pre) plan.pre = {};
if (!plan.post) plan.post = {};
if (!plan.pre.design) plan.pre.design = plan.design || [];
if (!plan.pre.upright) plan.pre.upright = plan.upright || [];
if (!plan.pre.oblique) plan.pre.oblique = plan.oblique || [];
if (!plan.pre.italic) plan.pre.italic = plan.italic || [];
if (!plan.post.design) plan.post.design = [];
if (!plan.post.upright) plan.post.upright = [];
if (!plan.post.oblique) plan.post.oblique = [];
if (!plan.post.italic) plan.post.italic = [];
}
for (const prefix in bp.collectPlans) {
bp.collectPlans[prefix].prefix = prefix;
}
return bp;
});
const BuildPlans = computed("metadata:build-plans", async target => {
const RawCollectPlans = computed("metadata:raw-collect-plans", async target => {
const [rp] = await target.need(RawPlans);
return rp.buildPlans;
return rp.collectPlans;
});
const CollectConfig = computed("metadata:collect-config", async target => {
const [rp] = await target.need(RawPlans);
return rp.collectConfig;
});
const ExportPlans = computed("metadata:export-plans", async target => {
const [rp] = await target.need(RawCollectPlans);
@ -105,27 +88,130 @@ const ExportPlans = computed("metadata:export-plans", async target => {
}
return result;
});
const RawCollectPlans = computed("metadata:raw-collect-plans", async target => {
const BuildPlans = computed("metadata:build-plans", async target => {
const [rp] = await target.need(RawPlans);
return rp.collectPlans;
const rawBuildPlans = rp.buildPlans;
const returnBuildPlans = {};
const fileNameToBpMap = {};
for (const prefix in rawBuildPlans) {
const bp = { ...rawBuildPlans[prefix] };
shimBuildPlans(bp, rp.weights, rp.slants, rp.widths);
bp.targets = [];
const suffixMapping = getSuffixMapping(bp.weights, bp.slants, bp.widths);
for (const suffix in suffixMapping) {
const sfi = suffixMapping[suffix];
if (bp.weights && !bp.weights[sfi.weight]) continue;
if (bp.slants && !bp.slants[sfi.slant]) continue;
const fileName = [prefix, suffix].join("-");
bp.targets.push(fileName);
fileNameToBpMap[fileName] = { prefix, suffix };
}
returnBuildPlans[prefix] = bp;
}
return { fileNameToBpMap, buildPlans: returnBuildPlans };
});
const Weights = computed("metadata:global-weights", async target => {
const [rp] = await target.need(RawPlans);
return rp.weights;
function shimBuildPlans(bp, dWeights, dSlants, dWidths) {
if (!bp.pre) bp.pre = {};
if (!bp.post) bp.post = {};
if (!bp.pre.design) bp.pre.design = bp.design || [];
if (!bp.pre.upright) bp.pre.upright = bp.upright || [];
if (!bp.pre.oblique) bp.pre.oblique = bp.oblique || [];
if (!bp.pre.italic) bp.pre.italic = bp.italic || [];
if (!bp.post.design) bp.post.design = [];
if (!bp.post.upright) bp.post.upright = [];
if (!bp.post.oblique) bp.post.oblique = [];
if (!bp.post.italic) bp.post.italic = [];
bp.weights = bp.weights || dWeights;
bp.slants = bp.slants || dSlants;
bp.widths = bp.widths || dWidths;
}
const BuildPlanOf = computed.group("metadata:build-plan-of", async (target, gid) => {
const [{ buildPlans }] = await target.need(BuildPlans);
const plan = buildPlans[gid];
if (!plan) fail(`Build plan for '${gid}' not found.` + whyBuildPlanIsnNotThere(gid));
return plan;
});
const Slants = computed("metadata:global-slants", async target => {
const [rp] = await target.need(RawPlans);
return rp.slants;
const GroupFontsOf = computed.group("metadata:group-fonts-of", async (target, gid) => {
const [plan] = await target.need(BuildPlanOf(gid));
return plan.targets;
});
const Widths = computed("metadata:global-widths", async target => {
const [rp] = await target.need(RawPlans);
return rp.widths;
});
const CollectConfig = computed("metadata:collect-config", async target => {
const [rp] = await target.need(RawPlans);
return rp.collectConfig;
const FontInfoOf = computed.group("metadata:font-info-of", async (target, fileName) => {
const [{ fileNameToBpMap, buildPlans }] = await target.need(BuildPlans);
const [version] = await target.need(Version);
const fi0 = fileNameToBpMap[fileName];
if (!fi0) fail(`Build plan for '${fileName}' not found.` + whyBuildPlanIsnNotThere(fileName));
const bp = buildPlans[fi0.prefix];
if (!bp) fail(`Build plan for '${fileName}' not found.` + whyBuildPlanIsnNotThere(fileName));
const sfi = getSuffixMapping(bp.weights, bp.slants, bp.widths)[fi0.suffix];
const preHives = [...bp.pre.design, ...bp.pre[sfi.slant]];
const postHives = [...bp.post.design, ...bp.post[sfi.slant]];
return {
name: fileName,
hives: ["iosevka", ...preHives, ...sfi.hives, ...postHives],
shape: {
weight: sfi.shapeWeight,
width: sfi.shapeWidth
},
menu: {
family: bp.family,
version: version,
width: sfi.menuWidth,
slant: sfi.menuSlant,
weight: sfi.menuWeight
},
css: {
weight: sfi.cssWeight,
stretch: sfi.cssStretch,
style: sfi.cssStyle
},
hintParams: bp.hintParams || [],
compatibilityLigatures: bp["compatibility-ligatures"] || null,
metricOverride: bp["metric-override"] || null,
excludedCharRanges: bp["exclude-chars"] ? bp["exclude-chars"].ranges || null : null
};
});
function getSuffixMapping(weights, slants, widths) {
const mapping = {};
for (const w in weights) {
validateRecommendedWeight(w, weights[w].menu, "Menu");
validateRecommendedWeight(w, weights[w].css, "CSS");
for (const s in slants) {
for (const wd in widths) {
const suffix = makeSuffix(w, wd, s, "regular");
mapping[suffix] = {
hives: [`shapeWeight`, `s-${s}`, `wd-${widths[wd].shape}`],
weight: w,
shapeWeight: nValidate("Shape weight of " + w, weights[w].shape, vlShapeWeight),
cssWeight: nValidate("CSS weight of " + w, weights[w].css, vlCssWeight),
menuWeight: nValidate("Menu weight of " + w, weights[w].menu, vlMenuWeight),
width: wd,
shapeWidth: nValidate("Shape width of " + wd, widths[wd].shape, vlShapeWidth),
cssStretch: widths[wd].css || wd,
menuWidth: nValidate("Menu width of " + wd, widths[wd].menu, vlMenuWidth),
slant: s,
cssStyle: slants[s] || s,
menuSlant: slants[s] || s
};
}
}
}
return mapping;
}
function makeSuffix(w, wd, s, fallback) {
return (
(wd === "normal" ? "" : wd) + (w === "regular" ? "" : w) + (s === "upright" ? "" : s) ||
@ -133,6 +219,15 @@ function makeSuffix(w, wd, s, fallback) {
);
}
function validateRecommendedWeight(w, value, label) {
if (recommendedMenuWeights[w] && recommendedMenuWeights[w] !== value) {
echo.warn(
`${label} weight settings of ${w} ( = ${value}) doesn't match ` +
`the recommended value ( = ${recommendedMenuWeights[w]}).`
);
}
}
function nValidate(key, v, f) {
if (typeof v !== "number" || !isFinite(v) || (f && !f(v))) {
throw new TypeError(`${key} = "${v}" is not a valid number.`);
@ -166,113 +261,50 @@ const recommendedMenuWeights = {
extrabold: 800,
heavy: 900
};
function validateRecommendedWeight(w, value, label) {
if (recommendedMenuWeights[w] && recommendedMenuWeights[w] !== value) {
echo.warn(
`${label} weight settings of ${w} ( = ${value}) doesn't match ` +
`the recommended value ( = ${recommendedMenuWeights[w]}).`
);
}
function whyBuildPlanIsnNotThere(gid) {
if (!fs.existsSync(PRIVATE_BUILD_PLANS))
return "\n -- Possible reason: Config file 'private-build-plans.toml' does not exist.";
return "";
}
function getSuffixSet(weights, slants, widths) {
const mapping = {};
for (const w in weights) {
validateRecommendedWeight(w, weights[w].menu, "Menu");
validateRecommendedWeight(w, weights[w].css, "CSS");
for (const s in slants) {
for (const wd in widths) {
const suffix = makeSuffix(w, wd, s, "regular");
mapping[suffix] = {
hives: [`shapeWeight`, `s-${s}`, `wd-${widths[wd].shape}`],
weight: w,
shapeWeight: nValidate("Shape weight of " + w, weights[w].shape, vlShapeWeight),
cssWeight: nValidate("CSS weight of " + w, weights[w].css, vlCssWeight),
menuWeight: nValidate("Menu weight of " + w, weights[w].menu, vlMenuWeight),
width: wd,
shapeWidth: nValidate("Shape width of " + wd, widths[wd].shape, vlShapeWidth),
cssStretch: widths[wd].css || wd,
menuWidth: nValidate("Menu width of " + wd, widths[wd].menu, vlMenuWidth),
slant: s,
cssStyle: slants[s] || s,
menuSlant: slants[s] || s
};
}
}
}
return mapping;
}
const Suffixes = computed(`metadata:suffixes`, async target => {
const [weights, slants, widths] = await target.need(Weights, Slants, Widths);
return getSuffixSet(weights, slants, widths);
const CollectPlans = computed(`metadata:collect-plans`, async target => {
const [rawCollectPlans, suffixMapping, collectConfig] = await target.need(
RawCollectPlans,
StandardSuffixes,
CollectConfig
);
return await getCollectPlans(
target,
rawCollectPlans,
suffixMapping,
collectConfig,
fnStandardTtc
);
});
const FontBuildingParameters = computed(`metadata:font-building-parameters`, async target => {
const [buildPlans, defaultWeights, defaultSlants, defaultWidths] = await target.need(
BuildPlans,
Weights,
Slants,
Widths
);
const fontInfos = {};
const bp = {};
for (const p in buildPlans) {
const { pre, post, prefix, family, weights, slants, widths, hintParams } = buildPlans[p];
const targets = [];
const suffixMapping = getSuffixSet(
weights || defaultWeights,
slants || defaultSlants,
widths || defaultWidths
);
for (const suffix in suffixMapping) {
if (weights && !weights[suffixMapping[suffix].weight]) continue;
if (slants && !slants[suffixMapping[suffix].slant]) continue;
const fileName = [prefix, suffix].join("-");
const preHives = [...pre.design, ...pre[suffixMapping[suffix].slant]];
const postHives = [...post.design, ...post[suffixMapping[suffix].slant]];
fontInfos[fileName] = {
name: fileName,
family,
hives: ["iosevka", ...preHives, ...suffixMapping[suffix].hives, ...postHives],
shapeWeight: suffixMapping[suffix].shapeWeight,
shapeWidth: suffixMapping[suffix].shapeWidth,
menuWeight: suffixMapping[suffix].menuWeight,
menuWidth: suffixMapping[suffix].menuWidth,
menuSlant: suffixMapping[suffix].menuSlant,
cssWeight: suffixMapping[suffix].cssWeight,
cssStretch: suffixMapping[suffix].cssStretch,
cssStyle: suffixMapping[suffix].cssStyle,
hintParams: hintParams || []
};
targets.push(fileName);
}
bp[prefix] = {
family,
prefix,
targets
};
}
return { fontInfos, buildPlans: bp };
const StandardSuffixes = computed(`metadata:standard-suffixes`, async target => {
const [rp] = await target.need(RawPlans);
return getSuffixMapping(rp.weights, rp.slants, rp.widths);
});
async function getCollectPlans(target, rawCollectPlans, suffixMapping, config, fnFileName) {
const ttcComposition = {},
ttcContents = {},
groupDecomposition = {};
for (const gid in rawCollectPlans) {
for (const collectPrefix in rawCollectPlans) {
const groupFileList = new Set();
const collect = rawCollectPlans[gid];
const collect = rawCollectPlans[collectPrefix];
if (!collect || !collect.from || !collect.from.length) continue;
for (const prefix of collect.from) {
const [gri] = await target.need(GroupInfo(prefix));
const [gri] = await target.need(BuildPlanOf(prefix));
const ttfFileNameSet = new Set(gri.targets);
for (const suffix in suffixMapping) {
const gr = suffixMapping[suffix];
const ttcFileName = fnFileName(
config,
collect.prefix,
collectPrefix,
gr.weight,
gr.width,
gr.slant
@ -285,8 +317,8 @@ async function getCollectPlans(target, rawCollectPlans, suffixMapping, config, f
groupFileList.add(ttcFileName);
}
}
ttcContents[gid] = [...groupFileList];
groupDecomposition[gid] = [...collect.from];
ttcContents[collectPrefix] = [...groupFileList];
groupDecomposition[collectPrefix] = [...collect.from];
}
return { ttcComposition, ttcContents, groupDecomposition };
}
@ -300,46 +332,6 @@ function fnStandardTtc(collectConfig, prefix, w, wd, s) {
return `${prefix}-${ttcSuffix}`;
}
const CollectPlans = computed(`metadata:collect-plans`, async target => {
const [rawCollectPlans, suffixMapping, collectConfig] = await target.need(
RawCollectPlans,
Suffixes,
CollectConfig
);
return await getCollectPlans(
target,
rawCollectPlans,
suffixMapping,
collectConfig,
fnStandardTtc
);
});
const HivesOf = computed.group("metadata:hives-of", async (target, gid) => {
const [{ fontInfos }] = await target.need(FontBuildingParameters);
const hvs = fontInfos[gid];
if (!hvs) fail(`Build plan for '${gid}' not found.` + whyBuildPlanIsnNotThere(gid));
return hvs;
});
const GroupInfo = computed.group("metadata:group-info", async (target, gid) => {
const [{ buildPlans }] = await target.need(FontBuildingParameters);
const plan = buildPlans[gid];
if (!plan) fail(`Build plan for '${gid}' not found.` + whyBuildPlanIsnNotThere(gid));
return plan;
});
function whyBuildPlanIsnNotThere(gid) {
if (!fs.existsSync(PRIVATE_BUILD_PLANS))
return "\n -- Possible reason: Config file 'private-build-plans.toml' does not exist.";
return "";
}
const GroupFontsOf = computed.group("metadata:group-fonts-of", async (target, gid) => {
const [plan] = await target.need(GroupInfo(gid));
return plan.targets;
});
const CollectionPartsOf = computed.group("metadata:collection-parts-of", async (target, id) => {
const [{ ttcComposition }] = await target.need(CollectPlans);
return ttcComposition[id];
@ -351,24 +343,11 @@ const CollectionPartsOf = computed.group("metadata:collection-parts-of", async (
const BuildOTD = file.make(
(gr, fn) => `${BUILD}/${gr}/${fn}.otd`,
async (target, output, _gr, fn) => {
const [
{ hives, family, shapeWeight, menuWeight, menuSlant, menuWidth },
version
] = await target.need(HivesOf(fn), Version);
async (target, output, gr, fn) => {
const [fi] = await target.need(FontInfoOf(fn), Version);
const charmap = output.dir + "/" + output.name + ".charmap";
await target.need(Scripts, fu`parameters.toml`, de`${output.dir}`);
await node("gen/index", {
o: output.full,
charmap,
family,
version,
shapeWeight,
menuWeight,
menuSlant,
menuWidth,
hives
});
await node("gen/index", { o: output.full, oCharMap: charmap, ...fi });
}
);
@ -407,7 +386,7 @@ const DistUnhintedTTF = file.make(
const DistHintedTTF = file.make(
(gr, fn) => `${DIST}/${gr}/ttf/${fn}.ttf`,
async (target, path, gr, f) => {
const [{ hintParams }] = await target.need(HivesOf(f));
const [{ hintParams }] = await target.need(FontInfoOf(f));
const [from] = await target.need(BuildTTF(gr, f), de`${path.dir}`);
await run("ttfautohint", hintParams, from.full, path.full);
}
@ -453,8 +432,8 @@ const DistWebFontCSS = file.make(
gid => `${DIST}/${gid}/${gid}.css`,
async (target, out, gid) => {
// Note: this target does NOT depend on the font files.
const [gr, ts] = await target.need(GroupInfo(gid), GroupFontsOf(gid), de(out.dir));
const hs = await target.need(...ts.map(HivesOf));
const [gr, ts] = await target.need(BuildPlanOf(gid), GroupFontsOf(gid), de(out.dir));
const hs = await target.need(...ts.map(FontInfoOf));
await node("utility/make-webfont-css.js", out.full, gr.family, hs, webfontFormats);
}
);