multiline.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  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-2013 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.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.parent.data.length === 1)) {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. var state = {
  82. n: 0, Y: 0,
  83. scale: this.HTMLgetScale(),
  84. isTop: isTop,
  85. values: {},
  86. VALUES: VALUES
  87. },
  88. align = this.HTMLgetAlign(state,{}),
  89. shift = this.HTMLgetShift(state,{},align),
  90. start = [],
  91. end = {
  92. index:[], penalty:PENALTY.nobreak,
  93. w:0, W:shift, shift:shift, scanW:shift,
  94. nest: 0
  95. },
  96. broken = false;
  97. while (this.HTMLbetterBreak(end,state) &&
  98. (end.scanW >= HTMLCSS.linebreakWidth || end.penalty == PENALTY.newline)) {
  99. this.HTMLaddLine(stack,start,end.index,state,end.values,broken);
  100. start = end.index.slice(0); broken = true;
  101. align = this.HTMLgetAlign(state,end.values);
  102. shift = this.HTMLgetShift(state,end.values,align);
  103. if (align === MML.INDENTALIGN.CENTER) {shift = 0}
  104. end.W = end.shift = end.scanW = shift; end.penalty = PENALTY.nobreak;
  105. }
  106. state.isLast = true;
  107. this.HTMLaddLine(stack,start,[],state,ENDVALUES,broken);
  108. //
  109. // Make top-level spans 100% wide.
  110. // Finish up the space and add the color again
  111. //
  112. if (isTop) {
  113. stack.style.width = "100%";
  114. if (parent.type === "math") {span.bbox.width = "100%"}
  115. }
  116. this.HTMLhandleSpace(span);
  117. this.HTMLhandleColor(span);
  118. span.bbox.isMultiline = true;
  119. return span;
  120. },
  121. /****************************************************************/
  122. //
  123. // Locate the next linebreak that is better than the current one
  124. //
  125. HTMLbetterBreak: function (info,state) {
  126. if (this.isToken) {return false} // FIXME: handle breaking of token elements
  127. if (this.isEmbellished()) {
  128. info.embellished = this;
  129. return this.CoreMO().HTMLbetterBreak(info,state);
  130. }
  131. if (this.linebreakContainer) {return false}
  132. //
  133. // Get the current breakpoint position and other data
  134. //
  135. var index = info.index.slice(0), i = info.index.shift(),
  136. m = this.data.length, W, w, scanW, broken = (info.index.length > 0), better = false;
  137. if (i == null) {i = -1}; if (!broken) {i++; info.W += info.w; info.w = 0}
  138. scanW = info.scanW = info.W; info.nest++;
  139. //
  140. // Look through the line for breakpoints,
  141. // (as long as we are not too far past the breaking width)
  142. //
  143. while (i < m && info.scanW < 1.33*HTMLCSS.linebreakWidth) {
  144. if (this.data[i]) {
  145. if (this.data[i].HTMLbetterBreak(info,state)) {
  146. better = true; index = [i].concat(info.index); W = info.W; w = info.w;
  147. if (info.penalty === PENALTY.newline) {info.index = index; info.nest--; return true}
  148. }
  149. scanW = (broken ? info.scanW : this.HTMLaddWidth(i,info,scanW));
  150. }
  151. info.index = []; i++; broken = false;
  152. }
  153. info.nest--; info.index = index;
  154. if (better) {info.W = W; info.w = w}
  155. return better;
  156. },
  157. HTMLaddWidth: function (i,info,scanW) {
  158. if (this.data[i]) {
  159. var span = this.data[i].HTMLspanElement();
  160. scanW += span.bbox.w;
  161. if (span.style.paddingLeft) {scanW += HTMLCSS.unEm(span.style.paddingLeft)}
  162. if (span.style.paddingRight) {scanW += HTMLCSS.unEm(span.style.paddingRight)}
  163. info.W = info.scanW = scanW; info.w = 0;
  164. }
  165. return scanW;
  166. },
  167. /****************************************************************/
  168. //
  169. // Create a new line and move the required elements into it
  170. // Position it using proper alignment and indenting
  171. //
  172. HTMLaddLine: function (stack,start,end,state,values,broken) {
  173. //
  174. // Create a box for the line, with empty BBox
  175. // fill it with the proper elements,
  176. // and clean up the bbox
  177. //
  178. line = HTMLCSS.createBox(stack);
  179. line.bbox = this.HTMLemptyBBox({});
  180. state.first = broken; state.last = true;
  181. this.HTMLmoveLine(start,end,line,state,values);
  182. this.HTMLcleanBBox(line.bbox);
  183. //
  184. // Get the alignment and shift values
  185. //
  186. var align = this.HTMLgetAlign(state,values),
  187. shift = this.HTMLgetShift(state,values,align);
  188. //
  189. // Add in space for the shift
  190. //
  191. if (shift) {
  192. HTMLCSS.createBlank(line,shift,(align !== MML.INDENTALIGN.RIGHT));
  193. line.bbox.w += shift; line.bbox.rw += shift;
  194. }
  195. //
  196. // Set the Y offset based on previous depth, leading, and current height
  197. //
  198. if (state.n > 0) {
  199. var LHD = HTMLCSS.FONTDATA.baselineskip * state.scale;
  200. var leading = (state.values.lineleading == null ? state.VALUES : state.values).lineleading;
  201. state.Y -= Math.max(LHD,state.d + line.bbox.h + leading);
  202. }
  203. //
  204. // Place the new line
  205. //
  206. HTMLCSS.alignBox(line,align,state.Y);
  207. //
  208. // Save the values needed for the future
  209. //
  210. state.d = line.bbox.d; state.values = values; state.n++;
  211. },
  212. /****************************************************************/
  213. //
  214. // Get alignment and shift values from the given data
  215. //
  216. HTMLgetAlign: function (state,values) {
  217. var cur = values, prev = state.values, def = state.VALUES, align;
  218. if (state.n === 0) {align = cur.indentalignfirst || prev.indentalignfirst || def.indentalignfirst}
  219. else if (state.isLast) {align = prev.indentalignlast || def.indentalignlast}
  220. else {align = prev.indentalign || def.indentalign}
  221. if (align === MML.INDENTALIGN.INDENTALIGN) {align = prev.indentalign || def.indentalign}
  222. if (align === MML.INDENTALIGN.AUTO) {align = (state.isTop ? this.displayAlign : MML.INDENTALIGN.LEFT)}
  223. return align;
  224. },
  225. HTMLgetShift: function (state,values,align) {
  226. if (align === MML.INDENTALIGN.CENTER) {return 0}
  227. var cur = values, prev = state.values, def = state.VALUES, shift;
  228. if (state.n === 0) {shift = cur.indentshiftfirst || prev.indentshiftfirst || def.indentshiftfirst}
  229. else if (state.isLast) {shift = prev.indentshiftlast || def.indentshiftlast}
  230. else {shift = prev.indentshift || def.indentshift}
  231. if (shift === MML.INDENTSHIFT.INDENTSHIFT) {shift = prev.indentshift || def.indentshift}
  232. if (shift === "auto" || shift === "") {shift = (state.isTSop ? this.displayIndent : "0")}
  233. return HTMLCSS.length2em(shift,0);
  234. },
  235. /****************************************************************/
  236. //
  237. // Move the selected elements into the new line's span,
  238. // moving whole items when possible, and parts of ones
  239. // that are split by a line break.
  240. //
  241. HTMLmoveLine: function (start,end,span,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].HTMLmoveSlice(start.slice(1),end.slice(1),span,state,values,"paddingLeft");
  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].HTMLmoveSpan(span,state,values)}
  258. else {this.data[i].HTMLmoveSlice(start.slice(1),[],span,state,values,"paddingLeft")}
  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].HTMLmoveSpan(span,state,values)}
  269. else {this.data[i].HTMLmoveSlice([],end.slice(1),span,state,values,"paddingRight")}
  270. }
  271. }
  272. },
  273. /****************************************************************/
  274. //
  275. // Split an element and copy the selected items into the new part
  276. //
  277. HTMLmoveSlice: function (start,end,span,state,values,padding) {
  278. //
  279. // Get rid of color, if any (added back in later)
  280. // Create a new span for the slice of the element
  281. // Move the selected portion into the slice
  282. // If it is the last slice
  283. // Remove the original (now empty) span
  284. // Rename the Continue-0 span with the original name (for HTMLspanElement)
  285. // Add back the color
  286. //
  287. this.HTMLremoveColor();
  288. var slice = this.HTMLcreateSliceSpan(span);
  289. this.HTMLmoveLine(start,end,slice,state,values);
  290. slice.style[padding] = "";
  291. this.HTMLcombineBBoxes(slice,span.bbox);
  292. this.HTMLcleanBBox(slice.bbox);
  293. if (end.length === 0) {
  294. span = this.HTMLspanElement();
  295. span.parentNode.removeChild(span);
  296. span.nextMathJaxSpan.id = span.id; var n = 0;
  297. while (span = span.nextMathJaxSpan) {
  298. var color = this.HTMLhandleColor(span);
  299. if (color) {color.id += "-MathJax-Continue-"+n; n++}
  300. }
  301. }
  302. return slice;
  303. },
  304. /****************************************************************/
  305. //
  306. // Create a new span for an element that is split in two
  307. // Clone the original and update its ID.
  308. // Link the old span to the new one so we can find it later
  309. //
  310. HTMLcreateSliceSpan: function (span) {
  311. var SPAN = this.HTMLspanElement(), n = 0;
  312. var LAST = SPAN; while (LAST.nextMathJaxSpan) {LAST = LAST.nextMathJaxSpan; n++}
  313. var SLICE = SPAN.cloneNode(false); LAST.nextMathJaxSpan = SLICE; SLICE.nextMathJaxSpan = null;
  314. SLICE.id += "-MathJax-Continue-"+n;
  315. SLICE.bbox = this.HTMLemptyBBox({});
  316. return span.appendChild(SLICE);
  317. },
  318. /****************************************************************/
  319. //
  320. // Move an element from its original span to its new location in
  321. // a split element or the new line's span
  322. //
  323. HTMLmoveSpan: function (line,state,values) {
  324. // FIXME: handle linebreakstyle === "duplicate"
  325. // FIXME: handle linebreakmultchar
  326. if (!(state.first || state.last) ||
  327. (state.first && state.values.linebreakstyle === MML.LINEBREAKSTYLE.BEFORE) ||
  328. (state.last && values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER)) {
  329. //
  330. // Move color and span
  331. //
  332. var color = document.getElementById("MathJax-Color-"+this.spanID+HTMLCSS.idPostfix);
  333. if (color) {line.appendChild(color)}
  334. var span = this.HTMLspanElement();
  335. line.appendChild(span);
  336. //
  337. // If it is last, remove right padding
  338. // If it is first, remove left padding and recolor
  339. //
  340. if (state.last) {span.style.paddingRight = ""}
  341. if (state.first || state.nextIsFirst) {
  342. delete state.nextIsFirst;
  343. span.style.paddingLeft = "";
  344. if (color) {this.HTMLremoveColor(span); this.HTMLhandleColor(span)}
  345. }
  346. //
  347. // Update bounding box
  348. //
  349. this.HTMLcombineBBoxes(this,line.bbox);
  350. } else if (state.first) {state.nextIsFirst = true} else {delete state.nextIsFirst}
  351. }
  352. });
  353. /**************************************************************************/
  354. MML.mfenced.Augment({
  355. HTMLbetterBreak: function (info,state) {
  356. //
  357. // Get the current breakpoint position and other data
  358. //
  359. var index = info.index.slice(0), i = info.index.shift(),
  360. m = this.data.length, W, w, scanW, broken = (info.index.length > 0), better = false;
  361. if (i == null) {i = -1}; if (!broken) {i++; info.W += info.w; info.w = 0}
  362. scanW = info.scanW = info.W; info.nest++;
  363. //
  364. // Create indices that include the delimiters and separators
  365. //
  366. if (!this.dataI) {
  367. this.dataI = [];
  368. if (this.data.open) {this.dataI.push("open")}
  369. if (m) {this.dataI.push(0)}
  370. for (var j = 1; j < m; j++) {
  371. if (this.data["sep"+j]) {this.dataI.push("sep"+j)}
  372. this.dataI.push(j);
  373. }
  374. if (this.data.close) {this.dataI.push("close")}
  375. }
  376. m = this.dataI.length;
  377. //
  378. // Look through the line for breakpoints, including the open, close, and separators
  379. // (as long as we are not too far past the breaking width)
  380. //
  381. while (i < m && info.scanW < 1.33*HTMLCSS.linebreakWidth) {
  382. var k = this.dataI[i];
  383. if (this.data[k]) {
  384. if (this.data[k].HTMLbetterBreak(info,state)) {
  385. better = true; index = [i].concat(info.index); W = info.W; w = info.w;
  386. if (info.penalty === PENALTY.newline) {info.index = index; info.nest--; return true}
  387. }
  388. scanW = (broken ? info.scanW : this.HTMLaddWidth(i,info,scanW));
  389. }
  390. info.index = []; i++; broken = false;
  391. }
  392. info.nest--; info.index = index;
  393. if (better) {info.W = W; info.w = w}
  394. return better;
  395. },
  396. HTMLmoveLine: function (start,end,span,state,values) {
  397. var i = start[0], j = end[0];
  398. if (i == null) {i = -1}; if (j == null) {j = this.dataI.length-1}
  399. if (i === j && start.length > 1) {
  400. //
  401. // If starting and ending in the same element move the subpiece to the new line
  402. //
  403. this.data[this.dataI[i]].HTMLmoveSlice(start.slice(1),end.slice(1),span,state,values,"paddingLeft");
  404. } else {
  405. //
  406. // Otherwise, move the remainder of the initial item
  407. // and any others (including open and separators) up to the last one
  408. //
  409. var last = state.last; state.last = false; var k = this.dataI[i];
  410. while (i < j) {
  411. if (this.data[k]) {
  412. if (start.length <= 1) {this.data[k].HTMLmoveSpan(span,state,values)}
  413. else {this.data[k].HTMLmoveSlice(start.slice(1),[],span,state,values,"paddingLeft")}
  414. }
  415. i++; k = this.dataI[i]; state.first = false; start = [];
  416. }
  417. //
  418. // If the last item is complete, move it
  419. //
  420. state.last = last;
  421. if (this.data[k]) {
  422. if (end.length <= 1) {this.data[k].HTMLmoveSpan(span,state,values)}
  423. else {this.data[k].HTMLmoveSlice([],end.slice(1),span,state,values,"paddingRight")}
  424. }
  425. }
  426. }
  427. });
  428. /**************************************************************************/
  429. MML.msubsup.Augment({
  430. HTMLbetterBreak: function (info,state) {
  431. if (!this.data[this.base]) {return false}
  432. //
  433. // Get the current breakpoint position and other data
  434. //
  435. var index = info.index.slice(0), i = info.index.shift(),
  436. W, w, scanW, broken = (info.index.length > 0), better = false;
  437. if (!broken) {info.W += info.w; info.w = 0}
  438. scanW = info.scanW = info.W;
  439. //
  440. // Record the width of the base and the super- and subscripts
  441. //
  442. if (i == null) {
  443. this.HTMLbaseW = this.data[this.base].HTMLspanElement().bbox.w;
  444. this.HTMLdw = this.HTMLspanElement().bbox.w - this.HTMLbaseW;
  445. }
  446. //
  447. // Check if the base can be broken
  448. //
  449. if (this.data[this.base].HTMLbetterBreak(info,state)) {
  450. better = true; index = [this.base].concat(info.index); W = info.W; w = info.w;
  451. if (info.penalty === PENALTY.newline) {better = broken = true}
  452. }
  453. //
  454. // Add in the base if it is unbroken, and add the scripts
  455. //
  456. if (!broken) {this.HTMLaddWidth(this.base,info,scanW)}
  457. info.scanW += this.HTMLdw; info.W = info.scanW;
  458. info.index = []; if (better) {info.W = W; info.w = w; info.index = index}
  459. return better;
  460. },
  461. HTMLmoveLine: function (start,end,span,state,values) {
  462. //
  463. // Move the proper part of the base
  464. //
  465. if (this.data[this.base]) {
  466. if (start.length > 1) {
  467. this.data[this.base].HTMLmoveSlice(start.slice(1),end.slice(1),span,state,values,"paddingLeft");
  468. } else {
  469. if (end.length <= 1) {this.data[this.base].HTMLmoveSpan(span,state,values)}
  470. else {this.data[this.base].HTMLmoveSlice([],end.slice(1),span,state,values,"paddingRight")}
  471. }
  472. }
  473. //
  474. // If this is the end, check for super and subscripts, and move those
  475. // by moving the stack that contains them, and shifting by the amount of the
  476. // base that has been removed. Remove the empty base box from the stack.
  477. //
  478. if (end.length === 0) {
  479. var s = this.data[this.sup] || this.data[this.sub];
  480. if (s) {
  481. var box = s.HTMLspanElement().parentNode, stack = box.parentNode;
  482. if (this.data[this.base]) {stack.removeChild(stack.firstChild)}
  483. for (box = stack.firstChild; box; box = box.nextSibling)
  484. {box.style.left = HTMLCSS.Em(HTMLCSS.unEm(box.style.left)-this.HTMLbaseW)}
  485. stack.bbox.w -= this.HTMLbaseW; stack.style.width = HTMLCSS.Em(stack.bbox.w);
  486. this.HTMLcombineBBoxes(stack,span.bbox);
  487. span.appendChild(stack);
  488. }
  489. }
  490. }
  491. });
  492. /**************************************************************************/
  493. MML.mo.Augment({
  494. //
  495. // Override the method for checking line breaks to properly handle <mo>
  496. //
  497. HTMLbetterBreak: function (info,state) {
  498. if (info.values && info.values.id === this.spanID) {return false}
  499. var values = this.getValues(
  500. "linebreak","linebreakstyle","lineleading","linebreakmultchar",
  501. "indentalign","indentshift",
  502. "indentalignfirst","indentshiftfirst",
  503. "indentalignlast","indentshiftlast",
  504. "texClass", "fence"
  505. );
  506. if (values.linebreakstyle === MML.LINEBREAKSTYLE.INFIXLINEBREAKSTYLE)
  507. {values.linebreakstyle = this.Get("infixlinebreakstyle")}
  508. //
  509. // Adjust nesting by TeX class (helps output that does not include
  510. // mrows for nesting, but can leave these unbalanced.
  511. //
  512. if (values.texClass === MML.TEXCLASS.OPEN) {info.nest++}
  513. if (values.texClass === MML.TEXCLASS.CLOSE) {info.nest--}
  514. //
  515. // Get the default penalty for this location
  516. //
  517. var W = info.scanW, mo = (info.embellished||this); delete info.embellished;
  518. var span = mo.HTMLspanElement(), w = span.bbox.w;
  519. if (span.style.paddingLeft) {w += HTMLCSS.unEm(span.style.paddingLeft)}
  520. if (values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER) {W += w; w = 0}
  521. if (W - info.shift === 0) {return false} // don't break at zero width (FIXME?)
  522. var offset = HTMLCSS.linebreakWidth - W;
  523. // Adjust offest for explicit first-line indent and align
  524. if (state.n === 0 && (values.indentshiftfirst !== state.VALUES.indentshiftfirst ||
  525. values.indentalignfirst !== state.VALUES.indentalignfirst)) {
  526. var align = this.HTMLgetAlign(state,values),
  527. shift = this.HTMLgetShift(state,values,align);
  528. offset += (info.shift - shift);
  529. }
  530. //
  531. var penalty = Math.floor(offset / HTMLCSS.linebreakWidth * 1000);
  532. if (penalty < 0) {penalty = PENALTY.toobig - 3*penalty}
  533. if (values.fence) {penalty += PENALTY.fence}
  534. if ((values.linebreakstyle === MML.LINEBREAKSTYLE.AFTER &&
  535. values.texClass === MML.TEXCLASS.OPEN) ||
  536. values.texClass === MML.TEXCLASS.CLOSE) {penalty += PENALTY.close}
  537. penalty += info.nest * PENALTY.nestfactor;
  538. //
  539. // Get the penalty for this type of break and
  540. // use it to modify the default penalty
  541. //
  542. var linebreak = PENALTY[values.linebreak||MML.LINEBREAK.AUTO];
  543. if (!(linebreak instanceof Array)) {
  544. // for breaks past the width, don't modify penalty
  545. if (offset >= 0) {penalty = linebreak * info.nest}
  546. } else {penalty = Math.max(1,penalty + linebreak[0] * info.nest)}
  547. //
  548. // If the penalty is no better than the current one, return false
  549. // Otherwise save the data for this breakpoint and return true
  550. //
  551. if (penalty >= info.penalty) {return false}
  552. info.penalty = penalty; info.values = values; info.W = W; info.w = w;
  553. values.lineleading = HTMLCSS.length2em(values.lineleading,state.VALUES.lineleading);
  554. values.id = this.spanID;
  555. return true;
  556. }
  557. });
  558. /**************************************************************************/
  559. MML.mspace.Augment({
  560. //
  561. // Override the method for checking line breaks to properly handle <mspace>
  562. //
  563. HTMLbetterBreak: function (info,state) {
  564. if (info.values && info.values.id === this.spanID) {return false}
  565. var values = this.getValues("linebreak");
  566. var linebreakValue = values.linebreak;
  567. if (!linebreakValue || this.hasDimAttr()) {
  568. // The MathML spec says that the linebreak attribute should be ignored
  569. // if any dimensional attribute is set.
  570. linebreakValue = MML.LINEBREAK.AUTO;
  571. }
  572. //
  573. // Get the default penalty for this location
  574. //
  575. var W = info.scanW, span = this.HTMLspanElement(), w = span.bbox.w;
  576. if (span.style.paddingLeft) {w += HTMLCSS.unEm(span.style.paddingLeft)}
  577. if (W - info.shift === 0) {return false} // don't break at zero width (FIXME?)
  578. var offset = HTMLCSS.linebreakWidth - W;
  579. //
  580. var penalty = Math.floor(offset / HTMLCSS.linebreakWidth * 1000);
  581. if (penalty < 0) {penalty = PENALTY.toobig - 3*penalty}
  582. penalty += info.nest * PENALTY.nestfactor;
  583. //
  584. // Get the penalty for this type of break and
  585. // use it to modify the default penalty
  586. //
  587. var linebreak = PENALTY[linebreakValue];
  588. if (linebreakValue === MML.LINEBREAK.AUTO && w >= PENALTY.spacelimit)
  589. {linebreak = [(w+PENALTY.spaceoffset)*PENALTY.spacefactor]}
  590. if (!(linebreak instanceof Array)) {
  591. // for breaks past the width, don't modify penalty
  592. if (offset >= 0) {penalty = linebreak * info.nest}
  593. } else {penalty = Math.max(1,penalty + linebreak[0] * info.nest)}
  594. //
  595. // If the penalty is no better than the current one, return false
  596. // Otherwise save the data for this breakpoint and return true
  597. //
  598. if (penalty >= info.penalty) {return false}
  599. info.penalty = penalty; info.values = values; info.W = W; info.w = w;
  600. values.lineleading = state.VALUES.lineleading;
  601. values.linebreakstyle = "before"; values.id = this.spanID;
  602. return true;
  603. }
  604. });
  605. //
  606. // Hook into the mathchoice extension
  607. //
  608. MathJax.Hub.Register.StartupHook("TeX mathchoice Ready",function () {
  609. MML.TeXmathchoice.Augment({
  610. HTMLbetterBreak: function (info,state) {
  611. return this.Core().HTMLbetterBreak(info,state);
  612. },
  613. HTMLmoveLine: function (start,end,span,state,values) {
  614. return this.Core().HTMLmoveSlice(start,end,span,state,values);
  615. }
  616. });
  617. });
  618. //
  619. // Have maction process only the selected item
  620. //
  621. MML.maction.Augment({
  622. HTMLbetterBreak: function (info,state) {
  623. return this.Core().HTMLbetterBreak(info,state);
  624. },
  625. HTMLmoveLine: function (start,end,span,state,values) {
  626. return this.Core().HTMLmoveSlice(start,end,span,state,values);
  627. },
  628. //
  629. // Split and move the hit boxes as well
  630. //
  631. HTMLmoveSlice: function (start,end,span,state,values,padding) {
  632. var hitbox = document.getElementById("MathJax-HitBox-"+this.spanID+HTMLCSS.idPostfix);
  633. if (hitbox) {hitbox.parentNode.removeChild(hitbox)}
  634. var slice = this.SUPER(arguments).HTMLmoveSlice.apply(this,arguments);
  635. if (end.length === 0) {
  636. span = this.HTMLspanElement(); var n = 0;
  637. while (span) {
  638. hitbox = this.HTMLhandleHitBox(span,"-Continue-"+n);
  639. span = span.nextMathJaxSpan; n++;
  640. }
  641. }
  642. return slice;
  643. }
  644. });
  645. //
  646. // Have semantics only do the first element
  647. // (FIXME: do we need to do anything special about annotation-xml?)
  648. //
  649. MML.semantics.Augment({
  650. HTMLbetterBreak: function (info,state) {
  651. return (this.data[0] ? this.data[0].HTMLbetterBreak(info,state) : false);
  652. },
  653. HTMLmoveLine: function (start,end,span,state,values) {
  654. return (this.data[0] ? this.data[0].HTMLmoveSlice(start,end,span,state,values) : null);
  655. }
  656. });
  657. /**************************************************************************/
  658. MathJax.Hub.Startup.signal.Post("HTML-CSS multiline Ready");
  659. MathJax.Ajax.loadComplete(HTMLCSS.autoloadDir+"/multiline.js");
  660. });