Hyphenopoly.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. /**
  2. * @license Hyphenopoly 3.1.1 - client side hyphenation for webbrowsers
  3. * ©2019 Mathias Nater, Zürich (mathiasnater at gmail dot com)
  4. * https://github.com/mnater/Hyphenopoly
  5. *
  6. * Released under the MIT license
  7. * http://mnater.github.io/Hyphenopoly/LICENSE
  8. */
  9. /* globals asmHyphenEngine */
  10. (function mainWrapper(w) {
  11. "use strict";
  12. const SOFTHYPHEN = String.fromCharCode(173);
  13. /**
  14. * Create Object without standard Object-prototype
  15. * @returns {Object} empty object
  16. */
  17. function empty() {
  18. return Object.create(null);
  19. }
  20. /**
  21. * Polyfill Math.imul
  22. * @param {number} a LHS
  23. * @param {number} b RHS
  24. * @returns {number} empty object
  25. */
  26. /* eslint-disable no-bitwise */
  27. Math.imul = Math.imul || function imul(a, b) {
  28. const aHi = (a >>> 16) & 0xffff;
  29. const aLo = a & 0xffff;
  30. const bHi = (b >>> 16) & 0xffff;
  31. const bLo = b & 0xffff;
  32. /*
  33. * The shift by 0 fixes the sign on the high part.
  34. * The final |0 converts the unsigned value into a signed value
  35. */
  36. return ((aLo * bLo) + ((((aHi * bLo) + (aLo * bHi)) << 16) >>> 0) | 0);
  37. };
  38. /* eslint-enable no-bitwise */
  39. /**
  40. * Set value and properties of object member
  41. * Argument <props> is a bit pattern:
  42. * 1. bit: configurable
  43. * 2. bit: enumerable
  44. * 3. bit writable
  45. * e.g. 011(2) = 3(10) => configurable: f, enumerable: t, writable: t
  46. * or 010(2) = 2(10) => configurable: f, enumerable: t, writable: f
  47. * @param {any} val The value
  48. * @param {number} props bitfield
  49. * @returns {Object} Property object
  50. */
  51. function setProp(val, props) {
  52. /* eslint-disable no-bitwise, sort-keys */
  53. return {
  54. "configurable": (props & 4) > 0,
  55. "enumerable": (props & 2) > 0,
  56. "writable": (props & 1) > 0,
  57. "value": val
  58. };
  59. /* eslint-enable no-bitwise, sort-keys */
  60. }
  61. /**
  62. * Register copy event on element
  63. * @param {Object} el The element
  64. * @returns {undefined}
  65. */
  66. function registerOnCopy(el) {
  67. el.addEventListener("copy", function oncopy(e) {
  68. e.preventDefault();
  69. const selectedText = window.getSelection().toString();
  70. /* eslint-disable security/detect-non-literal-regexp */
  71. e.clipboardData.setData("text/plain", selectedText.replace(new RegExp(SOFTHYPHEN, "g"), ""));
  72. /* eslint-enable security/detect-non-literal-regexp */
  73. }, true);
  74. }
  75. (function configurationFactory(H) {
  76. const generalDefaults = Object.create(null, {
  77. "defaultLanguage": setProp("en-us", 2),
  78. "dontHyphenate": setProp((function createList() {
  79. const r = empty();
  80. const list = "abbr,acronym,audio,br,button,code,img,input,kbd,label,math,option,pre,samp,script,style,sub,sup,svg,textarea,var,video";
  81. list.split(",").forEach(function add(value) {
  82. /* eslint-disable security/detect-object-injection */
  83. r[value] = true;
  84. /* eslint-enable security/detect-object-injection */
  85. });
  86. return r;
  87. }()), 2),
  88. "dontHyphenateClass": setProp("donthyphenate", 2),
  89. "exceptions": setProp(empty(), 2),
  90. "normalize": setProp(false, 2),
  91. "safeCopy": setProp(true, 2),
  92. "timeout": setProp(1000, 2)
  93. });
  94. const settings = Object.create(generalDefaults);
  95. const perClassDefaults = Object.create(null, {
  96. "compound": setProp("hyphen", 2),
  97. "hyphen": setProp(SOFTHYPHEN, 2),
  98. "leftmin": setProp(0, 2),
  99. "leftminPerLang": setProp(0, 2),
  100. "minWordLength": setProp(6, 2),
  101. "orphanControl": setProp(1, 2),
  102. "rightmin": setProp(0, 2),
  103. "rightminPerLang": setProp(0, 2)
  104. });
  105. Object.keys(H.setup).forEach(function copySettings(key) {
  106. if (key === "selectors") {
  107. const selectors = Object.keys(H.setup.selectors);
  108. Object.defineProperty(
  109. settings,
  110. "selectors",
  111. setProp(selectors, 2)
  112. );
  113. selectors.forEach(function copySelectors(sel) {
  114. const tmp = empty();
  115. /* eslint-disable security/detect-object-injection */
  116. Object.keys(H.setup.selectors[sel]).forEach(
  117. function copySelectorSettings(k) {
  118. tmp[k] = setProp(H.setup.selectors[sel][k], 2);
  119. }
  120. );
  121. /* eslint-enable security/detect-object-injection */
  122. Object.defineProperty(
  123. settings,
  124. sel,
  125. setProp(Object.create(perClassDefaults, tmp), 2)
  126. );
  127. });
  128. } else if (key === "dontHyphenate") {
  129. const tmp = empty();
  130. Object.keys(H.setup.dontHyphenate).forEach(
  131. function copyTagNames(k) {
  132. /* eslint-disable security/detect-object-injection */
  133. tmp[k] = setProp(H.setup.dontHyphenate[k], 2);
  134. /* eslint-enable security/detect-object-injection */
  135. }
  136. );
  137. Object.defineProperty(
  138. settings,
  139. key,
  140. setProp(
  141. Object.create(generalDefaults.dontHyphenate, tmp), 3
  142. )
  143. );
  144. } else {
  145. /* eslint-disable security/detect-object-injection */
  146. Object.defineProperty(
  147. settings,
  148. key,
  149. setProp(H.setup[key], 3)
  150. );
  151. /* eslint-enable security/detect-object-injection */
  152. }
  153. });
  154. H.c = settings;
  155. }(Hyphenopoly));
  156. (function H9Y(H) {
  157. const C = H.c;
  158. let mainLanguage = null;
  159. let elements = null;
  160. /**
  161. * Factory for elements
  162. * @returns {Object} elements-object
  163. */
  164. function makeElementCollection() {
  165. const list = new Map();
  166. /*
  167. * Counter counts the elements to be hyphenated.
  168. * Needs to be an object (Pass by reference)
  169. */
  170. const counter = [0];
  171. /**
  172. * Add element to elements
  173. * @param {object} el The element
  174. * @param {string} lang The language of the element
  175. * @param {string} sel The selector of the element
  176. * @returns {Object} An element-object
  177. */
  178. function add(el, lang, sel) {
  179. const elo = {
  180. "element": el,
  181. "selector": sel
  182. };
  183. if (!list.has(lang)) {
  184. list.set(lang, []);
  185. }
  186. list.get(lang).push(elo);
  187. counter[0] += 1;
  188. return elo;
  189. }
  190. /**
  191. * Removes elements from the list and updates the counter
  192. * @param {string} lang - The lang of the elements to remove
  193. */
  194. function rem(lang) {
  195. let langCount = 0;
  196. if (list.has(lang)) {
  197. langCount = list.get(lang).length;
  198. list.delete(lang);
  199. counter[0] -= langCount;
  200. if (counter[0] === 0) {
  201. H.events.dispatch("hyphenopolyEnd");
  202. }
  203. }
  204. }
  205. /**
  206. * Execute fn for each element
  207. * @param {function} fn The function to execute
  208. * @returns {undefined}
  209. */
  210. function each(fn) {
  211. list.forEach(function eachElement(val, key) {
  212. fn(key, val);
  213. });
  214. }
  215. return {
  216. "add": add,
  217. "counter": counter,
  218. "each": each,
  219. "list": list,
  220. "rem": rem
  221. };
  222. }
  223. /**
  224. * Get language of element by searching its parents or fallback
  225. * @param {Object} el The element
  226. * @param {boolean} fallback Will falback to mainlanguage
  227. * @returns {string|null} The language or null
  228. */
  229. function getLang(el, fallback) {
  230. try {
  231. return (el.getAttribute("lang"))
  232. ? el.getAttribute("lang").toLowerCase()
  233. : el.tagName.toLowerCase() === "html"
  234. ? fallback
  235. ? mainLanguage
  236. : null
  237. : getLang(el.parentNode, fallback);
  238. } catch (ignore) {
  239. return null;
  240. }
  241. }
  242. /**
  243. * Set mainLanguage
  244. * @returns {undefined}
  245. */
  246. function autoSetMainLanguage() {
  247. const el = w.document.getElementsByTagName("html")[0];
  248. mainLanguage = getLang(el, false);
  249. if (!mainLanguage && C.defaultLanguage !== "") {
  250. mainLanguage = C.defaultLanguage;
  251. }
  252. }
  253. /**
  254. * Check if node is matched by a given selector
  255. * @param {Node} n The Node to check
  256. * @param {String} sel Selector(s)
  257. * @returns {Boolean} true if matched, false if not matched
  258. */
  259. function nodeMatchedBy(n, sel) {
  260. if (!n.matches) {
  261. n.matches = n.msMatchesSelector || n.webkitMatchesSelector;
  262. }
  263. return n.matches(sel);
  264. }
  265. /**
  266. * Collect elements that have a selector defined in C.selectors
  267. * and add them to elements.
  268. * @returns {undefined}
  269. */
  270. function collectElements() {
  271. elements = makeElementCollection();
  272. const dontHyphenateSelector = (function createSel() {
  273. let s = "." + H.c.dontHyphenateClass;
  274. let k = null;
  275. for (k in C.dontHyphenate) {
  276. /* eslint-disable security/detect-object-injection */
  277. if (C.dontHyphenate[k]) {
  278. s += ", " + k;
  279. }
  280. /* eslint-enable security/detect-object-injection */
  281. }
  282. return s;
  283. }());
  284. const matchingSelectors = C.selectors.join(", ") + ", " + dontHyphenateSelector;
  285. /**
  286. * Get Language of Element or of one of its ancestors.
  287. * @param {Object} el The element to scan
  288. * @param {string} pLang The language of the parent element
  289. * @returns {string} the language
  290. */
  291. function getElementLanguage(el, pLang) {
  292. if (el.lang && typeof el.lang === "string") {
  293. return el.lang.toLowerCase();
  294. } else if (pLang && pLang !== "") {
  295. return pLang.toLowerCase();
  296. }
  297. return getLang(el, true);
  298. }
  299. /**
  300. * Recursively walk all elements in el, lending lang and selName
  301. * add them to elements if necessary.
  302. * @param {Object} el The element to scan
  303. * @param {string} pLang The language of the oarent element
  304. * @param {string} sel The selector of the parent element
  305. * @param {boolean} isChild If el is a child element
  306. * @returns {undefined}
  307. */
  308. function processElements(el, pLang, sel, isChild) {
  309. isChild = isChild || false;
  310. const eLang = getElementLanguage(el, pLang);
  311. /* eslint-disable security/detect-object-injection */
  312. if (H.cf.langs[eLang] === "H9Y") {
  313. elements.add(el, eLang, sel);
  314. if (!isChild && C.safeCopy) {
  315. registerOnCopy(el);
  316. }
  317. } else if (!H.cf.langs[eLang]) {
  318. H.events.dispatch("error", {
  319. "lvl": "warn",
  320. "msg": "Element with '" + eLang + "' found, but '" + eLang + ".hpb' not loaded. Check language tags!"
  321. });
  322. }
  323. /* eslint-enable security/detect-object-injection */
  324. const cn = el.childNodes;
  325. Array.prototype.forEach.call(cn, function eachChildNode(n) {
  326. if (n.nodeType === 1 &&
  327. !nodeMatchedBy(n, matchingSelectors)) {
  328. processElements(n, eLang, sel, true);
  329. }
  330. });
  331. }
  332. C.selectors.forEach(function eachSelector(sel) {
  333. const nl = w.document.querySelectorAll(sel);
  334. Array.prototype.forEach.call(nl, function eachNode(n) {
  335. processElements(n, getLang(n, true), sel, false);
  336. });
  337. });
  338. H.elementsReady = true;
  339. }
  340. const wordHyphenatorPool = new Map();
  341. /**
  342. * Factory for hyphenatorFunctions for a specific language and class
  343. * @param {Object} lo Language-Object
  344. * @param {string} lang The language
  345. * @param {string} sel The selector
  346. * @returns {function} The hyphenate function
  347. */
  348. function createWordHyphenator(lo, lang, sel) {
  349. /* eslint-disable-next-line security/detect-object-injection */
  350. const classSettings = C[sel];
  351. const hyphen = classSettings.hyphen;
  352. lo.cache.set(sel, new Map());
  353. /**
  354. * HyphenateFunction for compound words
  355. * @param {string} word The word
  356. * @returns {string} The hyphenated compound word
  357. */
  358. function hyphenateCompound(word) {
  359. const zeroWidthSpace = String.fromCharCode(8203);
  360. let parts = null;
  361. let wordHyphenator = null;
  362. if (classSettings.compound === "auto" ||
  363. classSettings.compound === "all") {
  364. wordHyphenator = createWordHyphenator(lo, lang, sel);
  365. parts = word.split("-").map(function h7eParts(p) {
  366. if (p.length >= classSettings.minWordLength) {
  367. return wordHyphenator(p);
  368. }
  369. return p;
  370. });
  371. if (classSettings.compound === "auto") {
  372. word = parts.join("-");
  373. } else {
  374. word = parts.join("-" + zeroWidthSpace);
  375. }
  376. } else {
  377. word = word.replace("-", "-" + zeroWidthSpace);
  378. }
  379. return word;
  380. }
  381. /**
  382. * HyphenateFunction for words (compound or not)
  383. * @param {string} word The word
  384. * @returns {string} The hyphenated word
  385. */
  386. function hyphenator(word) {
  387. let hw = lo.cache.get(sel).get(word);
  388. if (!hw) {
  389. if (lo.exceptions.has(word)) {
  390. hw = lo.exceptions.get(word).replace(
  391. /-/g,
  392. classSettings.hyphen
  393. );
  394. } else if (word.indexOf("-") === -1) {
  395. if (word.length > 61) {
  396. H.events.dispatch("error", {
  397. "lvl": "warn",
  398. "msg": "found word longer than 61 characters"
  399. });
  400. hw = word;
  401. } else {
  402. /* eslint-disable security/detect-object-injection */
  403. hw = lo.hyphenateFunction(
  404. word,
  405. hyphen,
  406. classSettings.leftminPerLang[lang],
  407. classSettings.rightminPerLang[lang]
  408. );
  409. /* eslint-enable security/detect-object-injection */
  410. }
  411. } else {
  412. hw = hyphenateCompound(word);
  413. }
  414. lo.cache.get(sel).set(word, hw);
  415. }
  416. return hw;
  417. }
  418. wordHyphenatorPool.set(lang + "-" + sel, hyphenator);
  419. return hyphenator;
  420. }
  421. const orphanControllerPool = new Map();
  422. /**
  423. * Factory for function that handles orphans
  424. * @param {string} sel The selector
  425. * @returns {function} The function created
  426. */
  427. function createOrphanController(sel) {
  428. /**
  429. * Function template
  430. * @param {string} ignore unused result of replace
  431. * @param {string} leadingWhiteSpace The leading whiteSpace
  432. * @param {string} lastWord The last word
  433. * @param {string} trailingWhiteSpace The trailing whiteSpace
  434. * @returns {string} Treated end of text
  435. */
  436. function controlOrphans(
  437. ignore,
  438. leadingWhiteSpace,
  439. lastWord,
  440. trailingWhiteSpace
  441. ) {
  442. /* eslint-disable security/detect-object-injection */
  443. const classSettings = C[sel];
  444. /* eslint-enable security/detect-object-injection */
  445. let h = classSettings.hyphen;
  446. if (".\\+*?[^]$(){}=!<>|:-".indexOf(classSettings.hyphen) !== -1) {
  447. h = "\\" + classSettings.hyphen;
  448. }
  449. if (classSettings.orphanControl === 3 && leadingWhiteSpace === " ") {
  450. leadingWhiteSpace = String.fromCharCode(160);
  451. }
  452. /* eslint-disable security/detect-non-literal-regexp */
  453. return leadingWhiteSpace + lastWord.replace(new RegExp(h, "g"), "") + trailingWhiteSpace;
  454. /* eslint-enable security/detect-non-literal-regexp */
  455. }
  456. orphanControllerPool.set(sel, controlOrphans);
  457. return controlOrphans;
  458. }
  459. /**
  460. * Hyphenate an entitiy (text string or Element-Object)
  461. * @param {string} lang - the language of the string
  462. * @param {string} sel - the selectorName of settings
  463. * @param {string} entity - the entity to be hyphenated
  464. * @returns {string | null} hyphenated str according to setting of sel
  465. */
  466. function hyphenate(lang, sel, entity) {
  467. const lo = H.languages.get(lang);
  468. /* eslint-disable security/detect-object-injection */
  469. const classSettings = C[sel];
  470. /* eslint-enable security/detect-object-injection */
  471. const minWordLength = classSettings.minWordLength;
  472. const normalize = C.normalize &&
  473. Boolean(String.prototype.normalize);
  474. const poolKey = lang + "-" + sel;
  475. const wordHyphenator = (wordHyphenatorPool.has(poolKey))
  476. ? wordHyphenatorPool.get(poolKey)
  477. : createWordHyphenator(lo, lang, sel);
  478. const orphanController = (orphanControllerPool.has(sel))
  479. ? orphanControllerPool.get(sel)
  480. : createOrphanController(sel);
  481. const re = lo.genRegExps.get(sel);
  482. /**
  483. * Hyphenate text according to setting in sel
  484. * @param {string} text - the strint to be hyphenated
  485. * @returns {string} hyphenated string according to setting of sel
  486. */
  487. function hyphenateText(text) {
  488. let tn = null;
  489. if (normalize) {
  490. tn = text.normalize("NFC").replace(re, wordHyphenator);
  491. } else {
  492. tn = text.replace(re, wordHyphenator);
  493. }
  494. if (classSettings.orphanControl !== 1) {
  495. tn = tn.replace(
  496. // eslint-disable-next-line prefer-named-capture-group
  497. /(\u0020*)(\S+)(\s*)$/,
  498. orphanController
  499. );
  500. }
  501. return tn;
  502. }
  503. /**
  504. * Hyphenate element according to setting in sel
  505. * @param {object} el - the HTMLElement to be hyphenated
  506. * @returns {undefined}
  507. */
  508. function hyphenateElement(el) {
  509. H.events.dispatch("beforeElementHyphenation", {
  510. "el": el,
  511. "lang": lang
  512. });
  513. const cn = el.childNodes;
  514. Array.prototype.forEach.call(cn, function eachChildNode(n) {
  515. if (
  516. n.nodeType === 3 &&
  517. n.data.length >= minWordLength
  518. ) {
  519. n.data = hyphenateText(n.data);
  520. }
  521. });
  522. elements.counter[0] -= 1;
  523. H.events.dispatch("afterElementHyphenation", {
  524. "el": el,
  525. "lang": lang
  526. });
  527. }
  528. let r = null;
  529. if (typeof entity === "string") {
  530. r = hyphenateText(entity);
  531. } else if (entity instanceof HTMLElement) {
  532. hyphenateElement(entity);
  533. }
  534. return r;
  535. }
  536. H.createHyphenator = function createHyphenator(lang) {
  537. return function hyphenator(entity, sel) {
  538. sel = sel || ".hyphenate";
  539. return hyphenate(lang, sel, entity);
  540. };
  541. };
  542. H.unhyphenate = function unhyphenate() {
  543. elements.each(function eachLang(lang, els) {
  544. els.forEach(function eachElem(elo) {
  545. const n = elo.element.firstChild;
  546. const h = C[elo.selector].hyphen;
  547. /* eslint-disable security/detect-non-literal-regexp */
  548. n.data = n.data.replace(new RegExp(h, "g"), "");
  549. /* eslint-enable security/detect-non-literal-regexp */
  550. });
  551. });
  552. };
  553. /**
  554. * Hyphenate all elements with a given language
  555. * @param {string} lang The language
  556. * @param {Array} elArr Array of elements
  557. * @returns {undefined}
  558. */
  559. function hyphenateLangElements(lang, elArr) {
  560. if (elArr) {
  561. elArr.forEach(function eachElem(elo) {
  562. hyphenate(lang, elo.selector, elo.element);
  563. });
  564. } else {
  565. H.events.dispatch("error", {
  566. "lvl": "warn",
  567. "msg": "engine for language '" + lang + "' loaded, but no elements found."
  568. });
  569. }
  570. if (elements.counter[0] === 0) {
  571. H.events.dispatch("hyphenopolyEnd");
  572. }
  573. }
  574. /**
  575. * Convert exceptions to object
  576. * @param {string} exc comma separated list of exceptions
  577. * @returns {Object} Map of exceptions
  578. */
  579. function convertExceptions(exc) {
  580. const r = new Map();
  581. exc.split(", ").forEach(function eachExc(e) {
  582. const key = e.replace(/-/g, "");
  583. r.set(key, e);
  584. });
  585. return r;
  586. }
  587. /**
  588. * Create lang Object
  589. * @param {string} lang The language
  590. * @returns {Object} The newly
  591. */
  592. function createLangObj(lang) {
  593. if (!H.languages) {
  594. H.languages = new Map();
  595. }
  596. if (!H.languages.has(lang)) {
  597. H.languages.set(lang, empty());
  598. }
  599. return H.languages.get(lang);
  600. }
  601. /**
  602. * Setup lo
  603. * @param {string} lang The language
  604. * @param {function} hyphenateFunction The hyphenateFunction
  605. * @param {string} alphabet List of used characters
  606. * @param {number} leftmin leftmin
  607. * @param {number} rightmin rightmin
  608. * @returns {undefined}
  609. */
  610. function prepareLanguagesObj(
  611. lang,
  612. hyphenateFunction,
  613. alphabet,
  614. leftmin,
  615. rightmin
  616. ) {
  617. alphabet = alphabet.replace(/-/g, "");
  618. const lo = createLangObj(lang);
  619. if (!lo.engineReady) {
  620. lo.cache = new Map();
  621. /* eslint-disable security/detect-object-injection */
  622. if (H.c.exceptions.global) {
  623. if (H.c.exceptions[lang]) {
  624. H.c.exceptions[lang] += ", " + H.c.exceptions.global;
  625. } else {
  626. H.c.exceptions[lang] = H.c.exceptions.global;
  627. }
  628. }
  629. if (H.c.exceptions[lang]) {
  630. lo.exceptions = convertExceptions(H.c.exceptions[lang]);
  631. delete H.c.exceptions[lang];
  632. } else {
  633. lo.exceptions = new Map();
  634. }
  635. /* eslint-enable security/detect-object-injection */
  636. lo.genRegExps = new Map();
  637. lo.leftmin = leftmin;
  638. lo.rightmin = rightmin;
  639. lo.hyphenateFunction = hyphenateFunction;
  640. C.selectors.forEach(function eachSelector(sel) {
  641. /* eslint-disable security/detect-object-injection */
  642. const classSettings = C[sel];
  643. /* eslint-enable security/detect-object-injection */
  644. if (classSettings.leftminPerLang === 0) {
  645. Object.defineProperty(
  646. classSettings,
  647. "leftminPerLang",
  648. setProp(empty(), 2)
  649. );
  650. }
  651. if (classSettings.rightminPerLang === 0) {
  652. Object.defineProperty(
  653. classSettings,
  654. "rightminPerLang",
  655. setProp(empty(), 2)
  656. );
  657. }
  658. /* eslint-disable security/detect-object-injection */
  659. if (classSettings.leftminPerLang[lang]) {
  660. classSettings.leftminPerLang[lang] = Math.max(
  661. lo.leftmin,
  662. classSettings.leftmin,
  663. classSettings.leftminPerLang[lang]
  664. );
  665. } else {
  666. classSettings.leftminPerLang[lang] = Math.max(
  667. lo.leftmin,
  668. classSettings.leftmin
  669. );
  670. }
  671. if (classSettings.rightminPerLang[lang]) {
  672. classSettings.rightminPerLang[lang] = Math.max(
  673. lo.rightmin,
  674. classSettings.rightmin,
  675. classSettings.rightminPerLang[lang]
  676. );
  677. } else {
  678. classSettings.rightminPerLang[lang] = Math.max(
  679. lo.rightmin,
  680. classSettings.rightmin
  681. );
  682. }
  683. /* eslint-enable security/detect-object-injection */
  684. /*
  685. * Find words with characters from `alphabet` and
  686. * `Zero Width Non-Joiner` and `-` with a min length.
  687. *
  688. * This regexp is not perfect. It also finds parts of words
  689. * that follow a character that is not in the `alphabet`.
  690. * Word delimiters are not taken in account.
  691. */
  692. /* eslint-disable security/detect-non-literal-regexp */
  693. lo.genRegExps.set(sel, new RegExp("[\\w" + alphabet + String.fromCharCode(8204) + "-]{" + classSettings.minWordLength + ",}", "gi"));
  694. /* eslint-enable security/detect-non-literal-regexp */
  695. });
  696. lo.engineReady = true;
  697. }
  698. Hyphenopoly.events.dispatch("engineReady", {"msg": lang});
  699. }
  700. /**
  701. * Calculate heap size for (w)asm
  702. * wasm page size: 65536 = 64 Ki
  703. * asm: http://asmjs.org/spec/latest/#linking-0
  704. * @param {number} targetSize The targetet Size
  705. * @returns {number} The necessary heap size
  706. */
  707. function calculateHeapSize(targetSize) {
  708. /* eslint-disable no-bitwise */
  709. if (H.cf.wasm) {
  710. return Math.ceil(targetSize / 65536) * 65536;
  711. }
  712. const exp = Math.ceil(Math.log(targetSize) * Math.LOG2E);
  713. if (exp <= 12) {
  714. return 1 << 12;
  715. }
  716. if (exp < 24) {
  717. return 1 << exp;
  718. }
  719. return Math.ceil(targetSize / (1 << 24)) * (1 << 24);
  720. /* eslint-enable no-bitwise */
  721. }
  722. /**
  723. * Polyfill for TextDecoder
  724. */
  725. const decode = (function makeDecoder() {
  726. if (window.TextDecoder) {
  727. const utf16ledecoder = new TextDecoder("utf-16le");
  728. return function decoder(ui16) {
  729. return utf16ledecoder.decode(ui16);
  730. };
  731. }
  732. return function decoder(ui16) {
  733. return String.fromCharCode.apply(null, ui16);
  734. };
  735. }());
  736. /**
  737. * Calculate Base Data
  738. *
  739. * Build Heap (the heap object's byteLength must be
  740. * either 2^n for n in [12, 24)
  741. * or 2^24 · n for n ≥ 1;)
  742. *
  743. * MEMORY LAYOUT:
  744. *
  745. * -------------------- <- Offset 0
  746. * | translateMap |
  747. * | keys: |
  748. * |256 chars * 2Bytes|
  749. * | + |
  750. * | values: |
  751. * |256 chars * 1Byte |
  752. * -------------------- <- 768 Bytes
  753. * | alphabet |
  754. * |256 chars * 2Bytes|
  755. * -------------------- <- valueStoreOffset (vs) = 1280
  756. * | valueStore |
  757. * | 1 Byte |
  758. * |* valueStoreLength|
  759. * --------------------
  760. * | align to 4Bytes |
  761. * -------------------- <- patternTrieOffset (pt)
  762. * | patternTrie |
  763. * | 4 Bytes |
  764. * |*patternTrieLength|
  765. * -------------------- <- wordOffset (wo)
  766. * | wordStore |
  767. * | Uint16[64] | 128 bytes
  768. * -------------------- <- translatedWordOffset (tw)
  769. * | transl.WordStore |
  770. * | Uint8[64] | 64 bytes
  771. * -------------------- <- hyphenPointsOffset (hp)
  772. * | hyphenPoints |
  773. * | Uint8[64] | 64 bytes
  774. * -------------------- <- hyphenatedWordOffset (hw)
  775. * | hyphenatedWord |
  776. * | Uint16[128] | 256 Bytes
  777. * -------------------- <- hpbOffset (ho) -
  778. * | HEADER | |
  779. * | 6*4 Bytes | |
  780. * | 24 Bytes | |
  781. * -------------------- |
  782. * | PATTERN LIC | |
  783. * | variable Length | |
  784. * -------------------- |
  785. * | align to 4Bytes | } this is the .hpb-file
  786. * -------------------- <- hpbTranslateOffset |
  787. * | TRANSLATE | |
  788. * | 2 + [0] * 2Bytes | |
  789. * -------------------- <-hpbPatternsOffset(po)|
  790. * | PATTERNS | |
  791. * | patternsLength | |
  792. * -------------------- <- heapEnd -
  793. * | align to 4Bytes |
  794. * -------------------- <- heapSize (hs)
  795. * @param {Object} hpbBuf FileBuffer from .hpb-file
  796. * @returns {Object} baseData-object
  797. */
  798. function calculateBaseData(hpbBuf) {
  799. const hpbMetaData = new Uint32Array(hpbBuf).subarray(0, 8);
  800. if (hpbMetaData[0] !== 40005736) {
  801. /*
  802. * Pattern files must begin with "hpb2"
  803. * Get current utf8 values with
  804. * `new Uint8Array(Uint32Array.of(hpbMetaData[0]).buffer)`
  805. */
  806. H.events.dispatch("error", {
  807. "lvl": "error",
  808. "msg": "Pattern file format error: " + new Uint8Array(Uint32Array.of(hpbMetaData[0]).buffer)
  809. });
  810. throw new Error("Pattern file format error!");
  811. }
  812. const valueStoreLength = hpbMetaData[7];
  813. const valueStoreOffset = 1280;
  814. const patternTrieOffset = valueStoreOffset + valueStoreLength +
  815. (4 - ((valueStoreOffset + valueStoreLength) % 4));
  816. const wordOffset = patternTrieOffset + (hpbMetaData[6] * 4);
  817. return {
  818. // Set hpbOffset
  819. "ho": wordOffset + 512,
  820. // Set hyphenPointsOffset
  821. "hp": wordOffset + 192,
  822. // Set heapSize
  823. "hs": Math.max(calculateHeapSize(wordOffset + 512 + hpbMetaData[2] + hpbMetaData[3]), 32 * 1024 * 64),
  824. // Set hyphenatedWordOffset
  825. "hw": wordOffset + 256,
  826. // Set leftmin
  827. "lm": hpbMetaData[4],
  828. // Set patternsLength
  829. "pl": hpbMetaData[3],
  830. // Set hpbPatternsOffset
  831. "po": wordOffset + 512 + hpbMetaData[2],
  832. // Set patternTrieOffset
  833. "pt": patternTrieOffset,
  834. // Set rightmin
  835. "rm": hpbMetaData[5],
  836. // Set translateOffset
  837. "to": wordOffset + 512 + hpbMetaData[1],
  838. // Set translatedWordOffset
  839. "tw": wordOffset + 128,
  840. // Set valueStoreOffset
  841. "vs": valueStoreOffset,
  842. // Set wordOffset
  843. "wo": wordOffset
  844. };
  845. }
  846. /**
  847. * Setup env for hyphenateFunction
  848. * @param {Object} baseData baseData
  849. * @param {function} hyphenateFunc hyphenateFunction
  850. * @returns {function} hyphenateFunction with closured environment
  851. */
  852. function encloseHyphenateFunction(baseData, hyphenateFunc) {
  853. /* eslint-disable no-bitwise */
  854. const heapBuffer = H.cf.wasm
  855. ? baseData.wasmMemory.buffer
  856. : baseData.heapBuffer;
  857. const wordStore = (new Uint16Array(heapBuffer)).subarray(
  858. baseData.wo >> 1,
  859. (baseData.wo >> 1) + 64
  860. );
  861. const defLeftmin = baseData.lm;
  862. const defRightmin = baseData.rm;
  863. const hydWrdStore = (new Uint16Array(heapBuffer)).subarray(
  864. baseData.hw >> 1,
  865. (baseData.hw >> 1) + 128
  866. );
  867. /* eslint-enable no-bitwise */
  868. wordStore[0] = 95;
  869. return function enclHyphenate(word, hyphenchar, leftmin, rightmin) {
  870. let i = 0;
  871. let cc = word.charCodeAt(i);
  872. leftmin = leftmin || defLeftmin;
  873. rightmin = rightmin || defRightmin;
  874. while (cc) {
  875. i += 1;
  876. // eslint-disable-next-line security/detect-object-injection
  877. wordStore[i] = cc;
  878. cc = word.charCodeAt(i);
  879. }
  880. wordStore[i + 1] = 95;
  881. wordStore[i + 2] = 0;
  882. if (hyphenateFunc(leftmin, rightmin) === 1) {
  883. word = decode(hydWrdStore.subarray(1, hydWrdStore[0] + 1));
  884. if (hyphenchar !== "\u00AD") {
  885. word = word.replace(/\u00AD/g, hyphenchar);
  886. }
  887. }
  888. return word;
  889. };
  890. }
  891. /**
  892. * Instantiate Wasm Engine
  893. * @param {string} lang The language
  894. * @returns {undefined}
  895. */
  896. function instantiateWasmEngine(lang) {
  897. Promise.all([H.bins.get(lang), H.bins.get("hyphenEngine")]).then(
  898. function onAll(binaries) {
  899. const hpbBuf = binaries[0];
  900. const baseData = calculateBaseData(hpbBuf);
  901. const wasmModule = binaries[1];
  902. const specMem = H.specMems.get(lang);
  903. const wasmMemory = (
  904. specMem.buffer.byteLength >= baseData.hs
  905. )
  906. ? specMem
  907. : new WebAssembly.Memory({
  908. "initial": baseData.hs / 65536,
  909. "maximum": 256
  910. });
  911. const ui32wasmMemory = new Uint32Array(wasmMemory.buffer);
  912. ui32wasmMemory.set(
  913. new Uint32Array(hpbBuf),
  914. // eslint-disable-next-line no-bitwise
  915. baseData.ho >> 2
  916. );
  917. baseData.wasmMemory = wasmMemory;
  918. WebAssembly.instantiate(wasmModule, {
  919. "env": {
  920. "memory": baseData.wasmMemory,
  921. "memoryBase": 0
  922. },
  923. "x": baseData
  924. }).then(
  925. function runWasm(result) {
  926. const alphalen = result.exports.convert();
  927. prepareLanguagesObj(
  928. lang,
  929. encloseHyphenateFunction(
  930. baseData,
  931. result.exports.hyphenate
  932. ),
  933. decode(
  934. (new Uint16Array(wasmMemory.buffer)).
  935. subarray(385, 384 + alphalen)
  936. ),
  937. baseData.lm,
  938. baseData.rm
  939. );
  940. }
  941. );
  942. }
  943. );
  944. }
  945. /**
  946. * Instantiate asm Engine
  947. * @param {string} lang The language
  948. * @returns {undefined}
  949. */
  950. function instantiateAsmEngine(lang) {
  951. const hpbBuf = H.bins.get(lang);
  952. const baseData = calculateBaseData(hpbBuf);
  953. const specMem = H.specMems.get(lang);
  954. const heapBuffer = (specMem.byteLength >= baseData.hs)
  955. ? specMem
  956. : new ArrayBuffer(baseData.hs);
  957. const ui8Heap = new Uint8Array(heapBuffer);
  958. const ui8Patterns = new Uint8Array(hpbBuf);
  959. ui8Heap.set(ui8Patterns, baseData.ho);
  960. baseData.heapBuffer = heapBuffer;
  961. const theHyphenEngine = asmHyphenEngine(
  962. {
  963. "Int32Array": window.Int32Array,
  964. "Math": Math,
  965. "Uint16Array": window.Uint16Array,
  966. "Uint8Array": window.Uint8Array
  967. },
  968. baseData,
  969. baseData.heapBuffer
  970. );
  971. const alphalen = theHyphenEngine.convert();
  972. prepareLanguagesObj(
  973. lang,
  974. encloseHyphenateFunction(baseData, theHyphenEngine.hyphenate),
  975. decode(
  976. (new Uint16Array(heapBuffer)).
  977. subarray(385, 384 + alphalen)
  978. ),
  979. baseData.lm,
  980. baseData.rm
  981. );
  982. }
  983. let engineInstantiator = null;
  984. const hpb = [];
  985. /**
  986. * Instantiate hyphenEngines for languages
  987. * @param {string} lang The language
  988. * @param {string} engineType The engineType: "wasm" or "asm"
  989. * @returns {undefined}
  990. */
  991. function prepare(lang, engineType) {
  992. if (lang === "*") {
  993. if (engineType === "wasm") {
  994. engineInstantiator = instantiateWasmEngine;
  995. } else if (engineType === "asm") {
  996. engineInstantiator = instantiateAsmEngine;
  997. }
  998. hpb.forEach(function eachHbp(hpbLang) {
  999. engineInstantiator(hpbLang);
  1000. });
  1001. } else if (engineInstantiator) {
  1002. engineInstantiator(lang);
  1003. } else {
  1004. hpb.push(lang);
  1005. }
  1006. }
  1007. H.events.define(
  1008. "contentLoaded",
  1009. function onContentLoaded() {
  1010. autoSetMainLanguage();
  1011. collectElements();
  1012. H.events.dispatch("elementsReady");
  1013. },
  1014. false
  1015. );
  1016. H.events.define(
  1017. "elementsReady",
  1018. function onElementsReady() {
  1019. elements.each(function eachElem(lang, values) {
  1020. if (H.languages &&
  1021. H.languages.has(lang) &&
  1022. H.languages.get(lang).engineReady
  1023. ) {
  1024. hyphenateLangElements(lang, values);
  1025. }
  1026. });
  1027. },
  1028. false
  1029. );
  1030. H.events.define(
  1031. "engineLoaded",
  1032. function onEngineLoaded(e) {
  1033. prepare("*", e.msg);
  1034. },
  1035. false
  1036. );
  1037. H.events.define(
  1038. "hpbLoaded",
  1039. function onHpbLoaded(e) {
  1040. prepare(e.msg, "*");
  1041. },
  1042. false
  1043. );
  1044. H.events.addListener(
  1045. "loadError",
  1046. function onLoadError(e) {
  1047. if (e.msg !== "wasm") {
  1048. elements.rem(e.name);
  1049. }
  1050. },
  1051. false
  1052. );
  1053. H.events.define(
  1054. "engineReady",
  1055. function onEngineReady(e) {
  1056. if (H.elementsReady) {
  1057. hyphenateLangElements(e.msg, elements.list.get(e.msg));
  1058. }
  1059. },
  1060. false
  1061. );
  1062. H.events.define(
  1063. "hyphenopolyStart",
  1064. null,
  1065. true
  1066. );
  1067. H.events.define(
  1068. "hyphenopolyEnd",
  1069. function def() {
  1070. w.clearTimeout(C.timeOutHandler);
  1071. if (H.c.hide !== "none") {
  1072. H.toggle("on");
  1073. }
  1074. },
  1075. false
  1076. );
  1077. H.events.define(
  1078. "beforeElementHyphenation",
  1079. null,
  1080. true
  1081. );
  1082. H.events.define(
  1083. "afterElementHyphenation",
  1084. null,
  1085. true
  1086. );
  1087. H.events.tempRegister.forEach(function eachEo(eo) {
  1088. H.events.addListener(eo.name, eo.handler, false);
  1089. });
  1090. delete H.events.tempRegister;
  1091. H.events.dispatch("hyphenopolyStart", {"msg": "Hyphenopoly started"});
  1092. w.clearTimeout(H.c.timeOutHandler);
  1093. Object.defineProperty(C, "timeOutHandler", setProp(
  1094. w.setTimeout(function ontimeout() {
  1095. H.events.dispatch("timeout", {"delay": C.timeout});
  1096. }, C.timeout),
  1097. 2
  1098. ));
  1099. H.events.deferred.forEach(function eachDeferred(deferredeo) {
  1100. H.events.dispatch(deferredeo.name, deferredeo.data);
  1101. });
  1102. delete H.events.deferred;
  1103. }(Hyphenopoly));
  1104. }(window));