@zsnout/ithkuil
Version:
A set of tools which can generate and parse romanized Ithkuil text and which can generate Ithkuil script from text and JSON data.
388 lines (387 loc) • 14.3 kB
JavaScript
import { getIntegerCs } from "../../data/affixes-map.js";
import { getIntegerCr } from "../../data/roots-map.js";
import { ALL_ASPECTS, ALL_CASES, ALL_CASES_SKIPPING_DEGREE_8, ALL_CASE_SCOPES, ALL_EFFECTS, ALL_ILLOCUTIONS, ALL_LEVELS, ALL_MOODS, ALL_PHASES, ALL_VALENCES, ALL_VALIDATIONS, ALL_VALIDATIONS_DIACRITIC_ORDER, AP1, AP2, AP3, AP4, EFE, IVL, LVL, MCS, PHS, VAL, caToIthkuil, deepFreeze, has, referentListToPersonalReferenceRoot, toAffix, } from "../../generate/index.js";
import { AnchorX, Primary, Row, Secondary, Tertiary, textToSecondaries, } from "../index.js";
import { numericAdjunctToNumerals, numericCxToSecondaries, } from "../numerals/from-number.js";
import { QUATERNARY_DIACRITIC_MAP, Quaternary, } from "../quaternary/index.js";
const isArray = /* @__PURE__ */ (() => Array.isArray)();
/** An object mapping affix degrees to their corresponding diacritics. */
export const AFFIX_DEGREES = /* @__PURE__ */ deepFreeze(
/* @__PURE__ */ Object.assign([
"CURVE_TO_RIGHT",
"DOT",
"HORIZ_WITH_BOTTOM_LINE",
"VERT_WITH_RIGHT_LINE",
"CURVE_TO_TOP",
"DIAG_BAR",
"CURVE_TO_BOTTOM",
"VERT_WITH_LEFT_LINE",
"HORIZ_WITH_TOP_LINE",
"HORIZ_BAR",
], { ca: "CURVE_TO_LEFT" }));
/**
* Converts an affix into script.
*
* @param cs The Cs of the affix to be converted.
* @param degree The degree of the affix.
* @param type The type of the affix.
* @param slot The slot the affix is placed in.
* @param handwritten Whether the outputted characters should be handwritten.
* @returns An array of `ConstructableCharacter`s.
*/
export function affixToScript(cs, degree, type, slot, handwritten) {
return ((typeof cs == "number" || typeof cs == "bigint" ?
numericCxToSecondaries(BigInt(cs), getIntegerCs, handwritten)
: textToSecondaries(cs, {
forcePlaceholderCharacters: true,
handwritten,
}).map((secondary) => attachConstructor(secondary, Secondary)))?.map((secondary, index) => index == 0 ?
{
...secondary,
rotated: slot == "vii",
superposed: type == 2 ? "DOT"
: type == 3 ? "HORIZ_BAR"
: undefined,
right: slot == "xi" ? "DOT" : undefined,
underposed: AFFIX_DEGREES[degree],
}
: secondary) ||
numericAdjunctToNumerals(cs, handwritten).map((x, index) => index == 0 ?
{
...x,
// rotated: slot == "vii",
superposed: type == 2 ? "DOT"
: type == 3 ? "HORIZ_BAR"
: undefined,
right: slot == "xi" ? "DOT" : undefined,
underposed: AFFIX_DEGREES[degree],
}
: x));
}
/**
* Attatches a constructor to a character.
*
* @param character The character to attach a constructor to.
* @param construct The constructor for the character.
* @returns The original `character`, updated to have a `construct` property.
*/
export function attachConstructor(character, construct) {
// @ts-ignore
;
character.construct = construct;
// @ts-ignore
return character;
}
/**
* Converts a formative into script.
*
* @param formative The formative to be converted.
* @param options Options that modify how a formative is rendered.
* @returns `ConstructableCharacter`s containing data from which the script can
* be constructed.
*/
export function formativeToScript(formative, options) {
const handwritten = !!options.handwritten;
/**
* Formative layout:
*
* 1. Primary
* 2. Secondary root
* 3. Slot V affixes
* 4. Slot VII affixes
* 5. Slot XI affixes
* 6. Tertiaries
* 7. Quaternaries
*/
const head = [];
head.push({
construct: Primary,
specification: formative.specification,
context: formative.context,
bottom: formative.type == "UNF/C" ?
formative.concatenationType == "none" ?
undefined
: formative.concatenationType
: formative.type,
affiliation: formative.ca?.affiliation,
configuration: formative.ca?.configuration,
extension: formative.ca?.extension,
perspective: formative.ca?.perspective,
essence: formative.ca?.essence,
function: formative.function,
version: formative.version,
stem: formative.stem,
handwritten,
});
let initialCrRoot;
if (typeof formative.root == "number" || typeof formative.root == "bigint") {
head.push(...numericAdjunctToNumerals(formative.root, handwritten));
}
else if (isArray(formative.root)) {
const referents = textToSecondaries(referentListToPersonalReferenceRoot(formative.root), { handwritten, forcePlaceholderCharacters: true }).map((secondary, index) => attachConstructor({
...secondary,
rotated: true,
superposed: index == 0 ? "DOT" : undefined,
}, Secondary));
head.push(...referents);
}
else if (typeof formative.root == "object") {
const { cs, degree } = formative.root;
const affix = (typeof cs == "number" || typeof cs == "bigint" ?
numericCxToSecondaries(BigInt(cs), getIntegerCr, handwritten)
: textToSecondaries(cs, {
handwritten,
forcePlaceholderCharacters: true,
}).map((x) => attachConstructor(x, Secondary)))?.map((secondary, index) => attachConstructor({
...secondary,
rotated: true,
underposed: index == 0 ? AFFIX_DEGREES[degree] : undefined,
}, Secondary)) ||
numericAdjunctToNumerals(BigInt(cs), handwritten).map((numeral, index) => ({
...numeral,
underposed: index == 0 ? AFFIX_DEGREES[degree] : undefined,
}));
head.push(...affix);
}
else {
const root = textToSecondaries(String(formative.root), {
handwritten,
forcePlaceholderCharacters: true,
}).map((secondary) => attachConstructor(secondary, Secondary));
if (options.useCaseIllValDiacritics !== false) {
initialCrRoot = root[0];
}
head.push(...root);
}
// Enough variables? Probably not.
const v = [];
const vii = [];
const xi = [];
const valences = [];
const segments = [];
const relativeLevels = [];
const absoluteLevels = [];
const moods = [];
const caseScopes = [];
const caseOrIvl = [];
const referents = [];
for (const [type, value] of [
["v", formative.slotVAffixes],
["vii", formative.slotVIIAffixes],
[
"vii",
formative.vn && formative.vn != "MNO" ? [toAffix(formative.vn)] : [],
],
[
"vii",
[formative.type == "UNF/K" ? formative.mood : formative.caseScope]
.filter((x) => !!x)
.map(toAffix),
],
[
"vii",
[
formative.type == "UNF/K" ?
formative.illocutionValidation
: formative.case,
]
.filter((x) => !!x)
.map(toAffix),
],
["xi", formative.slotXIAffixes],
]) {
if (!value) {
continue;
}
const group = type == "v" ? v
: type == "xi" ? xi
: vii;
for (const affix of value) {
if (!affix) {
continue;
}
if (affix.ca) {
group.push(affix);
continue;
}
if (affix.referents) {
referents.push(affix);
continue;
}
if (affix.case) {
if (affix.type) {
group.push(affix);
}
else {
caseOrIvl.push(affix.case);
}
continue;
}
if (affix.cs == VAL && affix.degree != 0 && affix.type == 1) {
valences.push(ALL_VALENCES[affix.degree - 1]);
continue;
}
if ((affix.cs == PHS ||
affix.cs == EFE ||
affix.cs == AP1 ||
affix.cs == AP2 ||
affix.cs == AP3 ||
affix.cs == AP4) &&
affix.degree != 0 &&
affix.type == 1) {
segments.push((affix.cs == PHS ? ALL_PHASES
: affix.cs == EFE ? ALL_EFFECTS
: affix.cs == AP1 ? ALL_ASPECTS
: affix.cs == AP2 ? ALL_ASPECTS.slice(9, 18)
: affix.cs == AP3 ? ALL_ASPECTS.slice(18, 27)
: ALL_ASPECTS.slice(27, 36))[(affix.degree - 1) % 9]);
continue;
}
if (affix.cs == LVL && affix.degree != 0 && affix.type != 3) {
;
(affix.type == 2 ? absoluteLevels : relativeLevels).push(ALL_LEVELS[affix.degree - (1 % 9)]);
continue;
}
if (affix.cs == MCS && affix.type == 1) {
if (affix.degree >= 1 && affix.degree <= 5) {
moods.push(ALL_MOODS[affix.degree]);
}
else if (affix.degree == 0) {
caseScopes.push("CCV");
}
else {
caseScopes.push(ALL_CASE_SCOPES[affix.degree - 5]);
}
continue;
}
if (affix.cs == IVL && affix.degree != 0 && affix.type != 3) {
if (affix.type == 1) {
caseOrIvl.push(ALL_ILLOCUTIONS[affix.degree - 1]);
}
else {
caseOrIvl.push(ALL_VALIDATIONS[affix.degree - 1]);
}
continue;
}
group.push(affix);
}
}
const tertiaries = [];
while (valences.length ||
segments.length ||
relativeLevels.length ||
absoluteLevels.length) {
tertiaries.push({
construct: Tertiary,
handwritten,
absoluteLevel: absoluteLevels.shift(),
top: segments.shift(),
valence: valences.shift(),
bottom: segments.shift(),
relativeLevel: relativeLevels.shift(),
});
}
const quaternaries = [];
while (moods.length || caseScopes.length || caseOrIvl.length) {
quaternaries.push({
construct: Quaternary,
handwritten,
caseScope: caseScopes.shift(),
mood: moods.shift(),
value: caseOrIvl.shift(),
});
}
const finalQuaternary = quaternaries[quaternaries.length - 1];
if (initialCrRoot &&
finalQuaternary &&
!finalQuaternary.caseScope &&
!finalQuaternary.mood &&
(formative.type == "UNF/K" ?
(has(ALL_ILLOCUTIONS, finalQuaternary.value) &&
finalQuaternary.value != "ASR") ||
has(ALL_VALIDATIONS, finalQuaternary.value)
: has(ALL_CASES, finalQuaternary.value))) {
quaternaries.pop();
if (has(ALL_ILLOCUTIONS, finalQuaternary.value)) {
initialCrRoot.superposed =
QUATERNARY_DIACRITIC_MAP[ALL_ILLOCUTIONS.indexOf(finalQuaternary.value)];
}
else if (has(ALL_VALIDATIONS, finalQuaternary.value)) {
initialCrRoot.underposed =
QUATERNARY_DIACRITIC_MAP[ALL_VALIDATIONS_DIACRITIC_ORDER.indexOf(finalQuaternary.value)];
}
else {
const index = ALL_CASES_SKIPPING_DEGREE_8.indexOf(finalQuaternary.value);
initialCrRoot.superposed = QUATERNARY_DIACRITIC_MAP[Math.floor(index / 9)];
initialCrRoot.underposed = QUATERNARY_DIACRITIC_MAP[index % 9];
}
}
const affixes = [
[v, "v"],
[vii, "vii"],
[xi, "xi"],
].flatMap(([affixes, type]) => affixes.flatMap((affix) => {
if (affix.ca) {
return affixToScript(caToIthkuil(affix.ca), "ca", 1, type, handwritten);
}
if (affix.case) {
return {
construct: Quaternary,
handwritten,
type: affix.type,
value: affix.case,
isInverse: affix.isInverse,
isSlotVIIAffix: type == "vii",
};
}
return affixToScript(affix.cs, affix.degree, affix.type, type, handwritten);
}));
const referentCharacters = referents.flatMap(({ referents, case: case_, perspective }) => {
if (perspective == "G" || perspective == "N") {
return formativeToScript({
type: "UNF/C",
root: referents,
ca: { perspective },
case: case_,
}, options);
}
return [
{
construct: Quaternary,
value: case_,
handwritten,
},
...textToSecondaries(referentListToPersonalReferenceRoot(referents), {
handwritten,
forcePlaceholderCharacters: true,
}).map((secondary, index) => attachConstructor({ ...secondary, superposed: index == 0 ? "HORIZ_BAR" : undefined }, Secondary)),
];
});
return head.concat(affixes, tertiaries, quaternaries, referentCharacters);
}
/**
* Creates a series of characters from `ConstructableCharacter`s.
*
* @param props Properties defining this character row.
* @returns A series of characters.
*/
export function CharacterRow(props) {
return AnchorX({
at: "l",
children: Row({
...props,
children: props.children.map((character) => {
const node = character.construct(character);
if (character.dimmed) {
if (character.handwritten) {
node.setAttribute("stroke", "#808080");
}
else {
node.setAttribute("fill", "#808080");
}
}
return node;
}),
}),
});
}