123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777 |
- /**
- * @license Hyphenopoly_Loader 3.1.1 - client side hyphenation
- * ©2019 Mathias Nater, Zürich (mathiasnater at gmail dot com)
- * https://github.com/mnater/Hyphenopoly
- *
- * Released under the MIT license
- * http://mnater.github.io/Hyphenopoly/LICENSE
- */
- /**
- * Wrap all code in an iife to keep a scope. Important objects are parameters
- * of this iife to keep codesize low.
- * @param {Object} w shorthand for window
- * @param {Object} d shorthand for document
- * @param {Object} H shorthand for Hyphenopoly
- * @param {Object} o shorthand for object
- */
- (function H9YL(w, d, H, o) {
- "use strict";
- const store = sessionStorage;
- const wa = w.WebAssembly;
- const lcFallbacks = new Map();
- const lcRequire = new Map();
- /**
- * Create Object without standard Object-prototype
- * @returns {Object} empty object
- */
- function empty() {
- return o.create(null);
- }
- /**
- * Shorthand for Object.keys(obj).forEach(function () {})
- * @param {Object} obj the object to iterate
- * @param {function} fn the function to execute
- * @returns {undefined}
- */
- function eachKey(obj, fn) {
- o.keys(obj).forEach(fn);
- }
- /**
- * Set H.cf (Hyphenopoly.clientFeatures) either by reading out previously
- * computed settings from sessionStorage or creating an template object.
- * This is in an iife to keep complexity low.
- */
- (function configFeat() {
- if (H.cacheFeatureTests && store.getItem("Hyphenopoly_Loader")) {
- H.cf = JSON.parse(store.getItem("Hyphenopoly_Loader"));
- } else {
- H.cf = {
- "langs": empty(),
- "polyfill": false,
- "wasm": null
- };
- }
- }());
- /**
- * Set H.paths defaults or overwrite with user settings.
- * This is in an iife to keep complexity low.
- */
- (function configPaths() {
- const maindir = (d.currentScript)
- ? d.currentScript.src.replace(/Hyphenopoly_Loader.js/i, "")
- : "../";
- const patterndir = maindir + "patterns/";
- if (H.paths) {
- H.paths.maindir = H.paths.maindir || maindir;
- H.paths.patterndir = H.paths.patterndir || patterndir;
- } else {
- H.paths = o.create({
- "maindir": maindir,
- "patterndir": patterndir
- });
- }
- }());
- /**
- * Set some H.setup fields to defaults or overwrite with user settings.
- * This is in an iife to keep complexity low.
- */
- (function configSetup() {
- if (H.setup) {
- H.setup.selectors = H.setup.selectors || {".hyphenate": {}};
- H.setup.timeout = H.setup.timeout || 1000;
- H.setup.hide = H.setup.hide || "all";
- } else {
- H.setup = {
- "hide": "all",
- "selectors": {".hyphenate": {}},
- "timeout": 1000
- };
- }
- }());
- /**
- * Copy required languages to local lcRequire and
- * eventually fallbacks to local lcFallbacks.
- * This is in an iife to keep complexity low.
- */
- (function configRequire() {
- eachKey(H.require, function copyRequire(k) {
- // eslint-disable-next-line security/detect-object-injection
- lcRequire.set(k.toLowerCase(), H.require[k]);
- });
- if (H.fallbacks) {
- eachKey(H.fallbacks, function copyFallbacks(k) {
- lcFallbacks.set(
- k.toLowerCase(),
- // eslint-disable-next-line security/detect-object-injection
- H.fallbacks[k].toLowerCase()
- );
- });
- }
- }());
- /**
- * Define function H.toggle.
- * This function hides or unhides (depending of the parameter state)
- * the whole document (H.setup.hide == "all") or
- * each selected element (H.setup.hide == "element") or
- * text of each selected element (H.setup.hide == "text")
- * @param {string} state State: either on (visible) or off (hidden)
- */
- H.toggle = function toggle(state) {
- if (state === "on") {
- const stylesNode = d.getElementById("H9Y_Styles");
- if (stylesNode) {
- stylesNode.parentNode.removeChild(stylesNode);
- }
- } else {
- const vis = " {visibility: hidden !important}\n";
- const sc = d.createElement("style");
- let myStyle = "";
- sc.id = "H9Y_Styles";
- switch (H.setup.hide) {
- case "all":
- myStyle = "html" + vis;
- break;
- case "element":
- eachKey(H.setup.selectors, function eachSelector(sel) {
- myStyle += sel + vis;
- });
- break;
- case "text":
- eachKey(H.setup.selectors, function eachSelector(sel) {
- myStyle += sel + " {color: transparent !important}\n";
- });
- break;
- default:
- myStyle = "";
- }
- sc.appendChild(d.createTextNode(myStyle));
- d.head.appendChild(sc);
- }
- };
- /**
- * Setup basic event system. Some events are defined but the definition of
- * what happens when they are triggered is deferred to Hyphenopoly.js
- * This is in an iife to keep complexity low.
- */
- (function setupEvents() {
- // Events known to the system
- const definedEvents = new Map();
- // Default events, execution deferred to Hyphenopoly.js
- const deferred = [];
- /*
- * Eegister for custom event handlers, where event is not yet defined
- * these events will be correctly registered in Hyphenopoly.js
- */
- const tempRegister = [];
- /**
- * Create Event Object
- * @param {string} name The Name of the event
- * @param {function} defFunc The default method of the event
- * @param {boolean} cancellable Is the default cancellable
- * @returns {undefined}
- */
- function define(name, defFunc, cancellable) {
- definedEvents.set(name, {
- "cancellable": cancellable,
- "default": defFunc,
- "register": []
- });
- }
- define(
- "timeout",
- function def(e) {
- H.toggle("on");
- w.console.info(
- "Hyphenopolys 'FOUHC'-prevention timed out after %dms",
- e.delay
- );
- },
- false
- );
- define(
- "error",
- function def(e) {
- switch (e.lvl) {
- case "info":
- w.console.info(e.msg);
- break;
- case "warn":
- w.console.warn(e.msg);
- break;
- default:
- w.console.error(e.msg);
- }
- },
- true
- );
- define(
- "contentLoaded",
- function def(e) {
- deferred.push({
- "data": e,
- "name": "contentLoaded"
- });
- },
- false
- );
- define(
- "engineLoaded",
- function def(e) {
- deferred.push({
- "data": e,
- "name": "engineLoaded"
- });
- },
- false
- );
- define(
- "hpbLoaded",
- function def(e) {
- deferred.push({
- "data": e,
- "name": "hpbLoaded"
- });
- },
- false
- );
- define(
- "loadError",
- function def(e) {
- deferred.push({
- "data": e,
- "name": "loadError"
- });
- },
- false
- );
- define(
- "tearDown",
- null,
- true
- );
- /**
- * Dispatch event <name> with arguments <data>
- * @param {string} name The name of the event
- * @param {Object|undefined} data Data of the event
- * @returns {undefined}
- */
- function dispatch(name, data) {
- data = data || empty();
- let defaultPrevented = false;
- definedEvents.get(name).register.forEach(
- function call(currentHandler) {
- data.preventDefault = function preventDefault() {
- if (definedEvents.get(name).cancellable) {
- defaultPrevented = true;
- }
- };
- currentHandler(data);
- }
- );
- if (
- !defaultPrevented &&
- definedEvents.get(name).default
- ) {
- definedEvents.get(name).default(data);
- }
- }
- /**
- * Add EventListender <handler> to event <name>
- * @param {string} name The name of the event
- * @param {function} handler Function to register
- * @param {boolean} defer If the registration is deferred
- * @returns {undefined}
- */
- function addListener(name, handler, defer) {
- if (definedEvents.has(name)) {
- definedEvents.get(name).register.push(handler);
- } else if (defer) {
- tempRegister.push({
- "handler": handler,
- "name": name
- });
- } else {
- H.events.dispatch(
- "error",
- {
- "lvl": "warn",
- "msg": "unknown Event \"" + name + "\" discarded"
- }
- );
- }
- }
- if (H.handleEvent) {
- eachKey(H.handleEvent, function add(name) {
- /* eslint-disable security/detect-object-injection */
- addListener(name, H.handleEvent[name], true);
- /* eslint-enable security/detect-object-injection */
- });
- }
- H.events = empty();
- H.events.deferred = deferred;
- H.events.tempRegister = tempRegister;
- H.events.dispatch = dispatch;
- H.events.define = define;
- H.events.addListener = addListener;
- }());
- /**
- * Feature test for wasm.
- * @returns {boolean} support
- */
- function runWasmTest() {
- /*
- * Wasm feature test with iOS bug detection
- * (https://bugs.webkit.org/show_bug.cgi?id=181781)
- */
- if (
- typeof wa === "object" &&
- typeof wa.Instance === "function"
- ) {
- /* eslint-disable array-element-newline */
- const module = new wa.Module(Uint8Array.from([
- 0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127,
- 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 8, 1, 4, 116, 101, 115,
- 116, 0, 0, 10, 16, 1, 14, 0, 32, 0, 65, 1, 54, 2, 0, 32,
- 0, 40, 2, 0, 11
- ]));
- /* eslint-enable array-element-newline */
- return (new wa.Instance(module).exports.test(4) !== 0);
- }
- return false;
- }
- /**
- * Load script by adding <script>-tag
- * @param {string} path Where the script is stored
- * @param {string} filename Filename of the script
- * @returns {undefined}
- */
- function loadScript(path, filename) {
- const script = d.createElement("script");
- script.src = path + filename;
- if (filename === "hyphenEngine.asm.js") {
- script.addEventListener("load", function listener() {
- H.events.dispatch("engineLoaded", {"msg": "asm"});
- });
- }
- d.head.appendChild(script);
- }
- const loadedBins = new Map();
- /**
- * Load binary files either with fetch (on new browsers that support wasm)
- * or with xmlHttpRequest
- * @param {string} path Where the script is stored
- * @param {string} fne Filename of the script with extension
- * @param {string} name Name of the ressource
- * @param {Object} msg Message
- * @returns {undefined}
- */
- function loadBinary(path, fne, name, msg) {
- /**
- * Get bin file using fetch
- * @param {string} p Where the script is stored
- * @param {string} f Filename of the script with extension
- * @param {string} n Name of the ressource
- * @param {Object} m Message
- * @returns {undefined}
- */
- function fetchBinary(p, f, n, m) {
- w.fetch(p + f).then(
- function resolve(response) {
- if (response.ok) {
- if (n === "hyphenEngine") {
- H.bins.set(n, response.arrayBuffer().then(
- function getModule(buf) {
- return new wa.Module(buf);
- }
- ));
- H.events.dispatch("engineLoaded", {"msg": m});
- } else {
- const files = loadedBins.get(f);
- files.forEach(function eachHpb(rn) {
- H.bins.set(
- rn,
- (files.length > 1)
- ? response.clone().arrayBuffer()
- : response.arrayBuffer()
- );
- H.events.dispatch(
- "hpbLoaded",
- {"msg": rn}
- );
- });
- }
- } else {
- H.events.dispatch("loadError", {
- "file": f,
- "msg": m,
- "name": n,
- "path": p
- });
- }
- }
- );
- }
- /**
- * Get bin file using XHR
- * @param {string} p Where the script is stored
- * @param {string} f Filename of the script with extension
- * @param {string} n Name of the ressource
- * @param {Object} m Message
- * @returns {undefined}
- */
- function requestBinary(p, f, n, m) {
- /* eslint-disable-next-line no-negated-condition */
- const xhr = new XMLHttpRequest();
- xhr.onload = function onload() {
- if (xhr.status === 200) {
- loadedBins.get(f).
- forEach(function eachHpb(rn) {
- H.bins.set(
- rn,
- xhr.response
- );
- H.events.dispatch(
- "hpbLoaded",
- {"msg": rn}
- );
- });
- } else {
- H.events.dispatch("loadError", {
- "file": f,
- "msg": m,
- "name": n,
- "path": p
- });
- }
- };
- xhr.open("GET", p + f);
- xhr.responseType = "arraybuffer";
- xhr.send();
- }
- if (!loadedBins.has(fne)) {
- loadedBins.set(fne, [msg]);
- if (H.cf.wasm) {
- fetchBinary(path, fne, name, msg);
- } else {
- requestBinary(path, fne, name, msg);
- }
- } else if (name !== "hyphenEngine") {
- loadedBins.get(fne).push(msg);
- }
- }
- /**
- * Pre-Allocate memory for (w)asm
- * Default is 32 wasm Pages (). For languages with larger .hpb
- * files a higher value is needed.
- * Get the value from baseData.heapSize in Hyphenopoly.js
- * @param {string} lang Language
- * @returns {undefined}
- */
- function allocateMemory(lang) {
- const specVal = new Map(
- [["de", 54], ["hu", 205], ["nb-no", 91], ["nl", 41]]
- );
- const wasmPages = specVal.get(lang) || 32;
- H.specMems = H.specMems || new Map();
- if (H.cf.wasm) {
- H.specMems.set(lang, new wa.Memory({
- "initial": wasmPages,
- "maximum": 256
- }));
- } else {
- /* eslint-disable no-bitwise */
- const asmPages = (2 << Math.floor(
- Math.log(wasmPages) * Math.LOG2E
- )) << 16;
- /* eslint-enable no-bitwise */
- H.specMems.set(lang, new ArrayBuffer(asmPages));
- }
- }
- (function testClientFeatures() {
- const tester = (function tester() {
- let fakeBody = null;
- /* eslint-disable array-element-newline */
- const css = [
- "visibility:hidden;",
- "-moz-hyphens:auto;",
- "-webkit-hyphens:auto;",
- "-ms-hyphens:auto;",
- "hyphens:auto;",
- "width:48px;",
- "font-size:12px;",
- "line-height:12px;",
- "border:none;",
- "padding:0;",
- "word-wrap:normal"
- ].join("");
- /* eslint-enable array-element-newline */
- /**
- * Create and append div with CSS-hyphenated word
- * @param {string} lang Language
- * @returns {undefined}
- */
- function create(lang) {
- /* eslint-disable security/detect-object-injection */
- if (H.cf.langs[lang]) {
- return;
- }
- /* eslint-enable security/detect-object-injection */
- fakeBody = fakeBody || d.createElement("body");
- const testDiv = d.createElement("div");
- testDiv.lang = lang;
- testDiv.style.cssText = css;
- testDiv.appendChild(d.createTextNode(lcRequire.get(lang)));
- fakeBody.appendChild(testDiv);
- }
- /**
- * Append fakeBody with tests to target (document)
- * @param {Object} target Where to append fakeBody
- * @returns {Object|null} The body element or null, if no tests
- */
- function append(target) {
- if (fakeBody) {
- target.appendChild(fakeBody);
- return fakeBody;
- }
- return null;
- }
- /**
- * Remove fakeBody
- * @returns {undefined}
- */
- function clear() {
- if (fakeBody) {
- fakeBody.parentNode.removeChild(fakeBody);
- }
- }
- return {
- "append": append,
- "clear": clear,
- "create": create
- };
- }());
- /**
- * Checks if hyphens (ev.prefixed) is set to auto for the element.
- * @param {Object} elm - the element
- * @returns {Boolean} result of the check
- */
- function checkCSSHyphensSupport(elm) {
- return (
- elm.style.hyphens === "auto" ||
- elm.style.webkitHyphens === "auto" ||
- elm.style.msHyphens === "auto" ||
- elm.style["-moz-hyphens"] === "auto"
- );
- }
- /**
- * Expose the hyphenate-function of a specific language to
- * Hyphenopoly.hyphenators.<language>
- *
- * Hyphenopoly.hyphenators.<language> is a Promise that fullfills
- * to hyphenate(entity, sel) as soon as the ressources are loaded
- * and the engine is ready.
- * If Promises aren't supported (e.g. IE11) a error message is produced.
- *
- * @param {string} lang - the language
- * @returns {undefined}
- */
- function exposeHyphenateFunction(lang) {
- /* eslint-disable security/detect-object-injection */
- H.hyphenators = H.hyphenators || empty();
- if (!H.hyphenators[lang]) {
- if (w.Promise) {
- H.hyphenators[lang] = new Promise(function pro(rs, rj) {
- H.events.addListener("engineReady", function handler(e) {
- if (e.msg === lang) {
- rs(H.createHyphenator(e.msg));
- }
- }, true);
- H.events.addListener("loadError", function handler(e) {
- if (e.name === lang || e.name === "hyphenEngine") {
- rj(new Error("File " + e.file + " can't be loaded from " + e.path));
- }
- }, false);
- });
- H.hyphenators[lang].catch(function catchPromiseError(e) {
- H.events.dispatch(
- "error",
- {
- "lvl": "error",
- "msg": e.message
- }
- );
- });
- } else {
- H.hyphenators[lang] = {
- /**
- * Fires an error message, if then is called
- * @returns {undefined}
- */
- "then": function () {
- H.events.dispatch(
- "error",
- {"msg": "Promises not supported in this engine. Use a polyfill."}
- );
- }
- };
- }
- }
- /* eslint-enable security/detect-object-injection */
- }
- /**
- * Load .hpb files
- * @param {string} lang The language
- * @returns {undefined}
- */
- function loadPattern(lang) {
- let filename = lang + ".hpb";
- let langFallback = lang;
- H.cf.polyfill = true;
- // eslint-disable-next-line security/detect-object-injection
- H.cf.langs[lang] = "H9Y";
- if (lcFallbacks && lcFallbacks.has(lang)) {
- langFallback = lcFallbacks.get(lang);
- filename = langFallback + ".hpb";
- }
- H.bins = H.bins || new Map();
- loadBinary(H.paths.patterndir, filename, langFallback, lang);
- }
- if (H.cf.wasm === null) {
- H.cf.wasm = runWasmTest();
- }
- lcRequire.forEach(function eachReq(value, lang) {
- if (value === "FORCEHYPHENOPOLY" ||
- // eslint-disable-next-line security/detect-object-injection
- (H.cf.langs[lang] && H.cf.langs[lang] === "H9Y")
- ) {
- loadPattern(lang);
- } else {
- tester.create(lang);
- }
- });
- const testContainer = tester.append(d.documentElement);
- if (testContainer !== null) {
- const nl = testContainer.getElementsByTagName("div");
- eachKey(nl, function eachNode(n) {
- /* eslint-disable security/detect-object-injection */
- if (checkCSSHyphensSupport(nl[n]) && nl[n].offsetHeight > 12) {
- H.cf.langs[nl[n].lang] = "CSS";
- } else {
- loadPattern(nl[n].lang);
- }
- /* eslint-enable security/detect-object-injection */
- });
- tester.clear();
- }
- if (H.cf.polyfill) {
- loadScript(H.paths.maindir, "Hyphenopoly.js");
- if (H.cf.wasm) {
- loadBinary(
- H.paths.maindir,
- "hyphenEngine.wasm",
- "hyphenEngine",
- "wasm"
- );
- } else {
- loadScript(H.paths.maindir, "hyphenEngine.asm.js");
- }
- eachKey(H.cf.langs, function prepareEach(lang) {
- /* eslint-disable security/detect-object-injection */
- if (H.cf.langs[lang] === "H9Y") {
- allocateMemory(lang);
- exposeHyphenateFunction(lang);
- }
- /* eslint-enable security/detect-object-injection */
- });
- }
- }());
- /**
- * Hides the specified elements and starts the process by
- * dispatching a "contentLoaded"-event in Hyphenopoly
- * @returns {undefined}
- */
- function handleDCL() {
- if (H.setup.hide.match(/^(?:element|text)$/)) {
- H.toggle("off");
- }
- H.events.dispatch(
- "contentLoaded",
- {"msg": ["contentLoaded"]}
- );
- }
- if (H.cf.polyfill) {
- if (H.setup.hide === "all") {
- H.toggle("off");
- }
- if (H.setup.hide !== "none") {
- H.setup.timeOutHandler = w.setTimeout(function timedOut() {
- H.toggle("on");
- H.events.dispatch("timeout", {"delay": H.setup.timeout});
- }, H.setup.timeout);
- }
- if (d.readyState === "loading") {
- d.addEventListener(
- "DOMContentLoaded",
- handleDCL,
- {
- "once": true,
- "passive": true
- }
- );
- } else {
- handleDCL();
- }
- } else {
- H.events.dispatch("tearDown", {});
- w.Hyphenopoly = null;
- }
- if (H.cacheFeatureTests) {
- store.setItem(
- "Hyphenopoly_Loader",
- JSON.stringify(H.cf)
- );
- }
- }(window, document, Hyphenopoly, Object));
|