layout_renderer.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.LayoutRenderer = void 0;
  4. const debugger_js_1 = require("../common/debugger.js");
  5. const DomUtil = require("../common/dom_util.js");
  6. const EngineConst = require("../common/engine_const.js");
  7. const engine_js_1 = require("../common/engine.js");
  8. const AudioUtil = require("./audio_util.js");
  9. const xml_renderer_js_1 = require("./xml_renderer.js");
  10. class LayoutRenderer extends xml_renderer_js_1.XmlRenderer {
  11. constructor() {
  12. super(...arguments);
  13. this.values = new Map();
  14. }
  15. finalize(str) {
  16. setRelValues(this.values.get('rel'));
  17. return setTwoDim(str);
  18. }
  19. pause(_pause) {
  20. return '';
  21. }
  22. prosodyElement(attr, value) {
  23. return attr === EngineConst.personalityProps.LAYOUT ? `<${value}>` : '';
  24. }
  25. closeTag(tag) {
  26. return `</${tag}>`;
  27. }
  28. markup(descrs) {
  29. this.values.clear();
  30. const result = [];
  31. let content = [];
  32. for (const descr of descrs) {
  33. if (!descr.layout) {
  34. content.push(descr);
  35. continue;
  36. }
  37. result.push(this.processContent(content));
  38. content = [];
  39. const [pref, layout, value] = this.layoutValue(descr.layout);
  40. if (pref === 'begin') {
  41. result.push('<' + layout + (value ? ` value="${value}"` : '') + '>');
  42. continue;
  43. }
  44. if (pref === 'end') {
  45. result.push('</' + layout + '>');
  46. continue;
  47. }
  48. console.warn('Something went wrong with layout markup: ' + layout);
  49. }
  50. result.push(this.processContent(content));
  51. return result.join('');
  52. }
  53. processContent(content) {
  54. const result = [];
  55. const markup = AudioUtil.personalityMarkup(content);
  56. for (let i = 0, descr; (descr = markup[i]); i++) {
  57. if (descr.span) {
  58. result.push(this.merge(descr.span));
  59. continue;
  60. }
  61. if (AudioUtil.isPauseElement(descr)) {
  62. continue;
  63. }
  64. }
  65. return result.join('');
  66. }
  67. layoutValue(layout) {
  68. const match = layout.match(/^(begin|end|)(.*\D)(\d*)$/);
  69. const value = match[3];
  70. if (!value) {
  71. return [match[1], match[2], ''];
  72. }
  73. layout = match[2];
  74. if (!this.values.has(layout)) {
  75. this.values.set(layout, {});
  76. }
  77. this.values.get(layout)[value] = true;
  78. return [match[1], layout, value];
  79. }
  80. }
  81. exports.LayoutRenderer = LayoutRenderer;
  82. LayoutRenderer.options = {
  83. cayleyshort: engine_js_1.Engine.getInstance().cayleyshort,
  84. linebreaks: engine_js_1.Engine.getInstance().linebreaks
  85. };
  86. let twodExpr = '';
  87. const handlers = {
  88. TABLE: handleTable,
  89. CASES: handleCases,
  90. CAYLEY: handleCayley,
  91. MATRIX: handleMatrix,
  92. CELL: recurseTree,
  93. FENCE: recurseTree,
  94. ROW: recurseTree,
  95. FRACTION: handleFraction,
  96. NUMERATOR: handleFractionPart,
  97. DENOMINATOR: handleFractionPart,
  98. REL: handleRelation,
  99. OP: handleRelation
  100. };
  101. function applyHandler(element) {
  102. const tag = DomUtil.tagName(element);
  103. const handler = handlers[tag];
  104. return handler ? handler(element) : element.textContent;
  105. }
  106. const relValues = new Map();
  107. function setRelValues(values) {
  108. relValues.clear();
  109. if (!values)
  110. return;
  111. const keys = Object.keys(values)
  112. .map((x) => parseInt(x))
  113. .sort();
  114. for (let i = 0, key; (key = keys[i]); i++) {
  115. relValues.set(key, i + 1);
  116. }
  117. }
  118. function setTwoDim(str) {
  119. twodExpr = '';
  120. const dom = DomUtil.parseInput(`<all>${str}</all>`);
  121. debugger_js_1.Debugger.getInstance().output(DomUtil.formatXml(dom.toString()));
  122. twodExpr = recurseTree(dom);
  123. return twodExpr;
  124. }
  125. function combineContent(str1, str2) {
  126. if (!str1 || !str2) {
  127. return str1 + str2;
  128. }
  129. const height1 = strHeight(str1);
  130. const height2 = strHeight(str2);
  131. const diff = height1 - height2;
  132. str1 = diff < 0 ? padCell(str1, height2, strWidth(str1)) : str1;
  133. str2 = diff > 0 ? padCell(str2, height1, strWidth(str2)) : str2;
  134. const lines1 = str1.split(/\r\n|\r|\n/);
  135. const lines2 = str2.split(/\r\n|\r|\n/);
  136. const result = [];
  137. for (let i = 0; i < lines1.length; i++) {
  138. result.push(lines1[i] + lines2[i]);
  139. }
  140. return result.join('\n');
  141. }
  142. function recurseTree(dom) {
  143. let result = '';
  144. for (const child of Array.from(dom.childNodes)) {
  145. if (child.nodeType === DomUtil.NodeType.TEXT_NODE) {
  146. result = combineContent(result, child.textContent);
  147. continue;
  148. }
  149. result = combineContent(result, applyHandler(child));
  150. }
  151. return result;
  152. }
  153. function strHeight(str) {
  154. return str.split(/\r\n|\r|\n/).length;
  155. }
  156. function strWidth(str) {
  157. return str.split(/\r\n|\r|\n/).reduce((max, x) => Math.max(x.length, max), 0);
  158. }
  159. function padHeight(str, height) {
  160. const padding = height - strHeight(str);
  161. return str + (padding > 0 ? new Array(padding + 1).join('\n') : '');
  162. }
  163. function padWidth(str, width) {
  164. const lines = str.split(/\r\n|\r|\n/);
  165. const result = [];
  166. for (const line of lines) {
  167. const padding = width - line.length;
  168. result.push(line + (padding > 0 ? new Array(padding + 1).join('⠀') : ''));
  169. }
  170. return result.join('\n');
  171. }
  172. function padCell(str, height, width) {
  173. str = padHeight(str, height);
  174. return padWidth(str, width);
  175. }
  176. function assembleRows(matrix) {
  177. const children = Array.from(matrix.childNodes);
  178. const mat = [];
  179. for (const row of children) {
  180. if (row.nodeType !== DomUtil.NodeType.ELEMENT_NODE) {
  181. continue;
  182. }
  183. mat.push(handleRow(row));
  184. }
  185. return mat;
  186. }
  187. function getMaxParameters(mat) {
  188. const maxHeight = mat.reduce((max, x) => Math.max(x.height, max), 0);
  189. const maxWidth = [];
  190. for (let i = 0; i < mat[0].width.length; i++) {
  191. maxWidth.push(mat.map((x) => x.width[i]).reduce((max, x) => Math.max(max, x), 0));
  192. }
  193. return [maxHeight, maxWidth];
  194. }
  195. function combineCells(mat, maxWidth) {
  196. const newMat = [];
  197. for (const row of mat) {
  198. if (row.height === 0) {
  199. continue;
  200. }
  201. const newCells = [];
  202. for (let i = 0; i < row.cells.length; i++) {
  203. newCells.push(padCell(row.cells[i], row.height, maxWidth[i]));
  204. }
  205. row.cells = newCells;
  206. newMat.push(row);
  207. }
  208. return newMat;
  209. }
  210. function combineRows(mat, maxHeight) {
  211. if (maxHeight === 1) {
  212. return mat
  213. .map((row) => row.lfence + row.cells.join(row.sep) + row.rfence)
  214. .join('\n');
  215. }
  216. const result = [];
  217. for (const row of mat) {
  218. const sep = verticalArrange(row.sep, row.height);
  219. let str = row.cells.shift();
  220. while (row.cells.length) {
  221. str = combineContent(str, sep);
  222. str = combineContent(str, row.cells.shift());
  223. }
  224. str = combineContent(verticalArrange(row.lfence, row.height), str);
  225. str = combineContent(str, verticalArrange(row.rfence, row.height));
  226. result.push(str);
  227. result.push(row.lfence + new Array(strWidth(str) - 3).join(row.sep) + row.rfence);
  228. }
  229. return result.slice(0, -1).join('\n');
  230. }
  231. function verticalArrange(char, height) {
  232. let str = '';
  233. while (height) {
  234. str += char + '\n';
  235. height--;
  236. }
  237. return str.slice(0, -1);
  238. }
  239. function getFence(node) {
  240. if (node.nodeType === DomUtil.NodeType.ELEMENT_NODE &&
  241. DomUtil.tagName(node) === 'FENCE') {
  242. return applyHandler(node);
  243. }
  244. return '';
  245. }
  246. function handleMatrix(matrix) {
  247. let mat = assembleRows(matrix);
  248. const [maxHeight, maxWidth] = getMaxParameters(mat);
  249. mat = combineCells(mat, maxWidth);
  250. return combineRows(mat, maxHeight);
  251. }
  252. function handleTable(table) {
  253. let mat = assembleRows(table);
  254. mat.forEach((row) => {
  255. row.cells = row.cells.slice(1).slice(0, -1);
  256. row.width = row.width.slice(1).slice(0, -1);
  257. });
  258. const [maxHeight, maxWidth] = getMaxParameters(mat);
  259. mat = combineCells(mat, maxWidth);
  260. return combineRows(mat, maxHeight);
  261. }
  262. function handleCases(cases) {
  263. let mat = assembleRows(cases);
  264. mat.forEach((row) => {
  265. row.cells = row.cells.slice(0, -1);
  266. row.width = row.width.slice(0, -1);
  267. });
  268. const [maxHeight, maxWidth] = getMaxParameters(mat);
  269. mat = combineCells(mat, maxWidth);
  270. return combineRows(mat, maxHeight);
  271. }
  272. function handleCayley(cayley) {
  273. let mat = assembleRows(cayley);
  274. mat.forEach((row) => {
  275. row.cells = row.cells.slice(1).slice(0, -1);
  276. row.width = row.width.slice(1).slice(0, -1);
  277. row.sep = row.sep + row.sep;
  278. });
  279. const [maxHeight, maxWidth] = getMaxParameters(mat);
  280. const bar = {
  281. lfence: '',
  282. rfence: '',
  283. cells: maxWidth.map((x) => '⠐' + new Array(x).join('⠒')),
  284. width: maxWidth,
  285. height: 1,
  286. sep: mat[0].sep
  287. };
  288. if (engine_js_1.Engine.getInstance().cayleyshort && mat[0].cells[0] === '⠀') {
  289. bar.cells[0] = '⠀';
  290. }
  291. mat.splice(1, 0, bar);
  292. mat = combineCells(mat, maxWidth);
  293. return combineRows(mat, maxHeight);
  294. }
  295. function handleRow(row) {
  296. const children = Array.from(row.childNodes);
  297. const lfence = getFence(children[0]);
  298. const rfence = getFence(children[children.length - 1]);
  299. if (lfence) {
  300. children.shift();
  301. }
  302. if (rfence) {
  303. children.pop();
  304. }
  305. let sep = '';
  306. const cells = [];
  307. for (const child of children) {
  308. if (child.nodeType === DomUtil.NodeType.TEXT_NODE) {
  309. sep = child.textContent;
  310. continue;
  311. }
  312. const result = applyHandler(child);
  313. cells.push(result);
  314. }
  315. return {
  316. lfence: lfence,
  317. rfence: rfence,
  318. sep: sep,
  319. cells: cells,
  320. height: cells.reduce((max, x) => Math.max(strHeight(x), max), 0),
  321. width: cells.map(strWidth)
  322. };
  323. }
  324. function centerCell(cell, width) {
  325. const cw = strWidth(cell);
  326. const center = (width - cw) / 2;
  327. const [lpad, rpad] = Math.floor(center) === center
  328. ? [center, center]
  329. : [Math.floor(center), Math.ceil(center)];
  330. const lines = cell.split(/\r\n|\r|\n/);
  331. const result = [];
  332. const [lstr, rstr] = [
  333. new Array(lpad + 1).join('⠀'),
  334. new Array(rpad + 1).join('⠀')
  335. ];
  336. for (const line of lines) {
  337. result.push(lstr + line + rstr);
  338. }
  339. return result.join('\n');
  340. }
  341. function handleFraction(frac) {
  342. const [open, num, , den, close] = Array.from(frac.childNodes);
  343. const numerator = applyHandler(num);
  344. const denominator = applyHandler(den);
  345. const nwidth = strWidth(numerator);
  346. const dwidth = strWidth(denominator);
  347. let maxWidth = Math.max(nwidth, dwidth);
  348. const bar = open + new Array(maxWidth + 1).join('⠒') + close;
  349. maxWidth = bar.length;
  350. return (`${centerCell(numerator, maxWidth)}\n${bar}\n` +
  351. `${centerCell(denominator, maxWidth)}`);
  352. }
  353. function handleFractionPart(prt) {
  354. const fchild = prt.firstChild;
  355. const content = recurseTree(prt);
  356. if (fchild && fchild.nodeType === DomUtil.NodeType.ELEMENT_NODE) {
  357. if (DomUtil.tagName(fchild) === 'ENGLISH') {
  358. return '⠰' + content;
  359. }
  360. if (DomUtil.tagName(fchild) === 'NUMBER') {
  361. return '⠼' + content;
  362. }
  363. }
  364. return content;
  365. }
  366. function handleRelation(rel) {
  367. if (!engine_js_1.Engine.getInstance().linebreaks) {
  368. return recurseTree(rel);
  369. }
  370. const value = relValues.get(parseInt(rel.getAttribute('value')));
  371. return (value ? `<br value="${value}"/>` : '') + recurseTree(rel);
  372. }