diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b66da41..fb46006e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,59 +1,55 @@ # Version History +## Version 2.6.1 (Jan 09, 2018) +* dontHyphenateClass is configurable (issue #48) +* fixed issue with StringDecoder in older node versions (issue #45) +* small refactorings for smaller codesize +* fixed a StateError in IE 11 + ## Version 2.6.0 (Dec 01, 2018) -* improve hiding of elements while hyphenating (issue #40) -* fix several issues with lang-fallbacks (issue #41 and #44) -* new feature: use selectors instead of classnames (issue #42) -* updated german patterns (issue #45) +* improve hiding of elements while hyphenating (issue #40) +* fix several issues with lang-fallbacks (issue #41 and #44) +* new feature: use selectors instead of classnames (issue #42) +* updated german patterns (issue #45) ## Version 2.5.1 (Nov 04, 2018) -* remove "Church Slavonic" patterns (see #38) -* fix issue #39 +* remove "Church Slavonic" patterns (see #38) +* fix issue #39 ## Version 2.5.0 (Oct 02, 2018) -### Hyphenopoly_Loader.js and Hyphenopoly.js: -* fix issues with very long word (#33 and #34) -* Hyphenopoly exposes [Hyphenators](https://github.com/mnater/Hyphenopoly/wiki/Hyphenators) - -### hyphenopoly.module.js: -* fix issues with very long word (#33 and #34) -* hyphenopoly.module.js is now [easy to use with browserify](https://github.com/mnater/Hyphenopoly/wiki/browserify) +* fix issues with very long word (#33 and #34) +* Hyphenopoly exposes [Hyphenators](https://github.com/mnater/Hyphenopoly/wiki/Hyphenators) +* fix issues with very long word (#33 and #34) +* hyphenopoly.module.js is now [easy to use with browserify](https://github.com/mnater/Hyphenopoly/wiki/browserify) ## Version 2.4.0 (Sept 01, 2018) -### Hyphenopoly_Loader.js and Hyphenopoly.js: -* Implement fallback mechanism for language subtags where no patterns are available (e.g. en-au -> en-gb) [#29](https://github.com/mnater/Hyphenopoly/issues/29) -* updated patterns for Thai (https://github.com/hyphenation/tex-hyphen/pull/25) - -### hyphenopoly.module.js: -* updated patterns for Thai (https://github.com/hyphenation/tex-hyphen/pull/25) +* Implement fallback mechanism for language subtags where no patterns are available (e.g. en-au -> en-gb) [#29](https://github.com/mnater/Hyphenopoly/issues/29) +* updated patterns for Thai [#25](https://github.com/hyphenation/tex-hyphen/pull/25) ## Version 2.3.0 (Juli 26, 2018) -### Hyphenopoly_Loader.js and Hyphenopoly.js: -* Don't use template strings [#28](https://github.com/mnater/Hyphenopoly/issues/28) -* run feature test for wasm support only if necessary - -### hyphenopoly.module.js: -* define node >=8.3.0 as requirement (for util.TextDecoder) -* small refactorings +* Don't use template strings [#28](https://github.com/mnater/Hyphenopoly/issues/28) +* run feature test for wasm support only if necessary +* define node >=8.3.0 as requirement (for util.TextDecoder) +* small refactorings ## Version 2.2.0 (June 26, 2018) -* provide example.js for RunKit -* use tap instead of mocha -* [6f9e539](https://github.com/mnater/Hyphenopoly/commit/6f9e539a5dab2d1eff5bdeb0c7857c6fda9eb41e) -* bugfix: [#24](https://github.com/mnater/Hyphenopoly/issues/24): [aeefe6e](https://github.com/mnater/Hyphenopoly/commit/aeefe6e3a59e8356abc99ca490acabf6c3374d7b) +* provide example.js for RunKit +* use tap instead of mocha +* [6f9e539](https://github.com/mnater/Hyphenopoly/commit/6f9e539a5dab2d1eff5bdeb0c7857c6fda9eb41e) +* bugfix: [#24](https://github.com/mnater/Hyphenopoly/issues/24): [aeefe6e](https://github.com/mnater/Hyphenopoly/commit/aeefe6e3a59e8356abc99ca490acabf6c3374d7b) ## Version 2.1.0 (Mai 27, 2018) -* Configure Travis-CI -* bugfixes +* Configure Travis-CI +* bugfixes ## 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) +* 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 [devDependencies](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. diff --git a/Hyphenopoly.js b/Hyphenopoly.js index e9a20d1c..e0d19b40 100644 --- a/Hyphenopoly.js +++ b/Hyphenopoly.js @@ -71,7 +71,7 @@ "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"; + const list = "abbr,acronym,audio,br,button,code,img,input,kbd,label,math,option,pre,samp,script,style,sub,sup,svg,textarea,var,video"; list.split(",").forEach(function add(value) { r[value] = true; }); @@ -267,7 +267,7 @@ elements = makeElementCollection(); const dontHyphenateSelector = (function createSel() { - let s = ".donthyphenate"; + let s = "." + H.c.dontHyphenateClass; let k = null; for (k in C.dontHyphenate) { if (C.dontHyphenate[k]) { @@ -349,8 +349,8 @@ let i = 0; let wordHyphenator = null; let hw = word; - switch (classSettings.compound) { - case "auto": + if (classSettings.compound === "auto" || + classSettings.compound === "all") { parts = word.split("-"); wordHyphenator = createWordHyphenator(lo, lang, sel); while (i < parts.length) { @@ -359,20 +359,12 @@ } i += 1; } - hw = parts.join("-"); - break; - case "all": - parts = word.split("-"); - wordHyphenator = createWordHyphenator(lo, lang, sel); - while (i < parts.length) { - if (parts[i].length >= classSettings.minWordLength) { - parts[i] = wordHyphenator(parts[i]); - } - i += 1; + if (classSettings.compound === "auto") { + hw = parts.join("-"); + } else { + hw = parts.join("-" + zeroWidthSpace); } - hw = parts.join("-" + zeroWidthSpace); - break; - default: + } else { hw = word.replace("-", "-" + zeroWidthSpace); } return hw; @@ -696,13 +688,7 @@ }; } else { decoder = function (ui16) { - let i = 0; - let str = ""; - while (i < ui16.length) { - str += String.fromCharCode(ui16[i]); - i += 1; - } - return str; + return String.fromCharCode.apply(null, ui16); }; } return decoder; @@ -794,25 +780,6 @@ }; } - /** - * Create basic import Object - * @param {Object} baseData baseData - * @returns {Object} import object - */ - function createImportObject(baseData) { - return { - "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 @@ -832,7 +799,7 @@ ); const defLeftmin = baseData.leftmin; const defRightmin = baseData.rightmin; - const hyphenatedWordStore = (new Uint16Array(heapBuffer)).subarray( + const hydWrdStore = (new Uint16Array(heapBuffer)).subarray( hyphenatedWordOffset >> 1, (hyphenatedWordOffset >> 1) + 128 ); @@ -855,12 +822,7 @@ wordStore[i + 2] = 95; if (hyphenateFunc(leftmin, rightmin) === 1) { - i = 1; - word = ""; - while (i < hyphenatedWordStore[0] + 1) { - word += String.fromCharCode(hyphenatedWordStore[i]); - i += 1; - } + word = decode(hydWrdStore.subarray(1, hydWrdStore[0] + 1)); if (hyphenchar !== "\u00AD") { word = word.replace(/\u00AD/g, hyphenchar); } @@ -900,10 +862,10 @@ "memory": baseData.wasmMemory, "memoryBase": 0 }, - "ext": createImportObject(baseData) + "ext": baseData }).then( function runWasm(result) { - result.exports.convert(); + const alphalen = result.exports.convert(); prepareLanguagesObj( lang, encloseHyphenateFunction( @@ -912,7 +874,7 @@ ), decode( (new Uint16Array(wasmMemory.buffer)). - subarray(384, 640) + subarray(385, 384 + alphalen) ), baseData.leftmin, baseData.rightmin @@ -947,14 +909,17 @@ "Uint16Array": window.Uint16Array, "Uint8Array": window.Uint8Array }, - createImportObject(baseData), + baseData, baseData.heapBuffer ); - theHyphenEngine.convert(); + const alphalen = theHyphenEngine.convert(); prepareLanguagesObj( lang, encloseHyphenateFunction(baseData, theHyphenEngine.hyphenate), - decode((new Uint16Array(heapBuffer)).subarray(384, 640)), + decode( + (new Uint16Array(heapBuffer)). + subarray(385, 384 + alphalen) + ), baseData.leftmin, baseData.rightmin ); diff --git a/Hyphenopoly_Loader.js b/Hyphenopoly_Loader.js index 8999d68f..8d8bd785 100644 --- a/Hyphenopoly_Loader.js +++ b/Hyphenopoly_Loader.js @@ -20,6 +20,17 @@ return Object.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) { + Object.keys(obj).forEach(fn); + } + (function config() { // Set H.clientFeat (either from sessionStorage or empty) if (H.cacheFeatureTests && sessionStorage.getItem("Hyphenopoly_Loader")) { @@ -32,38 +43,28 @@ }; } // Set defaults for paths and setup - if (H.paths) { - if (!H.paths.patterndir) { - H.paths.patterndir = "../Hyphenopoly/patterns/"; - } - if (!H.paths.maindir) { - H.paths.maindir = "../Hyphenopoly/"; - } - } else { - H.paths = { - "maindir": "../Hyphenopoly/", - "patterndir": "../Hyphenopoly/patterns/" - }; + H.dfltPaths = Object.create({ + "maindir": "../Hyphenopoly/", + "patterndir": "../Hyphenopoly/patterns/" + }); + if (H.paths && H.paths.patterndir) { + H.dfltPaths.patterndir = H.paths.patterndir; + } + if (H.paths && H.paths.maindir) { + H.dfltPaths.maindir = H.paths.maindir; } if (H.setup) { - if (!H.setup.selectors) { - H.setup.selectors = empty(); - H.setup.selectors[".hyphenate"] = empty(); - } + H.setup.selectors = H.setup.selectors || {".hyphenate": {}}; if (H.setup.classnames) { - Object.keys(H.setup.classnames).forEach(function cn2sel(cn) { + eachKey(H.setup.classnames, function cn2sel(cn) { H.setup.selectors["." + cn] = H.setup.classnames[cn]; }); H.setup.classnames = null; delete H.setup.classnames; } - if (!H.setup.timeout) { - H.setup.timeout = 1000; - } - if (!H.setup.hide) { - H.setup.hide = "all"; - } + H.setup.timeout = H.setup.timeout || 1000; + H.setup.hide = H.setup.hide || "all"; } else { H.setup = { "hide": "all", @@ -72,12 +73,12 @@ }; } H.lcRequire = empty(); - Object.keys(H.require).forEach(function copyRequire(k) { + eachKey(H.require, function copyRequire(k) { H.lcRequire[k.toLowerCase()] = H.require[k]; }); if (H.fallbacks) { H.lcFallbacks = empty(); - Object.keys(H.fallbacks).forEach(function copyFallbacks(k) { + eachKey(H.fallbacks, function copyFallbacks(k) { H.lcFallbacks[k.toLowerCase()] = H.fallbacks[k].toLowerCase(); }); } @@ -90,24 +91,22 @@ stylesNode.parentNode.removeChild(stylesNode); } } else { + const vis = " {visibility: hidden !important}\n"; const sc = d.createElement("style"); sc.id = "H9Y_Styles"; switch (H.setup.hide) { case "all": - sc.innerHTML = "html {visibility: hidden !important}"; + sc.innerHTML = "html" + vis; break; case "element": - Object.keys(H.setup.selectors). - forEach(function eachSelector(sel) { - sc.innerHTML += sel + " {visibility: hidden !important}\n"; - }); - + eachKey(H.setup.selectors, function eachSelector(sel) { + sc.innerHTML += sel + vis; + }); break; case "text": - Object.keys(H.setup.selectors). - forEach(function eachSelector(sel) { - sc.innerHTML += sel + " {color: transparent !important}\n"; - }); + eachKey(H.setup.selectors, function eachSelector(sel) { + sc.innerHTML += sel + " {color: transparent !important}\n"; + }); break; default: sc.innerHTML = ""; @@ -203,9 +202,7 @@ * @returns {undefined} */ function dispatch(name, data) { - if (!data) { - data = empty(); - } + data = data || empty(); let defaultPrevented = false; definedEvents[name].register.forEach(function call(currentHandler) { data.preventDefault = function preventDefault() { @@ -247,7 +244,7 @@ } if (H.handleEvent) { - Object.keys(H.handleEvent).forEach(function add(name) { + eachKey(H.handleEvent, function add(name) { addListener(name, H.handleEvent[name], true); }); } @@ -265,7 +262,7 @@ * @returns {undefined} */ function featureTestWasm() { - /* eslint-disable max-len, no-magic-numbers, no-prototype-builtins */ + /* eslint-disable no-prototype-builtins */ /** * Feature test for wasm * @returns {boolean} support @@ -311,7 +308,7 @@ } return false; } - /* eslint-enable max-len, no-magic-numbers, no-prototype-builtins */ + /* eslint-enable no-prototype-builtins */ if (H.clientFeat.wasm === null) { H.clientFeat.wasm = runWasmTest(); } @@ -326,7 +323,7 @@ * @param {string} filename Filename of the script * @returns {undefined} */ - function loadScript(path, filename) { + return function loadScript(path, filename) { if (!loadedScripts[filename]) { const script = d.createElement("script"); loadedScripts[filename] = true; @@ -338,8 +335,7 @@ } d.head.appendChild(script); } - } - return loadScript; + }; }()); const loadedBins = empty(); @@ -396,11 +392,11 @@ if (!loadedBins[n]) { loadedBins[n] = true; const xhr = new XMLHttpRequest(); - xhr.open("GET", p + f); xhr.onload = function onload() { H.binaries[n] = xhr.response; H.events.dispatch(m[0], {"msg": m[1]}); }; + xhr.open("GET", p + f); xhr.responseType = "arraybuffer"; xhr.send(); } @@ -413,31 +409,22 @@ } /** - * Allocate memory for (w)asm + * 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) { - let wasmPages = 0; - switch (lang) { - case "nl": - wasmPages = 41; - break; - case "de": - wasmPages = 75; - break; - case "nb-no": - wasmPages = 92; - break; - case "hu": - wasmPages = 207; - break; - default: - wasmPages = 32; - } - if (!H.specMems) { - H.specMems = empty(); - } + const specVal = { + "de": 55, + "hu": 207, + "nb-no": 92, + "nl": 41 + }; + const wasmPages = specVal[lang] || 32; + H.specMems = H.specMems || empty(); if (H.clientFeat.wasm) { H.specMems[lang] = new WebAssembly.Memory({ "initial": wasmPages, @@ -459,35 +446,6 @@ } } - /** - * Load all ressources for a required and check if wasm is supported - * @param {string} lang The language - * @returns {undefined} - */ - function loadRessources(lang) { - let filename = lang + ".hpb"; - if (H.lcFallbacks && H.lcFallbacks[lang]) { - filename = H.lcFallbacks[lang] + ".hpb"; - } - if (!H.binaries) { - H.binaries = empty(); - } - featureTestWasm(); - scriptLoader(H.paths.maindir, "Hyphenopoly.js"); - if (H.clientFeat.wasm) { - binLoader( - H.paths.maindir, - "hyphenEngine.wasm", - "hyphenEngine", - ["engineLoaded", "wasm"] - ); - } else { - scriptLoader(H.paths.maindir, "hyphenEngine.asm.js"); - } - binLoader(H.paths.patterndir, filename, lang, ["hpbLoaded", lang]); - allocateMemory(lang); - } - (function featureTestCSSHyphenation() { const tester = (function tester() { let fakeBody = null; @@ -516,13 +474,11 @@ * @param {string} lang Language * @returns {undefined} */ - function createTest(lang) { + function create(lang) { if (H.clientFeat.langs[lang]) { return; } - if (!fakeBody) { - fakeBody = d.createElement("body"); - } + fakeBody = fakeBody || d.createElement("body"); const testDiv = d.createElement("div"); testDiv.lang = lang; testDiv.id = lang; @@ -536,7 +492,7 @@ * @param {Object} target Where to append fakeBody * @returns {Object|null} The body element or null, if no tests */ - function appendTests(target) { + function append(target) { if (fakeBody) { target.appendChild(fakeBody); return fakeBody; @@ -548,15 +504,15 @@ * Remove fakeBody * @returns {undefined} */ - function clearTests() { + function clear() { if (fakeBody) { fakeBody.parentNode.removeChild(fakeBody); } } return { - "appendTests": appendTests, - "clearTests": clearTests, - "createTest": createTest + "append": append, + "clear": clear, + "create": create }; }()); @@ -587,9 +543,7 @@ * @returns {undefined} */ function exposeHyphenateFunction(lang) { - if (!H.hyphenators) { - H.hyphenators = {}; - } + H.hyphenators = H.hyphenators || empty(); if (!H.hyphenators[lang]) { if (window.Promise) { H.hyphenators[lang] = new Promise(function pro(rs, rj) { @@ -622,25 +576,52 @@ } } - Object.keys(H.lcRequire).forEach(function doReqLangs(lang) { + /** + * Load all ressources for a required , check if wasm is supported + * and expose the hyphenate function. + * @param {string} lang The language + * @returns {undefined} + */ + function loadRessources(lang) { + let filename = lang + ".hpb"; + if (H.lcFallbacks && H.lcFallbacks[lang]) { + filename = H.lcFallbacks[lang] + ".hpb"; + } + H.binaries = H.binaries || empty(); + featureTestWasm(); + scriptLoader(H.dfltPaths.maindir, "Hyphenopoly.js"); + if (H.clientFeat.wasm) { + binLoader( + H.dfltPaths.maindir, + "hyphenEngine.wasm", + "hyphenEngine", + ["engineLoaded", "wasm"] + ); + } else { + scriptLoader(H.dfltPaths.maindir, "hyphenEngine.asm.js"); + } + binLoader(H.dfltPaths.patterndir, filename, lang, ["hpbLoaded", lang]); + allocateMemory(lang); + exposeHyphenateFunction(lang); + } + + eachKey(H.lcRequire, function doReqLangs(lang) { if (H.lcRequire[lang] === "FORCEHYPHENOPOLY") { H.clientFeat.polyfill = true; H.clientFeat.langs[lang] = "H9Y"; loadRessources(lang); - exposeHyphenateFunction(lang); } else if ( H.clientFeat.langs[lang] && H.clientFeat.langs[lang] === "H9Y" ) { loadRessources(lang); - exposeHyphenateFunction(lang); } else { - tester.createTest(lang); + tester.create(lang); } }); - const testContainer = tester.appendTests(d.documentElement); + const testContainer = tester.append(d.documentElement); if (testContainer !== null) { - Object.keys(H.lcRequire).forEach(function checkReqLangs(lang) { + eachKey(H.lcRequire, function checkReqLangs(lang) { if (H.lcRequire[lang] !== "FORCEHYPHENOPOLY") { const el = d.getElementById(lang); if (checkCSSHyphensSupport(el) && el.offsetHeight > 12) { @@ -649,45 +630,42 @@ H.clientFeat.polyfill = true; H.clientFeat.langs[lang] = "H9Y"; loadRessources(lang); - exposeHyphenateFunction(lang); } } }); - tester.clearTests(); + tester.clear(); } }()); - (function run() { - if (H.clientFeat.polyfill) { - if (H.setup.hide === "all") { - H.toggle("off"); - } - if (H.setup.hide !== "none") { - H.setup.timeOutHandler = window.setTimeout(function timedOut() { - H.toggle("on"); - H.events.dispatch("timeout", {"delay": H.setup.timeout}); - }, H.setup.timeout); - } - d.addEventListener( - "DOMContentLoaded", - function DCL() { - if (H.setup.hide !== "none" && H.setup.hide !== "all") { - H.toggle("off"); - } - H.events.dispatch( - "contentLoaded", - {"msg": ["contentLoaded"]} - ); - }, - { - "once": true, - "passive": true - } - ); - } else { - window.Hyphenopoly = null; + if (H.clientFeat.polyfill) { + if (H.setup.hide === "all") { + H.toggle("off"); } - }()); + if (H.setup.hide !== "none") { + H.setup.timeOutHandler = window.setTimeout(function timedOut() { + H.toggle("on"); + H.events.dispatch("timeout", {"delay": H.setup.timeout}); + }, H.setup.timeout); + } + d.addEventListener( + "DOMContentLoaded", + function DCL() { + if (H.setup.hide.match(/^(element|text)$/)) { + H.toggle("off"); + } + H.events.dispatch( + "contentLoaded", + {"msg": ["contentLoaded"]} + ); + }, + { + "once": true, + "passive": true + } + ); + } else { + window.Hyphenopoly = null; + } if (H.cacheFeatureTests) { sessionStorage.setItem( diff --git a/README.md b/README.md index d9e8353d..e11ee3b6 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ Hyphenopoly.js is a __JavaScript-polyfill for hyphenation in HTML__: it hyphenates text if the user agent does not support CSS-hyphenation at all or not for the required languages and it is a __Node.js-module__. The package consists of the following parts: -- _Hyphenopoly_Loader.js_ (~24KB unpacked, ~3KB minified and compressed): feature-checks the client and loads other resources if necessary. -- _Hyphenopoly.js_ (~41KB unpacked, ~4KB minified and compressed): does the whole DOM-foo and wraps (w)asm. -- _hyphenEngine.wasm_ (~1KB uncompressed): wasm code for creating pattern trie and finding hyphenation points. -- _hyphenEngine.asm.js_ (~10KB uncompressed, ~1KB minified and compressed): fallback for clients that don't support wasm. -- _pattern.hpb_ (sizes differ! e.g. en-us.hpb: ~27KB uncompressed, ~16KB compressed): space saving binary format of the hyphenation patterns (including their license). -- _hyphenopoly.module.js_: the node module - -# Usage (Browser) +- _Hyphenopoly_Loader.js_ (~24KB unpacked, ~3KB minified and compressed): feature-checks the client and loads other resources if necessary. +- _Hyphenopoly.js_ (~41KB unpacked, ~4KB minified and compressed): does the whole DOM-foo and wraps (w)asm. +- _hyphenEngine.wasm_ (~1KB uncompressed): wasm code for creating pattern trie and finding hyphenation points. +- _hyphenEngine.asm.js_ (~10KB uncompressed, ~1KB minified and compressed): fallback for clients that don't support wasm. +- _pattern.hpb_ (sizes differ! e.g. en-us.hpb: ~27KB uncompressed, ~16KB compressed): space saving binary format of the hyphenation patterns (including their license). +- _hyphenopoly.module.js_: the node module + +## Usage (Browser) Place all code for Hyphenopoly at the top of the header (immediately after the `` tag) to ensure resources are loaded as early as possible. You'll have to insert two script blocks. In the first block provide the initial configurations for Hyphenopoly_Loader as inline script. In the second block load Hyphenopoly_Loader.js as external script. Also, don't forget to enable CSS hyphenation. @@ -68,32 +68,31 @@ Also, don't forget to enable CSS hyphenation. ``` Let's go through this example step by step: -## UTF-8 +### UTF-8 Make sure your page is encoded as utf-8. -## First script block – configurations +### First script block – configurations Hyphenopoly_Loader.js needs some information to run. This information is provided in a globally accessible Object called `Hyphenopoly`. Hyphenopoly_Loader.js and (if necessary) Hyphenopoly.js will add other methods and properties only to this object – there will be no other global variables/functions beyond this object. -### require +#### require The `Hyphenopoly` object must have exactly one property called `require` which itself is an object containing at least one nameValuePair where the name is a language code string (Some patterns are region-specific. See the patterns directory for supported languages. E.g. just using `en` won't work, use either `en-us`or `en-gb`) and the value is a long word string in that language (preferably more than 12 characters long). Hyphenator_Loader.js will feature test the client (aka browser, aka user agent) for CSS-hyphens support for the given languages with the given words respectively. In the example above it will test if the client supports CSS-hyphenation for latin, german and us-english. If you want to force the usage of Hyphenopoly.js for a language (e.g. for testing purposes) write `"FORCEHYPHENOPOLY"` instead of the long word. - -## Second script block – load and run Hyphenopoly_Loader.js +### Second script block – load and run Hyphenopoly_Loader.js Hyphenopoly_Loader.js tests if the browser supports CSS hyphenation for the language(s) given in `Hyphenopoly.require`. If one of the given languages isn't supported it automatically hides the documents contents and loads Hyphenopoly.js and the necessary patterns. Hyphenopoly.js – once loaded – will hyphenate the elements according to the settings and unhide the document when it's done. If something goes wrong and Hyphenopoly.js is unable to unhide the document Hyphenopoly_Loader.js has a timeout that kicks in after some time (defaults to 1000ms) and unhides the document and writes a message to the console. If the browser supports all required languages the script deletes the `Hyphenopoly`-object and terminates without further ado. -## enable CSS-hyphenation +### enable CSS-hyphenation Hyphenopoly by default hyphenates elements (and their children) with the classname `.hyphenate`. Don't forget to enable CSS-hyphenation for the classes eventually handled by Hyphenopoly. -# Usage (node) +## Usage (node) [![Try hyphenopoly on RunKit](https://badge.runkitcdn.com/hyphenopoly.svg)](https://npm.runkit.com/hyphenopoly) Install: -```` +````shell npm i hyphenopoly ```` @@ -124,17 +123,17 @@ hyphenate_en("hyphenation enhances justification."); hyphenate_de("Silbentrennung verbessert den Blocksatz."); ```` -# Automatic hyphenation +## Automatic hyphenation The algorithm used for hyphenation was developed by Franklin M. Liang for TeX. It works more or less like this: -1. Load a set of precomputed language specific patterns. The patterns are stored in a structure called a trie, which is very efficient for this task. -2. Collect all patterns that are a substring of the word to be hyphenated. -3. Combine the numerical values between characters: higher values overwrite lower values. -4. Odd values are hyphenation points (except if the hyphenation point is left from leftmin and right from rightmin), replace them with a soft hyphen and drop the other values. -5. Repeat 2. - 4. for all words longer than minWordLength +1. Load a set of precomputed language specific patterns. The patterns are stored in a structure called a trie, which is very efficient for this task. +2. Collect all patterns that are a substring of the word to be hyphenated. +3. Combine the numerical values between characters: higher values overwrite lower values. +4. Odd values are hyphenation points (except if the hyphenation point is left from leftmin and right from rightmin), replace them with a soft hyphen and drop the other values. +5. Repeat 2. - 4. for all words longer than minWordLength Example: -```` +````text Hyphenation h y p h e n a t i o n h y3p h @@ -154,18 +153,18 @@ The patterns are precomputed and available for many languages on CTAN. Hyphenopo These pattern vary in size. This is mostly due to the different linguistic characteristics of the languages. -# Hyphenopoly.js vs. Hyphenator.js -Hyphenator.js (https://github.com/mnater/Hyphenator) started 2007 and had evolved ever since. +## Hyphenopoly.js vs. Hyphenator.js +[Hyphenator.js](https://github.com/mnater/Hyphenator) started 2007 and had evolved ever since. But web browsers have evolved much more! -Almost all of them support native hyphenation (https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens) for a specific set of languages (https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#Browser_compatibility). So it was time for something new! +Almost all of them support [native hyphenation](https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens) for a [specific set of languages](https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#Browser_compatibility). So it was time for something new! Hyphenopoly.js is based on Hyphenator.js (they share some code) but - in favor of simplicity and speed – lacks many features of Hyphenator.js. Most of these features aren't needed in modern webdesign anymore: -- dropped support for usage as bookmarklet -- dropped support for frames -- dropped support for ancient browsers -- dropped caching of patterns in browser storage -- dropped breaking of non-textual content (urls, code etc.) -- and some more… +- dropped support for usage as bookmarklet +- dropped support for frames +- dropped support for ancient browsers +- dropped caching of patterns in browser storage +- dropped breaking of non-textual content (urls, code etc.) +- and some more… If you need one of those features use Hyphenator.js – or give some feedback and proof that the feature is really useful and should be implemented in Hyphenopoly.js diff --git a/hyphenEngine.asm.js b/hyphenEngine.asm.js index cf2e3345..df75b56b 100644 --- a/hyphenEngine.asm.js +++ b/hyphenEngine.asm.js @@ -97,6 +97,7 @@ function asmHyphenEngine(std, ext, heap) { alphabetCount = (alphabetCount + 1) | 0; i = (i + 4) | 0; } + return alphabetCount | 0; } function translateCharCode(cc) { @@ -124,11 +125,12 @@ function asmHyphenEngine(std, ext, heap) { var valueStoreStartIndex = 0; var valueStoreCurrentIdx = 0; var valueStorePrevIdx = 0; + var alphabetlength = 0; valueStoreStartIndex = (valueStoreOffset + 1) | 0; valueStoreCurrentIdx = (valueStoreOffset + 1) | 0; valueStorePrevIdx = (valueStoreOffset + 1) | 0; - createTranslateMap(); + alphabetlength = createTranslateMap() | 0; i = hpbPatternsOffset | 0; last_i = hpbPatternsOffset + patternsLength | 0; @@ -182,6 +184,7 @@ function asmHyphenEngine(std, ext, heap) { } i = (i + 1) | 0; } + return alphabetlength | 0; } function hyphenate(lm, rm) { diff --git a/hyphenEngine.wasm b/hyphenEngine.wasm index 73a4192a..06f26f74 100644 Binary files a/hyphenEngine.wasm and b/hyphenEngine.wasm differ diff --git a/hyphenopoly.module.js b/hyphenopoly.module.js index 768cf6c3..788a7e3e 100644 --- a/hyphenopoly.module.js +++ b/hyphenopoly.module.js @@ -14,10 +14,11 @@ const fs = require("fs"); const {StringDecoder} = require("string_decoder"); + const decode = (function makeDecoder() { const utf16ledecoder = new StringDecoder("utf-16le"); return function dec(ui16) { - return utf16ledecoder.write(ui16); + return utf16ledecoder.write(Buffer.from(ui16)); }; }()); @@ -414,8 +415,8 @@ function instantiateWasmEngine(lang) { result.instance.exports.hyphenate ), decode( - (new Uint16Array(wasmMemory.buffer)). - subarray(384, 640) + (new Uint8Array(wasmMemory.buffer)). + subarray(768, 1280) ), baseData.leftmin, baseData.rightmin diff --git a/package.json b/package.json index 57236b7e..7388e76a 100644 --- a/package.json +++ b/package.json @@ -43,9 +43,9 @@ }, "dependencies": {}, "devDependencies": { - "eslint": "^5.9.0", - "tap": "^12.1.0", - "terser": "^3.10.13" + "eslint": "^5.10.0", + "tap": "^12.1.1", + "terser": "^3.11.0" }, "eslintConfig": { "parserOptions": { diff --git a/test/errors.js b/test/errors.js index 95443780..a662f8e2 100644 --- a/test/errors.js +++ b/test/errors.js @@ -5,7 +5,6 @@ const t = require("tap"); let H9Y = null; -const H9YKey = require.resolve("../hyphenopoly.module"); t.beforeEach(function setup(done) { H9Y = require("../hyphenopoly.module"); done(); @@ -13,7 +12,7 @@ t.beforeEach(function setup(done) { t.afterEach(function tearDown(done) { H9Y = null; - delete require.cache[H9YKey]; + delete require.cache[require.resolve("../hyphenopoly.module")]; done(); }); diff --git a/testsuite/test1.html b/testsuite/test1.html index ff335b1f..270d0c81 100644 --- a/testsuite/test1.html +++ b/testsuite/test1.html @@ -10,8 +10,8 @@ "hy": "ձայնավորձայնավոր" }, paths: { - maindir: "../", - patterndir: "../patterns/" + "maindir": "../", + "patterndir": "../patterns/" }, handleEvent: { hyphenopolyEnd: function (e) { diff --git a/testsuite/test28.html b/testsuite/test28.html index b4a161d7..fb2fd85a 100644 --- a/testsuite/test28.html +++ b/testsuite/test28.html @@ -17,11 +17,11 @@ }, setup: { hide: hide_mode, - classnames: { - "hyphenate1": { + selectors: { + ".hyphenate1": { hyphen: "•" }, - "hyphenate2": { + ".hyphenate2": { hyphen: "|" } } @@ -39,7 +39,7 @@ elCount += 1; switch (hide_mode) { case "all": - testresult = testresult && (document.getElementById("H9Y_Styles").innerHTML === "html {visibility: hidden !important}"); + testresult = testresult && (document.getElementById("H9Y_Styles").innerHTML === "html {visibility: hidden !important}\n"); break; case "element": testresult = testresult && (document.getElementById("H9Y_Styles").innerHTML === ".hyphenate1 {visibility: hidden !important}\n.hyphenate2 {visibility: hidden !important}\n"); diff --git a/testsuite/test31.html b/testsuite/test31.html new file mode 100644 index 00000000..f99792a5 --- /dev/null +++ b/testsuite/test31.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <title>Test 031 + + + + + + + +

Test 031

+

Check dontHyphenateClass

+
+
+

Silbentrennung Silbentrennung Silbentrennung

+

Sil•ben•tren•nung Silbentrennung Sil•ben•tren•nung

+ +
+
Test Ref
+ + + \ No newline at end of file diff --git a/testsuite/test7.html b/testsuite/test7.html index 3bf8ce22..b10e23ec 100644 --- a/testsuite/test7.html +++ b/testsuite/test7.html @@ -1,5 +1,5 @@ - + Test 007 @@ -12,6 +12,19 @@ maindir: "../", patterndir: "../patterns/" }, + setup: { + selectors: { + ".class1": { + compound: "all" + }, + ".class2": { + compound: "auto" + }, + ".class3": { + compound: "hyphen" + } + } + }, handleEvent: { hyphenopolyEnd: function (e) { assert(); @@ -22,7 +35,7 @@