Allow the user to customize the characters' width freely (#554).

This commit is contained in:
Belleve Invis 2020-06-07 00:25:04 -07:00
parent 275fc23ba3
commit 966838af7b
9 changed files with 163 additions and 115 deletions

View file

@ -360,7 +360,7 @@ snapshotFamily = 'iosevka-aile'
design = ["diversity-2"]
[buildPlans.iosevka-aile.widths.normal]
shape = 7
shape = 576
menu = 5
css = "normal"
@ -379,7 +379,7 @@ snapshotFamily = 'iosevka-etoile'
design = ["diversity-1"]
[buildPlans.iosevka-etoile.widths.normal]
shape = 7
shape = 576
menu = 5
css = "normal"
@ -398,7 +398,7 @@ snapshotFamily = 'iosevka-sparkle'
design = ["diversity-1"]
[buildPlans.iosevka-sparkle.widths.normal]
shape = 7
shape = 576
menu = 5
css = "normal"
@ -614,12 +614,12 @@ italic = "italic"
# and "menu" only support 1 ... 9
[widths.normal]
shape = 5
shape = 500
menu = 5
css = "normal"
[widths.extended]
shape = 7
shape = 576
menu = 7
css = "expanded"

View file

@ -9,3 +9,4 @@
* Add parenthesis variant with larger contour (#570).
* Fix placement of U+0315 COMBINING COMMA ABOVE RIGHT (#583).
* Fix shape of U+1D24 LATIN LETTER VOICED LARYNGEAL SPIRANT (#584).
* Allow the user to customize the characters' width freely (#554).

View file

@ -32,7 +32,10 @@ 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.shape.weight });
const para = Parameters.build(parametersData, argv.hives, {
shapeWeight: argv.shape.weight,
shapeWidth: argv.shape.width
});
const variantsData = FormVariantData(rawVariantsData, para);
para.variants = variantsData;

View file

@ -271,7 +271,18 @@ spacing = 0
# NOTE: this section is highly experimental
# HANDLE WITH EXTREME CARE
# Expanded : I heard someone want it being wider...
[wd-9.multiplies]
[shapeWidth.multiplies.blend.500]
width = 1
stroke = 1
sb = 1
jut = 1
longjut = 1
rhook = 1
rbalance = 1
tbalance = 1
smallsmooth = 1
[shapeWidth.multiplies.blend.664]
width = 1.328 # 664 for normal char
stroke = 1.103 # Make strokes a little thicker
sb = 1.777
@ -282,10 +293,7 @@ rbalance = 1.236
tbalance = 1.210
smallsmooth = 1.103
[ultra-extended]
inherits = ['wd-9']
[wd-8.multiplies]
[shapeWidth.multiplies.blend.618]
width = 1.236 # 618 for normal char
stroke = 1.075 # Make strokes a little thicker
sb = 1.539
@ -296,10 +304,7 @@ rbalance = 1.236
tbalance = 1.154
smallsmooth = 1.075
[extra-extended]
inherits = ['wd-8']
[wd-7.multiplies]
[shapeWidth.multiplies.blend.576]
width = 1.152 # 576mem for normal char
stroke = 1.050 # Make strokes a little thicker
sb = 1.333
@ -310,10 +315,7 @@ rbalance = 1.152
tbalance = 1.100
smallsmooth = 1.050
[extended]
inherits = ['wd-7']
[wd-6.multiplies]
[shapeWidth.multiplies.blend.537]
width = 1.074 # 537mem for normal char
stroke = 1.023 # Make strokes a little thicker
sb = 1.154
@ -324,10 +326,7 @@ rbalance = 1.074
tbalance = 1.049
smallsmooth = 1.023
[semi-extended]
inherits = ['wd-6']
[wd-4.multiplies]
[shapeWidth.multiplies.blend.466]
width = 0.932 # 466mem for normal char
stroke = 0.975
sb = 0.866
@ -338,10 +337,7 @@ rbalance = 0.931
tbalance = 0.953
smallsmooth = 0.975
[semi-condensed]
inherits = ['wd-4']
[wd-3.multiplies]
[shapeWidth.multiplies.blend.434]
width = 0.868 # 434mem for normal char
stroke = 0.952
sb = 0.750
@ -352,8 +348,7 @@ rbalance = 0.868
tbalance = 0.909
smallsmooth = 0.952
[condensed]
inherits = ['wd-3']
###### Diversity
[diversity-1]
diversityM = 1.25

View file

@ -49,18 +49,20 @@ oblique = "oblique"
# Override default building widths
# When buildPlans.<plan name>.widths is absent, all widths would built and mapped to
# default values.
# IMPORTANT : Currently "shape" property only supports integers between 3 and 9 (inclusive), while
# "menu" only supports integers between 1 and 9 (inclusive).
# If you decide to use custom widths you have to define all the widths you
# plan to use otherwise they will not be built.
# IMPORTANT : Currently "shape" property only supports numbers between 434 and 664 (inclusive),
# while "menu" only supports integers between 1 and 9 (inclusive).
# The "shape" parameter specifies the unit width, measured in 1/1000 em. The glyphs'
# width are equal to, or a simple multiple of the unit width.
# If you decide to use custom widths you have to define all the widths you plan to use,
# otherwise they will not be built.
[buildPlans.iosevka-custom.widths.normal]
shape = 5 # Width grade of glyph shapes. NOT actual character width.
menu = 5 # Width grade for the font's names. NOT actual character width.
shape = 500 # Unit Width, measured in 1/1000 em.
menu = 5 # Width grade for the font's names.
css = "normal" # "font-stretch' property of webfont CSS.
[buildPlans.iosevka-custom.widths.extended]
shape = 7
shape = 576
menu = 7
css = "expanded"

View file

@ -1,31 +0,0 @@
"use strict";
const blend = require("./monotonic-interpolate");
module.exports = function (aspect, hive, params, sink) {
if (!hive || !hive.blend || !params) return;
const block = hive.blend;
let keys = new Set();
for (const grade in block) {
sink[grade] = block[grade];
if (!isFinite(parseFloat(grade))) continue;
for (const key in block[grade]) {
if (block[grade][key] == null) continue;
keys.add(key);
}
}
for (const key of keys) {
let xs = [],
ys = [];
for (const grade in block) {
if (!isFinite(parseFloat(grade))) continue;
if (block[grade][key] == null) continue;
xs.push(grade);
ys.push(block[grade][key]);
}
const fn = blend(xs, ys);
sink[key] = fn(params[aspect]);
}
};

100
support/parameters.js Normal file
View file

@ -0,0 +1,100 @@
"use strict";
const monotonicInterpolate = require("./monotonic-interpolate");
function build(parametersData, styles, blendArgs) {
const sink = {};
for (const item of styles) intro(parametersData, item, blendArgs, sink);
return sink;
}
exports.build = build;
function intro(source, style, blendArgs, sink) {
let hive = source[style];
if (!hive) return;
hive = { ...hive };
if (hive.inherits) {
for (const hn of hive.inherits) intro(source, hn, blendArgs, sink);
delete hive.inherits;
}
if (hive.multiplies) {
const mu = hiveBlend(hive.multiplies, getBlendArg(blendArgs, style), sink);
for (const k in mu) sink[k] = (sink[k] || 0) * mu[k];
delete hive.multiplies;
}
if (hive.adds) {
const mu = hiveBlend(hive.adds, getBlendArg(blendArgs, style), sink);
for (const k in mu) sink[k] = (sink[k] || 0) + mu[k];
delete hive.adds;
}
if (hive.appends) {
const mu = hive.appends;
for (const k in mu) sink[k] = [...(sink[k] || []), ...mu[k]];
delete hive.appends;
}
hive = hiveBlend(hive, getBlendArg(blendArgs, style), sink);
for (const k in hive) sink[k] = hive[k];
}
function getBlendArg(blendArgs, style) {
if (!blendArgs) return undefined;
return blendArgs[style];
}
function hiveBlend(hive, value, sink) {
if (!hive || !hive.blend || value == null) return hive;
const generatedHive = {};
const block = hive.blend;
let keys = new Set();
for (const grade in block) {
sink[grade] = block[grade];
if (!isFinite(parseFloat(grade))) continue;
for (const key in block[grade]) {
if (block[grade][key] == null) continue;
keys.add(key);
}
}
for (const key of keys) {
let xs = [],
ys = [];
for (const grade in block) {
if (!isFinite(parseFloat(grade))) continue;
if (block[grade][key] == null) continue;
xs.push(grade);
ys.push(block[grade][key]);
}
generatedHive[key] = monotonicInterpolate(xs, ys)(value);
}
return generatedHive;
}
function numericConfigExists(x) {
return x != null && isFinite(x);
}
function applyMetricOverride(para, mo) {
if (numericConfigExists(mo.leading)) {
para.leading = mo.leading;
}
if (numericConfigExists(mo.winMetricAscenderPad)) {
para.winMetricAscenderPad = mo.winMetricAscenderPad;
}
if (numericConfigExists(mo.winMetricDescenderPad)) {
para.winMetricDescenderPad = mo.winMetricDescenderPad;
}
if (numericConfigExists(mo.powerlineScaleY)) {
para.powerlineScaleY = mo.powerlineScaleY;
}
if (numericConfigExists(mo.powerlineScaleX)) {
para.powerlineScaleX = mo.powerlineScaleX;
}
if (numericConfigExists(mo.powerlineShiftY)) {
para.powerlineShiftY = mo.powerlineShiftY;
}
if (numericConfigExists(mo.powerlineShiftX)) {
para.powerlineShiftX = mo.powerlineShiftX;
}
}
exports.applyMetricOverride = applyMetricOverride;

View file

@ -1,43 +0,0 @@
import "./param-blend" as paramBlend
export : define [build parametersData styles blendParams] : begin
local param {.}
define [introStyle style] : begin
local hive parametersData.(style)
if (!hive) : return nothing
if hive.inherits : foreach [h : items-of hive.inherits] : introStyle h
foreach [k : items-of : Object.keys hive] : piecewise
(k === "multiplies") : foreach [k : items-of : Object.keys hive.multiplies] : begin
set param.(k) : param.(k) * hive.multiplies.(k)
(k === "adds") : foreach [k : items-of : Object.keys hive.adds] : begin
set param.(k) : param.(k) + hive.adds.(k)
(k === "appends") : foreach [k : items-of : Object.keys hive.appends] : begin
set param.(k) : (param.(k) || {}).concat(hive.appends.(k))
true : set param.(k) hive.(k)
paramBlend style hive blendParams param
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

@ -213,13 +213,18 @@ function getSuffixMapping(weights, slants, widths) {
for (const wd in widths) {
const suffix = makeSuffix(w, wd, s, "regular");
mapping[suffix] = {
hives: [`shapeWeight`, `s-${s}`, `wd-${widths[wd].shape}`],
hives: [`shapeWeight`, `s-${s}`, `shapeWidth`],
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),
shapeWidth: nValidate(
"Shape width of " + wd,
widths[wd].shape,
vlShapeWidth,
fixShapeWidth
),
cssStretch: widths[wd].css || wd,
menuWidth: nValidate("Menu width of " + wd, widths[wd].menu, vlMenuWidth),
slant: s,
@ -248,9 +253,10 @@ function validateRecommendedWeight(w, value, label) {
}
}
function nValidate(key, v, f) {
function nValidate(key, v, f, ft) {
if (ft) v = ft(v);
if (typeof v !== "number" || !isFinite(v) || (f && !f(v))) {
throw new TypeError(`${key} = "${v}" is not a valid number.`);
throw new TypeError(`${key} = ${v} is not a valid number.`);
}
return v;
}
@ -263,8 +269,23 @@ function vlCssWeight(x) {
function vlMenuWeight(x) {
return vlCssWeight(x);
}
const g_widthFixupMemory = new Map();
function fixShapeWidth(x) {
if (x >= 3 && x <= 9) {
if (g_widthFixupMemory.has(x)) return g_widthFixupMemory.get(x);
const xCorrected = Math.round(500 * Math.pow(Math.sqrt(576 / 500), x - 5));
echo.warn(
`The build plan is using legacy width grade ${x}. ` +
`Converting to unit width ${xCorrected}.`
);
g_widthFixupMemory.set(x, xCorrected);
return xCorrected;
} else {
return x;
}
}
function vlShapeWidth(x) {
return x >= 3 && x <= 9 && x % 1 === 0;
return x >= 433 && x <= 665;
}
function vlMenuWidth(x) {
return x >= 1 && x <= 9 && x % 1 === 0;