www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

multiline.js (31758B)


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