multiline.js 31 KB

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