mtable.js 15 KB

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