hyphenopoly.module.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. /**
  2. * @license Hyphenopoly.module.js 3.1.1 - hyphenation for node
  3. * ©2018 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. /* eslint-env node */
  10. "use strict";
  11. /*
  12. * Use 'fs' in node environment and fallback to http if the module gets executed
  13. * in a browser environment (e.g. browserified)
  14. */
  15. let loader = require("fs");
  16. const {StringDecoder} = require("string_decoder");
  17. const decode = (function makeDecoder() {
  18. const utf16ledecoder = new StringDecoder("utf-16le");
  19. return function dec(ui16) {
  20. return utf16ledecoder.write(Buffer.from(ui16));
  21. };
  22. }());
  23. /**
  24. * Create Object without standard Object-prototype
  25. * @returns {Object} empty object
  26. */
  27. function empty() {
  28. return Object.create(null);
  29. }
  30. /**
  31. * Set value and properties of object member
  32. * Argument <props> is a bit pattern:
  33. * 1. bit: configurable
  34. * 2. bit: enumerable
  35. * 3. bit writable
  36. * e.g. 011(2) = 3(10) => configurable: f, enumerable: t, writable: t
  37. * or 010(2) = 2(10) => configurable: f, enumerable: t, writable: f
  38. * @param {any} val The value
  39. * @param {number} props bitfield
  40. * @returns {Object} Property object
  41. */
  42. function setProp(val, props) {
  43. /* eslint-disable no-bitwise, sort-keys */
  44. return {
  45. "configurable": (props & 4) > 0,
  46. "enumerable": (props & 2) > 0,
  47. "writable": (props & 1) > 0,
  48. "value": val
  49. };
  50. /* eslint-enable no-bitwise, sort-keys */
  51. }
  52. const H = empty();
  53. H.binaries = new Map();
  54. H.supportedLanguages = [
  55. "af",
  56. "as",
  57. "be",
  58. "bg",
  59. "bn",
  60. "ca",
  61. "cs",
  62. "cy",
  63. "da",
  64. "de",
  65. "el-monoton",
  66. "el-polyton",
  67. "en-gb",
  68. "en-us",
  69. "eo",
  70. "es",
  71. "et",
  72. "eu",
  73. "fi",
  74. "fr",
  75. "fur",
  76. "ga",
  77. "gl",
  78. "grc",
  79. "gu",
  80. "hi",
  81. "hr",
  82. "hsb",
  83. "hu",
  84. "hy",
  85. "ia",
  86. "id",
  87. "is",
  88. "it",
  89. "ka",
  90. "kmr",
  91. "kn",
  92. "la-x-liturgic",
  93. "la",
  94. "lt",
  95. "lv",
  96. "ml",
  97. "mn-cyrl",
  98. "mr",
  99. "mul-ethi",
  100. "nb-no",
  101. "nl",
  102. "nn",
  103. "oc",
  104. "or",
  105. "pa",
  106. "pl",
  107. "pms",
  108. "pt",
  109. "rm",
  110. "ro",
  111. "ru",
  112. "sh-cyrl",
  113. "sh-latn",
  114. "sk",
  115. "sl",
  116. "sr-cyrl",
  117. "sv",
  118. "ta",
  119. "te",
  120. "th",
  121. "tk",
  122. "tr",
  123. "uk",
  124. "zh-latn-pinyin"
  125. ];
  126. /**
  127. * Read a file and call callback
  128. * Use "fs" (node) or "http" (browser)
  129. * @param {string} file - the filename
  130. * @param {function} cb - callback function with args (error, data)
  131. * @returns {undefined}
  132. */
  133. function readFile(file, cb, sync) {
  134. if (H.c.loader === "fs") {
  135. /* eslint-disable security/detect-non-literal-fs-filename */
  136. if (sync) {
  137. // eslint-disable-next-line no-sync
  138. return loader.readFileSync(file);
  139. }
  140. loader.readFile(file, cb);
  141. /* eslint-enable security/detect-non-literal-fs-filename */
  142. } else {
  143. loader.get(file, function onData(res) {
  144. const rawData = [];
  145. res.on("data", function onChunk(chunk) {
  146. rawData.push(chunk);
  147. });
  148. res.on("end", function onEnd() {
  149. cb(null, Buffer.concat(rawData));
  150. });
  151. });
  152. }
  153. return null;
  154. }
  155. /**
  156. * Read a wasm file, dispatch "engineLoaded" on success
  157. * @returns {undefined}
  158. */
  159. function loadWasm() {
  160. if (H.c.sync) {
  161. /* eslint-disable security/detect-non-literal-fs-filename */
  162. const data = readFile(`${H.c.paths.maindir}hyphenEngine.wasm`, null, true);
  163. /* eslint-enable security/detect-non-literal-fs-filename */
  164. H.binaries.set("hyphenEngine", new Uint8Array(data).buffer);
  165. H.events.dispatch("engineLoaded");
  166. } else {
  167. readFile(
  168. `${H.c.paths.maindir}hyphenEngine.wasm`,
  169. function cb(err, data) {
  170. if (err) {
  171. H.events.dispatch("error", {
  172. "key": "hyphenEngine",
  173. "msg": `${H.c.paths.maindir}hyphenEngine.wasm not found.`
  174. });
  175. } else {
  176. H.binaries.set("hyphenEngine", new Uint8Array(data).buffer);
  177. H.events.dispatch("engineLoaded");
  178. }
  179. },
  180. false
  181. );
  182. }
  183. }
  184. /**
  185. * Read a hpb file, dispatch "hpbLoaded" on success
  186. * @param {string} lang - The language
  187. * @returns {undefined}
  188. */
  189. function loadHpb(lang) {
  190. if (H.c.sync) {
  191. const data = readFile(`${H.c.paths.patterndir}${lang}.hpb`, null, true);
  192. H.binaries.set(lang, new Uint8Array(data).buffer);
  193. H.events.dispatch("hpbLoaded", {"msg": lang});
  194. } else {
  195. readFile(
  196. `${H.c.paths.patterndir}${lang}.hpb`,
  197. function cb(err, data) {
  198. if (err) {
  199. H.events.dispatch("error", {
  200. "key": lang,
  201. "msg": `${H.c.paths.patterndir}${lang}.hpb not found.`
  202. });
  203. } else {
  204. H.binaries.set(lang, new Uint8Array(data).buffer);
  205. H.events.dispatch("hpbLoaded", {"msg": lang});
  206. }
  207. },
  208. false
  209. );
  210. }
  211. }
  212. /**
  213. * Calculate heap size for wasm
  214. * wasm page size: 65536 = 64 Ki
  215. * @param {number} targetSize The targetet Size
  216. * @returns {number} The necessary heap size
  217. */
  218. function calculateHeapSize(targetSize) {
  219. return Math.ceil(targetSize / 65536) * 65536;
  220. }
  221. /**
  222. * Calculate Base Data
  223. *
  224. * Build Heap (the heap object's byteLength must be
  225. * either 2^n for n in [12, 24)
  226. * or 2^24 · n for n ≥ 1;)
  227. *
  228. * MEMORY LAYOUT:
  229. *
  230. * -------------------- <- Offset 0
  231. * | translateMap |
  232. * | keys: |
  233. * |256 chars * 2Bytes|
  234. * | + |
  235. * | values: |
  236. * |256 chars * 1Byte |
  237. * -------------------- <- 768 Bytes
  238. * | alphabet |
  239. * |256 chars * 2Bytes|
  240. * -------------------- <- valueStoreOffset (vs) = 1280
  241. * | valueStore |
  242. * | 1 Byte |
  243. * |* valueStoreLength|
  244. * --------------------
  245. * | align to 4Bytes |
  246. * -------------------- <- patternTrieOffset (pt)
  247. * | patternTrie |
  248. * | 4 Bytes |
  249. * |*patternTrieLength|
  250. * -------------------- <- wordOffset (wo)
  251. * | wordStore |
  252. * | Uint16[64] | 128 bytes
  253. * -------------------- <- translatedWordOffset (tw)
  254. * | transl.WordStore |
  255. * | Uint8[64] | 64 bytes
  256. * -------------------- <- hyphenPointsOffset (hp)
  257. * | hyphenPoints |
  258. * | Uint8[64] | 64 bytes
  259. * -------------------- <- hyphenatedWordOffset (hw)
  260. * | hyphenatedWord |
  261. * | Uint16[128] | 256 Bytes
  262. * -------------------- <- hpbOffset (ho) -
  263. * | HEADER | |
  264. * | 6*4 Bytes | |
  265. * | 24 Bytes | |
  266. * -------------------- |
  267. * | PATTERN LIC | |
  268. * | variable Length | |
  269. * -------------------- |
  270. * | align to 4Bytes | } this is the .hpb-file
  271. * -------------------- <- hpbTranslateOffset |
  272. * | TRANSLATE | |
  273. * | 2 + [0] * 2Bytes | |
  274. * -------------------- <-hpbPatternsOffset(po)|
  275. * | PATTERNS | |
  276. * | patternsLength | |
  277. * -------------------- <- heapEnd -
  278. * | align to 4Bytes |
  279. * -------------------- <- heapSize (hs)
  280. * @param {Object} hpbBuf FileBuffer from .hpb-file
  281. * @returns {Object} baseData-object
  282. */
  283. function calculateBaseData(hpbBuf) {
  284. const hpbMetaData = new Uint32Array(hpbBuf).subarray(0, 8);
  285. const valueStoreLength = hpbMetaData[7];
  286. const valueStoreOffset = 1280;
  287. const patternTrieOffset = valueStoreOffset + valueStoreLength +
  288. (4 - ((valueStoreOffset + valueStoreLength) % 4));
  289. const wordOffset = patternTrieOffset + (hpbMetaData[6] * 4);
  290. return {
  291. // Set hpbOffset
  292. "ho": wordOffset + 512,
  293. // Set hyphenPointsOffset
  294. "hp": wordOffset + 192,
  295. // Set heapSize
  296. "hs": Math.max(calculateHeapSize(wordOffset + 512 + hpbMetaData[2] + hpbMetaData[3]), 32 * 1024 * 64),
  297. // Set hyphenatedWordOffset
  298. "hw": wordOffset + 256,
  299. // Set leftmin
  300. "lm": hpbMetaData[4],
  301. // Set patternsLength
  302. "pl": hpbMetaData[3],
  303. // Set hpbPatternsOffset
  304. "po": wordOffset + 512 + hpbMetaData[2],
  305. // Set patternTrieOffset
  306. "pt": patternTrieOffset,
  307. // Set rightmin
  308. "rm": hpbMetaData[5],
  309. // Set translateOffset
  310. "to": wordOffset + 512 + hpbMetaData[1],
  311. // Set translatedWordOffset
  312. "tw": wordOffset + 128,
  313. // Set valueStoreOffset
  314. "vs": valueStoreOffset,
  315. // Set wordOffset
  316. "wo": wordOffset
  317. };
  318. }
  319. /**
  320. * Convert exceptions to Map
  321. * @param {string} exc comma separated list of exceptions
  322. * @returns {Object} Map of exceptions
  323. */
  324. function convertExceptions(exc) {
  325. const r = new Map();
  326. exc.split(", ").forEach(function eachExc(e) {
  327. const key = e.replace(/-/g, "");
  328. r.set(key, e);
  329. });
  330. return r;
  331. }
  332. /**
  333. * Create lang Object
  334. * @param {string} lang The language
  335. * @returns {Object} The newly created lang object
  336. */
  337. function createLangObj(lang) {
  338. if (!H.languages) {
  339. H.languages = new Map();
  340. }
  341. if (!H.languages.has(lang)) {
  342. H.languages.set(lang, empty());
  343. }
  344. return H.languages.get(lang);
  345. }
  346. /**
  347. * Setup a language object (lo) and dispatch "engineReady"
  348. * @param {string} lang The language
  349. * @param {function} hyphenateFunction The hyphenateFunction
  350. * @param {string} alphabet List of used characters
  351. * @param {number} leftmin leftmin
  352. * @param {number} rightmin rightmin
  353. * @returns {undefined}
  354. */
  355. function prepareLanguagesObj(
  356. lang,
  357. hyphenateFunction,
  358. alphabet,
  359. leftmin,
  360. rightmin
  361. ) {
  362. alphabet = alphabet.replace(/-/g, "");
  363. const lo = createLangObj(lang);
  364. if (!lo.engineReady) {
  365. lo.cache = new Map();
  366. /* eslint-disable security/detect-object-injection */
  367. if (H.c.exceptions.global) {
  368. if (H.c.exceptions[lang]) {
  369. H.c.exceptions[lang] += `, ${H.c.exceptions.global}`;
  370. } else {
  371. H.c.exceptions[lang] = H.c.exceptions.global;
  372. }
  373. }
  374. if (H.c.exceptions[lang]) {
  375. lo.exceptions = convertExceptions(H.c.exceptions[lang]);
  376. delete H.c.exceptions[lang];
  377. } else {
  378. lo.exceptions = new Map();
  379. }
  380. /* eslint-enable security/detect-object-injection */
  381. /* eslint-disable security/detect-non-literal-regexp */
  382. lo.genRegExp = new RegExp(`[\\w${alphabet}${String.fromCharCode(8204)}-]{${H.c.minWordLength},}`, "gi");
  383. /* eslint-enable security/detect-non-literal-regexp */
  384. lo.leftmin = leftmin;
  385. lo.rightmin = rightmin;
  386. lo.hyphenateFunction = hyphenateFunction;
  387. lo.engineReady = true;
  388. }
  389. H.events.dispatch("engineReady", {"msg": lang});
  390. }
  391. /**
  392. * Setup env for hyphenateFunction
  393. * @param {Object} baseData baseData
  394. * @param {function} hyphenateFunc hyphenateFunction
  395. * @returns {function} hyphenateFunction with closured environment
  396. */
  397. function encloseHyphenateFunction(baseData, hyphenateFunc) {
  398. /* eslint-disable no-bitwise */
  399. const heapBuffer = baseData.wasmMemory.buffer;
  400. const wordOffset = baseData.wo;
  401. const wordStore = (new Uint16Array(heapBuffer)).subarray(
  402. wordOffset >> 1,
  403. (wordOffset >> 1) + 64
  404. );
  405. const defLeftmin = baseData.lm;
  406. const defRightmin = baseData.rm;
  407. const hyphenatedWordStore = (new Uint16Array(heapBuffer)).subarray(
  408. baseData.hw >> 1,
  409. (baseData.hw >> 1) + 128
  410. );
  411. /* eslint-enable no-bitwise */
  412. /**
  413. * The hyphenateFunction that encloses the env above
  414. * Copies the word to wasm-Memory, calls wasm.hyphenateFunc and reads
  415. * the hyphenated word from wasm-Memory (eventually replacing hyphenchar)
  416. * @param {String} word - the word that has to be hyphenated
  417. * @param {String} hyphenchar – the hyphenate character
  418. * @param {Number} leftmin – min number of chars to remain on line
  419. * @param {Number} rightmin – min number of chars to go to new line
  420. * @returns {String} the hyphenated word
  421. */
  422. wordStore[0] = 95;
  423. return function enclHyphenate(word, hyphenchar, leftmin, rightmin) {
  424. let i = 0;
  425. let cc = word.charCodeAt(i);
  426. leftmin = leftmin || defLeftmin;
  427. rightmin = rightmin || defRightmin;
  428. while (cc) {
  429. i += 1;
  430. // eslint-disable-next-line security/detect-object-injection
  431. wordStore[i] = cc;
  432. cc = word.charCodeAt(i);
  433. }
  434. wordStore[i + 1] = 95;
  435. wordStore[i + 2] = 0;
  436. if (hyphenateFunc(leftmin, rightmin) === 1) {
  437. word = String.fromCharCode.apply(
  438. null,
  439. hyphenatedWordStore.subarray(
  440. 1,
  441. hyphenatedWordStore[0] + 1
  442. )
  443. );
  444. if (hyphenchar !== "\u00AD") {
  445. word = word.replace(/\u00AD/g, hyphenchar);
  446. }
  447. }
  448. return word;
  449. };
  450. }
  451. /**
  452. * Instantiate Wasm Engine, then compute the pattern trie and
  453. * call prepareLanguagesObj.
  454. * @param {string} lang The language
  455. * @returns {undefined}
  456. */
  457. function instantiateWasmEngine(lang) {
  458. const baseData = calculateBaseData(H.binaries.get(lang));
  459. const wasmMemory = new WebAssembly.Memory({
  460. "initial": baseData.hs / 65536,
  461. "maximum": 256
  462. });
  463. const ui32wasmMemory = new Uint32Array(wasmMemory.buffer);
  464. ui32wasmMemory.set(
  465. new Uint32Array(H.binaries.get(lang)),
  466. // eslint-disable-next-line no-bitwise
  467. baseData.ho >> 2
  468. );
  469. baseData.wasmMemory = wasmMemory;
  470. const importObj = {
  471. "env": {
  472. "memory": baseData.wasmMemory,
  473. "memoryBase": 0
  474. },
  475. "x": baseData
  476. };
  477. if (H.c.sync) {
  478. const heInstance = new WebAssembly.Instance(
  479. new WebAssembly.Module(H.binaries.get("hyphenEngine")),
  480. importObj
  481. );
  482. heInstance.exports.convert();
  483. prepareLanguagesObj(
  484. lang,
  485. encloseHyphenateFunction(
  486. baseData,
  487. heInstance.exports.hyphenate
  488. ),
  489. decode(
  490. (new Uint8Array(wasmMemory.buffer)).
  491. subarray(768, 1280)
  492. ),
  493. baseData.lm,
  494. baseData.rm
  495. );
  496. } else {
  497. WebAssembly.instantiate(H.binaries.get("hyphenEngine"), importObj).then(
  498. function runWasm(result) {
  499. result.instance.exports.convert();
  500. prepareLanguagesObj(
  501. lang,
  502. encloseHyphenateFunction(
  503. baseData,
  504. result.instance.exports.hyphenate
  505. ),
  506. decode(
  507. (new Uint8Array(wasmMemory.buffer)).
  508. subarray(768, 1280)
  509. ),
  510. baseData.lm,
  511. baseData.rm
  512. );
  513. }
  514. );
  515. }
  516. }
  517. let engineInstantiator = null;
  518. const hpb = [];
  519. /**
  520. * Instantiate hyphenEngines for languages
  521. * @param {string} lang The language
  522. * @returns {undefined}
  523. */
  524. function prepare(lang) {
  525. if (lang === "*") {
  526. engineInstantiator = instantiateWasmEngine;
  527. hpb.forEach(function eachHbp(hpbLang) {
  528. engineInstantiator(hpbLang);
  529. });
  530. } else if (engineInstantiator) {
  531. engineInstantiator(lang);
  532. } else {
  533. hpb.push(lang);
  534. }
  535. }
  536. const wordHyphenatorPool = new Map();
  537. /**
  538. * Factory for hyphenatorFunctions for a specific language and class
  539. * @param {Object} lo Language-Object
  540. * @param {string} lang The language
  541. * @returns {function} The hyphenate function
  542. */
  543. function createWordHyphenator(lo, lang) {
  544. /**
  545. * HyphenateFunction for compound words
  546. * @param {string} word The word
  547. * @returns {string} The hyphenated compound word
  548. */
  549. function hyphenateCompound(word) {
  550. const zeroWidthSpace = String.fromCharCode(8203);
  551. let parts = null;
  552. let wordHyphenator = null;
  553. if (H.c.compound === "auto" ||
  554. H.c.compound === "all") {
  555. wordHyphenator = createWordHyphenator(lo, lang);
  556. parts = word.split("-").map(function h7eParts(p) {
  557. if (p.length >= H.c.minWordLength) {
  558. return wordHyphenator(p);
  559. }
  560. return p;
  561. });
  562. if (H.c.compound === "auto") {
  563. word = parts.join("-");
  564. } else {
  565. word = parts.join("-" + zeroWidthSpace);
  566. }
  567. } else {
  568. word = word.replace("-", "-" + zeroWidthSpace);
  569. }
  570. return word;
  571. }
  572. /**
  573. * HyphenateFunction for words (compound or not)
  574. * @param {string} word The word
  575. * @returns {string} The hyphenated word
  576. */
  577. function hyphenator(word) {
  578. let hw = lo.cache.get(word);
  579. if (!hw) {
  580. if (lo.exceptions.has(word)) {
  581. hw = lo.exceptions.get(word).replace(
  582. /-/g,
  583. H.c.hyphen
  584. );
  585. } else if (word.indexOf("-") === -1) {
  586. if (word.length > 61) {
  587. H.events.dispatch("error", {"msg": "found word longer than 61 characters"});
  588. hw = word;
  589. } else {
  590. hw = lo.hyphenateFunction(
  591. word,
  592. H.c.hyphen,
  593. H.c.leftmin,
  594. H.c.rightmin
  595. );
  596. }
  597. } else {
  598. hw = hyphenateCompound(word);
  599. }
  600. lo.cache.set(word, hw);
  601. }
  602. return hw;
  603. }
  604. wordHyphenatorPool.set(lang, hyphenator);
  605. return hyphenator;
  606. }
  607. const orphanController = (function createOrphanController() {
  608. /**
  609. * Function template
  610. * @param {string} ignore unused result of replace
  611. * @param {string} leadingWhiteSpace The leading whiteSpace
  612. * @param {string} lastWord The last word
  613. * @param {string} trailingWhiteSpace The trailing whiteSpace
  614. * @returns {string} Treated end of text
  615. */
  616. function controlOrphans(
  617. ignore,
  618. leadingWhiteSpace,
  619. lastWord,
  620. trailingWhiteSpace
  621. ) {
  622. let h = H.c.hyphen;
  623. if (".\\+*?[^]$(){}=!<>|:-".indexOf(H.c.hyphen) !== -1) {
  624. h = `\\${H.c.hyphen}`;
  625. }
  626. if (H.c.orphanControl === 3 && leadingWhiteSpace === " ") {
  627. leadingWhiteSpace = String.fromCharCode(160);
  628. }
  629. /* eslint-disable security/detect-non-literal-regexp */
  630. return leadingWhiteSpace + lastWord.replace(new RegExp(h, "g"), "") + trailingWhiteSpace;
  631. /* eslint-enable security/detect-non-literal-regexp */
  632. }
  633. return controlOrphans;
  634. }());
  635. /**
  636. * Encloses hyphenateTextFunction
  637. * @param {string} lang - The language
  638. * @return {function} The hyphenateText-function
  639. */
  640. function createTextHyphenator(lang) {
  641. const lo = H.languages.get(lang);
  642. const wordHyphenator = (wordHyphenatorPool.has(lang))
  643. ? wordHyphenatorPool.get(lang)
  644. : createWordHyphenator(lo, lang);
  645. /**
  646. * Hyphenate text
  647. * @param {string} text The text
  648. * @param {string} lang The language of the text
  649. * @returns {string} Hyphenated text
  650. */
  651. return function hyphenateText(text) {
  652. if (H.c.normalize) {
  653. text = text.normalize("NFC");
  654. }
  655. let tn = text.replace(lo.genRegExp, wordHyphenator);
  656. if (H.c.orphanControl !== 1) {
  657. tn = tn.replace(
  658. // eslint-disable-next-line prefer-named-capture-group
  659. /(\u0020*)(\S+)(\s*)$/,
  660. orphanController
  661. );
  662. }
  663. return tn;
  664. };
  665. }
  666. (function setupEvents() {
  667. // Events known to the system
  668. const definedEvents = new Map();
  669. /**
  670. * Create Event Object
  671. * @param {string} name The Name of the event
  672. * @param {function|null} defFunc The default method of the event
  673. * @param {boolean} cancellable Is the default cancellable
  674. * @returns {undefined}
  675. */
  676. function define(name, defFunc, cancellable) {
  677. definedEvents.set(name, {
  678. "cancellable": cancellable,
  679. "default": defFunc,
  680. "register": []
  681. });
  682. }
  683. define(
  684. "error",
  685. function def(e) {
  686. // eslint-disable-next-line no-console
  687. console.error(e.msg);
  688. },
  689. true
  690. );
  691. define(
  692. "engineLoaded",
  693. function def() {
  694. prepare("*");
  695. },
  696. false
  697. );
  698. define(
  699. "hpbLoaded",
  700. function def(e) {
  701. prepare(e.msg);
  702. },
  703. false
  704. );
  705. define(
  706. "engineReady",
  707. null,
  708. false
  709. );
  710. /**
  711. * Dispatch error <name> with arguments <data>
  712. * @param {string} name The name of the event
  713. * @param {Object|undefined} data Data of the event
  714. * @returns {undefined}
  715. */
  716. function dispatch(name, data) {
  717. if (!data) {
  718. data = empty();
  719. }
  720. data.defaultPrevented = false;
  721. data.preventDefault = function preventDefault() {
  722. if (definedEvents.get(name).cancellable) {
  723. data.defaultPrevented = true;
  724. }
  725. };
  726. definedEvents.get(name).register.forEach(function call(currentHandler) {
  727. currentHandler(data);
  728. });
  729. if (!data.defaultPrevented && definedEvents.get(name).default) {
  730. definedEvents.get(name).default(data);
  731. }
  732. }
  733. /**
  734. * Add EventListender <handler> to event <name>
  735. * @param {string} name The name of the event
  736. * @param {function} handler Function to register
  737. * @returns {undefined}
  738. */
  739. function addListener(name, handler) {
  740. if (definedEvents.has(name)) {
  741. definedEvents.get(name).register.push(handler);
  742. } else {
  743. H.events.dispatch(
  744. "error",
  745. {"msg": `unknown Event "${name}" discarded`}
  746. );
  747. }
  748. }
  749. H.events = empty();
  750. H.events.dispatch = dispatch;
  751. H.events.define = define;
  752. H.events.addListener = addListener;
  753. }());
  754. H.config = function config(userConfig) {
  755. const defaults = Object.create(null, {
  756. "compound": setProp("hyphen", 2),
  757. "exceptions": setProp(empty(), 2),
  758. "hyphen": setProp(String.fromCharCode(173), 2),
  759. "leftmin": setProp(0, 2),
  760. "loader": setProp("fs", 2),
  761. "minWordLength": setProp(6, 2),
  762. "normalize": setProp(false, 2),
  763. "orphanControl": setProp(1, 2),
  764. "paths": setProp(Object.create(null, {
  765. "maindir": setProp(`${__dirname}/`, 2),
  766. "patterndir": setProp(`${__dirname}/patterns/`, 2)
  767. }), 2),
  768. "require": setProp([], 2),
  769. "rightmin": setProp(0, 2),
  770. "sync": setProp(false, 2)
  771. });
  772. const settings = Object.create(defaults);
  773. Object.keys(userConfig).forEach(function each(key) {
  774. Object.defineProperty(
  775. settings,
  776. key,
  777. /* eslint-disable security/detect-object-injection */
  778. setProp(userConfig[key], 3)
  779. /* eslint-enable security/detect-object-injection */
  780. );
  781. });
  782. H.c = settings;
  783. if (H.c.loader === "https" || H.c.loader === "http") {
  784. /* eslint-disable global-require, security/detect-non-literal-require */
  785. loader = require(H.c.loader);
  786. /* eslint-enable global-require, security/detect-non-literal-require */
  787. }
  788. if (H.c.handleEvent) {
  789. Object.keys(H.c.handleEvent).forEach(function add(name) {
  790. /* eslint-disable security/detect-object-injection */
  791. H.events.addListener(name, H.c.handleEvent[name]);
  792. /* eslint-enable security/detect-object-injection */
  793. });
  794. }
  795. const result = new Map();
  796. if (H.c.require.length === 0) {
  797. H.events.dispatch(
  798. "error",
  799. {"msg": "No language has been required. Setup config according to documenation."}
  800. );
  801. }
  802. H.c.require.forEach(function each(lang) {
  803. loadHpb(lang);
  804. if (H.c.sync) {
  805. H.events.addListener("engineReady", function handler(e) {
  806. if (e.msg === lang) {
  807. result.set(lang, createTextHyphenator(lang));
  808. }
  809. });
  810. } else {
  811. const prom = new Promise(function pro(resolve, reject) {
  812. H.events.addListener("engineReady", function handler(e) {
  813. if (e.msg === lang) {
  814. resolve(createTextHyphenator(lang));
  815. }
  816. });
  817. H.events.addListener("error", function handler(e) {
  818. e.preventDefault();
  819. if (e.key === lang || e.key === "hyphenEngine") {
  820. reject(e.msg);
  821. }
  822. });
  823. });
  824. result.set(lang, prom);
  825. }
  826. });
  827. loadWasm();
  828. return (result.size === 1)
  829. ? result.get(H.c.require[0])
  830. : result;
  831. };
  832. module.exports = H;