mtable.js (15091B)
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/mtable.js 7 * 8 * Implements the SVG output for <mtable> elements. 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 MML.mtable.Augment({ 34 toSVG: function (span) { 35 this.SVGgetStyles(); 36 var svg = this.SVG(), scale = this.SVGgetScale(svg); 37 if (this.data.length === 0) {this.SVGsaveData(svg);return svg} 38 var values = this.getValues("columnalign","rowalign","columnspacing","rowspacing", 39 "columnwidth","equalcolumns","equalrows", 40 "columnlines","rowlines","frame","framespacing", 41 "align","useHeight","width","side","minlabelspacing"); 42 // Handle relative width as fixed width in relation to container 43 if (values.width.match(/%$/)) 44 {svg.width = values.width = SVG.Em((SVG.cwidth/1000)*(parseFloat(values.width)/100))} 45 46 var mu = this.SVGgetMu(svg); 47 var LABEL = -1; 48 49 var H = [], D = [], W = [], A = [], C = [], i, j, J = -1, 50 m, M, s, row, cell, mo, HD; 51 var LH = SVG.FONTDATA.lineH * scale * values.useHeight, 52 LD = SVG.FONTDATA.lineD * scale * values.useHeight; 53 54 // 55 // Create cells and measure columns and rows 56 // 57 for (i = 0, m = this.data.length; i < m; i++) { 58 row = this.data[i]; s = (row.type === "mlabeledtr" ? LABEL : 0); 59 A[i] = []; H[i] = LH; D[i] = LD; 60 for (j = s, M = row.data.length + s; j < M; j++) { 61 if (W[j] == null) { 62 if (j > J) {J = j} 63 C[j] = BBOX.G(); 64 W[j] = -SVG.BIGDIMEN; 65 } 66 cell = row.data[j-s]; 67 A[i][j] = cell.toSVG(); 68 // if (row.data[j-s].isMultiline) {A[i][j].style.width = "100%"} 69 if (cell.isEmbellished()) { 70 mo = cell.CoreMO(); 71 var min = mo.Get("minsize",true); 72 if (min) { 73 if (mo.SVGcanStretch("Vertical")) { 74 HD = mo.SVGdata.h + mo.SVGdata.d; 75 if (HD) { 76 min = SVG.length2em(min,mu,HD); 77 if (min*mo.SVGdata.h/HD > H[i]) {H[i] = min*mo.SVGdata.h/HD} 78 if (min*mo.SVGdata.d/HD > D[i]) {D[i] = min*mo.SVGdata.d/HD} 79 } 80 } else if (mo.SVGcanStretch("Horizontal")) { 81 min = SVG.length2em(min,mu,mo.SVGdata.w); 82 if (min > W[j]) {W[j] = min} 83 } 84 } 85 } 86 if (A[i][j].h > H[i]) {H[i] = A[i][j].h} 87 if (A[i][j].d > D[i]) {D[i] = A[i][j].d} 88 if (A[i][j].w > W[j]) {W[j] = A[i][j].w} 89 } 90 } 91 92 // 93 // Determine spacing and alignment 94 // 95 var SPLIT = MathJax.Hub.SplitList; 96 var CSPACE = SPLIT(values.columnspacing), 97 RSPACE = SPLIT(values.rowspacing), 98 CALIGN = SPLIT(values.columnalign), 99 RALIGN = SPLIT(values.rowalign), 100 CLINES = SPLIT(values.columnlines), 101 RLINES = SPLIT(values.rowlines), 102 CWIDTH = SPLIT(values.columnwidth), 103 RCALIGN = []; 104 for (i = 0, m = CSPACE.length; i < m; i++) {CSPACE[i] = SVG.length2em(CSPACE[i],mu)} 105 for (i = 0, m = RSPACE.length; i < m; i++) {RSPACE[i] = SVG.length2em(RSPACE[i],mu)} 106 while (CSPACE.length < J) {CSPACE.push(CSPACE[CSPACE.length-1])} 107 while (CALIGN.length <= J) {CALIGN.push(CALIGN[CALIGN.length-1])} 108 while (CLINES.length < J) {CLINES.push(CLINES[CLINES.length-1])} 109 while (CWIDTH.length <= J) {CWIDTH.push(CWIDTH[CWIDTH.length-1])} 110 while (RSPACE.length < A.length) {RSPACE.push(RSPACE[RSPACE.length-1])} 111 while (RALIGN.length <= A.length) {RALIGN.push(RALIGN[RALIGN.length-1])} 112 while (RLINES.length < A.length) {RLINES.push(RLINES[RLINES.length-1])} 113 if (C[LABEL]) { 114 CALIGN[LABEL] = (values.side.substr(0,1) === "l" ? "left" : "right"); 115 CSPACE[LABEL] = -W[LABEL]; 116 } 117 // 118 // Override row data 119 // 120 for (i = 0, m = A.length; i < m; i++) { 121 row = this.data[i]; RCALIGN[i] = []; 122 if (row.rowalign) {RALIGN[i] = row.rowalign} 123 if (row.columnalign) { 124 RCALIGN[i] = SPLIT(row.columnalign); 125 while (RCALIGN[i].length <= J) {RCALIGN[i].push(RCALIGN[i][RCALIGN[i].length-1])} 126 } 127 } 128 129 // 130 // Handle equal heights 131 // 132 if (values.equalrows) { 133 // FIXME: should really be based on row align (below is for baseline) 134 var Hm = Math.max.apply(Math,H), Dm = Math.max.apply(Math,D); 135 for (i = 0, m = A.length; i < m; i++) 136 {s = ((Hm + Dm) - (H[i] + D[i])) / 2; H[i] += s; D[i] += s} 137 } 138 139 // FIXME: do background colors for entire cell (include half the intercolumn space?) 140 141 // 142 // Determine array total height 143 // 144 HD = H[0] + D[A.length-1]; 145 for (i = 0, m = A.length-1; i < m; i++) 146 {HD += Math.max(0,D[i]+H[i+1]+RSPACE[i])} 147 // 148 // Determine frame and line sizes 149 // 150 var fx = 0, fy = 0, fW, fH = HD; 151 if (values.frame !== "none" || 152 (values.columnlines+values.rowlines).match(/solid|dashed/)) { 153 var frameSpacing = SPLIT(values.framespacing); 154 if (frameSpacing.length != 2) { 155 // invalid attribute value: use the default. 156 frameSpacing = SPLIT(this.defaults.framespacing); 157 } 158 fx = SVG.length2em(frameSpacing[0],mu); 159 fy = SVG.length2em(frameSpacing[1],mu); 160 fH = HD + 2*fy; // fW waits until svg.w is determined 161 } 162 // 163 // Compute alignment 164 // 165 var Y, fY, n = ""; 166 if (typeof(values.align) !== "string") {values.align = String(values.align)} 167 if (values.align.match(/(top|bottom|center|baseline|axis)( +(-?\d+))?/)) 168 {n = RegExp.$3||""; values.align = RegExp.$1} else {values.align = this.defaults.align} 169 if (n !== "") { 170 // 171 // Find the height of the given row 172 // 173 n = parseInt(n); 174 if (n < 0) {n = A.length + 1 + n} 175 if (n < 1) {n = 1} else if (n > A.length) {n = A.length} 176 Y = 0; fY = -(HD + fy) + H[0]; 177 for (i = 0, m = n-1; i < m; i++) { 178 // FIXME: Should handle values.align for final row 179 var dY = Math.max(0,D[i]+H[i+1]+RSPACE[i]); 180 Y += dY; fY += dY; 181 } 182 } else { 183 Y = ({ 184 top: -(H[0] + fy), 185 bottom: HD + fy - H[0], 186 center: HD/2 - H[0], 187 baseline: HD/2 - H[0], 188 axis: HD/2 + SVG.TeX.axis_height*scale - H[0] 189 })[values.align]; 190 fY = ({ 191 top: -(HD + 2*fy), 192 bottom: 0, 193 center: -(HD/2 + fy), 194 baseline: -(HD/2 + fy), 195 axis: SVG.TeX.axis_height*scale - HD/2 - fy 196 })[values.align]; 197 } 198 199 var WW, WP = 0, Wt = 0, Wp = 0, p = 0, f = 0, P = [], F = [], Wf = 1; 200 // 201 if (values.equalcolumns && values.width !== "auto") { 202 // 203 // Handle equalcolumns for percent-width and fixed-width tables 204 // 205 206 // Get total width minus column spacing 207 WW = SVG.length2em(values.width,mu); 208 for (i = 0, m = Math.min(J,CSPACE.length); i < m; i++) {WW -= CSPACE[i]} 209 // Determine individual column widths 210 WW /= J; 211 for (i = 0, m = Math.min(J+1,CWIDTH.length); i < m; i++) {W[i] = WW} 212 } else { 213 // 214 // Get column widths for fit and percentage columns 215 // 216 // Calculate the natural widths and percentage widths, 217 // while keeping track of the fit and percentage columns 218 for(i = 0, m = Math.min(J+1,CWIDTH.length); i < m; i++) { 219 if (CWIDTH[i] === "auto") {Wt += W[i]} 220 else if (CWIDTH[i] === "fit") {F[f] = i; f++; Wt += W[i]} 221 else if (CWIDTH[i].match(/%$/)) 222 {P[p] = i; p++; Wp += W[i]; WP += SVG.length2em(CWIDTH[i],mu,1)} 223 else {W[i] = SVG.length2em(CWIDTH[i],mu); Wt += W[i]} 224 } 225 // Get the full width (excluding inter-column spacing) 226 if (values.width === "auto") { 227 if (WP > .98) {Wf = Wp/(Wt+Wp); WW = Wt + Wp} else {WW = Wt / (1-WP)} 228 } else { 229 WW = SVG.length2em(values.width,mu); 230 for (i = 0, m = Math.min(J,CSPACE.length); i < m; i++) {WW -= CSPACE[i]} 231 } 232 // Determine the relative column widths 233 for (i = 0, m = P.length; i < m; i++) { 234 W[P[i]] = SVG.length2em(CWIDTH[P[i]],mu,WW*Wf); Wt += W[P[i]]; 235 } 236 // Stretch fit columns, if any, otherwise stretch (or shrink) everything 237 if (Math.abs(WW - Wt) > .01) { 238 if (f && WW > Wt) { 239 WW = (WW - Wt) / f; for (i = 0, m = F.length; i < m; i++) {W[F[i]] += WW} 240 } else {WW = WW/Wt; for (j = 0; j <= J; j++) {W[j] *= WW}} 241 } 242 // 243 // Handle equal columns 244 // 245 if (values.equalcolumns) { 246 var Wm = Math.max.apply(Math,W); 247 for (j = 0; j <= J; j++) {W[j] = Wm} 248 } 249 } 250 251 // 252 // Lay out array columns 253 // 254 var y = Y, dy, align; s = (C[LABEL] ? LABEL : 0); 255 for (j = s; j <= J; j++) { 256 C[j].w = W[j]; 257 for (i = 0, m = A.length; i < m; i++) { 258 if (A[i][j]) { 259 s = (this.data[i].type === "mlabeledtr" ? LABEL : 0); 260 cell = this.data[i].data[j-s]; 261 if (cell.SVGcanStretch("Horizontal")) { 262 A[i][j] = cell.SVGstretchH(W[j]); 263 } else if (cell.SVGcanStretch("Vertical")) { 264 mo = cell.CoreMO(); 265 var symmetric = mo.symmetric; mo.symmetric = false; 266 A[i][j] = cell.SVGstretchV(H[i],D[i]); 267 mo.symmetric = symmetric; 268 } 269 align = cell.rowalign||this.data[i].rowalign||RALIGN[i]; 270 dy = ({top: H[i] - A[i][j].h, 271 bottom: A[i][j].d - D[i], 272 center: ((H[i]-D[i]) - (A[i][j].h-A[i][j].d))/2, 273 baseline: 0, axis: 0})[align] || 0; // FIXME: handle axis better? 274 align = (cell.columnalign||RCALIGN[i][j]||CALIGN[j]) 275 C[j].Align(A[i][j],align,0,y+dy); 276 } 277 if (i < A.length-1) {y -= Math.max(0,D[i]+H[i+1]+RSPACE[i])} 278 } 279 y = Y; 280 } 281 282 // 283 // Place the columns and add column lines 284 // 285 var lw = 1.5*SVG.em; 286 var x = fx - lw/2; 287 for (j = 0; j <= J; j++) { 288 svg.Add(C[j],x,0); x += W[j] + CSPACE[j]; 289 if (CLINES[j] !== "none" && j < J && j !== LABEL) 290 {svg.Add(BBOX.VLINE(fH,lw,CLINES[j]),x-CSPACE[j]/2,fY)} 291 } 292 svg.w += fx; svg.d = -fY; svg.h = fH+fY; 293 fW = svg.w; 294 295 // 296 // Add frame 297 // 298 if (values.frame !== "none") { 299 svg.Add(BBOX.HLINE(fW,lw,values.frame),0,fY+fH-lw); 300 svg.Add(BBOX.HLINE(fW,lw,values.frame),0,fY); 301 svg.Add(BBOX.VLINE(fH,lw,values.frame),0,fY); 302 svg.Add(BBOX.VLINE(fH,lw,values.frame),fW-lw,fY); 303 } 304 305 // 306 // Add row lines 307 // 308 y = Y - lw/2; 309 for (i = 0, m = A.length-1; i < m; i++) { 310 dy = Math.max(0,D[i]+H[i+1]+RSPACE[i]); 311 if (RLINES[i] !== "none") 312 {svg.Add(BBOX.HLINE(fW,lw,RLINES[i]),0,y-D[i]-(dy-D[i]-H[i+1])/2)} 313 y -= dy; 314 } 315 316 // 317 // Finish the table 318 // 319 svg.Clean(); 320 this.SVGhandleSpace(svg); 321 this.SVGhandleColor(svg); 322 323 // 324 // Place the labels, if any 325 // 326 if (C[LABEL]) { 327 svg.tw = Math.max(svg.w,svg.r) - Math.min(0,svg.l); 328 var indent = this.getValues("indentalignfirst","indentshiftfirst","indentalign","indentshift"); 329 if (indent.indentalignfirst !== MML.INDENTALIGN.INDENTALIGN) {indent.indentalign = indent.indentalignfirst} 330 if (indent.indentalign === MML.INDENTALIGN.AUTO) {indent.indentalign = this.displayAlign} 331 if (indent.indentshiftfirst !== MML.INDENTSHIFT.INDENTSHIFT) {indent.indentshift = indent.indentshiftfirst} 332 if (indent.indentshift === "auto" || indent.indentshift === "") {indent.indentshift = "0"} 333 var shift = SVG.length2em(indent.indentshift,mu,SVG.cwidth); 334 var labelspace = SVG.length2em(values.minlabelspacing,mu,SVG.cwidth); 335 var labelW = labelspace + C[LABEL].w, labelshift = 0, tw = svg.w; 336 var dIndent = SVG.length2em(this.displayIndent,mu,SVG.cwidth); 337 s = (CALIGN[LABEL] === MML.INDENTALIGN.RIGHT ? -1 : 1); 338 if (indent.indentalign === MML.INDENTALIGN.CENTER) { 339 var dx = (SVG.cwidth-tw)/2; shift += dIndent; 340 if (labelW + s*labelshift > dx + s*shift) { 341 indent.indentalign = CALIGN[LABEL]; 342 shift = s*(labelW + s*labelshift); tw += labelW + Math.max(0,shift); 343 } 344 } else if (CALIGN[LABEL] === indent.indentalign) { 345 if (dIndent < 0) {labelshift = s*dIndent; dIndent = 0} 346 shift += s*dIndent; if (labelW > s*shift) shift = s*labelW; shift += labelshift; 347 tw += s*shift; 348 } else { 349 shift -= s*dIndent; 350 if (tw - s*shift + labelW > SVG.cwidth) { 351 shift = s*(tw + labelW - SVG.cwidth); 352 if (s*shift > 0) {tw = SVG.cwidth + s*shift; shift = 0} 353 } 354 } 355 var eqn = svg; svg = this.SVG(); 356 svg.hasIndent = true; 357 svg.w = svg.r = Math.max(tw,SVG.cwidth); 358 svg.Align(C[LABEL],CALIGN[LABEL],0,0,labelshift); 359 svg.Align(eqn,indent.indentalign,0,0,shift); 360 svg.tw = tw; 361 } 362 363 this.SVGsaveData(svg); 364 return svg; 365 }, 366 SVGhandleSpace: function (svg) { 367 if (!this.hasFrame && !svg.width) {svg.x = svg.X = 167} 368 this.SUPER(arguments).SVGhandleSpace.call(this,svg); 369 } 370 }); 371 372 MML.mtd.Augment({ 373 toSVG: function (HW,D) { 374 var svg = this.svg = this.SVG(); 375 if (this.data[0]) { 376 svg.Add(this.SVGdataStretched(0,HW,D)); 377 svg.Clean(); 378 } 379 this.SVGhandleColor(svg); 380 this.SVGsaveData(svg); 381 return svg; 382 } 383 }); 384 385 MathJax.Hub.Startup.signal.Post("SVG mtable Ready"); 386 MathJax.Ajax.loadComplete(SVG.autoloadDir+"/mtable.js"); 387 388 }); 389