hyphenopoly.module.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. /**
  2. * @license Hyphenopoly.module.js 3.4.0 - 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. const data = readFile(`${H.c.paths.maindir}hyphenEngine.wasm`, null, true);
  162. H.binaries.set("hyphenEngine", new Uint8Array(data).buffer);
  163. H.events.dispatch("engineLoaded");
  164. } else {
  165. readFile(
  166. `${H.c.paths.maindir}hyphenEngine.wasm`,
  167. function cb(err, data) {
  168. if (err) {
  169. H.events.dispatch("error", {
  170. "key": "hyphenEngine",
  171. "msg": `${H.c.paths.maindir}hyphenEngine.wasm not found.`
  172. });
  173. } else {
  174. H.binaries.set("hyphenEngine", new Uint8Array(data).buffer);
  175. H.events.dispatch("engineLoaded");
  176. }
  177. },
  178. false
  179. );
  180. }
  181. }
  182. /**
  183. * Read a hpb file, dispatch "hpbLoaded" on success
  184. * @param {string} lang - The language
  185. * @returns {undefined}
  186. */
  187. function loadHpb(lang) {
  188. if (H.c.sync) {
  189. const data = readFile(`${H.c.paths.patterndir}${lang}.hpb`, null, true);
  190. H.binaries.set(lang, new Uint8Array(data).buffer);
  191. H.events.dispatch("hpbLoaded", {"msg": lang});
  192. } else {
  193. readFile(
  194. `${H.c.paths.patterndir}${lang}.hpb`,
  195. function cb(err, data) {
  196. if (err) {
  197. H.events.dispatch("error", {
  198. "key": lang,
  199. "msg": `${H.c.paths.patterndir}${lang}.hpb not found.`
  200. });
  201. } else {
  202. H.binaries.set(lang, new Uint8Array(data).buffer);
  203. H.events.dispatch("hpbLoaded", {"msg": lang});
  204. }
  205. },
  206. false
  207. );
  208. }
  209. }
  210. /**
  211. * Calculate heap size for wasm
  212. * wasm page size: 65536 = 64 Ki
  213. * @param {number} targetSize The targetet Size
  214. * @returns {number} The necessary heap size
  215. */
  216. function calculateHeapSize(targetSize) {
  217. return Math.ceil(targetSize / 65536) * 65536;
  218. }
  219. /**
  220. * Calculate Base Data
  221. *
  222. * Build Heap (the heap object's byteLength must be
  223. * either 2^n for n in [12, 24)
  224. * or 2^24 · n for n ≥ 1;)
  225. *
  226. * MEMORY LAYOUT:
  227. *
  228. * -------------------- <- Offset 0
  229. * | translateMap |
  230. * | keys: |
  231. * |256 chars * 2Bytes|
  232. * | + |
  233. * | values: |
  234. * |256 chars * 1Byte |
  235. * -------------------- <- 768 Bytes
  236. * | alphabet |
  237. * |256 chars * 2Bytes|
  238. * -------------------- <- valueStoreOffset (vs) = 1280
  239. * | valueStore |
  240. * | 1 Byte |
  241. * |* valueStoreLength|
  242. * --------------------
  243. * | align to 4Bytes |
  244. * -------------------- <- patternTrieOffset (pt)
  245. * | patternTrie |
  246. * | 4 Bytes |
  247. * |*patternTrieLength|
  248. * -------------------- <- wordOffset (wo)
  249. * | wordStore |
  250. * | Uint16[64] | 128 bytes
  251. * -------------------- <- translatedWordOffset (tw)
  252. * | transl.WordStore |
  253. * | Uint8[64] | 64 bytes
  254. * -------------------- <- hyphenPointsOffset (hp)
  255. * | hyphenPoints |
  256. * | Uint8[64] | 64 bytes
  257. * -------------------- <- hyphenatedWordOffset (hw)
  258. * | hyphenatedWord |
  259. * | Uint16[128] | 256 Bytes
  260. * -------------------- <- hpbOffset (ho) -
  261. * | HEADER | |
  262. * | 6*4 Bytes | |
  263. * | 24 Bytes | |
  264. * -------------------- |
  265. * | PATTERN LIC | |
  266. * | variable Length | |
  267. * -------------------- |
  268. * | align to 4Bytes | } this is the .hpb-file
  269. * -------------------- <- hpbTranslateOffset |
  270. * | TRANSLATE | |
  271. * | 2 + [0] * 2Bytes | |
  272. * -------------------- <-hpbPatternsOffset(po)|
  273. * | PATTERNS | |
  274. * | patternsLength | |
  275. * -------------------- <- heapEnd -
  276. * | align to 4Bytes |
  277. * -------------------- <- heapSize (hs)
  278. * @param {Object} hpbBuf FileBuffer from .hpb-file
  279. * @returns {Object} baseData-object
  280. */
  281. function calculateBaseData(hpbBuf) {
  282. const hpbMetaData = new Uint32Array(hpbBuf).subarray(0, 8);
  283. const valueStoreLength = hpbMetaData[7];
  284. const valueStoreOffset = 1280;
  285. const patternTrieOffset = valueStoreOffset + valueStoreLength +
  286. (4 - ((valueStoreOffset + valueStoreLength) % 4));
  287. const wordOffset = patternTrieOffset + (hpbMetaData[6] * 4);
  288. return {
  289. // Set hpbOffset
  290. "ho": wordOffset + 512,
  291. // Set hyphenPointsOffset
  292. "hp": wordOffset + 192,
  293. // Set heapSize
  294. "hs": Math.max(calculateHeapSize(wordOffset + 512 + hpbMetaData[2] + hpbMetaData[3]), 32 * 1024 * 64),
  295. // Set hyphenatedWordOffset
  296. "hw": wordOffset + 256,
  297. // Set leftmin
  298. "lm": hpbMetaData[4],
  299. // Set patternsLength
  300. "pl": hpbMetaData[3],
  301. // Set hpbPatternsOffset
  302. "po": wordOffset + 512 + hpbMetaData[2],
  303. // Set patternTrieOffset
  304. "pt": patternTrieOffset,
  305. // Set rightmin
  306. "rm": hpbMetaData[5],
  307. // Set translateOffset
  308. "to": wordOffset + 512 + hpbMetaData[1],
  309. // Set translatedWordOffset
  310. "tw": wordOffset + 128,
  311. // Set valueStoreOffset
  312. "vs": valueStoreOffset,
  313. // Set wordOffset
  314. "wo": wordOffset
  315. };
  316. }
  317. /**
  318. * Convert exceptions to Map
  319. * @param {string} exc comma separated list of exceptions
  320. * @returns {Object} Map of exceptions
  321. */
  322. function convertExceptions(exc) {
  323. const r = new Map();
  324. exc.split(", ").forEach(function eachExc(e) {
  325. const key = e.replace(/-/g, "");
  326. r.set(key, e);
  327. });
  328. return r;
  329. }
  330. /**
  331. * Create lang Object
  332. * @param {string} lang The language
  333. * @returns {Object} The newly created lang object
  334. */
  335. function createLangObj(lang) {
  336. if (!H.languages) {
  337. H.languages = new Map();
  338. }
  339. if (!H.languages.has(lang)) {
  340. H.languages.set(lang, empty());
  341. }
  342. return H.languages.get(lang);
  343. }
  344. /**
  345. * Setup a language object (lo) and dispatch "engineReady"
  346. * @param {string} lang The language
  347. * @param {function} hyphenateFunction The hyphenateFunction
  348. * @param {string} alphabet List of used characters
  349. * @param {number} leftmin leftmin
  350. * @param {number} rightmin rightmin
  351. * @returns {undefined}
  352. */
  353. function prepareLanguagesObj(
  354. lang,
  355. hyphenateFunction,
  356. alphabet,
  357. leftmin,
  358. rightmin
  359. ) {
  360. alphabet = alphabet.replace(/-/g, "");
  361. const lo = createLangObj(lang);
  362. if (!lo.engineReady) {
  363. lo.cache = new Map();
  364. /* eslint-disable security/detect-object-injection */
  365. if (H.c.exceptions.global) {
  366. if (H.c.exceptions[lang]) {
  367. H.c.exceptions[lang] += `, ${H.c.exceptions.global}`;
  368. } else {
  369. H.c.exceptions[lang] = H.c.exceptions.global;
  370. }
  371. }
  372. if (H.c.exceptions[lang]) {
  373. lo.exceptions = convertExceptions(H.c.exceptions[lang]);
  374. delete H.c.exceptions[lang];
  375. } else {
  376. lo.exceptions = new Map();
  377. }
  378. /* eslint-enable security/detect-object-injection */
  379. /* eslint-disable security/detect-non-literal-regexp */
  380. lo.genRegExp = new RegExp(`[\\w${alphabet}${String.fromCharCode(8204)}-]{${H.c.minWordLength},}`, "gi");
  381. /* eslint-enable security/detect-non-literal-regexp */
  382. lo.leftmin = leftmin;
  383. lo.rightmin = rightmin;
  384. lo.hyphenateFunction = hyphenateFunction;
  385. lo.engineReady = true;
  386. }
  387. H.events.dispatch("engineReady", {"msg": lang});
  388. }
  389. /**
  390. * Setup env for hyphenateFunction
  391. * @param {Object} baseData baseData
  392. * @param {function} hyphenateFunc hyphenateFunction
  393. * @returns {function} hyphenateFunction with closured environment
  394. */
  395. function encloseHyphenateFunction(baseData, hyphenateFunc) {
  396. /* eslint-disable no-bitwise */
  397. const heapBuffer = baseData.wasmMemory.buffer;
  398. const wordStore = (new Uint16Array(heapBuffer)).subarray(
  399. baseData.wo >> 1,
  400. (baseData.wo >> 1) + 64
  401. );
  402. const hyphenatedWordStore = (new Uint16Array(heapBuffer)).subarray(
  403. baseData.hw >> 1,
  404. (baseData.hw >> 1) + 128
  405. );
  406. /* eslint-enable no-bitwise */
  407. const defLeftmin = baseData.lm;
  408. const defRightmin = baseData.rm;
  409. /**
  410. * The hyphenateFunction that encloses the env above
  411. * Copies the word to wasm-Memory, calls wasm.hyphenateFunc and reads
  412. * the hyphenated word from wasm-Memory (eventually replacing hyphenchar)
  413. * @param {String} word - the word that has to be hyphenated
  414. * @param {String} hyphenchar – the hyphenate character
  415. * @param {Number} leftmin – min number of chars to remain on line
  416. * @param {Number} rightmin – min number of chars to go to new line
  417. * @returns {String} the hyphenated word
  418. */
  419. wordStore[0] = 95;
  420. return function enclHyphenate(word, hyphencc, leftmin, rightmin) {
  421. leftmin = leftmin || defLeftmin;
  422. rightmin = rightmin || defRightmin;
  423. let i = 0;
  424. let cc = 0;
  425. do {
  426. cc = word.charCodeAt(i);
  427. i += 1;
  428. // eslint-disable-next-line security/detect-object-injection
  429. wordStore[i] = cc;
  430. } while (cc);
  431. /* eslint-disable security/detect-object-injection */
  432. wordStore[i] = 95;
  433. wordStore[i + 1] = 0;
  434. if (hyphenateFunc(leftmin, rightmin, hyphencc) === 1) {
  435. word = String.fromCharCode.apply(
  436. null,
  437. hyphenatedWordStore.subarray(
  438. 1,
  439. hyphenatedWordStore[0] + 1
  440. )
  441. );
  442. }
  443. return word;
  444. };
  445. }
  446. /**
  447. * Instantiate Wasm Engine, then compute the pattern trie and
  448. * call prepareLanguagesObj.
  449. * @param {string} lang The language
  450. * @returns {undefined}
  451. */
  452. function instantiateWasmEngine(lang) {
  453. const baseData = calculateBaseData(H.binaries.get(lang));
  454. const wasmMemory = new WebAssembly.Memory({
  455. "initial": baseData.hs / 65536,
  456. "maximum": 256
  457. });
  458. const ui32wasmMemory = new Uint32Array(wasmMemory.buffer);
  459. ui32wasmMemory.set(
  460. new Uint32Array(H.binaries.get(lang)),
  461. // eslint-disable-next-line no-bitwise
  462. baseData.ho >> 2
  463. );
  464. baseData.wasmMemory = wasmMemory;
  465. const importObj = {
  466. "env": {
  467. "memory": baseData.wasmMemory,
  468. "memoryBase": 0
  469. },
  470. "x": baseData
  471. };
  472. if (H.c.sync) {
  473. const heInstance = new WebAssembly.Instance(
  474. new WebAssembly.Module(H.binaries.get("hyphenEngine")),
  475. importObj
  476. );
  477. heInstance.exports.convert();
  478. prepareLanguagesObj(
  479. lang,
  480. encloseHyphenateFunction(
  481. baseData,
  482. heInstance.exports.hyphenate
  483. ),
  484. decode(
  485. (new Uint8Array(wasmMemory.buffer)).
  486. subarray(768, 1280)
  487. ),
  488. baseData.lm,
  489. baseData.rm
  490. );
  491. } else {
  492. WebAssembly.instantiate(H.binaries.get("hyphenEngine"), importObj).then(
  493. function runWasm(result) {
  494. result.instance.exports.convert();
  495. prepareLanguagesObj(
  496. lang,
  497. encloseHyphenateFunction(
  498. baseData,
  499. result.instance.exports.hyphenate
  500. ),
  501. decode(
  502. (new Uint8Array(wasmMemory.buffer)).
  503. subarray(768, 1280)
  504. ),
  505. baseData.lm,
  506. baseData.rm
  507. );
  508. }
  509. );
  510. }
  511. }
  512. let engineInstantiator = null;
  513. const hpb = [];
  514. /**
  515. * Instantiate hyphenEngines for languages
  516. * @param {string} lang The language
  517. * @returns {undefined}
  518. */
  519. function prepare(lang) {
  520. if (lang === "*") {
  521. engineInstantiator = instantiateWasmEngine;
  522. hpb.forEach(function eachHbp(hpbLang) {
  523. engineInstantiator(hpbLang);
  524. });
  525. } else if (engineInstantiator) {
  526. engineInstantiator(lang);
  527. } else {
  528. hpb.push(lang);
  529. }
  530. }
  531. const wordHyphenatorPool = new Map();
  532. /**
  533. * Factory for hyphenatorFunctions for a specific language and class
  534. * @param {Object} lo Language-Object
  535. * @param {string} lang The language
  536. * @returns {function} The hyphenate function
  537. */
  538. function createWordHyphenator(lo, lang) {
  539. /**
  540. * HyphenateFunction for compound words
  541. * @param {string} word The word
  542. * @returns {string} The hyphenated compound word
  543. */
  544. function hyphenateCompound(word) {
  545. const zeroWidthSpace = String.fromCharCode(8203);
  546. let parts = null;
  547. let wordHyphenator = null;
  548. if (H.c.compound === "auto" ||
  549. H.c.compound === "all") {
  550. wordHyphenator = createWordHyphenator(lo, lang);
  551. parts = word.split("-").map(function h7eParts(p) {
  552. if (p.length >= H.c.minWordLength) {
  553. return wordHyphenator(p);
  554. }
  555. return p;
  556. });
  557. if (H.c.compound === "auto") {
  558. word = parts.join("-");
  559. } else {
  560. word = parts.join("-" + zeroWidthSpace);
  561. }
  562. } else {
  563. word = word.replace("-", "-" + zeroWidthSpace);
  564. }
  565. return word;
  566. }
  567. /**
  568. * Checks if a string is mixed case
  569. * @param {string} s The string
  570. * @returns {boolean} true if s is mixed case
  571. */
  572. function isMixedCase(s) {
  573. return Array.prototype.map.call(s, function mapper(c) {
  574. return (c === c.toLowerCase());
  575. }).some(function checker(v, i, a) {
  576. return (v !== a[0]);
  577. });
  578. }
  579. /* eslint-disable complexity */
  580. /**
  581. * HyphenateFunction for words (compound or not)
  582. * @param {string} word The word
  583. * @returns {string} The hyphenated word
  584. */
  585. function hyphenator(word) {
  586. let hw = lo.cache.get(word);
  587. if (!H.c.mixedCase && isMixedCase(word)) {
  588. hw = word;
  589. }
  590. if (!hw) {
  591. if (lo.exceptions.has(word)) {
  592. hw = lo.exceptions.get(word).replace(
  593. /-/g,
  594. H.c.hyphen
  595. );
  596. } else if (word.indexOf("-") === -1) {
  597. if (word.length > 61) {
  598. H.events.dispatch("error", {"msg": "found word longer than 61 characters"});
  599. hw = word;
  600. } else {
  601. hw = lo.hyphenateFunction(
  602. word,
  603. H.c.hyphen.charCodeAt(0),
  604. H.c.leftmin,
  605. H.c.rightmin
  606. );
  607. }
  608. } else {
  609. hw = hyphenateCompound(word);
  610. }
  611. lo.cache.set(word, hw);
  612. }
  613. return hw;
  614. }
  615. /* eslint-enable complexity */
  616. wordHyphenatorPool.set(lang, hyphenator);
  617. return hyphenator;
  618. }
  619. const orphanController = (function createOrphanController() {
  620. /**
  621. * Function template
  622. * @param {string} ignore unused result of replace
  623. * @param {string} leadingWhiteSpace The leading whiteSpace
  624. * @param {string} lastWord The last word
  625. * @param {string} trailingWhiteSpace The trailing whiteSpace
  626. * @returns {string} Treated end of text
  627. */
  628. function controlOrphans(
  629. ignore,
  630. leadingWhiteSpace,
  631. lastWord,
  632. trailingWhiteSpace
  633. ) {
  634. let h = H.c.hyphen;
  635. if (".\\+*?[^]$(){}=!<>|:-".indexOf(H.c.hyphen) !== -1) {
  636. h = `\\${H.c.hyphen}`;
  637. }
  638. if (H.c.orphanControl === 3 && leadingWhiteSpace === " ") {
  639. leadingWhiteSpace = String.fromCharCode(160);
  640. }
  641. /* eslint-disable security/detect-non-literal-regexp */
  642. return leadingWhiteSpace + lastWord.replace(new RegExp(h, "g"), "") + trailingWhiteSpace;
  643. /* eslint-enable security/detect-non-literal-regexp */
  644. }
  645. return controlOrphans;
  646. }());
  647. /**
  648. * Encloses hyphenateTextFunction
  649. * @param {string} lang - The language
  650. * @return {function} The hyphenateText-function
  651. */
  652. function createTextHyphenator(lang) {
  653. const lo = H.languages.get(lang);
  654. const wordHyphenator = (wordHyphenatorPool.has(lang))
  655. ? wordHyphenatorPool.get(lang)
  656. : createWordHyphenator(lo, lang);
  657. /**
  658. * Hyphenate text
  659. * @param {string} text The text
  660. * @param {string} lang The language of the text
  661. * @returns {string} Hyphenated text
  662. */
  663. return function hyphenateText(text) {
  664. if (H.c.normalize) {
  665. text = text.normalize("NFC");
  666. }
  667. let tn = text.replace(lo.genRegExp, wordHyphenator);
  668. if (H.c.orphanControl !== 1) {
  669. tn = tn.replace(
  670. // eslint-disable-next-line prefer-named-capture-group
  671. /(\u0020*)(\S+)(\s*)$/,
  672. orphanController
  673. );
  674. }
  675. return tn;
  676. };
  677. }
  678. (function setupEvents() {
  679. // Events known to the system
  680. const definedEvents = new Map();
  681. /**
  682. * Create Event Object
  683. * @param {string} name The Name of the event
  684. * @param {function|null} defFunc The default method of the event
  685. * @param {boolean} cancellable Is the default cancellable
  686. * @returns {undefined}
  687. */
  688. function define(name, defFunc, cancellable) {
  689. definedEvents.set(name, {
  690. "cancellable": cancellable,
  691. "default": defFunc,
  692. "register": []
  693. });
  694. }
  695. define(
  696. "error",
  697. function def(e) {
  698. // eslint-disable-next-line no-console
  699. console.error(e.msg);
  700. },
  701. true
  702. );
  703. define(
  704. "engineLoaded",
  705. function def() {
  706. prepare("*");
  707. },
  708. false
  709. );
  710. define(
  711. "hpbLoaded",
  712. function def(e) {
  713. prepare(e.msg);
  714. },
  715. false
  716. );
  717. define(
  718. "engineReady",
  719. null,
  720. false
  721. );
  722. /**
  723. * Dispatch error <name> with arguments <data>
  724. * @param {string} name The name of the event
  725. * @param {Object|undefined} data Data of the event
  726. * @returns {undefined}
  727. */
  728. function dispatch(name, data) {
  729. if (!data) {
  730. data = empty();
  731. }
  732. data.defaultPrevented = false;
  733. data.preventDefault = function preventDefault() {
  734. if (definedEvents.get(name).cancellable) {
  735. data.defaultPrevented = true;
  736. }
  737. };
  738. definedEvents.get(name).register.forEach(function call(currentHandler) {
  739. currentHandler(data);
  740. });
  741. if (!data.defaultPrevented && definedEvents.get(name).default) {
  742. definedEvents.get(name).default(data);
  743. }
  744. }
  745. /**
  746. * Add EventListender <handler> to event <name>
  747. * @param {string} name The name of the event
  748. * @param {function} handler Function to register
  749. * @returns {undefined}
  750. */
  751. function addListener(name, handler) {
  752. if (definedEvents.has(name)) {
  753. definedEvents.get(name).register.push(handler);
  754. } else {
  755. H.events.dispatch(
  756. "error",
  757. {"msg": `unknown Event "${name}" discarded`}
  758. );
  759. }
  760. }
  761. H.events = empty();
  762. H.events.dispatch = dispatch;
  763. H.events.define = define;
  764. H.events.addListener = addListener;
  765. }());
  766. H.config = function config(userConfig) {
  767. const defaults = Object.create(null, {
  768. "compound": setProp("hyphen", 2),
  769. "exceptions": setProp(empty(), 2),
  770. "hyphen": setProp(String.fromCharCode(173), 2),
  771. "leftmin": setProp(0, 2),
  772. "loader": setProp("fs", 2),
  773. "minWordLength": setProp(6, 2),
  774. "mixedCase": setProp(true, 2),
  775. "normalize": setProp(false, 2),
  776. "orphanControl": setProp(1, 2),
  777. "paths": setProp(Object.create(null, {
  778. "maindir": setProp(`${__dirname}/`, 2),
  779. "patterndir": setProp(`${__dirname}/patterns/`, 2)
  780. }), 2),
  781. "require": setProp([], 2),
  782. "rightmin": setProp(0, 2),
  783. "sync": setProp(false, 2)
  784. });
  785. const settings = Object.create(defaults);
  786. Object.keys(userConfig).forEach(function each(key) {
  787. Object.defineProperty(
  788. settings,
  789. key,
  790. /* eslint-disable security/detect-object-injection */
  791. setProp(userConfig[key], 3)
  792. /* eslint-enable security/detect-object-injection */
  793. );
  794. });
  795. H.c = settings;
  796. if (H.c.loader === "https") {
  797. // eslint-disable-next-line global-require
  798. loader = require("https");
  799. }
  800. if (H.c.loader === "http") {
  801. // eslint-disable-next-line global-require
  802. loader = require("http");
  803. }
  804. if (H.c.handleEvent) {
  805. Object.keys(H.c.handleEvent).forEach(function add(name) {
  806. /* eslint-disable security/detect-object-injection */
  807. H.events.addListener(name, H.c.handleEvent[name]);
  808. /* eslint-enable security/detect-object-injection */
  809. });
  810. }
  811. const result = new Map();
  812. if (H.c.require.length === 0) {
  813. H.events.dispatch(
  814. "error",
  815. {"msg": "No language has been required. Setup config according to documenation."}
  816. );
  817. }
  818. H.c.require.forEach(function each(lang) {
  819. loadHpb(lang);
  820. if (H.c.sync) {
  821. H.events.addListener("engineReady", function handler(e) {
  822. if (e.msg === lang) {
  823. result.set(lang, createTextHyphenator(lang));
  824. }
  825. });
  826. } else {
  827. const prom = new Promise(function pro(resolve, reject) {
  828. H.events.addListener("engineReady", function handler(e) {
  829. if (e.msg === lang) {
  830. resolve(createTextHyphenator(lang));
  831. }
  832. });
  833. H.events.addListener("error", function handler(e) {
  834. e.preventDefault();
  835. if (e.key === lang || e.key === "hyphenEngine") {
  836. reject(e.msg);
  837. }
  838. });
  839. });
  840. result.set(lang, prom);
  841. }
  842. });
  843. loadWasm();
  844. return (result.size === 1)
  845. ? result.get(H.c.require[0])
  846. : result;
  847. };
  848. module.exports = H;