Add support for slope customization (#599, #1165).

This commit is contained in:
be5invis 2021-07-23 21:23:08 -07:00
parent 89bbcd4a17
commit 5d38569238
11 changed files with 232 additions and 177 deletions

146
README.md
View file

@ -53,156 +53,137 @@ Monospace Iosevka contains various stylistic sets to change the shape of certain
<table> <table>
<tr> <tr>
<td><code>ss01</code></td> <td colspan="2"><code>ss01</code> — Andale Mono Style</td>
<td colspan="3">Andale Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss01-1.png"/></td> <td><img src="images/stylistic-set-u-ss01-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss01-1.png"/></td> <td><img src="images/stylistic-set-i-ss01-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss02</code></td> <td colspan="2"><code>ss02</code> — Anonymous Pro Style</td>
<td colspan="3">Anonymous Pro Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss02-1.png"/></td> <td><img src="images/stylistic-set-u-ss02-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss02-1.png"/></td> <td><img src="images/stylistic-set-i-ss02-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss03</code></td> <td colspan="2"><code>ss03</code> — Consolas Style</td>
<td colspan="3">Consolas Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss03-1.png"/></td> <td><img src="images/stylistic-set-u-ss03-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss03-1.png"/></td> <td><img src="images/stylistic-set-i-ss03-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss04</code></td> <td colspan="2"><code>ss04</code> — Menlo Style</td>
<td colspan="3">Menlo Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss04-1.png"/></td> <td><img src="images/stylistic-set-u-ss04-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss04-1.png"/></td> <td><img src="images/stylistic-set-i-ss04-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss05</code></td> <td colspan="2"><code>ss05</code> — Fira Mono Style</td>
<td colspan="3">Fira Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss05-1.png"/></td> <td><img src="images/stylistic-set-u-ss05-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss05-1.png"/></td> <td><img src="images/stylistic-set-i-ss05-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss06</code></td> <td colspan="2"><code>ss06</code> — Liberation Mono Style</td>
<td colspan="3">Liberation Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss06-1.png"/></td> <td><img src="images/stylistic-set-u-ss06-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss06-1.png"/></td> <td><img src="images/stylistic-set-i-ss06-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss07</code></td> <td colspan="2"><code>ss07</code> — Monaco Style</td>
<td colspan="3">Monaco Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss07-1.png"/></td> <td><img src="images/stylistic-set-u-ss07-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss07-1.png"/></td> <td><img src="images/stylistic-set-i-ss07-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss08</code></td> <td colspan="2"><code>ss08</code> — Pragmata Pro Style</td>
<td colspan="3">Pragmata Pro Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss08-1.png"/></td> <td><img src="images/stylistic-set-u-ss08-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss08-1.png"/></td> <td><img src="images/stylistic-set-i-ss08-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss09</code></td> <td colspan="2"><code>ss09</code> — Source Code Pro Style</td>
<td colspan="3">Source Code Pro Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss09-1.png"/></td> <td><img src="images/stylistic-set-u-ss09-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss09-1.png"/></td> <td><img src="images/stylistic-set-i-ss09-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss10</code></td> <td colspan="2"><code>ss10</code> — Envy Code R Style</td>
<td colspan="3">Envy Code R Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss10-1.png"/></td> <td><img src="images/stylistic-set-u-ss10-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss10-1.png"/></td> <td><img src="images/stylistic-set-i-ss10-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss11</code></td> <td colspan="2"><code>ss11</code> — X Window Style</td>
<td colspan="3">X Window Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss11-1.png"/></td> <td><img src="images/stylistic-set-u-ss11-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss11-1.png"/></td> <td><img src="images/stylistic-set-i-ss11-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss12</code></td> <td colspan="2"><code>ss12</code> — Ubuntu Mono Style</td>
<td colspan="3">Ubuntu Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss12-1.png"/></td> <td><img src="images/stylistic-set-u-ss12-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss12-1.png"/></td> <td><img src="images/stylistic-set-i-ss12-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss13</code></td> <td colspan="2"><code>ss13</code> — Lucida Style</td>
<td colspan="3">Lucida Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss13-1.png"/></td> <td><img src="images/stylistic-set-u-ss13-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss13-1.png"/></td> <td><img src="images/stylistic-set-i-ss13-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss14</code></td> <td colspan="2"><code>ss14</code> — JetBrains Mono Style</td>
<td colspan="3">JetBrains Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss14-1.png"/></td> <td><img src="images/stylistic-set-u-ss14-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss14-1.png"/></td> <td><img src="images/stylistic-set-i-ss14-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss15</code></td> <td colspan="2"><code>ss15</code> — IBM Plex Mono Style</td>
<td colspan="3">IBM Plex Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss15-1.png"/></td> <td><img src="images/stylistic-set-u-ss15-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss15-1.png"/></td> <td><img src="images/stylistic-set-i-ss15-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss16</code></td> <td colspan="2"><code>ss16</code> — PT Mono Style</td>
<td colspan="3">PT Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss16-1.png"/></td> <td><img src="images/stylistic-set-u-ss16-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss16-1.png"/></td> <td><img src="images/stylistic-set-i-ss16-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss17</code></td> <td colspan="2"><code>ss17</code> — Recursive Mono Style</td>
<td colspan="3">Recursive Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss17-1.png"/></td> <td><img src="images/stylistic-set-u-ss17-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss17-1.png"/></td> <td><img src="images/stylistic-set-i-ss17-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss18</code></td> <td colspan="2"><code>ss18</code> — Input Mono Style</td>
<td colspan="3">Input Mono Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss18-1.png"/></td> <td><img src="images/stylistic-set-u-ss18-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss18-1.png"/></td> <td><img src="images/stylistic-set-i-ss18-1.png"/></td>
</tr> </tr>
<tr> <tr>
<td><code>ss20</code></td> <td colspan="2"><code>ss20</code> — Curly Style</td>
<td colspan="3">Curly Style</td>
</tr> </tr>
<tr> <tr>
<td colspan="2"><img src="images/stylistic-set-u-ss20-1.png"/></td> <td><img src="images/stylistic-set-u-ss20-1.png"/></td>
<td colspan="2"><img src="images/stylistic-set-i-ss20-1.png"/></td> <td><img src="images/stylistic-set-i-ss20-1.png"/></td>
</tr> </tr>
</table> </table>
@ -2971,16 +2952,23 @@ Subsection `variants` is used to configure character variants in the font. Prope
Subsection `weights` is used to change the weight grades that the custom family needs. It is a dictionary of sub-objects with properties: Subsection `weights` is used to change the weight grades that the custom family needs. It is a dictionary of sub-objects with properties:
* `shape`: Integer, configures the weight grade of the glyphs' shapes. * `shape`: Number, configures the weight grade of the glyphs' shapes.
* `menu`: Integer, configures the weight grade used when naming fonts. * `menu`: Integer, configures the weight grade used when naming fonts.
* `css`: Integer, configures the weight grade used in web font CSS. * `css`: Integer, configures the weight grade used in web font CSS.
Subsection `widths` is used to change the weight grades that the custom family needs. It is a dictionary of sub-objects with properties: Subsection `widths` is used to change the width grades that the custom family needs. It is a dictionary of sub-objects with properties:
* `shape`: Integer, configures the width of the glyphs' shapes, measured in 1/1000 em. * `shape`: Number, configures the width of the glyphs' shapes, measured in 1/1000 em.
* `menu`: Integer, configures the width grade used when naming fonts. The valid values are `1` to `9`, inclusive. * `menu`: Integer, configures the width grade used when naming fonts. The valid values are `1` to `9`, inclusive.
* `css`: String, configures the [font-stretch](https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch) value used in web font CSS. * `css`: String, configures the [font-stretch](https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch) value used in web font CSS.
Subsection `slopes` is used to change the slope angles and grades that the custom family needs. It is a dictionary of sub-objects with properties:
* `angle`: Number, configures the slope angle in degrees. The valid vales are `0` to `15`, inclusive.
* `shape`: String from `upright`, `italic` or `oblique`. Configures the slope used for variant selection.
* `menu`: String from `upright`, `italic` or `oblique`. Configures the slope grade used when naming fonts.
* `css`: String from `normal`, `italic` or `oblique`. Configures the [CSS font-style](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-style) value.
Subsection `slopes` is a simple string-to-string dictionary maps slopes (`upright`, `italic` or `oblique`) to [CSS font-style](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-style) values, represented in string. Subsection `slopes` is a simple string-to-string dictionary maps slopes (`upright`, `italic` or `oblique`) to [CSS font-style](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-style) values, represented in string.
#### Compatibility Ligatures #### Compatibility Ligatures

View file

@ -990,11 +990,6 @@ from = ["iosevka-aile"]
release = true release = true
from = ["iosevka-etoile"] from = ["iosevka-etoile"]
[collectConfig]
distinguishWeights = true
distinguishWidths = false
distinguishSlope = false
################################################################################################### ###################################################################################################
# Weight mappings (style => shape weight, menu weight, CSS weight) # Weight mappings (style => shape weight, menu weight, CSS weight)
# Shape weight : affects the shape of the glyphs # Shape weight : affects the shape of the glyphs
@ -1048,13 +1043,24 @@ shape = 900
menu = 900 menu = 900
css = 900 css = 900
# slope mappings (style => CSS) # slope mappings (style => slope angle, shape slope grade, menu slope, CSS slope)
# NOTE: This mapping does NOT affect the font's metadata, only affects [slopes.upright]
# the webfont CSS. Change `params/parameters.toml` instead. angle = 0
[slopes] shape = "upright"
upright = "normal" menu = "upright"
oblique = "oblique" css = "normal"
italic = "italic"
[slopes.oblique]
angle = 9.4
shape = "oblique"
menu = "oblique"
css = "oblique"
[slopes.italic]
angle = 9.4
shape = "italic"
menu = "italic"
css = "italic"
# Width mappings (style => shape width, menu width, CSS stretch) # Width mappings (style => shape width, menu width, CSS stretch)
# Shape width : affects the shape of the glyphs # Shape width : affects the shape of the glyphs

22
changes/8.0.0.md Normal file
View file

@ -0,0 +1,22 @@
* \[**Breaking**\] Add support for slope customization (#599, #1165).
- Slope customization format has a major change, giving ability to customize slope grade used for variant selection, as well as slope angle.
- The format will look like this:
``` toml
[buildPlans.iosevka-custom.slopes.upright]
angle = 0 # Angle in degrees. Valid range [0, 15]
shape = "upright" # Slope grade used for shape selection. `upright` | `oblique` | `italic`
menu = "upright" # Slope grade used for naming. `upright` | `oblique` | `italic`
css = "normal" # Slope grade used for webfont CSS. `normal` | `oblique` | `italic`
[buildPlans.iosevka-custom.slopes.oblique]
angle = 9.4
shape = "oblique"
menu = "oblique"
css = "oblique"
[buildPlans.iosevka-custom.slopes.italic]
angle = 9.4
shape = "italic"
menu = "italic"
css = "italic"
```

View file

@ -26,6 +26,7 @@ async function getParameters() {
const PARAMETERS_TOML = path.resolve(__dirname, "../params/parameters.toml"); const PARAMETERS_TOML = path.resolve(__dirname, "../params/parameters.toml");
const WEIGHTS_TOML = path.resolve(__dirname, "../params/shape-weight.toml"); const WEIGHTS_TOML = path.resolve(__dirname, "../params/shape-weight.toml");
const WIDTHS_TOML = path.resolve(__dirname, "../params/shape-width.toml"); const WIDTHS_TOML = path.resolve(__dirname, "../params/shape-width.toml");
const SLOPES_TOML = path.resolve(__dirname, "../params/shape-slope.toml");
const PRIVATE_TOML = path.resolve(__dirname, "../params/private-parameters.toml"); const PRIVATE_TOML = path.resolve(__dirname, "../params/private-parameters.toml");
const VARIANTS_TOML = path.resolve(__dirname, "../params/variants.toml"); const VARIANTS_TOML = path.resolve(__dirname, "../params/variants.toml");
const LIGATIONS_TOML = path.resolve(__dirname, "../params/ligation-set.toml"); const LIGATIONS_TOML = path.resolve(__dirname, "../params/ligation-set.toml");
@ -35,6 +36,7 @@ async function getParameters() {
await tryParseToml(PARAMETERS_TOML), await tryParseToml(PARAMETERS_TOML),
await tryParseToml(WEIGHTS_TOML), await tryParseToml(WEIGHTS_TOML),
await tryParseToml(WIDTHS_TOML), await tryParseToml(WIDTHS_TOML),
await tryParseToml(SLOPES_TOML),
fs.existsSync(PRIVATE_TOML) ? await tryParseToml(PRIVATE_TOML) : {} fs.existsSync(PRIVATE_TOML) ? await tryParseToml(PRIVATE_TOML) : {}
); );
const rawVariantsData = await tryParseToml(VARIANTS_TOML); const rawVariantsData = await tryParseToml(VARIANTS_TOML);

View file

@ -11,6 +11,7 @@ function initPara(data, argv) {
applyBlendingParam(argv, para, data, "shapeWeight", "weight"); applyBlendingParam(argv, para, data, "shapeWeight", "weight");
applyBlendingParam(argv, para, data, "shapeWidth", "width"); applyBlendingParam(argv, para, data, "shapeWidth", "width");
applyBlendingParam(argv, para, data, "shapeSlopeAngle", "slopeAngle");
applyAlternatesParam(argv, para, data, "slope", "slope"); applyAlternatesParam(argv, para, data, "slope", "slope");
if (argv.featureControl.noCvSs) para.enableCvSs = false; if (argv.featureControl.noCvSs) para.enableCvSs = false;

View file

@ -1,6 +1,6 @@
{ {
"name": "iosevka", "name": "iosevka",
"version": "7.3.3", "version": "8.0.0",
"main": "./font-src/index.js", "main": "./font-src/index.js",
"scripts": { "scripts": {
"build": "node utility/ensure-verda-exists && verda -f verdafile.js", "build": "node utility/ensure-verda-exists && verda -f verdafile.js",

View file

@ -127,8 +127,6 @@ diversityII = 0.50
###### Slopes ###### Slopes
[slope-italic] [slope-italic]
isItalic = true isItalic = true
slopeAngle = 9.4
[slope-oblique] [slope-oblique]
isOblique = true isOblique = true
slopeAngle = 9.4

8
params/shape-slope.toml Normal file
View file

@ -0,0 +1,8 @@
[shapeSlopeAngle.blend.0]
slopeAngle = 0
[shapeSlopeAngle.blend.-15]
slopeAngle = -15
[shapeSlopeAngle.blend.15]
slopeAngle = 15

View file

@ -67,14 +67,25 @@ css = 700
################################################################################################### ###################################################################################################
# Override default building slope sets # Override default building slope sets
# Format: <upright|italic|oblique> = <"normal"|"italic"|"oblique">
# When this section is absent, all slopes would be built. # When this section is absent, all slopes would be built.
[buildPlans.iosevka-custom.slopes] [buildPlans.iosevka-custom.slopes.upright]
upright = "normal" angle = 0 # Angle in degrees. Valid range [0, 15]
italic = "italic" shape = "upright" # Slope grade used for shape selection. `upright` | `oblique` | `italic`
oblique = "oblique" menu = "upright" # Slope grade used for naming. `upright` | `oblique` | `italic`
css = "normal" # Slope grade used for webfont CSS. `normal` | `oblique` | `italic`
[buildPlans.iosevka-custom.slopes.oblique]
angle = 9.4
shape = "oblique"
menu = "oblique"
css = "oblique"
[buildPlans.iosevka-custom.slopes.italic]
angle = 9.4
shape = "italic"
menu = "italic"
css = "italic"
# End slope section # End slope section
################################################################################################### ###################################################################################################

View file

@ -39,18 +39,13 @@ async function processSsOt() {
if (!ss.rank) continue; if (!ss.rank) continue;
{ {
md.log(`<tr>`); md.log(`<tr>`);
md.log(`<td><code>${ss.tag}</code></td>`); md.log(`<td colspan="2"><code>${ss.tag}</code> — ${ss.description}</td>`);
md.log(`<td colspan="3">${ss.description}</td>`);
md.log(`</tr>`); md.log(`</tr>`);
} }
{ {
md.log(`<tr>`); md.log(`<tr>`);
md.log( md.log(`<td><img src="images/stylistic-set-u-${ss.tag}-${ss.rank}.png"/></td>`);
`<td colspan="2"><img src="images/stylistic-set-u-${ss.tag}-${ss.rank}.png"/></td>` md.log(`<td><img src="images/stylistic-set-i-${ss.tag}-${ss.rank}.png"/></td>`);
);
md.log(
`<td colspan="2"><img src="images/stylistic-set-i-${ss.tag}-${ss.rank}.png"/></td>`
);
md.log(`</tr>`); md.log(`</tr>`);
} }
} }

View file

@ -36,7 +36,6 @@ const webfontFormatsPages = [["woff2", "woff2"]];
const WIDTH_NORMAL = "normal"; const WIDTH_NORMAL = "normal";
const WEIGHT_NORMAL = "regular"; const WEIGHT_NORMAL = "regular";
const SLOPE_NORMAL = "upright"; const SLOPE_NORMAL = "upright";
const SLOPE_OBLIQUE = "oblique";
const DEFAULT_SUBFAMILY = "regular"; const DEFAULT_SUBFAMILY = "regular";
const BUILD_PLANS = "build-plans.toml"; const BUILD_PLANS = "build-plans.toml";
@ -189,8 +188,9 @@ const FontInfoOf = computed.group("metadata:font-info-of", async (target, fileNa
serifs: bp.serifs || null, serifs: bp.serifs || null,
spacing: bp.spacing || null, spacing: bp.spacing || null,
weight: sfi.shapeWeight, weight: sfi.shapeWeight,
slope: sfi.slope, width: sfi.shapeWidth,
width: sfi.shapeWidth slope: sfi.shapeSlope,
slopeAngle: sfi.shapeSlopeAngle
}, },
// Menu // Menu
menu: { menu: {
@ -228,23 +228,28 @@ function getSuffixMapping(weights, slopes, widths) {
return mapping; return mapping;
} }
function getSuffixMappingItem(weights, w, slopes, s, widths, wd) { function getSuffixMappingItem(weights, w, slopes, s, widths, wd) {
const weightDef = wwsDefValidate("Weight definition of " + s, weights[w]);
const widthDef = wwsDefValidate("Width definition of " + s, widths[wd]);
const slopeDef = wwsDefValidate("Slope definition of " + s, slopes[s]);
return { return {
// Weights // Weights
weight: w, weight: w,
shapeWeight: nValidate("Shape weight of " + w, weights[w].shape, VlShapeWeight), shapeWeight: nValidate("Shape weight of " + w, weightDef.shape, VlShapeWeight),
cssWeight: nValidate("CSS weight of " + w, weights[w].css, VlCssWeight), cssWeight: nValidate("CSS weight of " + w, weightDef.css, VlCssWeight),
menuWeight: nValidate("Menu weight of " + w, weights[w].menu, VlMenuWeight), menuWeight: nValidate("Menu weight of " + w, weightDef.menu, VlMenuWeight),
// Widths // Widths
width: wd, width: wd,
shapeWidth: nValidate("Shape width of " + wd, widths[wd].shape, VlShapeWidth), shapeWidth: nValidate("Shape width of " + wd, widthDef.shape, VlShapeWidth),
cssStretch: widths[wd].css || wd, cssStretch: sValidate("CSS stretch of " + wd, widthDef.css, VlCssFontStretch),
menuWidth: nValidate("Menu width of " + wd, widths[wd].menu, VlMenuWidth), menuWidth: nValidate("Menu width of " + wd, widthDef.menu, VlMenuWidth),
// Slopes // Slopes
slope: s, slope: s,
cssStyle: slopes[s] || s, shapeSlope: sValidate("Shape slope of " + s, slopeDef.shape, VlShapeSlope),
menuSlope: slopes[s] || s shapeSlopeAngle: nValidate("Angle of " + s, slopeDef.angle, VlSlopeAngle),
cssStyle: sValidate("CSS style of " + s, slopeDef.css, VlCssStyle),
menuSlope: sValidate("Menu slope of " + s, slopeDef.menu, VlShapeSlope)
}; };
} }
@ -366,18 +371,11 @@ const DistWoff2 = file.make(
const CollectPlans = computed(`metadata:collect-plans`, async target => { const CollectPlans = computed(`metadata:collect-plans`, async target => {
const [rawPlans] = await target.need(RawPlans); const [rawPlans] = await target.need(RawPlans);
return await getCollectPlans( return await getCollectPlans(target, rawPlans.collectPlans);
target,
rawPlans.collectPlans,
rawPlans.collectConfig,
fnStandardTtc
);
}); });
async function getCollectPlans(target, rawCollectPlans, config, fnFileName) { async function getCollectPlans(target, rawCollectPlans) {
const glyfTtcComposition = {}, const plans = {};
ttcComposition = {},
plans = {};
let allCollectableGroups = new Set(); let allCollectableGroups = new Set();
for (const collectPrefix in rawCollectPlans) { for (const collectPrefix in rawCollectPlans) {
@ -392,65 +390,60 @@ async function getCollectPlans(target, rawCollectPlans, config, fnFileName) {
} }
for (const collectPrefix in amendedRawCollectPlans) { for (const collectPrefix in amendedRawCollectPlans) {
const groupFileList = new Set(); const glyfTtcComposition = {};
const ttcComposition = {};
const collect = amendedRawCollectPlans[collectPrefix]; const collect = amendedRawCollectPlans[collectPrefix];
if (!collect || !collect.from || !collect.from.length) continue; if (!collect || !collect.from || !collect.from.length) continue;
for (const prefix of collect.from) { for (const prefix of collect.from) {
const [gri] = await target.need(BuildPlanOf(prefix)); const [gri] = await target.need(BuildPlanOf(prefix));
const ttfFileNameSet = new Set(gri.targets); const ttfFileNameSet = new Set(gri.targets);
const suffixMapping = getSuffixMapping(gri.weights, gri.slopes, gri.widths); const suffixMap = getSuffixMapping(gri.weights, gri.slopes, gri.widths);
for (const suffix in suffixMapping) { for (const suffix in suffixMap) {
const sfi = suffixMapping[suffix]; const sfi = suffixMap[suffix];
const ttcFileName = fnFileName(
config,
collectPrefix,
sfi.weight,
sfi.width,
sfi.slope
);
const glyfTtcFileName = fnFileName(
{ ...config, distinguishWidths: true, distinguishWhetherUpright: true },
collectPrefix,
sfi.weight,
sfi.width,
sfi.slope
);
const ttfTargetName = makeFileName(prefix, suffix); const ttfTargetName = makeFileName(prefix, suffix);
if (!ttfFileNameSet.has(ttfTargetName)) continue; if (!ttfFileNameSet.has(ttfTargetName)) continue;
const glyfTtcFileName = fnStandardTtc(true, collectPrefix, suffixMap, sfi);
if (!glyfTtcComposition[glyfTtcFileName]) glyfTtcComposition[glyfTtcFileName] = []; if (!glyfTtcComposition[glyfTtcFileName]) glyfTtcComposition[glyfTtcFileName] = [];
glyfTtcComposition[glyfTtcFileName].push({ dir: prefix, file: ttfTargetName }); glyfTtcComposition[glyfTtcFileName].push({ dir: prefix, file: ttfTargetName });
const ttcFileName = fnStandardTtc(false, collectPrefix, suffixMap, sfi);
if (!ttcComposition[ttcFileName]) ttcComposition[ttcFileName] = []; if (!ttcComposition[ttcFileName]) ttcComposition[ttcFileName] = [];
ttcComposition[ttcFileName].push(glyfTtcFileName); ttcComposition[ttcFileName].push(glyfTtcFileName);
groupFileList.add(ttcFileName);
} }
} }
plans[collectPrefix] = { plans[collectPrefix] = {
ttcContents: [...groupFileList], glyfTtcComposition,
ttcComposition,
groupDecomposition: [...collect.from], groupDecomposition: [...collect.from],
inRelease: !!collect.release, inRelease: !!collect.release,
isAmended: !!collect.isAmended isAmended: !!collect.isAmended
}; };
} }
return { glyfTtcComposition, ttcComposition, plans }; return plans;
} }
function fnStandardTtc(collectConfig, prefix, w, wd, s) {
const ttcSuffix = makeSuffix( function fnStandardTtc(fIsGlyfTtc, prefix, suffixMapping, sfi) {
collectConfig.distinguishWeights ? w : WEIGHT_NORMAL, let optimalSfi = null,
collectConfig.distinguishWidths ? wd : WIDTH_NORMAL, maxScore = 0;
collectConfig.distinguishSlope for (const ttcSuffix in suffixMapping) {
? s const sfiT = suffixMapping[ttcSuffix];
: collectConfig.distinguishWhetherUpright if (sfi.shapeWeight !== sfiT.shapeWeight) continue;
? s === SLOPE_NORMAL if (sfi.shapeWidth !== sfiT.shapeWidth) continue;
? SLOPE_NORMAL if (fIsGlyfTtc && sfi.shapeSlopeAngle !== sfiT.shapeSlopeAngle) continue;
: SLOPE_OBLIQUE const score =
: SLOPE_NORMAL, (sfiT.weight === WEIGHT_NORMAL ? 1 : 0) +
DEFAULT_SUBFAMILY (sfiT.width === WIDTH_NORMAL ? 1 : 0) +
); (sfiT.slope === SLOPE_NORMAL ? 1 : 0);
return `${prefix}-${ttcSuffix}`; if (!optimalSfi || score > maxScore) {
maxScore = score;
optimalSfi = sfiT;
}
}
if (!optimalSfi) throw new Error("Unreachable: TTC name decision");
return `${prefix}-${makeSuffix(optimalSfi.weight, optimalSfi.width, optimalSfi.slope)}`;
} }
/////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////
@ -464,25 +457,25 @@ const CollectedSuperTtcFile = file.make(
cgr => `${DIST_SUPER_TTC}/${cgr}.ttc`, cgr => `${DIST_SUPER_TTC}/${cgr}.ttc`,
async (target, out, cgr) => { async (target, out, cgr) => {
const [cp] = await target.need(CollectPlans, de(out.dir)); const [cp] = await target.need(CollectPlans, de(out.dir));
const parts = Array.from(new Set(cp.plans[cgr].ttcContents)); const parts = Array.from(Object.keys(cp[cgr].glyfTtcComposition));
const [inputs] = await target.need(parts.map(pt => CollectedTtcFile(cgr, pt))); const [inputs] = await target.need(parts.map(pt => GlyfTtc(cgr, pt)));
await buildCompositeTtc(out, inputs); await buildCompositeTtc(out, inputs);
} }
); );
const CollectedTtcFile = file.make( const CollectedTtcFile = file.make(
(cgr, f) => `${BUILD}/ttc-collect/${cgr}/ttc/${f}.ttc`, (cgr, f) => `${BUILD}/ttc-collect/${cgr}/${f}.ttc`,
async (target, out, gr, f) => { async (target, out, cgr, f) => {
const [cp] = await target.need(CollectPlans, de`${out.dir}`); const [cp] = await target.need(CollectPlans, de`${out.dir}`);
const parts = Array.from(new Set(cp.ttcComposition[f])); const parts = Array.from(new Set(cp[cgr].ttcComposition[f]));
const [inputs] = await target.need(parts.map(pt => GlyfTtc(gr, pt))); const [inputs] = await target.need(parts.map(pt => GlyfTtc(cgr, pt)));
await buildCompositeTtc(out, inputs); await buildCompositeTtc(out, inputs);
} }
); );
const GlyfTtc = file.make( const GlyfTtc = file.make(
(cgr, f) => `${BUILD}/glyf-ttc/${cgr}/${f}.ttc`, (cgr, f) => `${BUILD}/glyf-ttc/${cgr}/${f}.ttc`,
async (target, out, gr, f) => { async (target, out, cgr, f) => {
const [cp] = await target.need(CollectPlans); const [cp] = await target.need(CollectPlans);
const parts = cp.glyfTtcComposition[f]; const parts = cp[cgr].glyfTtcComposition[f];
await buildGlyphSharingTtc(target, parts, out); await buildGlyphSharingTtc(target, parts, out);
} }
); );
@ -508,13 +501,13 @@ async function buildGlyphSharingTtc(target, parts, out) {
const TtcArchiveFile = file.make( const TtcArchiveFile = file.make(
(cgr, version) => `${ARCHIVE_DIR}/ttc-${cgr}-${version}.zip`, (cgr, version) => `${ARCHIVE_DIR}/ttc-${cgr}-${version}.zip`,
async (target, out, cgr) => { async (target, out, cgr) => {
const [collectPlans] = await target.need(CollectPlans, de`${out.dir}`); const [cp] = await target.need(CollectPlans, de`${out.dir}`);
const ttcFiles = Array.from(new Set(collectPlans.plans[cgr].ttcContents)); const ttcFiles = Array.from(Object.keys(cp[cgr].ttcComposition));
await target.need(ttcFiles.map(pt => CollectedTtcFile(cgr, pt))); await target.need(ttcFiles.map(pt => CollectedTtcFile(cgr, pt)));
// Packaging // Packaging
await rm(out.full); await rm(out.full);
await cd(`${BUILD}/ttc-collect/${cgr}/ttc`).run( await cd(`${BUILD}/ttc-collect/${cgr}`).run(
["7z", "a"], ["7z", "a"],
["-tzip", "-r", "-mx=9"], ["-tzip", "-r", "-mx=9"],
`../../../../${out.full}`, `../../../../${out.full}`,
@ -795,7 +788,7 @@ phony(`clean`, async () => {
phony(`release`, async target => { phony(`release`, async target => {
const [collectPlans] = await target.need(CollectPlans); const [collectPlans] = await target.need(CollectPlans);
let goals = []; let goals = [];
for (const [cgr, plan] of Object.entries(collectPlans.plans)) { for (const [cgr, plan] of Object.entries(collectPlans)) {
if (!plan.inRelease) continue; if (!plan.inRelease) continue;
goals.push(ReleaseGroup(cgr)); goals.push(ReleaseGroup(cgr));
} }
@ -804,7 +797,7 @@ phony(`release`, async target => {
}); });
const ReleaseGroup = phony.group("release-group", async (target, cgr) => { const ReleaseGroup = phony.group("release-group", async (target, cgr) => {
const [version, collectPlans] = await target.need(Version, CollectPlans); const [version, collectPlans] = await target.need(Version, CollectPlans);
const subGroups = collectPlans.plans[cgr].groupDecomposition; const subGroups = collectPlans[cgr].groupDecomposition;
let goals = [TtcArchiveFile(cgr, version), SuperTtcArchiveFile(cgr, version)]; let goals = [TtcArchiveFile(cgr, version), SuperTtcArchiveFile(cgr, version)];
for (const gr of subGroups) { for (const gr of subGroups) {
@ -887,6 +880,7 @@ const Parameters = task(`meta:parameters`, async target => {
sfu`params/parameters.toml`, sfu`params/parameters.toml`,
sfu`params/shape-weight.toml`, sfu`params/shape-weight.toml`,
sfu`params/shape-width.toml`, sfu`params/shape-width.toml`,
sfu`params/shape-slope.toml`,
ofu`params/private-parameters.toml`, ofu`params/private-parameters.toml`,
sfu`params/variants.toml`, sfu`params/variants.toml`,
sfu`params/ligation-set.toml` sfu`params/ligation-set.toml`
@ -937,6 +931,13 @@ function validateRecommendedWeight(w, value, label) {
} }
// Value validation // Value validation
function wwsDefValidate(key, obj) {
if (!obj || typeof obj === "string") {
throw new TypeError(`${key} is invalid.`);
}
return obj;
}
function nValidate(key, v, validator) { function nValidate(key, v, validator) {
if (validator.fixup) v = validator.fix(v); if (validator.fixup) v = validator.fix(v);
if (typeof v !== "number" || !isFinite(v) || !validator.validate(v)) { if (typeof v !== "number" || !isFinite(v) || !validator.validate(v)) {
@ -968,3 +969,26 @@ const VlShapeWidth = {
} }
}; };
const VlMenuWidth = { validate: x => x >= 1 && x <= 9 && x % 1 === 0 }; const VlMenuWidth = { validate: x => x >= 1 && x <= 9 && x % 1 === 0 };
const VlSlopeAngle = { validate: x => x >= 0 && x <= 15 };
function sValidate(key, v, validator) {
if (validator.fixup) v = validator.fix(v);
if (typeof v !== "string" || !validator.validate(v)) {
throw new TypeError(`${key} = ${v} is not a valid string.`);
}
return v;
}
const VlShapeSlope = { validate: x => x === "upright" || x === "oblique" || x === "italic" };
const VlCssStyle = { validate: x => x === "normal" || x === "oblique" || x === "italic" };
const VlCssFontStretch = {
validate: x =>
x == "ultra-condensed" ||
x == "extra-condensed" ||
x == "condensed" ||
x == "semi-condensed" ||
x == "normal" ||
x == "semi-expanded" ||
x == "expanded" ||
x == "extra-expanded" ||
x == "ultra-expanded"
};