mirror of
https://github.com/be5invis/Iosevka.git
synced 2026-10-28 00:18:43 +00:00
266 lines
7.9 KiB
TypeScript
266 lines
7.9 KiB
TypeScript
import * as Cv from "../data-import/cv";
|
|
import * as Gr from "../data-import/grades";
|
|
import * as Ligation from "../data-import/ligation";
|
|
|
|
export type CharacterVariantChoice = {
|
|
readonly inherits?: Cv.StylisticSet;
|
|
readonly design?: Cv.VariantComposition;
|
|
readonly upright?: Cv.VariantComposition;
|
|
readonly italic?: Cv.VariantComposition;
|
|
readonly oblique?: Cv.VariantComposition;
|
|
};
|
|
export const DefaultCvChoice: CharacterVariantChoice = { inherits: Cv.NopStylisticSet };
|
|
export type ChoiceResolutionContext = {
|
|
readonly isSlab: boolean;
|
|
readonly slope: Gr.Slope;
|
|
};
|
|
export type Highlights = ReadonlyMap<string, Gr.FeatureAssignment>;
|
|
|
|
export type SlopeKey = "design" | "upright" | "italic" | "oblique";
|
|
export const SlopeKeys: SlopeKey[] = ["design", "upright", "italic", "oblique"];
|
|
|
|
export function variantChoiceIsDefault(uc: CharacterVariantChoice) {
|
|
if (uc.inherits) return false;
|
|
for (const k of SlopeKeys) {
|
|
const dict = uc[k];
|
|
if (dict && Object.keys(dict).length) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function resolveComposition(uc: CharacterVariantChoice, ctx: ChoiceResolutionContext) {
|
|
const variantDefaults = styleSlopePick6(ctx.isSlab, ctx.slope, Cv.CharacterVariantDefaults);
|
|
const ss = uc.inherits || null;
|
|
const compositionInherited = !ss
|
|
? null
|
|
: styleSlopePick6(ctx.isSlab, ctx.slope, ss.composition);
|
|
const resolvedComposition: Cv.VariantComposition = Object.assign(
|
|
{},
|
|
variantDefaults,
|
|
compositionInherited,
|
|
uc.design,
|
|
slopePick3ABC(ctx.slope, uc.upright, uc.italic, uc.oblique),
|
|
);
|
|
return { variantDefaults, ss, compositionInherited, resolvedComposition };
|
|
}
|
|
|
|
export function deriveHotChars(uc: CharacterVariantChoice, ctx: ChoiceResolutionContext) {
|
|
const { variantDefaults, resolvedComposition } = resolveComposition(uc, ctx);
|
|
|
|
const hotChars = new Map<string, Gr.FeatureAssignment>();
|
|
for (const k in resolvedComposition) {
|
|
if (resolvedComposition[k] === variantDefaults[k]) continue;
|
|
const prime = Cv.CharacterVariants.get(k);
|
|
if (!prime?.tag) continue;
|
|
const variant = prime.variants.get(resolvedComposition[k]);
|
|
if (!variant) continue;
|
|
for (const hc of prime.hotChars) {
|
|
hotChars.set(hc, { ...hotChars.get(hc), [prime.tag]: variant.rank || 0 });
|
|
}
|
|
}
|
|
|
|
return hotChars;
|
|
}
|
|
|
|
export function resolveUserChoice(uc: CharacterVariantChoice, ctx: ChoiceResolutionContext) {
|
|
const { variantDefaults, ss, compositionInherited, resolvedComposition } = resolveComposition(
|
|
uc,
|
|
ctx,
|
|
);
|
|
const resolvedCompositionInherited: Cv.VariantComposition = Object.assign(
|
|
{},
|
|
variantDefaults,
|
|
ss?.rank ? compositionInherited : null,
|
|
);
|
|
const resolvedCompositionNS: Cv.VariantComposition = Object.assign(
|
|
{},
|
|
variantDefaults,
|
|
compositionInherited,
|
|
uc.design,
|
|
);
|
|
|
|
const resolvedNonDefaultComposition: Cv.VariantCompositionW = {};
|
|
const hotChars = new Map<string, Gr.FeatureAssignment>();
|
|
const featureAssignment: Gr.FeatureAssignmentW = {};
|
|
const nonDefaultFeatureAssignment: Gr.FeatureAssignmentW = {};
|
|
const userFriendlyFeatureAssignment: Gr.FeatureAssignmentW = {};
|
|
if (ss?.rank) userFriendlyFeatureAssignment[ss.tag] = 1;
|
|
|
|
for (const k in resolvedComposition) {
|
|
const prime = Cv.CharacterVariants.get(k);
|
|
if (!prime?.tag) continue;
|
|
const variant = prime.variants.get(resolvedComposition[k]);
|
|
if (!variant) continue;
|
|
|
|
featureAssignment[prime.tag] = variant.rank || 0;
|
|
if (resolvedComposition[k] !== variantDefaults[k]) {
|
|
resolvedNonDefaultComposition[k] = resolvedComposition[k];
|
|
nonDefaultFeatureAssignment[prime.tag] = variant.rank || 0;
|
|
for (const hc of prime.hotChars) {
|
|
hotChars.set(hc, { ...hotChars.get(hc), [prime.tag]: variant.rank || 0 });
|
|
}
|
|
}
|
|
if (resolvedComposition[k] !== resolvedCompositionInherited[k]) {
|
|
userFriendlyFeatureAssignment[prime.tag] = variant.rank || 0;
|
|
}
|
|
}
|
|
|
|
const stepHotChars: Map<string, Gr.FeatureAssignment> = new Map(hotChars);
|
|
for (const k in resolvedCompositionNS) {
|
|
if (resolvedCompositionNS[k] !== resolvedCompositionInherited[k]) {
|
|
const prime = Cv.CharacterVariants.get(k);
|
|
if (!prime?.tag) continue;
|
|
const variant = prime.variants.get(resolvedComposition[k]);
|
|
if (!variant) continue;
|
|
for (const hc of prime.hotChars) {
|
|
stepHotChars.set(hc, { ...stepHotChars.get(hc), [prime.tag]: variant.rank || 0 });
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
resolvedComposition,
|
|
resolvedNonDefaultComposition,
|
|
featureAssignment,
|
|
nonDefaultFeatureAssignment,
|
|
userFriendlyFeatureAssignment,
|
|
hotChars,
|
|
stepHotChars: stepHotChars,
|
|
};
|
|
}
|
|
|
|
export function cvSort(subject: Cv.VariantComposition): Cv.VariantComposition {
|
|
const out: Cv.VariantCompositionW = {};
|
|
for (const [k, _] of Cv.CharacterVariants) {
|
|
if (subject[k]) out[k] = subject[k];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function getCompositionOverride(
|
|
subject: Cv.VariantComposition,
|
|
defaultComp: Cv.VariantComposition,
|
|
): null | Cv.VariantComposition {
|
|
let empty = true;
|
|
const out: Cv.VariantCompositionW = {};
|
|
|
|
for (const k in subject) {
|
|
if (subject[k] !== defaultComp[k]) {
|
|
out[k] = subject[k];
|
|
empty = false;
|
|
}
|
|
}
|
|
if (empty) {
|
|
return null;
|
|
} else {
|
|
return cvSort(out);
|
|
}
|
|
}
|
|
|
|
function styleSlopePick6<T>(fSlab: boolean, slope: Gr.Slope, ss: Cv.PerStyleSlope<T>) {
|
|
if (fSlab) return slopePick3(slope, ss.slab);
|
|
else return slopePick3(slope, ss.sans);
|
|
}
|
|
function slopePick3<T>(slope: Gr.Slope, a: Cv.PerSlope<T>): T {
|
|
switch (slope) {
|
|
case Gr.Slope.Upright:
|
|
return a.upright;
|
|
case Gr.Slope.Italic:
|
|
return a.italic;
|
|
case Gr.Slope.Oblique:
|
|
return a.oblique;
|
|
default:
|
|
return a.upright;
|
|
}
|
|
}
|
|
function slopePick3ABC<T>(slope: Gr.Slope, a: T, b: T, c: T): T {
|
|
switch (slope) {
|
|
case Gr.Slope.Upright:
|
|
return a;
|
|
case Gr.Slope.Italic:
|
|
return b;
|
|
case Gr.Slope.Oblique:
|
|
return c;
|
|
default:
|
|
return a;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
export type DeliverableCustomizerProps = {
|
|
readonly serifStyle: Gr.SerifStyle;
|
|
readonly charVariants: CharacterVariantChoice;
|
|
readonly ligationSet: Ligation.LigationSet;
|
|
};
|
|
|
|
export function createCustomizerPropsFromUrl<T extends DeliverableCustomizerProps>(st0: T): T {
|
|
let st = { ...st0 };
|
|
if (!globalThis?.location) return st;
|
|
const urlParams = new URLSearchParams(globalThis.location.search);
|
|
|
|
if (urlParams.get("slab")) {
|
|
st = { ...st, serifStyle: Gr.SerifStyle.Slab };
|
|
}
|
|
|
|
if (urlParams.get("ss-inherits")) {
|
|
const ss = urlParams.get("ss-inherits");
|
|
if (ss && Cv.StylisticSets.has(ss))
|
|
st = { ...st, charVariants: { inherits: Cv.StylisticSets.get(ss) } };
|
|
}
|
|
|
|
const designOverride: { [k: string]: string } = {};
|
|
for (const [k0, v] of urlParams) {
|
|
if (k0.slice(0, 3) !== "cv-") continue;
|
|
const k = k0.slice(3);
|
|
if (Cv.CharacterVariants.get(k)) designOverride[k] = v;
|
|
}
|
|
if (Object.keys(designOverride).length) {
|
|
st = { ...st, charVariants: { ...st.charVariants, design: designOverride } };
|
|
}
|
|
|
|
if (urlParams.get("ligation-off") === "true") {
|
|
st = { ...st, ligationSet: Ligation.AvailableLigationSets[0] };
|
|
} else {
|
|
const lsUser = urlParams.get("ligations");
|
|
for (const ls of Ligation.AvailableLigationSets) {
|
|
if (lsUser && ls.selector && ls.selector === lsUser) st = { ...st, ligationSet: ls };
|
|
}
|
|
}
|
|
|
|
setTimeout(() => {
|
|
globalThis.history.pushState(
|
|
null,
|
|
"Iosevka Customizer",
|
|
globalThis.location.origin + globalThis.location.pathname,
|
|
);
|
|
}, 0);
|
|
|
|
return st;
|
|
}
|
|
|
|
export function createUrlFromCustomizerProps<T extends DeliverableCustomizerProps>(st: T) {
|
|
let s = "";
|
|
if (st.serifStyle === Gr.SerifStyle.Slab) {
|
|
s += `${s ? "&" : ""}slab=1`;
|
|
}
|
|
if (st.charVariants.inherits && st.charVariants.inherits !== Cv.NopStylisticSet) {
|
|
s += `${s ? "&" : ""}ss-inherits=${st.charVariants.inherits.key}`;
|
|
}
|
|
if (st.charVariants.design) {
|
|
for (const key in st.charVariants.design) {
|
|
s +=
|
|
(s ? "&" : "") +
|
|
"cv-" +
|
|
key +
|
|
"=" +
|
|
encodeURIComponent(st.charVariants.design[key]);
|
|
}
|
|
}
|
|
if (st.ligationSet.selector) {
|
|
s += `${s ? "&" : ""}ligations=${st.ligationSet.selector}`;
|
|
} else {
|
|
s += `${s ? "&" : ""}ligation-off=true`;
|
|
}
|
|
return s;
|
|
}
|