www

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

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