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