volumetric-regimes-book/command-line/js/hyphens/Hyphenopoly_Loader.js
2021-11-26 18:59:40 +01:00

350 lines
10 KiB
JavaScript

/**
* @license Hyphenopoly_Loader 4.12.0 - client side hyphenation
* ©2021 Mathias Nater, Güttingen (mathiasnater at gmail dot com)
* https://github.com/mnater/Hyphenopoly
*
* Released under the MIT license
* http://mnater.github.io/Hyphenopoly/LICENSE
*/
/* globals Hyphenopoly:readonly */
((w, d, H, o) => {
"use strict";
/**
* Shortcut for new Map
* @param {any} init - initialiser for new Map
* @returns {Map}
*/
const mp = (init) => {
return new Map(init);
};
/**
* Sets default properties for an Object
* @param {object} obj - The object to set defaults to
* @param {object} defaults - The defaults to set
* @returns {object}
*/
const setDefaults = (obj, defaults) => {
if (obj) {
o.entries(defaults).forEach(([k, v]) => {
// eslint-disable-next-line security/detect-object-injection
obj[k] = obj[k] || v;
});
return obj;
}
return defaults;
};
const store = sessionStorage;
const scriptName = "Hyphenopoly_Loader.js";
const lcRequire = mp();
const shortcuts = {
"ac": "appendChild",
"ce": "createElement",
"ct": "createTextNode"
};
/**
* Set H.cf (Hyphenopoly.clientFeatures) either by reading out previously
* computed settings from sessionStorage or creating a template object.
* This is in an iife to keep complexity low.
*/
(() => {
if (H.cacheFeatureTests && store.getItem(scriptName)) {
H.cf = JSON.parse(store.getItem(scriptName));
H.cf.langs = mp(H.cf.langs);
} else {
H.cf = {
"langs": mp(),
"pf": false
};
}
})();
/**
* Set H.paths and some H.s (setup) fields to defaults or
* overwrite with user settings.
* These is an iife to keep complexity low.
*/
(() => {
const thisScript = d.currentScript.src;
const maindir = thisScript.slice(0, (thisScript.lastIndexOf("/") + 1));
const patterndir = maindir + "patterns/";
H.paths = setDefaults(H.paths, {
maindir,
patterndir
});
H.s = setDefaults(H.setup, {
"CORScredentials": "include",
"hide": "all",
"selectors": {".hyphenate": {}},
"timeout": 1000
});
// Change mode string to mode int
H.s.hide = ["all", "element", "text"].indexOf(H.s.hide);
})();
/**
* Copy required languages to local lcRequire (lowercaseRequire) and
* eventually fallbacks to local lcFallbacks (lowercaseFallbacks).
* This is in an iife to keep complexity low.
*/
(() => {
const fallbacks = mp(o.entries(H.fallbacks || {}));
o.entries(H.require).forEach(([lang, wo]) => {
lcRequire.set(lang.toLowerCase(), {
"fn": fallbacks.get(lang) || lang,
wo
});
});
})();
/**
* Create deferred Promise
*
* Kudos to http://lea.verou.me/2016/12/resolve-promises-externally-with-
* this-one-weird-trick/
* @return {promise}
*/
const defProm = () => {
let res = null;
let rej = null;
const promise = new Promise((resolve, reject) => {
res = resolve;
rej = reject;
});
promise.resolve = res;
promise.reject = rej;
return promise;
};
let stylesNode = null;
/**
* Define function H.hide.
* This function hides (state = 1) or unhides (state = 0)
* the whole document (mode == 0) or
* each selected element (mode == 1) or
* text of each selected element (mode == 2) or
* nothing (mode == -1)
* @param {integer} state - State
* @param {integer} mode - Mode
*/
H.hide = (state, mode) => {
if (state === 0) {
if (stylesNode) {
stylesNode.remove();
}
} else {
const vis = "{visibility:hidden!important}";
stylesNode = d[shortcuts.ce]("style");
let myStyle = "";
if (mode === 0) {
myStyle = "html" + vis;
} else {
o.keys(H.s.selectors).forEach((sel) => {
if (mode === 1) {
myStyle += sel + vis;
} else {
myStyle += sel + "{color:transparent!important}";
}
});
}
stylesNode[shortcuts.ac](d[shortcuts.ct](myStyle));
d.head[shortcuts.ac](stylesNode);
}
};
const tester = (() => {
let fakeBody = null;
return {
/**
* Append fakeBody with tests to document
* @returns {Object|null} The body element or null, if no tests
*/
"ap": () => {
if (fakeBody) {
d.documentElement[shortcuts.ac](fakeBody);
return fakeBody;
}
return null;
},
/**
* Remove fakeBody
* @returns {undefined}
*/
"cl": () => {
if (fakeBody) {
fakeBody.remove();
}
},
/**
* Create and append div with CSS-hyphenated word
* @param {string} lang Language
* @returns {undefined}
*/
"cr": (lang) => {
if (H.cf.langs.has(lang)) {
return;
}
fakeBody = fakeBody || d[shortcuts.ce]("body");
const testDiv = d[shortcuts.ce]("div");
const ha = "hyphens:auto";
testDiv.lang = lang;
testDiv.style.cssText = `visibility:hidden;-webkit-${ha};-ms-${ha};${ha};width:48px;font-size:12px;line-height:12px;border:none;padding:0;word-wrap:normal`;
testDiv[shortcuts.ac](
d[shortcuts.ct](lcRequire.get(lang).wo.toLowerCase())
);
fakeBody[shortcuts.ac](testDiv);
}
};
})();
/**
* Checks if hyphens (ev.prefixed) is set to auto for the element.
* @param {Object} elm - the element
* @returns {Boolean} result of the check
*/
const checkCSSHyphensSupport = (elmStyle) => {
const h = elmStyle.hyphens ||
elmStyle.webkitHyphens ||
elmStyle.msHyphens;
return (h === "auto");
};
H.res = {
"he": mp()
};
const fw = mp();
/**
* Load hyphenEngines
*
* Make sure each .wasm is loaded exactly once, even for fallbacks
* fw: fetched wasm (maps filename to language)
* he: hyphenEngines (maps lang to wasm and counter)
* c (counter) is needed in Hyphenopoly.js to decide
* if wasm needs to be cloned
* @param {string} lang The language
* @returns {undefined}
*/
const loadhyphenEngine = (lang) => {
const filename = lcRequire.get(lang).fn + ".wasm";
H.cf.pf = true;
H.cf.langs.set(lang, "H9Y");
if (fw.has(filename)) {
const hyphenEngineWrapper = H.res.he.get(fw.get(filename));
hyphenEngineWrapper.c = true;
H.res.he.set(lang, hyphenEngineWrapper);
} else {
H.res.he.set(
lang,
{
"w": w.fetch(H.paths.patterndir + filename, {"credentials": H.s.CORScredentials})
}
);
fw.set(filename, lang);
}
};
lcRequire.forEach((value, lang) => {
if (value.wo === "FORCEHYPHENOPOLY" || H.cf.langs.get(lang) === "H9Y") {
loadhyphenEngine(lang);
} else {
tester.cr(lang);
}
});
const testContainer = tester.ap();
if (testContainer) {
testContainer.querySelectorAll("div").forEach((n) => {
if (checkCSSHyphensSupport(n.style) && n.offsetHeight > 12) {
H.cf.langs.set(n.lang, "CSS");
} else {
loadhyphenEngine(n.lang);
}
});
tester.cl();
}
const he = H.handleEvent;
if (H.cf.pf) {
H.res.DOM = new Promise((res) => {
if (d.readyState === "loading") {
d.addEventListener(
"DOMContentLoaded",
res,
{
"once": true,
"passive": true
}
);
} else {
res();
}
});
const hide = H.s.hide;
if (hide === 0) {
H.hide(1, 0);
}
if (hide !== -1) {
H.timeOutHandler = w.setTimeout(() => {
H.hide(0, null);
// eslint-disable-next-line no-console
console.info(scriptName + " timed out.");
}, H.s.timeout);
}
H.res.DOM.then(() => {
if (hide > 0) {
H.hide(1, hide);
}
});
// Load main script
const script = d[shortcuts.ce]("script");
script.src = H.paths.maindir + "Hyphenopoly.js";
d.head[shortcuts.ac](script);
H.hy6ors = mp();
H.cf.langs.forEach((langDef, lang) => {
if (langDef === "H9Y") {
H.hy6ors.set(lang, defProm());
}
});
H.hy6ors.set("HTML", defProm());
H.hyphenators = new Proxy(H.hy6ors, {
"get": (target, key) => {
return target.get(key);
},
"set": () => {
// Inhibit setting of hyphenators
return true;
}
});
(() => {
if (he && he.polyfill) {
he.polyfill();
}
})();
} else {
(() => {
if (he && he.tearDown) {
he.tearDown();
}
w.Hyphenopoly = null;
})();
}
(() => {
if (H.cacheFeatureTests) {
store.setItem(scriptName, JSON.stringify(
{
"langs": [...H.cf.langs.entries()],
"pf": H.cf.pf
}
));
}
})();
})(window, document, Hyphenopoly, Object);