multiline.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
  2. /* vim: set ts=2 et sw=2 tw=80: */
  3. /*************************************************************
  4. *
  5. * MathJax/jax/output/CommonHTML/autoload/multiline.js
  6. *
  7. * Implements the CommonHTML output for <mrow>'s that contain line breaks.
  8. *
  9. * ---------------------------------------------------------------------
  10. *
  11. * Copyright (c) 2015-2019 The MathJax Consortium
  12. *
  13. * Licensed under the Apache License, Version 2.0 (the "License");
  14. * you may not use this file except in compliance with the License.
  15. * You may obtain a copy of the License at
  16. *
  17. * http://www.apache.org/licenses/LICENSE-2.0
  18. *
  19. * Unless required by applicable law or agreed to in writing, software
  20. * distributed under the License is distributed on an "AS IS" BASIS,
  21. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  22. * See the License for the specific language governing permissions and
  23. * limitations under the License.
  24. */
  25. MathJax.Hub.Register.StartupHook("CommonHTML Jax Ready",function () {
  26. var VERSION = "2.7.7";
  27. var MML = MathJax.ElementJax.mml,
  28. CONFIG = MathJax.Hub.config,
  29. CHTML = MathJax.OutputJax.CommonHTML;
  30. //
  31. // Fake node used for testing end-of-line potential breakpoint
  32. //
  33. var MO = MML.mo().With({CHTML: CHTML.BBOX.empty()});
  34. //
  35. // Penalties for the various line breaks
  36. //
  37. var PENALTY = {
  38. newline: 0,
  39. nobreak: 1000000,
  40. goodbreak: [-200],
  41. badbreak: [+200],
  42. auto: [0],
  43. maxwidth: 1.33, // stop looking for breaks after this time the line-break width
  44. toobig: 800,
  45. nestfactor: 400,
  46. spacefactor: -100,
  47. spaceoffset: 2,
  48. spacelimit: 1, // spaces larger than this get a penalty boost
  49. fence: 500,
  50. close: 500
  51. };
  52. var ENDVALUES = {linebreakstyle: "after"};
  53. /**************************************************************************/
  54. MML.mbase.Augment({
  55. CHTMLlinebreakPenalty: PENALTY,
  56. /****************************************************************/
  57. //
  58. // Handle breaking an mrow into separate lines
  59. //
  60. CHTMLmultiline: function (node) {
  61. //
  62. // Find the parent element and mark it as multiline
  63. //
  64. var parent = this;
  65. while (parent.inferred || (parent.parent && parent.parent.type === "mrow" &&
  66. parent.parent.isEmbellished())) {parent = parent.parent}
  67. var isTop = ((parent.type === "math" && parent.Get("display") === "block") ||
  68. parent.type === "mtd");
  69. parent.isMultiline = true;
  70. //
  71. // Default values for the line-breaking parameters
  72. //
  73. var VALUES = this.getValues(
  74. "linebreak","linebreakstyle","lineleading","linebreakmultchar",
  75. "indentalign","indentshift",
  76. "indentalignfirst","indentshiftfirst",
  77. "indentalignlast","indentshiftlast"
  78. );
  79. if (VALUES.linebreakstyle === MML.LINEBREAKSTYLE.INFIXLINEBREAKSTYLE)
  80. VALUES.linebreakstyle = this.Get("infixlinebreakstyle");
  81. VALUES.lineleading = this.CHTMLlength2em(VALUES.lineleading,0.5);
  82. //
  83. // Break the math at its best line breaks
  84. //
  85. CHTML.BBOX.empty(this.CHTML);
  86. var stack = CHTML.addElement(node,"mjx-stack");
  87. var state = {
  88. BBOX: this.CHTML,
  89. n: 0, Y: 0,
  90. scale: (this.CHTML.scale||1),
  91. isTop: isTop,
  92. values: {},
  93. VALUES: VALUES
  94. },
  95. align = this.CHTMLgetAlign(state,{}),
  96. shift = this.CHTMLgetShift(state,{},align),
  97. start = [],
  98. end = {
  99. index:[], penalty:PENALTY.nobreak,
  100. w:0, W:shift, shift:shift, scanW:shift,
  101. nest: 0
  102. },
  103. broken = false;
  104. while (this.CHTMLbetterBreak(end,state,true) &&
  105. (end.scanW >= CHTML.linebreakWidth || end.penalty === PENALTY.newline)) {
  106. this.CHTMLaddLine(stack,start,end.index,state,end.values,broken);
  107. start = end.index.slice(0); broken = true;
  108. align = this.CHTMLgetAlign(state,end.values);
  109. shift = this.CHTMLgetShift(state,end.values,align);
  110. end.W = end.shift = end.scanW = shift; end.penalty = PENALTY.nobreak;
  111. }
  112. state.isLast = true;
  113. this.CHTMLaddLine(stack,start,[],state,ENDVALUES,broken);
  114. node.style.width = stack.style.width = this.CHTML.pwidth = "100%";
  115. this.CHTML.mwidth = CHTML.Em(this.CHTML.w);
  116. this.CHTML.isMultiline = parent.CHTML.isMultiline = true;
  117. stack.style.verticalAlign = CHTML.Em(state.d - this.CHTML.d);
  118. return node;
  119. },
  120. /****************************************************************/
  121. //
  122. // Locate the next linebreak that is better than the current one
  123. //
  124. CHTMLbetterBreak: function (info,state,toplevel) {
  125. if (this.isToken) return false; // FIXME: handle breaking of token elements
  126. if (this.isEmbellished()) {
  127. info.embellished = this;
  128. return this.CoreMO().CHTMLbetterBreak(info,state);
  129. }
  130. if (this.linebreakContainer) return false;
  131. //
  132. // Get the current breakpoint position and other data
  133. //
  134. var index = info.index.slice(0), i = info.index.shift(),
  135. m = this.data.length, W, w, scanW, broken = (info.index.length > 0), better = false;
  136. if (i == null) i = -1; if (!broken) {i++; info.W += info.w; info.w = 0}
  137. scanW = info.scanW = info.W; info.nest++;
  138. //
  139. // Look through the line for breakpoints,
  140. // (as long as we are not too far past the breaking width)
  141. //
  142. while (i < m && (info.scanW < PENALTY.maxwidth*CHTML.linebreakWidth || info.w === 0)) {
  143. if (this.data[i]) {
  144. if (this.data[i].CHTMLbetterBreak(info,state)) {
  145. better = true; index = [i].concat(info.index); W = info.W; w = info.w;
  146. if (info.penalty === PENALTY.newline) {
  147. info.index = index;
  148. if (info.nest) {info.nest--}
  149. return true;
  150. }
  151. }
  152. scanW = (broken ? info.scanW : this.CHTMLaddWidth(i,info,scanW));
  153. }
  154. info.index = []; i++; broken = false;
  155. }
  156. //
  157. // Check if end-of-line is a better breakpoint
  158. //
  159. if (toplevel && better) {
  160. MO.parent = this.parent; MO.inherit = this.inherit;
  161. if (MO.CHTMLbetterBreak(info,state)) {better = false; index = info.index}
  162. }
  163. if (info.nest) {info.nest--}
  164. info.index = index;
  165. if (better) {info.W = W; info.w = w}
  166. return better;
  167. },
  168. CHTMLaddWidth: function (i,info,scanW) {
  169. if (this.data[i]) {
  170. var bbox = this.data[i].CHTML;
  171. scanW += (bbox.w + (bbox.L||0) + (bbox.R||0)) * (bbox.scale || 1);
  172. info.W = info.scanW = scanW; info.w = 0;
  173. }
  174. return scanW;
  175. },
  176. /****************************************************************/
  177. //
  178. // Create a new line and move the required elements into it
  179. // Position it using proper alignment and indenting
  180. //
  181. CHTMLaddLine: function (stack,start,end,state,values,broken) {
  182. //
  183. // Create a box for the line, with empty BBox
  184. // fill it with the proper elements,
  185. // and clean up the bbox
  186. //
  187. var block = CHTML.addElement(stack,"mjx-block",{},[["mjx-box"]]), line = block.firstChild;
  188. var bbox = state.bbox = CHTML.BBOX.empty();
  189. state.first = broken; state.last = true;
  190. this.CHTMLmoveLine(start,end,line,state,values);
  191. bbox.clean();
  192. //
  193. // Get the alignment and shift values
  194. //
  195. var align = this.CHTMLgetAlign(state,values),
  196. shift = this.CHTMLgetShift(state,values,align,true);
  197. //
  198. // Set the Y offset based on previous depth, leading, and current height
  199. //
  200. var dY = 0;
  201. if (state.n > 0) {
  202. var LHD = CHTML.FONTDATA.baselineskip;
  203. var leading = (state.values.lineleading == null ? state.VALUES : state.values).lineleading * state.scale;
  204. var Y = state.Y;
  205. state.Y -= Math.max(LHD,state.d + bbox.h + leading);
  206. dY = Y - state.Y - state.d - bbox.h;
  207. }
  208. //
  209. // Place the new line
  210. //
  211. if (shift) line.style.margin = "0 "+CHTML.Em(-shift)+" 0 "+CHTML.Em(shift);
  212. if (align !== MML.INDENTALIGN.LEFT) block.style.textAlign = align;
  213. if (dY) block.style.paddingTop = CHTML.Em(dY);
  214. state.BBOX.combine(bbox,shift,state.Y);
  215. //
  216. // Save the values needed for the future
  217. //
  218. state.d = state.bbox.d; state.values = values; state.n++;
  219. },
  220. /****************************************************************/
  221. //
  222. // Get alignment and shift values from the given data
  223. //
  224. CHTMLgetAlign: function (state,values) {
  225. var cur = values, prev = state.values, def = state.VALUES, align;
  226. if (state.n === 0) align = cur.indentalignfirst || prev.indentalignfirst || def.indentalignfirst;
  227. else if (state.isLast) align = prev.indentalignlast || def.indentalignlast;
  228. else align = prev.indentalign || def.indentalign;
  229. if (align === MML.INDENTALIGN.INDENTALIGN) align = prev.indentalign || def.indentalign;
  230. if (align === MML.INDENTALIGN.AUTO) align = (state.isTop ? CONFIG.displayAlign : MML.INDENTALIGN.LEFT);
  231. return align;
  232. },
  233. CHTMLgetShift: function (state,values,align,noadjust) {
  234. var cur = values, prev = state.values, def = state.VALUES, shift;
  235. if (state.n === 0) shift = cur.indentshiftfirst || prev.indentshiftfirst || def.indentshiftfirst;
  236. else if (state.isLast) shift = prev.indentshiftlast || def.indentshiftlast;
  237. else shift = prev.indentshift || def.indentshift;
  238. if (shift === MML.INDENTSHIFT.INDENTSHIFT) shift = prev.indentshift || def.indentshift;
  239. if (shift === "auto" || shift === "") shift = "0";
  240. shift = this.CHTMLlength2em(shift,CHTML.cwidth);
  241. if (state.isTop && CONFIG.displayIndent !== "0") {
  242. var indent = this.CHTMLlength2em(CONFIG.displayIndent,CHTML.cwidth);
  243. shift += (align === MML.INDENTALIGN.RIGHT ? -indent : indent);
  244. }
  245. return (align === MML.INDENTALIGN.RIGHT && !noadjust ? -shift : shift);
  246. },
  247. /****************************************************************/
  248. //
  249. // Move the selected elements into the new line's box,
  250. // moving whole items when possible, and parts of ones
  251. // that are split by a line break.
  252. //
  253. CHTMLmoveLine: function (start,end,node,state,values) {
  254. var i = start[0], j = end[0];
  255. if (i == null) i = -1; if (j == null) j = this.data.length-1;
  256. if (i === j && start.length > 1) {
  257. //
  258. // If starting and ending in the same element move the subpiece to the new line
  259. //
  260. this.data[i].CHTMLmoveSlice(start.slice(1),end.slice(1),node,state,values,"marginLeft");
  261. } else {
  262. //
  263. // Otherwise, move the remainder of the initial item
  264. // and any others up to the last one
  265. //
  266. var last = state.last; state.last = false;
  267. while (i < j) {
  268. if (this.data[i]) {
  269. if (start.length <= 1) this.data[i].CHTMLmoveNode(node,state,values);
  270. else this.data[i].CHTMLmoveSlice(start.slice(1),[],node,state,values,"marginLeft");
  271. }
  272. i++; state.first = false; start = [];
  273. }
  274. //
  275. // If the last item is complete, move it,
  276. // otherwise move the first part of it up to the split
  277. //
  278. state.last = last;
  279. if (this.data[i]) {
  280. if (end.length <= 1) this.data[i].CHTMLmoveNode(node,state,values);
  281. else this.data[i].CHTMLmoveSlice([],end.slice(1),node,state,values,"marginRight");
  282. }
  283. }
  284. },
  285. /****************************************************************/
  286. //
  287. // Split an element and copy the selected items into the new part
  288. //
  289. CHTMLmoveSlice: function (start,end,node,state,values,margin) {
  290. //
  291. // Create a new box for the slice of the element
  292. // Move the selected portion into the slice
  293. // If it is the last slice
  294. // Remove the original (now empty) node
  295. // Rename the Continue-0 node with the original name (for CHTMLnodeElement)
  296. //
  297. var slice = this.CHTMLcreateSliceNode(node);
  298. this.CHTMLmoveLine(start,end,slice,state,values);
  299. if (slice.style[margin]) slice.style[margin] = "";
  300. if (this.CHTML.L) {
  301. if (margin !== "marginLeft") state.bbox.w += this.CHTML.L;
  302. else slice.className = slice.className.replace(/ MJXc-space\d/,"");
  303. }
  304. if (this.CHTML.R && margin !== "marginRight") state.bbox.w += this.CHTML.R;
  305. if (end.length === 0) {
  306. node = this.CHTMLnodeElement();
  307. if (this.href) node = node.parentNode;
  308. node.parentNode.removeChild(node);
  309. node.nextMathJaxNode.id = node.id;
  310. }
  311. return slice;
  312. },
  313. /****************************************************************/
  314. //
  315. // Create a new node for an element that is split in two
  316. // Clone the original and update its ID.
  317. // Link the old node to the new one so we can find it later
  318. //
  319. CHTMLcreateSliceNode: function (node) {
  320. var NODE = this.CHTMLnodeElement(), n = 0;
  321. if (this.href) NODE = NODE.parentNode;
  322. var LAST = NODE; while (LAST.nextMathJaxNode) {LAST = LAST.nextMathJaxNode; n++}
  323. var SLICE = NODE.cloneNode(false); LAST.nextMathJaxNode = SLICE; SLICE.nextMathJaxNode = null;
  324. SLICE.id += "-MJX-Continue-"+n;
  325. return node.appendChild(SLICE);
  326. },
  327. /****************************************************************/
  328. //
  329. // Move an element from its original node to its new location in
  330. // a split element or the new line's node
  331. //
  332. CHTMLmoveNode: function (line,state,values) {
  333. // FIXME: handle linebreakstyle === "duplicate"
  334. // FIXME: handle linebreakmultchar
  335. if (!(state.first || state.last) ||
  336. (state.first && state.values.linebreakstyle === MML.LINEBREAKSTYLE.BEFORE) ||
  337. (state.last && values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER)) {
  338. //
  339. // Move node
  340. //
  341. var node = this.CHTMLnodeElement();
  342. if (this.href) node = node.parentNode;
  343. line.appendChild(node);
  344. if (this.CHTML.pwidth && !line.style.width) line.style.width = this.CHTML.pwidth;
  345. //
  346. // If it is last, remove right margin
  347. // If it is first, remove left margin
  348. //
  349. if (state.last) node.style.marginRight = "";
  350. if (state.first || state.nextIsFirst) {
  351. node.style.marginLeft = ""; this.CHTML.L = 0;
  352. node.className = node.className.replace(/ MJXc-space\d/,"");
  353. }
  354. if (state.first && this.CHTML.w === 0) state.nextIsFirst = true;
  355. else delete state.nextIsFirst;
  356. //
  357. // Update bounding box
  358. //
  359. state.bbox.combine(this.CHTML,state.bbox.w,0);
  360. }
  361. }
  362. });
  363. /**************************************************************************/
  364. MML.mfenced.Augment({
  365. CHTMLbetterBreak: function (info,state) {
  366. //
  367. // Get the current breakpoint position and other data
  368. //
  369. var index = info.index.slice(0), i = info.index.shift(),
  370. m = this.data.length, W, w, scanW, broken = (info.index.length > 0), better = false;
  371. if (i == null) i = -1; if (!broken) {i++; info.W += info.w; info.w = 0}
  372. scanW = info.scanW = info.W; info.nest++;
  373. //
  374. // Create indices that include the delimiters and separators
  375. //
  376. if (!this.dataI) {
  377. this.dataI = [];
  378. if (this.data.open) this.dataI.push("open");
  379. if (m) this.dataI.push(0);
  380. for (var j = 1; j < m; j++) {
  381. if (this.data["sep"+j]) this.dataI.push("sep"+j);
  382. this.dataI.push(j);
  383. }
  384. if (this.data.close) this.dataI.push("close");
  385. }
  386. m = this.dataI.length;
  387. //
  388. // Look through the line for breakpoints, including the open, close, and separators
  389. // (as long as we are not too far past the breaking width)
  390. //
  391. while (i < m && (info.scanW < PENALTY.maxwidth*CHTML.linebreakWidth || info.w === 0)) {
  392. var k = this.dataI[i];
  393. if (this.data[k]) {
  394. if (this.data[k].CHTMLbetterBreak(info,state)) {
  395. better = true; index = [i].concat(info.index); W = info.W; w = info.w;
  396. if (info.penalty === PENALTY.newline) {
  397. info.index = index;
  398. if (info.nest) info.nest--;
  399. return true;
  400. }
  401. }
  402. scanW = (broken ? info.scanW : this.CHTMLaddWidth(i,info,scanW));
  403. }
  404. info.index = []; i++; broken = false;
  405. }
  406. if (info.nest) info.nest--;
  407. info.index = index;
  408. if (better) {info.W = W; info.w = w}
  409. return better;
  410. },
  411. CHTMLmoveLine: function (start,end,node,state,values) {
  412. var i = start[0], j = end[0];
  413. if (i == null) i = -1; if (j == null) j = this.dataI.length-1;
  414. if (i === j && start.length > 1) {
  415. //
  416. // If starting and ending in the same element move the subpiece to the new line
  417. //
  418. this.data[this.dataI[i]].CHTMLmoveSlice(start.slice(1),end.slice(1),node,state,values,"marginLeft");
  419. } else {
  420. //
  421. // Otherwise, move the remainder of the initial item
  422. // and any others (including open and separators) up to the last one
  423. //
  424. var last = state.last; state.last = false; var k = this.dataI[i];
  425. while (i < j) {
  426. if (this.data[k]) {
  427. if (start.length <= 1) this.data[k].CHTMLmoveNode(node,state,values);
  428. else this.data[k].CHTMLmoveSlice(start.slice(1),[],node,state,values,"marginLeft");
  429. }
  430. i++; k = this.dataI[i]; state.first = false; start = [];
  431. }
  432. //
  433. // If the last item is complete, move it
  434. //
  435. state.last = last;
  436. if (this.data[k]) {
  437. if (end.length <= 1) this.data[k].CHTMLmoveNode(node,state,values);
  438. else this.data[k].CHTMLmoveSlice([],end.slice(1),node,state,values,"marginRight");
  439. }
  440. }
  441. }
  442. });
  443. /**************************************************************************/
  444. MML.msubsup.Augment({
  445. CHTMLbetterBreak: function (info,state) {
  446. if (!this.data[this.base]) {return false}
  447. //
  448. // Get the current breakpoint position and other data
  449. //
  450. var index = info.index.slice(0), i = info.index.shift(),
  451. W, w, scanW, broken = (info.index.length > 0), better = false;
  452. if (!broken) {info.W += info.w; info.w = 0}
  453. scanW = info.scanW = info.W;
  454. //
  455. // Record the width of the base and the super- and subscripts
  456. //
  457. if (i == null) {
  458. this.CHTML.baseW = this.data[this.base].CHTML.w;
  459. this.CHTML.dw = this.CHTML.w - this.CHTML.baseW;
  460. }
  461. //
  462. // Check if the base can be broken
  463. //
  464. if (this.data[this.base].CHTMLbetterBreak(info,state)) {
  465. better = true; index = [this.base].concat(info.index); W = info.W; w = info.w;
  466. if (info.penalty === PENALTY.newline) better = broken = true;
  467. }
  468. //
  469. // Add in the base if it is unbroken, and add the scripts
  470. //
  471. if (!broken) this.CHTMLaddWidth(this.base,info,scanW);
  472. info.scanW += this.CHTML.dw; info.W = info.scanW;
  473. info.index = []; if (better) {info.W = W; info.w = w; info.index = index}
  474. return better;
  475. },
  476. CHTMLmoveLine: function (start,end,node,state,values) {
  477. //
  478. // Move the proper part of the base
  479. //
  480. if (this.data[this.base]) {
  481. var base = CHTML.addElement(node,"mjx-base");
  482. if (start.length > 1) {
  483. this.data[this.base].CHTMLmoveSlice(start.slice(1),end.slice(1),base,state,values,"marginLeft");
  484. } else {
  485. if (end.length <= 1) this.data[this.base].CHTMLmoveNode(base,state,values);
  486. else this.data[this.base].CHTMLmoveSlice([],end.slice(1),base,state,values,"marginRight");
  487. }
  488. }
  489. //
  490. // If this is the end, check for super and subscripts, and move those
  491. // by moving the elements that contains them. Adjust the bounding box
  492. // to include the super and subscripts.
  493. //
  494. if (end.length === 0) {
  495. var NODE = this.CHTMLnodeElement(),
  496. stack = CHTML.getNode(NODE,"mjx-stack"),
  497. sup = CHTML.getNode(NODE,"mjx-sup"),
  498. sub = CHTML.getNode(NODE,"mjx-sub");
  499. if (stack) node.appendChild(stack);
  500. else if (sup) node.appendChild(sup);
  501. else if (sub) node.appendChild(sub);
  502. var w = state.bbox.w, bbox;
  503. if (sup) {
  504. bbox = this.data[this.sup].CHTML;
  505. state.bbox.combine(bbox,w,bbox.Y);
  506. }
  507. if (sub) {
  508. bbox = this.data[this.sub].CHTML;
  509. state.bbox.combine(bbox,w,bbox.Y);
  510. }
  511. }
  512. }
  513. });
  514. /**************************************************************************/
  515. MML.mmultiscripts.Augment({
  516. CHTMLbetterBreak: function (info,state) {
  517. if (!this.data[this.base]) return false;
  518. //
  519. // Get the current breakpoint position and other data
  520. //
  521. var index = info.index.slice(0); info.index.shift();
  522. var W, w, scanW, broken = (info.index.length > 0), better = false;
  523. if (!broken) {info.W += info.w; info.w = 0}
  524. info.scanW = info.W;
  525. //
  526. // Get the bounding boxes and the width of the scripts
  527. //
  528. var bbox = this.CHTML, base = this.data[this.base].CHTML;
  529. var dw = bbox.w - base.w - (bbox.X||0);
  530. //
  531. // Add in the width of the prescripts
  532. //
  533. info.scanW += bbox.X||0; scanW = info.scanW;
  534. //
  535. // Check if the base can be broken
  536. //
  537. if (this.data[this.base].CHTMLbetterBreak(info,state)) {
  538. better = true; index = [this.base].concat(info.index); W = info.W; w = info.w;
  539. if (info.penalty === PENALTY.newline) better = broken = true;
  540. }
  541. //
  542. // Add in the base if it is unbroken, and add the scripts
  543. //
  544. if (!broken) this.CHTMLaddWidth(this.base,info,scanW);
  545. info.scanW += dw; info.W = info.scanW;
  546. info.index = []; if (better) {info.W = W; info.w = w; info.index = index}
  547. return better;
  548. },
  549. CHTMLmoveLine: function (start,end,node,state,values) {
  550. var NODE, BOX = this.CHTMLbbox, w;
  551. //
  552. // If this is the start, move the prescripts, if any.
  553. //
  554. if (start.length < 1) {
  555. NODE = this.CHTMLnodeElement();
  556. var prestack = CHTML.getNode(NODE,"mjx-prestack"),
  557. presup = CHTML.getNode(NODE,"mjx-presup"),
  558. presub = CHTML.getNode(NODE,"mjx-presub");
  559. if (prestack) node.appendChild(prestack);
  560. else if (presup) node.appendChild(presup);
  561. else if (presub) node.appendChild(presub);
  562. w = state.bbox.w;
  563. if (presup) state.bbox.combine(BOX.presup,w+BOX.presup.X,BOX.presup.Y);
  564. if (presub) state.bbox.combine(BOX.presub,w+BOX.presub.X,BOX.presub.Y);
  565. }
  566. //
  567. // Move the proper part of the base
  568. //
  569. if (this.data[this.base]) {
  570. var base = CHTML.addElement(node,"mjx-base");
  571. if (start.length > 1) {
  572. this.data[this.base].CHTMLmoveSlice(start.slice(1),end.slice(1),base,state,values,"marginLeft");
  573. } else {
  574. if (end.length <= 1) this.data[this.base].CHTMLmoveNode(base,state,values);
  575. else this.data[this.base].CHTMLmoveSlice([],end.slice(1),base,state,values,"marginRight");
  576. }
  577. }
  578. //
  579. // If this is the end, check for super and subscripts, and move those
  580. // by moving the elements that contains them. Adjust the bounding box
  581. // to include the super and subscripts.
  582. //
  583. if (end.length === 0) {
  584. NODE = this.CHTMLnodeElement();
  585. var stack = CHTML.getNode(NODE,"mjx-stack"),
  586. sup = CHTML.getNode(NODE,"mjx-sup"),
  587. sub = CHTML.getNode(NODE,"mjx-sub");
  588. if (stack) node.appendChild(stack);
  589. else if (sup) node.appendChild(sup);
  590. else if (sub) node.appendChild(sub);
  591. w = state.bbox.w;
  592. if (sup) state.bbox.combine(BOX.sup,w,BOX.sup.Y);
  593. if (sub) state.bbox.combine(BOX.sub,w,BOX.sub.Y);
  594. }
  595. }
  596. });
  597. /**************************************************************************/
  598. MML.mo.Augment({
  599. //
  600. // Override the method for checking line breaks to properly handle <mo>
  601. //
  602. CHTMLbetterBreak: function (info,state) {
  603. if (info.values && info.values.id === this.CHTMLnodeID) return false;
  604. var values = this.getValues(
  605. "linebreak","linebreakstyle","lineleading","linebreakmultchar",
  606. "indentalign","indentshift",
  607. "indentalignfirst","indentshiftfirst",
  608. "indentalignlast","indentshiftlast",
  609. "texClass", "fence"
  610. );
  611. if (values.linebreakstyle === MML.LINEBREAKSTYLE.INFIXLINEBREAKSTYLE)
  612. values.linebreakstyle = this.Get("infixlinebreakstyle");
  613. //
  614. // Adjust nesting by TeX class (helps output that does not include
  615. // mrows for nesting, but can leave these unbalanced.
  616. //
  617. if (values.texClass === MML.TEXCLASS.OPEN) info.nest++;
  618. if (values.texClass === MML.TEXCLASS.CLOSE && info.nest) info.nest--;
  619. //
  620. // Get the default penalty for this location
  621. //
  622. var W = info.scanW; delete info.embellished;
  623. var w = this.CHTML.w + (this.CHTML.L||0) + (this.CHTML.R||0);
  624. if (values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER) {W += w; w = 0}
  625. if (W - info.shift === 0 && values.linebreak !== MML.LINEBREAK.NEWLINE)
  626. return false; // don't break at zero width (FIXME?)
  627. var offset = CHTML.linebreakWidth - W;
  628. // Adjust offest for explicit first-line indent and align
  629. if (state.n === 0 && (values.indentshiftfirst !== state.VALUES.indentshiftfirst ||
  630. values.indentalignfirst !== state.VALUES.indentalignfirst)) {
  631. var align = this.CHTMLgetAlign(state,values),
  632. shift = this.CHTMLgetShift(state,values,align);
  633. offset += (info.shift - shift);
  634. }
  635. //
  636. var penalty = Math.floor(offset / CHTML.linebreakWidth * 1000);
  637. if (penalty < 0) penalty = PENALTY.toobig - 3*penalty;
  638. if (values.fence) penalty += PENALTY.fence;
  639. if ((values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER &&
  640. values.texClass === MML.TEXCLASS.OPEN) ||
  641. values.texClass === MML.TEXCLASS.CLOSE) penalty += PENALTY.close;
  642. penalty += info.nest * PENALTY.nestfactor;
  643. //
  644. // Get the penalty for this type of break and
  645. // use it to modify the default penalty
  646. //
  647. var linebreak = PENALTY[values.linebreak||MML.LINEBREAK.AUTO]||0;
  648. if (!MathJax.Object.isArray(linebreak)) {
  649. // for breaks past the width, keep original penalty for newline
  650. if (linebreak || offset >= 0) {penalty = linebreak * info.nest}
  651. } else {penalty = Math.max(1,penalty + linebreak[0] * info.nest)}
  652. //
  653. // If the penalty is no better than the current one, return false
  654. // Otherwise save the data for this breakpoint and return true
  655. //
  656. if (penalty >= info.penalty) return false;
  657. info.penalty = penalty; info.values = values; info.W = W; info.w = w;
  658. values.lineleading = this.CHTMLlength2em(values.lineleading,state.VALUES.lineleading);
  659. values.id = this.CHTMLnodeID;
  660. return true;
  661. }
  662. });
  663. /**************************************************************************/
  664. MML.mspace.Augment({
  665. //
  666. // Override the method for checking line breaks to properly handle <mspace>
  667. //
  668. CHTMLbetterBreak: function (info,state) {
  669. if (info.values && info.values.id === this.CHTMLnodeID) return false;
  670. var values = this.getValues("linebreak");
  671. var linebreakValue = values.linebreak;
  672. if (!linebreakValue || this.hasDimAttr()) {
  673. // The MathML spec says that the linebreak attribute should be ignored
  674. // if any dimensional attribute is set.
  675. linebreakValue = MML.LINEBREAK.AUTO;
  676. }
  677. //
  678. // Get the default penalty for this location
  679. //
  680. var W = info.scanW, w = this.CHTML.w + (this.CHTML.L||0) + (this.CHTML.R||0);
  681. if (W - info.shift === 0) return false; // don't break at zero width (FIXME?)
  682. var offset = CHTML.linebreakWidth - W;
  683. //
  684. var penalty = Math.floor(offset / CHTML.linebreakWidth * 1000);
  685. if (penalty < 0) penalty = PENALTY.toobig - 3*penalty;
  686. penalty += info.nest * PENALTY.nestfactor;
  687. //
  688. // Get the penalty for this type of break and
  689. // use it to modify the default penalty
  690. //
  691. var linebreak = PENALTY[linebreakValue]||0;
  692. if (linebreakValue === MML.LINEBREAK.AUTO && w >= PENALTY.spacelimit &&
  693. !this.mathbackground && !this.background)
  694. linebreak = [(w+PENALTY.spaceoffset)*PENALTY.spacefactor];
  695. if (!MathJax.Object.isArray(linebreak)) {
  696. // for breaks past the width, keep original penalty for newline
  697. if (linebreak || offset >= 0) {penalty = linebreak * info.nest}
  698. } else {penalty = Math.max(1,penalty + linebreak[0] * info.nest)}
  699. //
  700. // If the penalty is no better than the current one, return false
  701. // Otherwise save the data for this breakpoint and return true
  702. //
  703. if (penalty >= info.penalty) return false;
  704. info.penalty = penalty; info.values = values; info.W = W; info.w = w;
  705. values.lineleading = state.VALUES.lineleading;
  706. values.linebreakstyle = "before"; values.id = this.CHTMLnodeID;
  707. return true;
  708. }
  709. });
  710. //
  711. // Hook into the mathchoice extension
  712. //
  713. MathJax.Hub.Register.StartupHook("TeX mathchoice Ready",function () {
  714. MML.TeXmathchoice.Augment({
  715. CHTMLbetterBreak: function (info,state) {
  716. return this.Core().CHTMLbetterBreak(info,state);
  717. },
  718. CHTMLmoveLine: function (start,end,node,state,values) {
  719. return this.Core().CHTMLmoveSlice(start,end,node,state,values);
  720. }
  721. });
  722. });
  723. //
  724. // Have maction process only the selected item
  725. //
  726. MML.maction.Augment({
  727. CHTMLbetterBreak: function (info,state) {
  728. return this.Core().CHTMLbetterBreak(info,state);
  729. },
  730. CHTMLmoveLine: function (start,end,node,state,values) {
  731. return this.Core().CHTMLmoveSlice(start,end,node,state,values);
  732. }
  733. });
  734. //
  735. // Have semantics only do the first element
  736. // (FIXME: do we need to do anything special about annotation-xml?)
  737. //
  738. MML.semantics.Augment({
  739. CHTMLbetterBreak: function (info,state) {
  740. return (this.data[0] ? this.data[0].CHTMLbetterBreak(info,state) : false);
  741. },
  742. CHTMLmoveLine: function (start,end,node,state,values) {
  743. return (this.data[0] ? this.data[0].CHTMLmoveSlice(start,end,node,state,values) : null);
  744. }
  745. });
  746. /**************************************************************************/
  747. MathJax.Hub.Startup.signal.Post("CommonHTML multiline Ready");
  748. MathJax.Ajax.loadComplete(CHTML.autoloadDir+"/multiline.js");
  749. });