1/** 2 * @license Hyphenopoly_Loader 3.0.2 - client side hyphenation 3 * ©2019 Mathias Nater, Zürich (mathiasnater at gmail dot com) 4 * https://github.com/mnater/Hyphenopoly 5 * 6 * Released under the MIT license 7 * http://mnater.github.io/Hyphenopoly/LICENSE 8 */ 9 10/* global Hyphenopoly */ 11 12(function H9YL() { 13 "use strict"; 14 const d = document; 15 const H = Hyphenopoly; 16 17 /** 18 * Create Object without standard Object-prototype 19 * @returns {Object} empty object 20 */ 21 function empty() { 22 return Object.create(null); 23 } 24 25 26 /** 27 * Shorthand for Object.keys(obj).forEach(function () {}) 28 * @param {Object} obj the object to iterate 29 * @param {function} fn the function to execute 30 * @returns {undefined} 31 */ 32 function eachKey(obj, fn) { 33 Object.keys(obj).forEach(fn); 34 } 35 36 (function configFeat() { 37 // Set H.clientFeat (either from sessionStorage or empty) 38 if (H.cacheFeatureTests && sessionStorage.getItem("Hyphenopoly_Loader")) { 39 H.clientFeat = JSON.parse(sessionStorage.getItem("Hyphenopoly_Loader")); 40 } else { 41 H.clientFeat = { 42 "langs": empty(), 43 "polyfill": false, 44 "wasm": null 45 }; 46 } 47 }()); 48 (function configPaths() { 49 // Set defaults for paths and setup 50 H.dfltPaths = Object.create({ 51 "maindir": "../Hyphenopoly/", 52 "patterndir": "../Hyphenopoly/patterns/" 53 }); 54 if (H.paths) { 55 if (H.paths.patterndir) { 56 H.dfltPaths.patterndir = H.paths.patterndir; 57 } 58 if (H.paths.maindir) { 59 H.dfltPaths.maindir = H.paths.maindir; 60 } 61 } 62 }()); 63 (function configSetup() { 64 if (H.setup) { 65 H.setup.selectors = H.setup.selectors || {".hyphenate": {}}; 66 H.setup.timeout = H.setup.timeout || 1000; 67 H.setup.hide = H.setup.hide || "all"; 68 } else { 69 H.setup = { 70 "hide": "all", 71 "selectors": {".hyphenate": {}}, 72 "timeout": 1000 73 }; 74 } 75 }()); 76 (function configRequire() { 77 H.lcRequire = new Map(); 78 eachKey(H.require, function copyRequire(k) { 79 /* eslint-disable security/detect-object-injection */ 80 H.lcRequire.set(k.toLowerCase(), H.require[k]); 81 /* eslint-enable security/detect-object-injection */ 82 }); 83 if (H.fallbacks) { 84 H.lcFallbacks = new Map(); 85 eachKey(H.fallbacks, function copyFallbacks(k) { 86 /* eslint-disable security/detect-object-injection */ 87 H.lcFallbacks.set( 88 k.toLowerCase(), 89 H.fallbacks[k].toLowerCase() 90 ); 91 /* eslint-enable security/detect-object-injection */ 92 }); 93 } 94 }()); 95 96 H.toggle = function toggle(state) { 97 if (state === "on") { 98 const stylesNode = d.getElementById("H9Y_Styles"); 99 if (stylesNode) { 100 stylesNode.parentNode.removeChild(stylesNode); 101 } 102 } else { 103 const vis = " {visibility: hidden !important}\n"; 104 const sc = d.createElement("style"); 105 let myStyle = ""; 106 sc.id = "H9Y_Styles"; 107 switch (H.setup.hide) { 108 case "all": 109 myStyle = "html" + vis; 110 break; 111 case "element": 112 eachKey(H.setup.selectors, function eachSelector(sel) { 113 myStyle += sel + vis; 114 }); 115 break; 116 case "text": 117 eachKey(H.setup.selectors, function eachSelector(sel) { 118 myStyle += sel + " {color: transparent !important}\n"; 119 }); 120 break; 121 default: 122 myStyle = ""; 123 } 124 sc.appendChild(document.createTextNode(myStyle)); 125 d.getElementsByTagName("head")[0].appendChild(sc); 126 } 127 }; 128 129 (function setupEvents() { 130 // Events known to the system 131 const definedEvents = new Map(); 132 // Default events, execution deferred to Hyphenopoly.js 133 const deferred = []; 134 135 /* 136 * Eegister for custom event handlers, where event is not yet defined 137 * these events will be correctly registered in Hyphenopoly.js 138 */ 139 const tempRegister = []; 140 141 /** 142 * Create Event Object 143 * @param {string} name The Name of the event 144 * @param {function} defFunc The default method of the event 145 * @param {boolean} cancellable Is the default cancellable 146 * @returns {undefined} 147 */ 148 function define(name, defFunc, cancellable) { 149 definedEvents.set(name, { 150 "cancellable": cancellable, 151 "default": defFunc, 152 "register": [] 153 }); 154 } 155 156 define( 157 "timeout", 158 function def(e) { 159 H.toggle("on"); 160 window.console.info( 161 "Hyphenopolys 'FOUHC'-prevention timed out after %dms", 162 e.delay 163 ); 164 }, 165 false 166 ); 167 168 define( 169 "error", 170 function def(e) { 171 switch (e.lvl) { 172 case "info": 173 window.console.info(e.msg); 174 break; 175 case "warn": 176 window.console.warn(e.msg); 177 break; 178 default: 179 window.console.error(e.msg); 180 } 181 }, 182 true 183 ); 184 185 define( 186 "contentLoaded", 187 function def(e) { 188 deferred.push({ 189 "data": e, 190 "name": "contentLoaded" 191 }); 192 }, 193 false 194 ); 195 196 define( 197 "engineLoaded", 198 function def(e) { 199 deferred.push({ 200 "data": e, 201 "name": "engineLoaded" 202 }); 203 }, 204 false 205 ); 206 207 define( 208 "hpbLoaded", 209 function def(e) { 210 deferred.push({ 211 "data": e, 212 "name": "hpbLoaded" 213 }); 214 }, 215 false 216 ); 217 218 define( 219 "loadError", 220 function def(e) { 221 deferred.push({ 222 "data": e, 223 "name": "loadError" 224 }); 225 } 226 ); 227 228 define( 229 "tearDown", 230 null, 231 true 232 ); 233 234 /**
235 * Dispatch event <name> with arguments <data> 236 * @param {string} name The name of the event 237 * @param {Object|undefined} data Data of the event 238 * @returns {undefined} 239 */ 240 function dispatch(name, data) { 241 data = data || empty(); 242 let defaultPrevented = false; 243 definedEvents.get(name).register.forEach( 244 function call(currentHandler) { 245 data.preventDefault = function preventDefault() { 246 if (definedEvents.get(name).cancellable) { 247 defaultPrevented = true; 248 } 249 }; 250 currentHandler(data); 251 } 252 ); 253 if ( 254 !defaultPrevented && 255 definedEvents.get(name).default 256 ) { 257 definedEvents.get(name).default(data); 258 } 259 } 260 261 /** 262 * Add EventListender <handler> to event <name> 263 * @param {string} name The name of the event 264 * @param {function} handler Function to register 265 * @param {boolean} defer If the registration is deferred 266 * @returns {undefined} 267 */ 268 function addListener(name, handler, defer) { 269 if (definedEvents.has(name)) { 270 definedEvents.get(name).register.push(handler); 271 } else if (defer) { 272 tempRegister.push({ 273 "handler": handler, 274 "name": name 275 }); 276 } else { 277 H.events.dispatch( 278 "error", 279 { 280 "lvl": "warn", 281 "msg": "unknown Event \"" + name + "\" discarded" 282 } 283 ); 284 } 285 } 286 287 if (H.handleEvent) { 288 eachKey(H.handleEvent, function add(name) { 289 /* eslint-disable security/detect-object-injection */ 290 addListener(name, H.handleEvent[name], true); 291 /* eslint-enable security/detect-object-injection */ 292 }); 293 } 294 295 H.events = empty(); 296 H.events.deferred = deferred; 297 H.events.tempRegister = tempRegister; 298 H.events.dispatch = dispatch; 299 H.events.define = define; 300 H.events.addListener = addListener; 301 }()); 302 303 /** 304 * Test if wasm is supported 305 * @returns {undefined} 306 */ 307 function featureTestWasm() { 308 /* eslint-disable no-prototype-builtins */ 309 /** 310 * Feature test for wasm 311 * @returns {boolean} support 312 */ 313 function runWasmTest() { 314 /* 315 * This is the original test, without webkit workaround 316 * if (typeof WebAssembly === "object" && 317 * typeof WebAssembly.instantiate === "function") { 318 * const module = new WebAssembly.Module(Uint8Array.from( 319 * [0, 97, 115, 109, 1, 0, 0, 0] 320 * )); 321 * if (WebAssembly.Module.prototype.isPrototypeOf(module)) { 322 * return WebAssembly.Instance.prototype.isPrototypeOf( 323 * new WebAssembly.Instance(module) 324 * ); 325 * } 326 * } 327 * return false; 328 */ 329 330 /* 331 * Wasm feature test with iOS bug detection 332 * (https://bugs.webkit.org/show_bug.cgi?id=181781) 333 */ 334 if ( 335 typeof WebAssembly === "object" && 336 typeof WebAssembly.instantiate === "function" 337 ) { 338 /* eslint-disable array-element-newline */ 339 const module = new WebAssembly.Module(Uint8Array.from([ 340 0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127, 341 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 8, 1, 4, 116, 101, 115, 342 116, 0, 0, 10, 16, 1, 14, 0, 32, 0, 65, 1, 54, 2, 0, 32, 343 0, 40, 2, 0, 11 344 ])); 345 /* eslint-enable array-element-newline */ 346 if (WebAssembly.Module.prototype.isPrototypeOf(module)) { 347 const inst = new WebAssembly.Instance(module); 348 return WebAssembly.Instance.prototype.isPrototypeOf(inst) && 349 (inst.exports.test(4) !== 0); 350 } 351 } 352 return false;
353 } 354 /* eslint-enable no-prototype-builtins */ 355 if (H.clientFeat.wasm === null) { 356 H.clientFeat.wasm = runWasmTest(); 357 } 358 } 359 360 /** 361 * Load script by adding <script>-tag 362 * @param {string} path Where the script is stored 363 * @param {string} filename Filename of the script 364 * @returns {undefined} 365 */ 366 function scriptLoader(path, filename) { 367 const script = d.createElement("script"); 368 script.src = path + filename; 369 if (filename === "hyphenEngine.asm.js") { 370 script.addEventListener("load", function listener() { 371 H.events.dispatch("engineLoaded", {"msg": "asm"}); 372 }); 373 } 374 d.head.appendChild(script); 375 } 376 377 const loadedBins = new Map(); 378 379 /** 380 * Load binary files either with fetch (on new browsers that support wasm) 381 * or with xmlHttpRequest 382 * @param {string} path Where the script is stored 383 * @param {string} fne Filename of the script with extension 384 * @param {string} name Name of the ressource 385 * @param {Object} msg Message 386 * @returns {undefined} 387 */ 388 function binLoader(path, fne, name, msg) { 389 /** 390 * Get bin file using fetch 391 * @param {string} p Where the script is stored 392 * @param {string} f Filename of the script with extension 393 * @param {string} n Name of the ressource 394 * @param {Object} m Message 395 * @returns {undefined} 396 */ 397 function fetchBinary(p, f, n, m) { 398 if (!loadedBins.has(f)) { 399 loadedBins.set(f, [m]); 400 window.fetch(p + f).then( 401 function resolve(response) { 402 if (response.ok) { 403 if (n === "hyphenEngine") { 404 H.binaries.set(n, response.arrayBuffer().then( 405 function getModule(buf) { 406 return new WebAssembly.Module(buf); 407 } 408 )); 409 H.events.dispatch("engineLoaded", {"msg": m}); 410 } else { 411 const files = loadedBins.get(f); 412 files.forEach(function eachHpb(rn) { 413 H.binaries.set( 414 rn, 415 (files.length > 1) 416 ? response.clone().arrayBuffer() 417 : response.arrayBuffer() 418 ); 419 H.events.dispatch( 420 "hpbLoaded", 421 {"msg": rn} 422 ); 423 }); 424 } 425 } else { 426 H.events.dispatch("loadError", { 427 "file": f, 428 "msg": m, 429 "name": n, 430 "path": p 431 }); 432 } 433 } 434 ); 435 } else if (n !== "hyphenEngine") { 436 loadedBins.get(f).push(m); 437 } 438 } 439 440 /** 441 * Get bin file using XHR 442 * @param {string} p Where the script is stored 443 * @param {string} f Filename of the script with extension 444 * @param {string} n Name of the ressource 445 * @param {Object} m Message 446 * @returns {undefined} 447 */ 448 function requestBinary(p, f, n, m) { 449 /* eslint-disable-next-line no-negated-condition */ 450 if (!loadedBins.has(f)) { 451 loadedBins.set(f, [m]); 452 const xhr = new XMLHttpRequest(); 453 xhr.onload = function onload() { 454 if (xhr.statusText === "OK") { 455 loadedBins.get(f).
456 forEach(function eachHpb(rn) { 457 H.binaries.set( 458 rn, 459 xhr.response 460 ); 461 H.events.dispatch( 462 "hpbLoaded", 463 {"msg": rn} 464 ); 465 }); 466 } else { 467 H.events.dispatch("loadError", { 468 "file": f, 469 "msg": m, 470 "name": n, 471 "path": p 472 }); 473 } 474 }; 475 xhr.open("GET", p + f); 476 xhr.responseType = "arraybuffer"; 477 xhr.send(); 478 } else { 479 loadedBins.get(f).push(m); 480 } 481 } 482 if (H.clientFeat.wasm) { 483 fetchBinary(path, fne, name, msg); 484 } else { 485 requestBinary(path, fne, name, msg); 486 } 487 } 488 489 /** 490 * Pre-Allocate memory for (w)asm 491 * Default is 32 wasm Pages (). For languages with larger .hpb 492 * files a higher value is needed. 493 * Get the value from baseData.heapSize in Hyphenopoly.js 494 * @param {string} lang Language 495 * @returns {undefined} 496 */ 497 function allocateMemory(lang) { 498 const specVal = new Map( 499 [["de", 54], ["hu", 205], ["nb-no", 91], ["nl", 41]] 500 ); 501 const wasmPages = specVal.get(lang) || 32; 502 H.specMems = H.specMems || new Map(); 503 if (H.clientFeat.wasm) { 504 H.specMems.set(lang, new WebAssembly.Memory({ 505 "initial": wasmPages, 506 "maximum": 256 507 })); 508 } else { 509 /** 510 * Polyfill Math.log2 511 * @param {number} x argument 512 * @return {number} Log2(x) 513 */ 514 Math.log2 = Math.log2 || function polyfillLog2(x) { 515 return Math.log(x) * Math.LOG2E; 516 }; 517 /* eslint-disable no-bitwise */ 518 const asmPages = (2 << Math.floor(Math.log2(wasmPages))) * 65536; 519 /* eslint-enable no-bitwise */ 520 H.specMems.set(lang, new ArrayBuffer(asmPages)); 521 } 522 } 523 524 (function featureTestCSSHyphenation() { 525 const tester = (function tester() { 526 let fakeBody = null; 527 528 const css = (function createCss() { 529 /* eslint-disable array-element-newline */ 530 const props = [ 531 "visibility:hidden;", 532 "-moz-hyphens:auto;", 533 "-webkit-hyphens:auto;", 534 "-ms-hyphens:auto;", 535 "hyphens:auto;", 536 "width:48px;", 537 "font-size:12px;", 538 "line-height:12px;", 539 "border:none;", 540 "padding:0;", 541 "word-wrap:normal" 542 ]; 543 /* eslint-enable array-element-newline */ 544 return props.join(""); 545 }()); 546 547 /** 548 * Create and append div with CSS-hyphenated word 549 * @param {string} lang Language 550 * @returns {undefined} 551 */ 552 function create(lang) { 553 /* eslint-disable security/detect-object-injection */ 554 if (H.clientFeat.langs[lang]) { 555 return; 556 } 557 /* eslint-enable security/detect-object-injection */ 558 fakeBody = fakeBody || d.createElement("body"); 559 const testDiv = d.createElement("div"); 560 testDiv.lang = lang; 561 testDiv.id = lang; 562 testDiv.style.cssText = css; 563 testDiv.appendChild(d.createTextNode(H.lcRequire.get(lang))); 564 fakeBody.appendChild(testDiv); 565 } 566 567 /** 568 * Append fakeBody with tests to target (document) 569 * @param {Object} target Where to append fakeBody 570 * @returns {Object|null} The body element or null, if no tests 571 */ 572 function append(target) { 573 if (fakeBody) { 574 target.appendChild(fakeBody); 575 return fakeBody; 576 } 577 return null; 578 } 579 580 /** 581 * Remove fakeBody 582 * @returns {undefined} 583 */ 584 function clear() { 585 if (fakeBody) { 586 fakeBody.parentNode.removeChild(fakeBody); 587 } 588 } 589 return { 590 "append": append, 591 "clear": clear, 592 "create": create 593 }; 594 }()); 595 596 /** 597 * Checks if hyphens (ev.prefixed) is set to auto for the element. 598 * @param {Object} elm - the element 599 * @returns {Boolean} result of the check 600 */ 601 function checkCSSHyphensSupport(elm) { 602 return ( 603 elm.style.hyphens === "auto" || 604 elm.style.webkitHyphens === "auto" || 605 elm.style.msHyphens === "auto" || 606 elm.style["-moz-hyphens"] === "auto" 607 ); 608 } 609 610 /** 611 * Expose the hyphenate-function of a specific language to 612 * Hyphenopoly.hyphenators.<language> 613 * 614 * Hyphenopoly.hyphenators.<language> is a Promise that fullfills 615 * to hyphenate(entity, sel) as soon as the ressources are loaded 616 * and the engine is ready. 617 * If Promises aren't supported (e.g. IE11) a error message is produced. 618 * 619 * @param {string} lang - the language 620 * @returns {undefined} 621 */ 622 function exposeHyphenateFunction(lang) { 623 /* eslint-disable security/detect-object-injection */ 624 H.hyphenators = H.hyphenators || empty(); 625 if (!H.hyphenators[lang]) { 626 if (window.Promise) { 627 H.hyphenators[lang] = new Promise(function pro(rs, rj) { 628 H.events.addListener("engineReady", function handler(e) { 629 if (e.msg === lang) { 630 rs(H.createHyphenator(e.msg)); 631 } 632 }, true); 633 H.events.addListener("error", function handler(e) { 634 if (e.key === lang || e.key === "hyphenEngine") { 635 rj(e.msg); 636 } 637 }, true); 638 }); 639 } else { 640 H.hyphenators[lang] = { 641 642 /** 643 * Fires an error message, if then is called 644 * @returns {undefined} 645 */ 646 "then": function () { 647 H.events.dispatch( 648 "error", 649 {"msg": "Promises not supported in this engine. Use a polyfill (e.g. https://github.com/taylorhakes/promise-polyfill)!"} 650 ); 651 } 652 }; 653 } 654 } 655 /* eslint-enable security/detect-object-injection */ 656 } 657 658 /** 659 * Load all ressources for a required <lang>, check if wasm is supported 660 * and expose the hyphenate function. 661 * @param {string} lang The language 662 * @returns {undefined} 663 */ 664 function loadPattern(lang) { 665 let filename = lang + ".hpb"; 666 let langFallback = lang; 667 if (H.lcFallbacks && H.lcFallbacks.has(lang)) { 668 langFallback = H.lcFallbacks.get(lang); 669 filename = langFallback + ".hpb"; 670 } 671 H.binaries = H.binaries || new Map(); 672 binLoader(H.dfltPaths.patterndir, filename, langFallback, lang); 673 } 674 featureTestWasm(); 675 H.lcRequire.forEach(function eachReq(value, lang) { 676 /* eslint-disable security/detect-object-injection */ 677 if (value === "FORCEHYPHENOPOLY") { 678 H.clientFeat.polyfill = true; 679 H.clientFeat.langs[lang] = "H9Y"; 680 loadPattern(lang); 681 } else if ( 682 H.clientFeat.langs[lang] && 683 H.clientFeat.langs[lang] === "H9Y" 684 ) { 685 loadPattern(lang); 686 } else { 687 tester.create(lang); 688 } 689 /* eslint-enable security/detect-object-injection */ 690 }); 691
692 const testContainer = tester.append(d.documentElement); 693 if (testContainer !== null) { 694 H.lcRequire.forEach(function eachReq(value, lang) { 695 if (value !== "FORCEHYPHENOPOLY") { 696 const el = d.getElementById(lang); 697 /* eslint-disable security/detect-object-injection */ 698 if (checkCSSHyphensSupport(el) && el.offsetHeight > 12) { 699 H.clientFeat.langs[lang] = "CSS"; 700 } else { 701 H.clientFeat.polyfill = true; 702 H.clientFeat.langs[lang] = "H9Y"; 703 loadPattern(lang); 704 } 705 /* eslint-enable security/detect-object-injection */ 706 } 707 }); 708 tester.clear(); 709 } 710 if (H.clientFeat.polyfill) { 711 scriptLoader(H.dfltPaths.maindir, "Hyphenopoly.js"); 712 if (H.clientFeat.wasm) { 713 binLoader( 714 H.dfltPaths.maindir, 715 "hyphenEngine.wasm", 716 "hyphenEngine", 717 "wasm" 718 ); 719 } else { 720 scriptLoader(H.dfltPaths.maindir, "hyphenEngine.asm.js"); 721 } 722 eachKey(H.clientFeat.langs, function prepareEach(lang) { 723 /* eslint-disable security/detect-object-injection */ 724 if (H.clientFeat.langs[lang] === "H9Y") { 725 allocateMemory(lang); 726 exposeHyphenateFunction(lang); 727 } 728 /* eslint-enable security/detect-object-injection */ 729 }); 730 } 731 }()); 732 733 /** 734 * Hides the specified elements and starts the process by 735 * dispatching a "contentLoaded"-event in Hyphenopoly 736 * @returns {undefined} 737 */ 738 function handleDCL() { 739 if (H.setup.hide.match(/^(?:element|text)$/)) { 740 H.toggle("off"); 741 } 742 H.events.dispatch( 743 "contentLoaded", 744 {"msg": ["contentLoaded"]} 745 ); 746 } 747 748 if (H.clientFeat.polyfill) { 749 if (H.setup.hide === "all") { 750 H.toggle("off"); 751 } 752 if (H.setup.hide !== "none") { 753 H.setup.timeOutHandler = window.setTimeout(function timedOut() { 754 H.toggle("on"); 755 H.events.dispatch("timeout", {"delay": H.setup.timeout}); 756 }, H.setup.timeout); 757 } 758 if (d.readyState === "loading") { 759 d.addEventListener( 760 "DOMContentLoaded", 761 handleDCL, 762 { 763 "once": true, 764 "passive": true 765 } 766 ); 767 } else { 768 handleDCL(); 769 } 770 } else { 771 H.events.dispatch("tearDown", {}); 772 window.Hyphenopoly = null; 773 } 774 775 if (H.cacheFeatureTests) { 776 sessionStorage.setItem( 777 "Hyphenopoly_Loader", 778 JSON.stringify(H.clientFeat) 779 ); 780 } 781}());
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.