diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f8e813..b4fea785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Version History -## Version 1.0.1 (Mai 13, 2008) +## Version 2.0.0 (Mai 27, 2018) +* Provide node module (https://github.com/mnater/Hyphenopoly/wiki/Node-Module) +* default file locations better reflect usual installations [#19](https://github.com/mnater/Hyphenopoly/issues/19) +* Add ability to store results of feature tests (optional) [#22](https://github.com/mnater/Hyphenopoly/issues/22) +* better error handling (f4bbaa7759eed24208e5cd7c744f1131262abb20, 1c7b0b67666b507d6f6b02eea38460562a5835e4) +* correct implementation of e.preventDefault (df988788db6fb7120fc0c8a1cff1c91aac5a3998) +* fix string normalization (a3229f730f79ccdd3054cbac257b2345f5c8e11a) +* Better tooling: minify, eslint, testing (mocha), compiling (https://github.com/mnater/Hyphenopoly/wiki/Usage-of-devDependencies) + +## Version 1.0.1 (Mai 13, 2018) Prevent browsers to force layout on feature test in some cases. ## Version 1.0.0 (Mai 12, 2018) diff --git a/Hyphenopoly.js b/Hyphenopoly.js index 178504e8..d2bccda2 100644 --- a/Hyphenopoly.js +++ b/Hyphenopoly.js @@ -1,120 +1,165 @@ -/** @license Hyphenopoly 1.0.1 - client side hyphenation for webbrowsers - * ©2018 Mathias Nater, Zürich (mathiasnater at gmail dot com) - * https://github.com/mnater/Hyphenopoly +/** + * @license Hyphenopoly 2.0.0 - client side hyphenation for webbrowsers + * ©2018 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 + * Released under the MIT license + * http://mnater.github.io/Hyphenopoly/LICENSE */ -;/*jslint browser, bitwise*/ -/*global window, Hyphenopoly, TextDecoder, WebAssembly, asmHyphenEngine*/ +/* globals asmHyphenEngine */ + (function mainWrapper(w) { "use strict"; const H = Hyphenopoly; const SOFTHYPHEN = String.fromCharCode(173); + /** + * Create Object without standard Object-prototype + * @returns {Object} empty object + */ function empty() { return Object.create(null); } - Math.imul = Math.imul || function(a, b) { - var aHi = (a >>> 16) & 0xffff; - var aLo = a & 0xffff; - var bHi = (b >>> 16) & 0xffff; - var bLo = b & 0xffff; - // the shift by 0 fixes the sign on the high part - // the final |0 converts the unsigned value into a signed value - return ((aLo * bLo) + (((aHi * bLo + aLo * bHi) << 16) >>> 0) | 0); + /** + * Polyfill Math.imul + * @param {number} a LHS + * @param {number} b RHS + * @returns {number} empty object + */ + /* eslint-disable no-bitwise */ + Math.imul = Math.imul || function imul(a, b) { + const aHi = (a >>> 16) & 0xffff; + const aLo = a & 0xffff; + const bHi = (b >>> 16) & 0xffff; + const bLo = b & 0xffff; + + /* + * The shift by 0 fixes the sign on the high part. + * The final |0 converts the unsigned value into a signed value + */ + return ((aLo * bLo) + ((((aHi * bLo) + (aLo * bHi)) << 16) >>> 0) | 0); }; + /* eslint-enable no-bitwise */ + /** + * Set value and properties of object member + * Argument is a bit pattern: + * 1. bit: configurable + * 2. bit: enumerable + * 3. bit writable + * e.g. 011(2) = 3(10) => configurable: f, enumerable: t, writable: t + * or 010(2) = 2(10) => configurable: f, enumerable: t, writable: f + * @param {any} val The value + * @param {number} props bitfield + * @returns {Object} Property object + */ function setProp(val, props) { - /* props is a bit pattern: - * 1. bit: configurable - * 2. bit: enumerable - * 3. bit writable - * e.g. 011(2) = 3(10) => configurable: false, enumerable: true, writable: true - * or 010(2) = 2(10) => configurable: false, enumerable: true, writable: false - */ + /* eslint-disable no-bitwise, sort-keys */ return { - configurable: (props & 4) > 0, - enumerable: (props & 2) > 0, - writable: (props & 1) > 0, - value: val + "configurable": (props & 4) > 0, + "enumerable": (props & 2) > 0, + "writable": (props & 1) > 0, + "value": val }; + /* eslint-enable no-bitwise, sort-keys */ } (function configurationFactory() { const generalDefaults = Object.create(null, { - timeout: setProp(1000, 2), - defaultLanguage: setProp("en-us", 2), - dontHyphenateClass: setProp("donthyphenate", 2), - dontHyphenate: setProp((function () { + "defaultLanguage": setProp("en-us", 2), + "dontHyphenate": setProp((function createList() { const r = empty(); const list = "video,audio,script,code,pre,img,br,samp,kbd,var,abbr,acronym,sub,sup,button,option,label,textarea,input,math,svg,style"; - list.split(",").forEach(function (value) { + list.split(",").forEach(function add(value) { r[value] = true; }); return r; }()), 2), - safeCopy: setProp(true, 2), - normalize: setProp(false, 2), - exceptions: setProp(empty(), 2) + "dontHyphenateClass": setProp("donthyphenate", 2), + "exceptions": setProp(empty(), 2), + "normalize": setProp(false, 2), + "safeCopy": setProp(true, 2), + "timeout": setProp(1000, 2) }); const settings = Object.create(generalDefaults); const perClassDefaults = Object.create(null, { - minWordLength: setProp(6, 2), - leftmin: setProp(0, 2), - leftminPerLang: setProp(0, 2), - rightmin: setProp(0, 2), - rightminPerLang: setProp(0, 2), - hyphen: setProp(SOFTHYPHEN, 2), //soft hyphen - orphanControl: setProp(1, 2), - compound: setProp("hyphen", 2) + "compound": setProp("hyphen", 2), + "hyphen": setProp(SOFTHYPHEN, 2), + "leftmin": setProp(0, 2), + "leftminPerLang": setProp(0, 2), + "minWordLength": setProp(6, 2), + "orphanControl": setProp(1, 2), + "rightmin": setProp(0, 2), + "rightminPerLang": setProp(0, 2) }); - //copy settings if not yet set - Object.keys(H.setup).forEach(function (key) { + Object.keys(H.setup).forEach(function copySettings(key) { if (key === "classnames") { const classNames = Object.keys(H.setup.classnames); - Object.defineProperty(settings, "classNames", setProp(classNames, 2)); - classNames.forEach(function (cn) { + Object.defineProperty( + settings, + "classNames", + setProp(classNames, 2) + ); + classNames.forEach(function copyClassnames(cn) { const tmp = {}; - Object.keys(H.setup.classnames[cn]).forEach(function (pcnkey) { - tmp[pcnkey] = setProp(H.setup.classnames[cn][pcnkey], 2); - }); - Object.defineProperty(settings, cn, setProp(Object.create(perClassDefaults, tmp), 2)); + Object.keys(H.setup.classnames[cn]).forEach( + function copyClassSettings(k) { + tmp[k] = setProp(H.setup.classnames[cn][k], 2); + } + ); + Object.defineProperty( + settings, + cn, + setProp(Object.create(perClassDefaults, tmp), 2) + ); }); } else { - Object.defineProperty(settings, key, setProp(H.setup[key], 3)); + Object.defineProperty( + settings, + key, + setProp(H.setup[key], 3) + ); } }); H.c = settings; }()); - (function H9Y(w) { + (function H9Y() { const C = H.c; - let mainLanguage = null; + let elements = null; - let elements; + /** + * Factory for elements + * @returns {Object} elements-object + */ function makeElementCollection() { - //counter counts the elements to be hyphenated. Needs to be an object (Pass by reference) - let counter = [0]; - const list = empty(); - function makeElement(element, cn) { - return { - element: element, - class: cn - }; - } + /* + * Counter counts the elements to be hyphenated. + * Needs to be an object (Pass by reference) + */ + const counter = [0]; + /** + * Add element to elements + * @param {object} el The element + * @param {string} lang The language of the element + * @param {string} cn The classname of the element + * @returns {Object} An element-object + */ function add(el, lang, cn) { - const elo = makeElement(el, cn); - if (list[lang] === undefined) { + const elo = { + "class": cn, + "element": el + }; + if (!list[lang]) { list[lang] = []; } list[lang].push(elo); @@ -122,97 +167,139 @@ return elo; } + /** + * Execute fn for each element + * @param {function} fn The function to execute + * @returns {undefined} + */ function each(fn) { - Object.keys(list).forEach(function (k) { + Object.keys(list).forEach(function forEachElem(k) { fn(k, list[k]); }); } return { - counter: counter, - list: list, - add: add, - each: each + "add": add, + "counter": counter, + "each": each, + "list": list }; } - const registerOnCopy = function (el) { - el.addEventListener("copy", function (e) { + /** + * Register copy event on element + * @param {Object} el The element + * @returns {undefined} + */ + function registerOnCopy(el) { + el.addEventListener("copy", function oncopy(e) { e.preventDefault(); const selectedText = window.getSelection().toString(); e.clipboardData.setData("text/plain", selectedText.replace(new RegExp(SOFTHYPHEN, "g"), "")); }, true); - }; + } + /** + * Get language of element by searching its parents or fallback + * @param {Object} el The element + * @param {boolean} fallback Will falback to mainlanguage + * @returns {string|null} The language or null + */ function getLang(el, fallback) { try { return (el.getAttribute("lang")) ? el.getAttribute("lang").toLowerCase() - : el.tagName.toLowerCase() !== "html" - ? getLang(el.parentNode, fallback) - : fallback + : el.tagName.toLowerCase() === "html" + ? fallback ? mainLanguage - : null; - } catch (ignore) {} + : null + : getLang(el.parentNode, fallback); + } catch (ignore) { + return null; + } } + /** + * Set mainLanguage + * @returns {undefined} + */ function autoSetMainLanguage() { const el = w.document.getElementsByTagName("html")[0]; - mainLanguage = getLang(el, false); - //fallback to defaultLang if set if (!mainLanguage && C.defaultLanguage !== "") { mainLanguage = C.defaultLanguage; } - //el.lang = mainLanguage; //this trigger recalculate style! is it really necessary? } + /** + * Sort out subclasses + * @param {Array} x Array of classnames + * @param {Array} y Array of classnames to sort out of x + * @returns {Array} Array of classes + */ function sortOutSubclasses(x, y) { return (x[0] === "") ? [] - : x.filter(function (i) { + : x.filter(function filter(i) { return y.indexOf(i) !== -1; }); } + /** + * Collect elements that have a classname defined in C.classnames + * and add them to elements. + * @returns {undefined} + */ function collectElements() { elements = makeElementCollection(); - function processText(el, pLang, cn, isChild) { - let eLang; - let n; + + /** + * Recursively walk all elements in el, lending lang and className + * add them to elements if necessary. + * @param {Object} el The element to scan + * @param {string} pLang The language of the oarent element + * @param {string} cn The className of the parent element + * @param {boolean} isChild If el is a child element + * @returns {undefined} + */ + function processElements(el, pLang, cn, isChild) { + let eLang = null; + let n = null; let j = 0; isChild = isChild || false; - //set eLang to the lang of the element if (el.lang && typeof el.lang === "string") { - eLang = el.lang.toLowerCase(); //copy attribute-lang to internal eLang - } else if (pLang !== undefined && pLang !== "") { + eLang = el.lang.toLowerCase(); + } else if (pLang && pLang !== "") { eLang = pLang.toLowerCase(); } else { eLang = getLang(el, true); } - if (H.testResults.languages[eLang] === "H9Y") { + if (H.clientFeat.langs[eLang] === "H9Y") { elements.add(el, eLang, cn); if (!isChild && C.safeCopy) { registerOnCopy(el); } + } else if (!H.clientFeat.langs[eLang]) { + H.events.dispatch("error", {"msg": `Element with '${eLang}' found, but '${eLang}.hpb' not loaded. Check language tags!`}); } n = el.childNodes[j]; - while (n !== undefined) { - if (n.nodeType === 1 && !C.dontHyphenate[n.nodeName.toLowerCase()] && n.className.indexOf(C.dontHyphenateClass) === -1) { + while (n) { + if (n.nodeType === 1 && + !C.dontHyphenate[n.nodeName.toLowerCase()] && + n.className.indexOf(C.dontHyphenateClass) === -1) { if (sortOutSubclasses(n.className.split(" "), C.classNames).length === 0) { - //this child element doesn't contain a hyphenopoly-class - processText(n, eLang, cn, true); + processElements(n, eLang, cn, true); } } j += 1; n = el.childNodes[j]; } } - C.classNames.forEach(function (cn) { - const nl = w.document.querySelectorAll("." + cn); - Array.prototype.forEach.call(nl, function (n) { - processText(n, getLang(n, true), cn, false); + C.classNames.forEach(function eachClassName(cn) { + const nl = w.document.querySelectorAll(`.${cn}`); + Array.prototype.forEach.call(nl, function eachNode(n) { + processElements(n, getLang(n, true), cn, false); }); }); H.elementsReady = true; @@ -220,17 +307,29 @@ const wordHyphenatorPool = empty(); + /** + * Factory for hyphenatorFunctions for a specific language and class + * @param {Object} lo Language-Object + * @param {string} lang The language + * @param {string} cn The className + * @returns {function} The hyphenate function + */ function createWordHyphenator(lo, lang, cn) { const classSettings = C[cn]; - const normalize = C.normalize && (String.prototype.normalize !== undefined); const hyphen = classSettings.hyphen; lo.cache[cn] = empty(); - function hyphenateCompound(lo, lang, word) { + + /** + * HyphenateFunction for compound words + * @param {string} word The word + * @returns {string} The hyphenated compound word + */ + function hyphenateCompound(word) { const zeroWidthSpace = String.fromCharCode(8203); - let parts; + let parts = null; let i = 0; - let wordHyphenator; + let wordHyphenator = null; let hw = word; switch (classSettings.compound) { case "auto": @@ -253,44 +352,71 @@ } i += 1; } - hw = parts.join("-" + zeroWidthSpace); + hw = parts.join(`-${zeroWidthSpace}`); break; - default: //"hyphen" and others - hw = word.replace("-", "-" + zeroWidthSpace); + default: + hw = word.replace("-", `-${zeroWidthSpace}`); } return hw; } + /** + * HyphenateFunction for words (compound or not) + * @param {string} word The word + * @returns {string} The hyphenated word + */ function hyphenator(word) { - if (normalize) { - word = word.normalize("NFC"); - } let hw = lo.cache[cn][word]; if (!hw) { - if (lo.exceptions[word] !== undefined) { //the word is in the exceptions list - hw = lo.exceptions[word].replace(/-/g, classSettings.hyphen); - } else if (word.indexOf("-") !== -1) { - hw = hyphenateCompound(lo, lang, word); + if (lo.exceptions[word]) { + hw = lo.exceptions[word].replace( + /-/g, + classSettings.hyphen + ); + } else if (word.indexOf("-") === -1) { + hw = lo.hyphenateFunction( + word, + hyphen, + classSettings.leftminPerLang[lang], + classSettings.rightminPerLang[lang] + ); } else { - hw = lo.hyphenateFunction(word, hyphen, classSettings.leftminPerLang[lang], classSettings.rightminPerLang[lang]); + hw = hyphenateCompound(word); } lo.cache[cn][word] = hw; } return hw; } - wordHyphenatorPool[lang + "-" + cn] = hyphenator; + wordHyphenatorPool[`${lang}-${cn}`] = hyphenator; return hyphenator; } const orphanControllerPool = empty(); + /** + * Factory for function that handles orphans + * @param {string} cn The className + * @returns {function} The function created + */ function createOrphanController(cn) { - function controlOrphans(ignore, leadingWhiteSpace, lastWord, trailingWhiteSpace) { + /** + * Function template + * @param {string} ignore unused result of replace + * @param {string} leadingWhiteSpace The leading whiteSpace + * @param {string} lastWord The last word + * @param {string} trailingWhiteSpace The trailing whiteSpace + * @returns {string} Treated end of text + */ + function controlOrphans( + ignore, + leadingWhiteSpace, + lastWord, + trailingWhiteSpace + ) { const classSettings = C[cn]; let h = classSettings.hyphen; - //escape hyphen if (".\\+*?[^]$(){}=!<>|:-".indexOf(classSettings.hyphen) !== -1) { - h = "\\" + classSettings.hyphen; + h = `\\${classSettings.hyphen}`; } if (classSettings.orphanControl === 3 && leadingWhiteSpace === " ") { leadingWhiteSpace = String.fromCharCode(160); @@ -301,17 +427,29 @@ return controlOrphans; } + /** + * Hyphenate text in element + * @param {string} lang The language of the element + * @param {Object} elo The element-object + * @returns {undefined} + */ function hyphenateElement(lang, elo) { const el = elo.element; const lo = H.languages[lang]; const cn = elo.class; const classSettings = C[cn]; const minWordLength = classSettings.minWordLength; - H.events.dispatch("beforeElementHyphenation", {el: el, lang: lang}); - const wordHyphenator = (wordHyphenatorPool[lang + "-" + cn] !== undefined) - ? wordHyphenatorPool[lang + "-" + cn] + const normalize = C.normalize && + Boolean(String.prototype.normalize); + H.events.dispatch("beforeElementHyphenation", { + "el": el, + "lang": lang + }); + const poolKey = `${lang}-${cn}`; + const wordHyphenator = (wordHyphenatorPool[poolKey]) + ? wordHyphenatorPool[poolKey] : createWordHyphenator(lo, lang, cn); - const orphanController = (orphanControllerPool[cn] !== undefined) + const orphanController = (orphanControllerPool[cn]) ? orphanControllerPool[cn] : createOrphanController(cn); const re = lo.genRegExps[cn]; @@ -319,13 +457,20 @@ let n = el.childNodes[i]; while (n) { if ( - n.nodeType === 3 //type 3 = #text - && n.data.length >= minWordLength //longer then min + n.nodeType === 3 && + n.data.length >= minWordLength ) { - let tn = n.data.replace(re, wordHyphenator); + let tn = null; + if (normalize) { + tn = n.data.normalize("NFC").replace(re, wordHyphenator); + } else { + tn = n.data.replace(re, wordHyphenator); + } if (classSettings.orphanControl !== 1) { - //prevent last word from being hyphenated - tn = tn.replace(/(\u0020*)(\S+)(\s*)$/, orphanController); + tn = tn.replace( + /(\u0020*)(\S+)(\s*)$/, + orphanController + ); } n.data = tn; } @@ -333,32 +478,45 @@ n = el.childNodes[i]; } elements.counter[0] -= 1; - H.events.dispatch("afterElementHyphenation", {el: el, lang: lang}); + H.events.dispatch("afterElementHyphenation", { + "el": el, + "lang": lang + }); } + /** + * Hyphenate all elements with a given language + * @param {string} lang The language + * @param {Array} elArr Array of elements + * @returns {undefined} + */ function hyphenateLangElements(lang, elArr) { - if (elArr !== undefined) { + if (elArr) { elArr.forEach(function eachElem(elo) { hyphenateElement(lang, elo); }); } else { - H.events.dispatch("error", {msg: "engine for language '" + lang + "' loaded, but no elements found."}); + H.events.dispatch("error", {"msg": `engine for language '${lang}' loaded, but no elements found.`}); } if (elements.counter[0] === 0) { H.events.dispatch("hyphenopolyEnd"); } - } - function convertExceptionsToObject(exc) { + /** + * Convert exceptions to object + * @param {string} exc comma separated list of exceptions + * @returns {Object} Map of exceptions + */ + function convertExceptions(exc) { const words = exc.split(", "); const r = empty(); const l = words.length; let i = 0; - let key; + let key = null; while (i < l) { key = words[i].replace(/-/g, ""); - if (r[key] === undefined) { + if (!r[key]) { r[key] = words[i]; } i += 1; @@ -366,28 +524,41 @@ return r; } - function prepareLanguagesObj(lang, hyphenateFunction, alphabet, leftmin, rightmin) { + /** + * Setup lo + * @param {string} lang The language + * @param {function} hyphenateFunction The hyphenateFunction + * @param {string} alphabet List of used characters + * @param {number} leftmin leftmin + * @param {number} rightmin rightmin + * @returns {undefined} + */ + function prepareLanguagesObj( + lang, + hyphenateFunction, + alphabet, + leftmin, + rightmin + ) { alphabet = alphabet.replace(/-/g, ""); - if (!H.hasOwnProperty("languages")) { + if (!H.languages) { H.languages = {}; } - if (!H.languages.hasOwnProperty(lang)) { + if (!H.languages.lang) { H.languages[lang] = empty(); } const lo = H.languages[lang]; if (!lo.engineReady) { lo.cache = empty(); - //copy global exceptions to the language specific exceptions - if (H.c.exceptions.global !== undefined) { - if (H.c.exceptions[lang] !== undefined) { - H.c.exceptions[lang] += ", " + H.c.exceptions.global; + if (H.c.exceptions.global) { + if (H.c.exceptions[lang]) { + H.c.exceptions[lang] += `, ${H.c.exceptions.global}`; } else { H.c.exceptions[lang] = H.c.exceptions.global; } } - //move exceptions from the the local "exceptions"-obj to the "language"-object - if (H.c.exceptions[lang] !== undefined) { - lo.exceptions = convertExceptionsToObject(H.c.exceptions[lang]); + if (H.c.exceptions[lang]) { + lo.exceptions = convertExceptions(H.c.exceptions[lang]); delete H.c.exceptions[lang]; } else { lo.exceptions = empty(); @@ -396,52 +567,82 @@ lo.leftmin = leftmin; lo.rightmin = rightmin; lo.hyphenateFunction = hyphenateFunction; - C.classNames.forEach(function (cn) { + C.classNames.forEach(function eachClassName(cn) { const classSettings = C[cn]; - //merge leftmin/rightmin to config if (classSettings.leftminPerLang === 0) { - Object.defineProperty(classSettings, "leftminPerLang", setProp(empty(), 2)); + Object.defineProperty( + classSettings, + "leftminPerLang", + setProp(empty(), 2) + ); } if (classSettings.rightminPerLang === 0) { - Object.defineProperty(classSettings, "rightminPerLang", setProp(empty(), 2)); + Object.defineProperty( + classSettings, + "rightminPerLang", + setProp(empty(), 2) + ); } - if (classSettings.leftminPerLang[lang] === undefined) { - classSettings.leftminPerLang[lang] = Math.max(lo.leftmin, classSettings.leftmin); + if (classSettings.leftminPerLang[lang]) { + classSettings.leftminPerLang[lang] = Math.max( + lo.leftmin, + classSettings.leftmin, + classSettings.leftminPerLang[lang] + ); } else { - classSettings.leftminPerLang[lang] = Math.max(lo.leftmin, classSettings.leftmin, classSettings.leftminPerLang[lang]); + classSettings.leftminPerLang[lang] = Math.max( + lo.leftmin, + classSettings.leftmin + ); } - if (classSettings.rightminPerLang[lang] === undefined) { - classSettings.rightminPerLang[lang] = Math.max(lo.rightmin, classSettings.rightmin); + if (classSettings.rightminPerLang[lang]) { + classSettings.rightminPerLang[lang] = Math.max( + lo.rightmin, + classSettings.rightmin, + classSettings.rightminPerLang[lang] + ); } else { - classSettings.rightminPerLang[lang] = Math.max(lo.rightmin, classSettings.rightmin, classSettings.rightminPerLang[lang]); + classSettings.rightminPerLang[lang] = Math.max( + lo.rightmin, + classSettings.rightmin + ); } - lo.genRegExps[cn] = new RegExp("[\\w" + alphabet + String.fromCharCode(8204) + "-]{" + classSettings.minWordLength + ",}", "gi"); + lo.genRegExps[cn] = new RegExp(`[\\w${alphabet}${String.fromCharCode(8204)}-]{${classSettings.minWordLength},}`, "gi"); }); lo.engineReady = true; } - Hyphenopoly.events.dispatch("engineReady", {msg: lang}); + Hyphenopoly.events.dispatch("engineReady", {"msg": lang}); } + /** + * Calculate heap size for (w)asm + * wasm page size: 65536 = 64 Ki + * asm: http://asmjs.org/spec/latest/#linking-0 + * @param {number} targetSize The targetet Size + * @returns {number} The necessary heap size + */ function calculateHeapSize(targetSize) { - if (H.isWASMsupported) { - //wasm page size: 65536 = 64 Ki + /* eslint-disable no-bitwise */ + if (H.clientFeat.wasm) { return Math.ceil(targetSize / 65536) * 65536; - } else { - //http://asmjs.org/spec/latest/#linking-0 - const exp = Math.ceil(Math.log2(targetSize)); - if (exp <= 12) { - return 1 << 12; - } - if (exp < 24) { - return 1 << exp; - } - return Math.ceil(targetSize / (1 << 24)) * (1 << 24); } + const exp = Math.ceil(Math.log2(targetSize)); + if (exp <= 12) { + return 1 << 12; + } + if (exp < 24) { + return 1 << exp; + } + return Math.ceil(targetSize / (1 << 24)) * (1 << 24); + /* eslint-enable no-bitwise */ } + /** + * Polyfill for TextDecoder + */ const decode = (function makeDecoder() { - let decoder; - if (window.TextDecoder !== undefined) { + let decoder = null; + if (window.TextDecoder) { const utf16ledecoder = new TextDecoder("utf-16le"); decoder = function (ui16) { return utf16ledecoder.decode(ui16); @@ -460,118 +661,135 @@ return decoder; }()); + /** + * Calculate Base Data + * + * Build Heap (the heap object's byteLength must be + * either 2^n for n in [12, 24) + * or 2^24 · n for n ≥ 1;) + * + * MEMORY LAYOUT: + * + * -------------------- <- Offset 0 + * | translateMap | + * | keys: | + * |256 chars * 2Bytes| + * | + | + * | values: | + * |256 chars * 1Byte | + * -------------------- <- 768 Bytes + * | alphabet | + * |256 chars * 2Bytes| + * -------------------- <- valueStoreOffset = 1280 + * | valueStore | + * | 1 Byte | + * |* valueStoreLength| + * -------------------- + * | align to 4Bytes | + * -------------------- <- patternTrieOffset + * | patternTrie | + * | 4 Bytes | + * |*patternTrieLength| + * -------------------- <- wordOffset + * | wordStore | + * | Uint16[64] | 128 bytes + * -------------------- <- translatedWordOffset + * | transl.WordStore | + * | Uint16[64] | 128 bytes + * -------------------- <- hyphenPointsOffset + * | hyphenPoints | + * | Uint8[64] | 64 bytes + * -------------------- <- hyphenatedWordOffset + * | hyphenatedWord | + * | Uint16[128] | 256 Bytes + * -------------------- <- hpbOffset - + * | HEADER | | + * | 6*4 Bytes | | + * | 24 Bytes | | + * -------------------- | + * | PATTERN LIC | | + * | variable Length | | + * -------------------- | + * | align to 4Bytes | } this is the .hpb-file + * -------------------- <- hpbTranslateOffset | + * | TRANSLATE | | + * | 2 + [0] * 2Bytes | | + * -------------------- <- hpbPatternsOffset | + * | PATTERNS | | + * | patternsLength | | + * -------------------- <- heapEnd - + * | align to 4Bytes | + * -------------------- <- heapSize + * @param {Object} hpbBuf FileBuffer from .hpb-file + * @returns {Object} baseData-object + */ function calculateBaseData(hpbBuf) { - /* Build Heap (the heap object's byteLength must be either 2^n for n in [12, 24) or 2^24 · n for n ≥ 1;) - * NEW MEMORY LAYOUT: - * - * -------------------- <- Offset 0 - * | translateMap | - * | keys: | - * |256 chars * 2Bytes| - * | + | - * | values: | - * |256 chars * 1Byte | - * -------------------- <- 768 Bytes - * | alphabet | - * |256 chars * 2Bytes| - * -------------------- <- valueStoreOffset = 1280 - * | valueStore | - * | 1 Byte | - * |* valueStoreLength| - * -------------------- - * | align to 4Bytes | - * -------------------- <- patternTrieOffset - * | patternTrie | - * | 4 Bytes | - * |*patternTrieLength| - * -------------------- <- wordOffset - * | wordStore | - * | Uint16[64] | 128 bytes - * -------------------- <- translatedWordOffset - * | transl.WordStore | - * | Uint16[64] | 128 bytes - * -------------------- <- hyphenPointsOffset - * | hyphenPoints | - * | Uint8[64] | 64 bytes - * -------------------- <- hyphenatedWordOffset - * | hyphenatedWord | - * | Uint16[128] | 256 Bytes - * -------------------- <- hpbOffset - - * | HEADER | | - * | 6*4 Bytes | | - * | 24 Bytes | | - * -------------------- | - * | PATTERN LIC | | - * | variable Length | | - * -------------------- | - * | align to 4Bytes | } this is the .hpb-file - * -------------------- <- hpbTranslateOffset | - * | TRANSLATE | | - * | 2 + [0] * 2Bytes | | - * -------------------- <- hpbPatternsOffset | - * | PATTERNS | | - * | patternsLength | | - * -------------------- <- heapEnd - - * | align to 4Bytes | - * -------------------- <- heapSize - */ - - /* - * [0]: magic number 0x01627068 (\hpb1, 1 is the version) - * [1]: TRANSLATE offset (to skip LICENSE) - * [2]: PATTERNS offset (skip LICENSE + TRANSLATE) - * [3]: patternlength (bytes) - * [4]: leftmin - * [5]: rightmin - * [6]: Trie Array Size (needed to preallocate memory) - * [7]: Values Size (needed to preallocate memory) - */ const hpbMetaData = new Uint32Array(hpbBuf).subarray(0, 8); const valueStoreLength = hpbMetaData[7]; const valueStoreOffset = 1280; - const patternTrieOffset = valueStoreOffset + valueStoreLength + (4 - ((valueStoreOffset + valueStoreLength) % 4)); + const patternTrieOffset = valueStoreOffset + valueStoreLength + + (4 - ((valueStoreOffset + valueStoreLength) % 4)); const wordOffset = patternTrieOffset + (hpbMetaData[6] * 4); return { - valueStoreOffset: valueStoreOffset, - patternTrieOffset: patternTrieOffset, - wordOffset: wordOffset, - translatedWordOffset: wordOffset + 128, - hyphenPointsOffset: wordOffset + 256, - hyphenatedWordOffset: wordOffset + 320, - hpbOffset: wordOffset + 576, - hpbTranslateOffset: wordOffset + 576 + hpbMetaData[1], - hpbPatternsOffset: wordOffset + 576 + hpbMetaData[2], - heapSize: Math.max(calculateHeapSize(wordOffset + 576 + hpbMetaData[2] + hpbMetaData[3]), 32 * 1024 * 64), - leftmin: hpbMetaData[4], - rightmin: hpbMetaData[5], - patternsLength: hpbMetaData[3] + "heapSize": Math.max(calculateHeapSize(wordOffset + 576 + hpbMetaData[2] + hpbMetaData[3]), 32 * 1024 * 64), + "hpbOffset": wordOffset + 576, + "hpbPatternsOffset": wordOffset + 576 + hpbMetaData[2], + "hpbTranslateOffset": wordOffset + 576 + hpbMetaData[1], + "hyphenatedWordOffset": wordOffset + 320, + "hyphenPointsOffset": wordOffset + 256, + "leftmin": hpbMetaData[4], + "patternsLength": hpbMetaData[3], + "patternTrieOffset": patternTrieOffset, + "rightmin": hpbMetaData[5], + "translatedWordOffset": wordOffset + 128, + "valueStoreOffset": valueStoreOffset, + "wordOffset": wordOffset }; } + /** + * Create basic import Object + * @param {Object} baseData baseData + * @returns {Object} import object + */ function createImportObject(baseData) { return { - valueStoreOffset: baseData.valueStoreOffset, - patternTrieOffset: baseData.patternTrieOffset, - wordOffset: baseData.wordOffset, - translatedWordOffset: baseData.translatedWordOffset, - hyphenPointsOffset: baseData.hyphenPointsOffset, - hyphenatedWordOffset: baseData.hyphenatedWordOffset, - hpbTranslateOffset: baseData.hpbTranslateOffset, - hpbPatternsOffset: baseData.hpbPatternsOffset, - patternsLength: baseData.patternsLength + "hpbPatternsOffset": baseData.hpbPatternsOffset, + "hpbTranslateOffset": baseData.hpbTranslateOffset, + "hyphenatedWordOffset": baseData.hyphenatedWordOffset, + "hyphenPointsOffset": baseData.hyphenPointsOffset, + "patternsLength": baseData.patternsLength, + "patternTrieOffset": baseData.patternTrieOffset, + "translatedWordOffset": baseData.translatedWordOffset, + "valueStoreOffset": baseData.valueStoreOffset, + "wordOffset": baseData.wordOffset }; } + /** + * Setup env for hyphenateFunction + * @param {Object} baseData baseData + * @param {function} hyphenateFunc hyphenateFunction + * @returns {function} hyphenateFunction with closured environment + */ function encloseHyphenateFunction(baseData, hyphenateFunc) { - const heapBuffer = H.isWASMsupported + /* eslint-disable no-bitwise */ + const heapBuffer = H.clientFeat.wasm ? baseData.wasmMemory.buffer : baseData.heapBuffer; const wordOffset = baseData.wordOffset; const hyphenatedWordOffset = baseData.hyphenatedWordOffset; - const wordStore = (new Uint16Array(heapBuffer)).subarray(wordOffset >> 1, (wordOffset >> 1) + 64); + const wordStore = (new Uint16Array(heapBuffer)).subarray( + wordOffset >> 1, + (wordOffset >> 1) + 64 + ); const defLeftmin = baseData.leftmin; const defRightmin = baseData.rightmin; - const hyphenatedWordStore = (new Uint16Array(heapBuffer)).subarray(hyphenatedWordOffset >> 1, (hyphenatedWordOffset >> 1) + 64); + const hyphenatedWordStore = (new Uint16Array(heapBuffer)).subarray( + hyphenatedWordOffset >> 1, + (hyphenatedWordOffset >> 1) + 64 + ); + /* eslint-enable no-bitwise */ return function hyphenate(word, hyphenchar, leftmin, rightmin) { let i = 0; const wordLength = word.length; @@ -600,34 +818,51 @@ }; } + /** + * Instantiate Wasm Engine + * @param {string} lang The language + * @returns {undefined} + */ function instantiateWasmEngine(lang) { Promise.all([H.binaries[lang], H.binaries.hyphenEngine]).then( function onAll(binaries) { const hpbBuf = binaries[0]; const baseData = calculateBaseData(hpbBuf); const wasmModule = binaries[1]; - const wasmMemory = (H.specMems[lang].buffer.byteLength >= baseData.heapSize) + const wasmMemory = ( + H.specMems[lang].buffer.byteLength >= baseData.heapSize + ) ? H.specMems[lang] : new WebAssembly.Memory({ - initial: baseData.heapSize / 65536, - maximum: 256 + "initial": baseData.heapSize / 65536, + "maximum": 256 }); const ui32wasmMemory = new Uint32Array(wasmMemory.buffer); - ui32wasmMemory.set(new Uint32Array(hpbBuf), baseData.hpbOffset >> 2); + ui32wasmMemory.set( + new Uint32Array(hpbBuf), + // eslint-disable-next-line no-bitwise + baseData.hpbOffset >> 2 + ); baseData.wasmMemory = wasmMemory; WebAssembly.instantiate(wasmModule, { - ext: createImportObject(baseData), - env: { - memory: baseData.wasmMemory, - memoryBase: 0 - } + "env": { + "memory": baseData.wasmMemory, + "memoryBase": 0 + }, + "ext": createImportObject(baseData) }).then( function runWasm(result) { result.exports.convert(); prepareLanguagesObj( lang, - encloseHyphenateFunction(baseData, result.exports.hyphenate), - decode((new Uint16Array(wasmMemory.buffer)).subarray(384, 640)), + encloseHyphenateFunction( + baseData, + result.exports.hyphenate + ), + decode( + (new Uint16Array(wasmMemory.buffer)). + subarray(384, 640) + ), baseData.leftmin, baseData.rightmin ); @@ -637,10 +872,17 @@ ); } + /** + * Instantiate asm Engine + * @param {string} lang The language + * @returns {undefined} + */ function instantiateAsmEngine(lang) { const hpbBuf = H.binaries[lang]; const baseData = calculateBaseData(hpbBuf); - const heapBuffer = (H.specMems[lang].byteLength >= baseData.heapSize) + const heapBuffer = ( + H.specMems[lang].byteLength >= baseData.heapSize + ) ? H.specMems[lang] : new ArrayBuffer(baseData.heapSize); const ui8Heap = new Uint8Array(heapBuffer); @@ -649,10 +891,10 @@ baseData.heapBuffer = heapBuffer; const theHyphenEngine = asmHyphenEngine( { - Uint8Array: window.Uint8Array, - Uint16Array: window.Uint16Array, - Int32Array: window.Int32Array, - Math: Math + "Int32Array": window.Int32Array, + "Math": Math, + "Uint16Array": window.Uint16Array, + "Uint8Array": window.Uint8Array }, createImportObject(baseData), baseData.heapBuffer @@ -667,8 +909,15 @@ ); } - let engineInstantiator; + let engineInstantiator = null; const hpb = []; + + /** + * Instantiate hyphenEngines for languages + * @param {string} lang The language + * @param {string} engineType The engineType: "wasm" or "asm" + * @returns {undefined} + */ function prepare(lang, engineType) { if (lang === "*") { if (engineType === "wasm") { @@ -676,21 +925,19 @@ } else if (engineType === "asm") { engineInstantiator = instantiateAsmEngine; } - hpb.forEach(function (lang) { - engineInstantiator(lang); + hpb.forEach(function eachHbp(hpbLang) { + engineInstantiator(hpbLang); }); + } else if (engineInstantiator) { + engineInstantiator(lang); } else { - if (engineInstantiator) { - engineInstantiator(lang); - } else { - hpb.push(lang); - } + hpb.push(lang); } } H.events.define( "contentLoaded", - function () { + function def() { autoSetMainLanguage(); collectElements(); H.events.dispatch("elementsReady"); @@ -700,11 +947,14 @@ H.events.define( "elementsReady", - function () { - elements.each(function (lang, values) { - if (H.hasOwnProperty("languages") && H.languages.hasOwnProperty(lang) && H.languages[lang].engineReady) { + function def() { + elements.each(function eachElem(lang, values) { + if (H.languages && + H.languages[lang] && + H.languages[lang].engineReady + ) { hyphenateLangElements(lang, values); - }//else wait for "engineReady"-evt + } }); }, false @@ -712,7 +962,7 @@ H.events.define( "engineLoaded", - function (e) { + function def(e) { prepare("*", e.msg); }, false @@ -720,19 +970,18 @@ H.events.define( "hpbLoaded", - function (e) { + function def(e) { prepare(e.msg, "*"); - //fires "engineReady", e.msg); }, false ); H.events.define( "engineReady", - function (e) { + function def(e) { if (H.elementsReady) { hyphenateLangElements(e.msg, elements.list[e.msg]); - } //else wait for "elementsReady"-evt + } }, false ); @@ -745,7 +994,7 @@ H.events.define( "hyphenopolyEnd", - function () { + function def() { w.clearTimeout(C.timeOutHandler); w.document.documentElement.style.visibility = "visible"; }, @@ -764,28 +1013,27 @@ true ); - //register temporary defined handlers let eo = H.events.tempRegister.shift(); while (eo) { - H.events.addListener(eo.name, eo.handler, true); + H.events.addListener(eo.name, eo.handler, false); eo = H.events.tempRegister.shift(); } delete H.events.tempRegister; - H.events.dispatch("hyphenopolyStart", {msg: "Hyphenopoly started"}); + H.events.dispatch("hyphenopolyStart", {"msg": "Hyphenopoly started"}); - //clear Loader-timeout w.clearTimeout(H.setup.timeOutHandler); - //renew timeout for the case something fails - Object.defineProperty(C, "timeOutHandler", setProp(w.setTimeout(function () { - H.events.dispatch("timeout", {delay: C.timeout}); - }, C.timeout), 2)); - - //import and exec triggered events from loader - H.events.deferred.forEach(function (eo) { - H.events.dispatch(eo.name, eo.data); + + Object.defineProperty(C, "timeOutHandler", setProp( + w.setTimeout(function ontimeout() { + H.events.dispatch("timeout", {"delay": C.timeout}); + }, C.timeout), + 2 + )); + + H.events.deferred.forEach(function eachDeferred(deferredeo) { + H.events.dispatch(deferredeo.name, deferredeo.data); }); delete H.events.deferred; - - }(w)); + }()); }(window)); diff --git a/Hyphenopoly_Loader.js b/Hyphenopoly_Loader.js index 3b33d53d..034dd142 100644 --- a/Hyphenopoly_Loader.js +++ b/Hyphenopoly_Loader.js @@ -1,56 +1,79 @@ -/** @license Hyphenopoly_Loader 1.0.1 - client side hyphenation for webbrowsers +/* + * @license Hyphenopoly_Loader 2.0.0 - client side hyphenation * ©2018 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 */ -/*jslint browser*/ -/*global window, Hyphenopoly, fetch, WebAssembly*/ (function H9YL() { "use strict"; const d = document; const H = Hyphenopoly; - //polyfill Math.log2 - Math.log2 = Math.log2 || function (x) { - return Math.log(x) * Math.LOG2E; - }; - + /** + * Create Object without standard Object-prototype + * @returns {Object} empty object + */ function empty() { return Object.create(null); } + if (H.cacheFeatureTests && sessionStorage.getItem("Hyphenopoly_Loader")) { + H.clientFeat = JSON.parse(sessionStorage.getItem("Hyphenopoly_Loader")); + } else { + H.clientFeat = { + "langs": empty(), + "polyfill": false, + "wasm": null + }; + } + + const t = H.clientFeat; + (function setupEvents() { - //events known to the system + // Events known to the system const definedEvents = empty(); - //default events, execution deferred to Hyphenopoly.js + // Default events, execution deferred to Hyphenopoly.js const deferred = []; - //register for custom event handlers, where event is not yet defined - //these events will be correctly registered in Hyphenopoly.js + + /* + * 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[name] = { - default: defFunc, - cancellable: cancellable, - register: [] + "cancellable": cancellable, + "default": defFunc, + "register": [] }; } define( "timeout", - function (e) { + function def(e) { d.documentElement.style.visibility = "visible"; - window.console.info("Hyphenopolys 'flash of unhyphenated content'-prevention timed out after %dms", e.delay); + window.console.info( + "Hyphenopolys 'FOUHC'-prevention timed out after %dms", + e.delay + ); }, false ); define( "error", - function (e) { + function def(e) { window.console.error(e.msg); }, true @@ -58,10 +81,10 @@ define( "contentLoaded", - function (e) { + function def(e) { deferred.push({ - name: "contentLoaded", - data: e + "data": e, + "name": "contentLoaded" }); }, false @@ -69,10 +92,10 @@ define( "engineLoaded", - function (e) { + function def(e) { deferred.push({ - name: "engineLoaded", - data: e + "data": e, + "name": "engineLoaded" }); }, false @@ -80,49 +103,72 @@ define( "hpbLoaded", - function (e) { + function def(e) { deferred.push({ - name: "hpbLoaded", - data: e + "data": e, + "name": "hpbLoaded" }); }, false ); + /** + * Dispatch error with arguments + * @param {string} name The name of the event + * @param {Object|undefined} data Data of the event + * @returns {undefined} + */ function dispatch(name, data) { if (!data) { data = empty(); } - data.defaultPrevented = false; - data.preventDefault = function () { - if (definedEvents[name].cancellable) { - data.defaultPrevented = true; - } - }; - definedEvents[name].register.forEach(function (currentHandler) { + let defaultHasRun = false; + definedEvents[name].register.forEach(function call(currentHandler) { + let defaultPrevented = false; + data.preventDefault = function preventDefault() { + if (definedEvents[name].cancellable) { + defaultPrevented = true; + } + }; currentHandler(data); + if (!defaultPrevented && + !defaultHasRun && + definedEvents[name].default) { + definedEvents[name].default(data); + defaultHasRun = true; + } }); - if (!data.defaultPrevented && definedEvents[name].default) { + if (!defaultHasRun && definedEvents[name].default) { definedEvents[name].default(data); } } - function addListener(name, handler, final) { + /** + * Add EventListender to event + * @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[name]) { definedEvents[name].register.push(handler); - } else if (!final) { + } else if (defer) { tempRegister.push({ - name: name, - handler: handler + "handler": handler, + "name": name }); } else { - H.events.dispatch("error", {msg: "unknown Event \"" + name + "\" discarded"}); + H.events.dispatch( + "error", + {"msg": `unknown Event "${name}" discarded`} + ); } } if (H.handleEvent) { - Object.keys(H.handleEvent).forEach(function (name) { - addListener(name, H.handleEvent[name], false); + Object.keys(H.handleEvent).forEach(function add(name) { + addListener(name, H.handleEvent[name], true); }); } @@ -132,44 +178,59 @@ H.events.dispatch = dispatch; H.events.define = define; H.events.addListener = addListener; - }()); - //normal wasm feature-test - /*const isWASMsupported = (function featureTestWASM() { - if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.Module(Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0])); - if (WebAssembly.Module.prototype.isPrototypeOf(module)) { - return WebAssembly.Instance.prototype.isPrototypeOf(new WebAssembly.Instance(module)); + (function featureTestWasm() { + /* eslint-disable max-len, no-magic-numbers, no-prototype-builtins */ + /** + * Feature test for wasm + * @returns {boolean} support + */ + function runWasmTest() { + /* + * This is the original test, without webkit workaround + * if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { + * const module = new WebAssembly.Module(Uint8Array.from([0, 97, 115, 109, 1, 0, 0, 0])); + * if (WebAssembly.Module.prototype.isPrototypeOf(module)) { + * return WebAssembly.Instance.prototype.isPrototypeOf(new WebAssembly.Instance(module)); + * } + * } + * return false; + */ + + // Wasm feature test with iOS bug detection (https://bugs.webkit.org/show_bug.cgi?id=181781) + if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.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])); + if (WebAssembly.Module.prototype.isPrototypeOf(module)) { + const inst = new WebAssembly.Instance(module); + return WebAssembly.Instance.prototype.isPrototypeOf(inst) && (inst.exports.test(4) !== 0); + } } + return false; } - return false; - }());*/ - - - //wasm feature test with iOS bug detection (https://bugs.webkit.org/show_bug.cgi?id=181781) - const isWASMsupported = (function featureTestWASM() { - if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.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])); - if (WebAssembly.Module.prototype.isPrototypeOf(module)) { - const inst = new WebAssembly.Instance(module); - return WebAssembly.Instance.prototype.isPrototypeOf(inst) && (inst.exports.test(4) !== 0); - } + /* eslint-enable max-len, no-magic-numbers, no-prototype-builtins */ + if (t.wasm === null) { + t.wasm = runWasmTest(); } - return false; }()); - - const scriptLoader = (function () { + const scriptLoader = (function scriptLoader() { const loadedScripts = empty(); + + /** + * Load script by adding + + + + + + +

Test 020

+

Inherit language

+
+
+

exception

+

ex•cep•tion

+

beinhalten

+

be•inhal•ten

+

exception

+

ex•cep•tion

+

exception

+

ex•cep•tion

+
+
Test Ref
+ + + \ No newline at end of file diff --git a/testsuite/test21.html b/testsuite/test21.html new file mode 100644 index 00000000..d7ee23a6 --- /dev/null +++ b/testsuite/test21.html @@ -0,0 +1,109 @@ + + + + + Test 021 + + + + + + + + +

Test 021

+

Register multiple error handlers (also watch console)

+
+
+

1: en-us

+

hyphenation algorithm

+

hy•phen•ation al•go•rithm

+ +

2: de

+

Silbentrennungsalgorithmus

+

Sil•ben•tren•nungs•al•go•rith•mus

+
+
Test Ref
+ + + \ No newline at end of file diff --git a/testsuite/test22.html b/testsuite/test22.html new file mode 100644 index 00000000..608390d1 --- /dev/null +++ b/testsuite/test22.html @@ -0,0 +1,93 @@ + + + + + Test 022 + + + + + + + +

Test 022

+

Normalize combined characters (not supported in all clients)

+
+
+

Bärentöter Bärentöter

+

Bä•ren•tö•ter Bä•ren•tö•ter

+ +
+
Test Ref
+ + + \ No newline at end of file diff --git a/testsuite/test3.html b/testsuite/test3.html index 72c4452b..a2b0cdd3 100644 --- a/testsuite/test3.html +++ b/testsuite/test3.html @@ -9,6 +9,10 @@ require: { "en-us": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, handleEvent: { hyphenopolyEnd: function (e) { assert(); diff --git a/testsuite/test4.html b/testsuite/test4.html index 18f63977..245f7492 100644 --- a/testsuite/test4.html +++ b/testsuite/test4.html @@ -9,6 +9,10 @@ require: { "en-us": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, handleEvent: { hyphenopolyStart: function () { startTime = performance.now(); diff --git a/testsuite/test5.html b/testsuite/test5.html index 56c9e3e4..36d3c0b0 100644 --- a/testsuite/test5.html +++ b/testsuite/test5.html @@ -32,6 +32,10 @@ "et": "FORCEHYPHENOPOLY", "ga": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, setup: { classnames: { "hyphenate": { diff --git a/testsuite/test6.html b/testsuite/test6.html index 717f67c3..9c5c327f 100644 --- a/testsuite/test6.html +++ b/testsuite/test6.html @@ -13,6 +13,10 @@ "uk": "FORCEHYPHENOPOLY", "ka": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, setup: { classnames: { "hyphenate": { diff --git a/testsuite/test7.html b/testsuite/test7.html index 75701e36..3bf8ce22 100644 --- a/testsuite/test7.html +++ b/testsuite/test7.html @@ -8,6 +8,10 @@ require: { "en-us": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, handleEvent: { hyphenopolyEnd: function (e) { assert(); diff --git a/testsuite/test8.html b/testsuite/test8.html index c97963f3..47b1a7d4 100644 --- a/testsuite/test8.html +++ b/testsuite/test8.html @@ -8,6 +8,10 @@ require: { "en-us": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, setup: { classnames: { "class1": { diff --git a/testsuite/test9.html b/testsuite/test9.html index c6ddf132..1113de4f 100644 --- a/testsuite/test9.html +++ b/testsuite/test9.html @@ -8,6 +8,10 @@ require: { "en-us": "FORCEHYPHENOPOLY" }, + paths: { + maindir: "../", + patterndir: "../patterns/" + }, setup: { classnames: { "class1": { diff --git a/testsuite/testdriver.js b/testsuite/testdriver.js index 05bf1cab..3e77290a 100644 --- a/testsuite/testdriver.js +++ b/testsuite/testdriver.js @@ -24,7 +24,10 @@ {exec: true, path: "test16.html"}, {exec: true, path: "test17.html"}, {exec: true, path: "test18.html"}, - {exec: true, path: "test19.html"} + {exec: true, path: "test19.html"}, + {exec: true, path: "test20.html"}, + {exec: true, path: "test21.html"}, + {exec: true, path: "test22.html"} ]; var testframe = document.getElementById("testframe"); var currentTest = 1; diff --git a/tools/compileWASM.sh b/tools/compileWASM.sh index 5ffc600a..e0bd555c 100644 --- a/tools/compileWASM.sh +++ b/tools/compileWASM.sh @@ -14,7 +14,7 @@ asm2wasm $SRCFILE -Oz -m 2097152 -mm 16777216 > $WASTNAME echo 'optimize > WASM...' wasm-opt $WASTNAME -O3 -o $WASMNAME -#rm $WASTNAME +rm $WASTNAME -echo 'disassemble WASM...' -wasm2wat $WASMNAME > $DISNAME +#echo 'disassemble WASM...' +#wasm2wat $WASMNAME > $DISNAME diff --git a/tools/minify.sh b/tools/minify.sh new file mode 100644 index 00000000..32076d86 --- /dev/null +++ b/tools/minify.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# -*- coding: utf-8 -*- + +mkdir -p min + +uglifyjs Hyphenopoly_Loader.js -o min/Hyphenopoly_Loader.js --comments -c -m --warn +uglifyjs Hyphenopoly.js -o min/Hyphenopoly.js --comments -c -m --warn +uglifyjs hyphenEngine.asm.js -o min/hyphenEngine.asm.js --comments -c -m --warn --verbose + +cp hyphenEngine.wasm min/hyphenEngine.wasm +cp -R patterns min/patterns +cp -R testsuite min/testsuite \ No newline at end of file