www

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

multiline.js (30613B)


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