multiline.js 28 KB

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