- LATIN SMALL LIGATURE FI (`U+FB01`) (#1497). - LATIN SMALL LIGATURE FFI (`U+FB03`) (#1497). - LATIN SMALL LIGATURE FFL (`U+FB04`) (#1497). - LATIN SMALL LIGATURE LONG S T (`U+FB05`) (#1497). - LATIN SMALL LIGATURE ST (`U+FB06`) (#1497).
240 lines
7.3 KiB
JavaScript
240 lines
7.3 KiB
JavaScript
import { Ot } from "ot-builder";
|
|
import semver from "semver";
|
|
|
|
export function createNamingDictFromArgv(argv) {
|
|
return {
|
|
family: argv.menu.family,
|
|
version: argv.menu.version,
|
|
weight: argv.menu.weight - 0,
|
|
width: argv.menu.width - 0,
|
|
slope: argv.menu.slope
|
|
};
|
|
}
|
|
|
|
export function assignFontNames(font, naming, isQuasiProportional) {
|
|
// Preferred names
|
|
const family = naming.family.trim();
|
|
const style = getStyle(naming.weight, naming.width, naming.slope);
|
|
|
|
nameFont(font, Ot.Name.NameID.PreferredFamily, family);
|
|
nameFont(font, Ot.Name.NameID.PreferredSubfamily, style);
|
|
nameFont(font, Ot.Name.NameID.WwsFamily, family);
|
|
nameFont(font, Ot.Name.NameID.WwsSubfamily, style);
|
|
|
|
// Compat banes
|
|
const compat = getStyleLinkedStyles(naming.weight, naming.width, naming.slope);
|
|
let compatFamily = family;
|
|
if (compat.familySuffix !== "Regular") compatFamily = family + " " + compat.familySuffix;
|
|
if (compatFamily.length >= 31) compatFamily = family + " " + compat.familySuffixShort;
|
|
|
|
nameFont(font, Ot.Name.NameID.LegacyFamily, compatFamily);
|
|
nameFont(font, Ot.Name.NameID.LegacySubfamily, compat.style);
|
|
|
|
// Unique and Full name
|
|
const uniqueName = `${family} ${style} ${naming.version}`;
|
|
const fullName = style !== "Regular" ? `${family} ${style}` : family;
|
|
const postscriptName = fullName.replace(/ /g, "-");
|
|
nameFont(font, Ot.Name.NameID.UniqueFontId, uniqueName);
|
|
nameFont(font, Ot.Name.NameID.FullFontName, fullName);
|
|
nameFont(font, Ot.Name.NameID.PostscriptName, postscriptName);
|
|
|
|
// Weight, width and slope numbers
|
|
font.os2.usWeightClass = naming.weight;
|
|
font.os2.usWidthClass = naming.width;
|
|
font.os2.panose.bWeight = 1 + naming.weight / 100;
|
|
font.os2.sFamilyClass = 0x809;
|
|
|
|
// HEAD and OS/2 flags
|
|
const isItalic = naming.slope === "italic";
|
|
const isOblique = naming.slope === "oblique";
|
|
const isBold = naming.weight > 650;
|
|
|
|
// prettier-ignore
|
|
font.os2.fsSelection = accumulateFlags(
|
|
[Ot.Os2.FsSelection.OBLIQUE, isOblique],
|
|
[Ot.Os2.FsSelection.BOLD, isBold],
|
|
[Ot.Os2.FsSelection.ITALIC, isItalic || isOblique],
|
|
[Ot.Os2.FsSelection.REGULAR, !isBold && !isItalic && !isOblique],
|
|
[Ot.Os2.FsSelection.USE_TYPO_METRICS, true]
|
|
);
|
|
|
|
// prettier-ignore
|
|
font.head.macStyle = accumulateFlags(
|
|
[Ot.Head.MacStyle.Bold, isBold],
|
|
[Ot.Head.MacStyle.Italic, isItalic || isOblique],
|
|
[Ot.Head.MacStyle.Condensed, naming.width < 5],
|
|
[Ot.Head.MacStyle.Extended, naming.width > 5]
|
|
);
|
|
|
|
// Panose
|
|
font.os2.panose.bFamilyType = 2;
|
|
font.os2.panose.bContrast = 3;
|
|
font.os2.panose.bXHeight = 4;
|
|
|
|
// Pitch
|
|
if (!isQuasiProportional) {
|
|
font.os2.panose.bProportion = 9; // Monospaced
|
|
font.post.isFixedPitch = true;
|
|
} else {
|
|
font.os2.panose.bProportion = 0;
|
|
font.post.isFixedPitch = false;
|
|
}
|
|
|
|
// Licensing
|
|
if (naming.miscNames) {
|
|
nameFont(font, Ot.Name.NameID.Copyright, ancNameEntry(naming.miscNames.copyright));
|
|
nameFont(font, Ot.Name.NameID.Manufacturer, ancNameEntry(naming.miscNames.manufacturer));
|
|
nameFont(font, Ot.Name.NameID.Designer, ancNameEntry(naming.miscNames.designer));
|
|
nameFont(font, Ot.Name.NameID.Description, ancNameEntry(naming.miscNames.description));
|
|
nameFont(font, Ot.Name.NameID.LicenseDescription, ancNameEntry(naming.miscNames.licence));
|
|
}
|
|
|
|
// Version
|
|
nameFont(font, Ot.Name.NameID.VersionString, `Version ${naming.version}`);
|
|
const majorVersion = semver.major(naming.version);
|
|
const minorVersion = semver.minor(naming.version);
|
|
const patchVersion = semver.patch(naming.version);
|
|
if (minorVersion > 99 || patchVersion > 99) throw new RangeError("Version number overflow");
|
|
font.head.fontRevision = majorVersion + (minorVersion * 10 + patchVersion) / 1000;
|
|
|
|
// Misc
|
|
font.os2.ulCodePageRange1 = 0x2000011f;
|
|
font.os2.ulCodePageRange2 = 0xc4000000;
|
|
font.head.flags = accumulateFlags(
|
|
[Ot.Head.Flags.BaseLineYAt0, true],
|
|
[Ot.Head.Flags.LeftSidebearingAtX0, true],
|
|
[Ot.Head.Flags.InstructionsMayDependOnPointSize, true],
|
|
[Ot.Head.Flags.ForcePpemToBeInteger, true],
|
|
[Ot.Head.Flags.InstructionMayAlterAdvanceWidth, true]
|
|
);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function accumulateFlags(...entries) {
|
|
let s = 0;
|
|
for (const [flag, cond] of entries) {
|
|
if (cond) s |= flag;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
function getStyleLinkedStyles(weight, width, slope) {
|
|
let linkWeight = weight;
|
|
let linkSlope = slope;
|
|
let nameSuffixWeight = 400;
|
|
let nameSuffixWidth = width;
|
|
let nameSuffixSlope = "normal";
|
|
|
|
if (!(linkWeight === 400 || linkWeight == 700)) {
|
|
nameSuffixWeight = linkWeight;
|
|
linkWeight = 400;
|
|
}
|
|
|
|
if (!(linkSlope === "normal" || linkSlope === "italic")) {
|
|
nameSuffixSlope = linkSlope;
|
|
linkSlope = "normal";
|
|
}
|
|
|
|
return {
|
|
style: getStyle(linkWeight, 5, linkSlope),
|
|
familySuffix: getStyle(nameSuffixWeight, nameSuffixWidth, nameSuffixSlope),
|
|
familySuffixShort: getShortStyle(nameSuffixWeight, nameSuffixWidth, nameSuffixSlope)
|
|
};
|
|
}
|
|
|
|
function nameFont(font, nameID, str) {
|
|
nameFontImpl(font.name.records, 1, 0, 0, nameID, Buffer.from(str, "utf-8")); // Mac Roman
|
|
nameFontImpl(font.name.records, 3, 1, 1033, nameID, str); // Windows Unicode English
|
|
}
|
|
function nameFontImpl(records, platformID, encodingID, languageID, nameID, value) {
|
|
for (let record of records) {
|
|
if (record.platformID !== platformID) continue;
|
|
if (record.encodingID !== encodingID) continue;
|
|
if (record.languageID !== languageID) continue;
|
|
if (record.nameID !== nameID) continue;
|
|
record.value = value;
|
|
return;
|
|
}
|
|
records.push({ platformID, encodingID, languageID, nameID, value });
|
|
}
|
|
|
|
function getStyle(weight, width, slope) {
|
|
const weightPart = weightToMenuStyleMap[weight] ?? "W" + weight;
|
|
const widthPart = widthToMenuStyleMap[width] ?? "Wd" + width;
|
|
const slopePart = slopeToMenuStyleMap[slope] ?? "";
|
|
const rawName = weightPart + " " + widthPart + " " + slopePart;
|
|
return rawName.replace(/ +/g, " ").trim() || "Regular";
|
|
}
|
|
function getShortStyle(weight, width, slope) {
|
|
const weightPart = weightToMenuStyleMapShort[weight] ?? "W" + weight;
|
|
const widthPart = widthToMenuStyleMapShort[width] ?? "Wd" + width;
|
|
const slopePart = slopeToMenuStyleMapShort[slope] ?? "";
|
|
const rawName = weightPart + " " + widthPart + " " + slopePart;
|
|
return rawName.replace(/ +/g, " ").trim() || "Regular";
|
|
}
|
|
|
|
const weightToMenuStyleMap = {
|
|
100: "Thin",
|
|
200: "Extralight",
|
|
300: "Light",
|
|
350: "SemiLight",
|
|
400: "",
|
|
450: "Book",
|
|
500: "Medium",
|
|
600: "Semibold",
|
|
700: "Bold",
|
|
800: "Extrabold",
|
|
900: "Heavy"
|
|
};
|
|
const widthToMenuStyleMap = {
|
|
1: "Ultra-Condensed",
|
|
2: "Extra-Condensed",
|
|
3: "Condensed",
|
|
4: "Semi-Condensed",
|
|
5: "",
|
|
6: "Semi-Extended",
|
|
7: "Extended",
|
|
8: "Extra-Extended",
|
|
9: "Ultra-Extended"
|
|
};
|
|
const slopeToMenuStyleMap = {
|
|
normal: "",
|
|
italic: "Italic",
|
|
oblique: "Oblique"
|
|
};
|
|
const weightToMenuStyleMapShort = {
|
|
100: "Th",
|
|
200: "XLt",
|
|
300: "Lt",
|
|
350: "SmLt",
|
|
400: "",
|
|
450: "Bk",
|
|
500: "Md",
|
|
600: "SmBd",
|
|
700: "Bd",
|
|
800: "XBd",
|
|
900: "Hv"
|
|
};
|
|
const widthToMenuStyleMapShort = {
|
|
1: "UltCn",
|
|
2: "XCn",
|
|
3: "Cn",
|
|
4: "SmCn",
|
|
5: "",
|
|
6: "SmEx",
|
|
7: "Ex",
|
|
8: "XEx",
|
|
9: "UltEx"
|
|
};
|
|
const slopeToMenuStyleMapShort = {
|
|
normal: "",
|
|
italic: "It",
|
|
oblique: "Obl"
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function ancNameEntry(input) {
|
|
return input.replace(/\{\{currentYear\}\}/g, () => String(new Date().getFullYear()));
|
|
}
|