Commit 046b7882 authored by julien's avatar julien

add hyphen lib

parent 31ebe13e
/* regular */
@font-face {
font-family: 'Minion Pro';
src: url('../fonts/MinionPro-Regular.otf') format('truetype');
font-weight: 400;
font-style: normal;
}
/* italic */
@font-face {
font-family: 'Minion Pro';
src: url('../fonts/MinionPro-It.otf') format('truetype');
font-weight: 400;
font-style: italic;
}
/* Bold */
@font-face {
font-family: 'Minion Pro';
src: url('../fonts/MinionPro-Bold.otf') format('truetype');
font-weight: 600;
font-style: normal;
}
/* bold italic */
@font-face {
font-family: 'Minion Pro';
src: url('../fonts/MinionPro-BoldIt.otf') format('truetype');
font-weight: 600;
font-style: italic;
}
\ No newline at end of file
language: node_js
cache: bundler
node_js:
- "10"
notifications:
email: false
\ No newline at end of file
# Version History
## 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
## 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)
## Version 2.1.0 (Mai 27, 2018)
* 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)
## 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)
First release
/**
* @license Hyphenopoly 2.4.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
*/
/* 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);
}
/**
* 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 <props> 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) {
/* eslint-disable no-bitwise, sort-keys */
return {
"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, {
"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 add(value) {
r[value] = true;
});
return r;
}()), 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, {
"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)
});
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 copyClassnames(cn) {
const tmp = {};
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)
);
}
});
H.c = settings;
}());
(function H9Y() {
const C = H.c;
let mainLanguage = null;
let elements = null;
/**
* Factory for elements
* @returns {Object} elements-object
*/
function makeElementCollection() {
const list = empty();
/*
* 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 = {
"class": cn,
"element": el
};
if (!list[lang]) {
list[lang] = [];
}
list[lang].push(elo);
counter[0] += 1;
return elo;
}
/**
* Execute fn for each element
* @param {function} fn The function to execute
* @returns {undefined}
*/
function each(fn) {
Object.keys(list).forEach(function forEachElem(k) {
fn(k, list[k]);
});
}
return {
"add": add,
"counter": counter,
"each": each,
"list": list
};
}
/**
* 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"
? fallback
? mainLanguage
: 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);
if (!mainLanguage && C.defaultLanguage !== "") {
mainLanguage = C.defaultLanguage;
}
}
/**
* 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 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();
/**
* 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;
if (el.lang && typeof el.lang === "string") {
eLang = el.lang.toLowerCase();
} else if (pLang && pLang !== "") {
eLang = pLang.toLowerCase();
} else {
eLang = getLang(el, true);
}
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) {
if (n.nodeType === 1 &&
!C.dontHyphenate[n.nodeName.toLowerCase()] &&
n.className.indexOf(C.dontHyphenateClass) === -1) {
if (sortOutSubclasses(n.className.split(" "), C.classNames).length === 0) {
processElements(n, eLang, cn, true);
}
}
j += 1;
n = el.childNodes[j];
}
}
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;
}
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 hyphen = classSettings.hyphen;
lo.cache[cn] = empty();
/**
* 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 = null;
let i = 0;
let wordHyphenator = null;
let hw = word;
switch (classSettings.compound) {
case "auto":
parts = word.split("-");
wordHyphenator = createWordHyphenator(lo, lang, cn);
while (i < parts.length) {
if (parts[i].length >= classSettings.minWordLength) {
parts[i] = wordHyphenator(parts[i]);
}
i += 1;
}
hw = parts.join("-");
break;
case "all":
parts = word.split("-");
wordHyphenator = createWordHyphenator(lo, lang, cn);
while (i < parts.length) {
if (parts[i].length >= classSettings.minWordLength) {
parts[i] = wordHyphenator(parts[i]);
}
i += 1;
}
hw = parts.join("-" + zeroWidthSpace);
break;
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) {
let hw = lo.cache[cn][word];
if (!hw) {
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 = hyphenateCompound(word);
}
lo.cache[cn][word] = hw;
}
return hw;
}
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 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;
if (".\\+*?[^]$(){}=!<>|:-".indexOf(classSettings.hyphen) !== -1) {
h = "\\" + classSettings.hyphen;
}
if (classSettings.orphanControl === 3 && leadingWhiteSpace === " ") {
leadingWhiteSpace = String.fromCharCode(160);
}
return leadingWhiteSpace + lastWord.replace(new RegExp(h, "g"), "") + trailingWhiteSpace;
}
orphanControllerPool[cn] = controlOrphans;
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;
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])
? orphanControllerPool[cn]
: createOrphanController(cn);
const re = lo.genRegExps[cn];
let i = 0;
let n = el.childNodes[i];
while (n) {
if (
n.nodeType === 3 &&
n.data.length >= minWordLength
) {
let tn = null;
if (normalize) {
tn = n.data.normalize("NFC").replace(re, wordHyphenator);
} else {
tn = n.data.replace(re, wordHyphenator);
}
if (classSettings.orphanControl !== 1) {
tn = tn.replace(
/(\u0020*)(\S+)(\s*)$/,
orphanController
);
}
n.data = tn;
}
i += 1;
n = el.childNodes[i];
}
elements.counter[0] -= 1;
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) {
elArr.forEach(function eachElem(elo) {
hyphenateElement(lang, elo);
});
} else {
H.events.dispatch("error", {"msg": "engine for language '" + lang + "' loaded, but no elements found."});
}
if (elements.counter[0] === 0) {
H.events.dispatch("hyphenopolyEnd");
}
}
/**
* 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 = null;
while (i < l) {
key = words[i].replace(/-/g, "");
if (!r[key]) {
r[key] = words[i];
}
i += 1;
}
return r;
}
/**
* 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.languages) {
H.languages = empty();
}
if (!H.languages[lang]) {
H.languages[lang] = empty();
}
const lo = H.languages[lang];
if (!lo.engineReady) {
lo.cache = empty();
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;
}
}
if (H.c.exceptions[lang]) {
lo.exceptions = convertExceptions(H.c.exceptions[lang]);
delete H.c.exceptions[lang];
} else {
lo.exceptions = empty();
}
lo.genRegExps = empty();
lo.leftmin = leftmin;
lo.rightmin = rightmin;
lo.hyphenateFunction = hyphenateFunction;
C.classNames.forEach(function eachClassName(cn) {
const classSettings = C[cn];
if (classSettings.leftminPerLang === 0) {
Object.defineProperty(
classSettings,
"leftminPerLang",
setProp(empty(), 2)
);
}
if (classSettings.rightminPerLang === 0) {
Object.defineProperty(
classSettings,
"rightminPerLang",
setProp(empty(), 2)
);
}
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
);
}
if (classSettings.rightminPerLang[lang]) {
classSettings.rightminPerLang[lang] = Math.max(
lo.rightmin,