Hyphenopoly.js 45 KB

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