multiline.js 30 KB

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