multiline.js 32 KB

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