@@ -33,11 +33,16 @@ import {DIRECTION} from '../FontData.js';
3333
3434/*
3535 * The heights, depths, and widths of the rows and columns
36+ * Plus the natural height and depth (i.e., without the labels)
37+ * Plus the label column width
3638 */
3739export type TableData = {
3840 H : number [ ] ;
3941 D : number [ ] ;
4042 W : number [ ] ;
43+ NH : number [ ] ;
44+ ND : number [ ] ;
45+ L : number ;
4146} ;
4247
4348/*
@@ -82,15 +87,18 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
8287 public static styles : StyleList = {
8388 'mjx-mtable' : {
8489 'vertical-align' : '.25em' ,
85- 'text-align' : 'center'
90+ 'text-align' : 'center' ,
91+ 'position' : 'relative'
8692 } ,
8793 'mjx-mtable > mjx-itable' : {
8894 'vertical-align' : 'middle' ,
8995 'text-align' : 'left' ,
9096 'box-sizing' : 'border-box'
9197 } ,
92- 'mjx-mtable[width] > mjx-itable' : {
93- width : '100%'
98+ 'mjx-labels' : {
99+ display : 'inline-table' ,
100+ position : 'absolute' ,
101+ top : 0
94102 } ,
95103 'mjx-mtable[align]' : {
96104 'vertical-align' : 'baseline'
@@ -109,6 +117,11 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
109117 }
110118 } ;
111119
120+ /*
121+ * The column for labels
122+ */
123+ public labels : N ;
124+
112125 /*
113126 * The number of columns and rows in the table
114127 */
@@ -139,6 +152,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
139152 */
140153 constructor ( factory : CHTMLWrapperFactory < N , T , D > , node : MmlNode , parent : CHTMLWrapper < N , T , D > = null ) {
141154 super ( factory , node , parent ) ;
155+ this . labels = this . html ( 'mjx-labels' , { align : node . attributes . get ( 'side' ) } ) ;
142156 //
143157 // Determine the number of columns and rows
144158 //
@@ -264,6 +278,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
264278 this . handleEqualRows ( ) ;
265279 this . handleFrame ( ) ;
266280 this . handleWidth ( ) ;
281+ this . handleLabels ( ) ;
267282 this . handleAlign ( ) ;
268283 }
269284
@@ -280,22 +295,45 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
280295 const H = new Array ( this . numRows ) . fill ( 0 ) ;
281296 const D = new Array ( this . numRows ) . fill ( 0 ) ;
282297 const W = new Array ( this . numCols ) . fill ( 0 ) ;
298+ const NH = new Array ( this . numRows ) ;
299+ const ND = new Array ( this . numRows ) ;
300+ const LW = [ 0 ] ;
283301 for ( let j = 0 ; j < this . numRows ; j ++ ) {
284302 const row = this . childNodes [ j ] as CHTMLmtr < N , T , D > ;
285- for ( let i = 0 ; i < row . childNodes . length ; i ++ ) {
286- const cbox = row . childNodes [ i ] . getBBox ( ) ;
287- const h = Math . max ( cbox . h , .75 ) ;
288- const d = Math . max ( cbox . d , .25 ) ;
289- if ( h > H [ j ] ) H [ j ] = h ;
290- if ( d > D [ j ] ) D [ j ] = d ;
291- if ( cbox . w > W [ i ] ) W [ i ] = cbox . w ;
303+ const cellCount = row . numCells ;
304+ const firstCell = row . firstCell ;
305+ for ( let i = 0 ; i < cellCount ; i ++ ) {
306+ this . updateHDW ( row . childNodes [ i + firstCell ] , i , j , H , D , W ) ;
307+ }
308+ NH [ j ] = H [ j ] ;
309+ ND [ j ] = D [ j ] ;
310+ if ( firstCell > 0 ) {
311+ this . updateHDW ( row . childNodes [ 0 ] , 0 , j , H , D , LW ) ;
292312 }
293313 }
294314 const w = this . node . attributes . get ( 'width' ) as string ;
295- this . data = { H, D, W} ;
315+ const L = LW [ 0 ] ;
316+ this . data = { H, D, W, NH , ND , L} ;
296317 return this . data ;
297318 }
298319
320+ /*
321+ * @param {CHTMLWrapper } cell The cell whose height, depth, and width are to be added into the H, D, W arrays
322+ * @param {number } i The column number for the cell
323+ * @param {number } j The row number for the cell
324+ * @param {number[] } H The maximum height for each of the rows
325+ * @param {number[] } D The maximum depth for each of the rows
326+ * @param {number[] } W The maximum width for each column
327+ */
328+ protected updateHDW ( cell : CHTMLWrapper < N , T , D > , i : number , j : number , H : number [ ] , D : number [ ] , W : number [ ] = null ) {
329+ let { h, d, w} = cell . getBBox ( ) ;
330+ if ( h < .75 ) h = .75 ;
331+ if ( d < .25 ) d = .25 ;
332+ if ( h > H [ j ] ) H [ j ] = h ;
333+ if ( d > D [ j ] ) D [ j ] = d ;
334+ if ( W && w > W [ i ] ) W [ i ] = w ;
335+ }
336+
299337 /*
300338 * @override
301339 */
@@ -396,12 +434,13 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
396434 //
397435 // For each row...
398436 //
399- for ( const row of this . childNodes ) {
437+ for ( const row of ( this . childNodes as CHTMLmtr < N , T , D > [ ] ) ) {
400438 let i = 0 ;
401439 //
402440 // For each cell in the row...
403441 //
404- for ( const cell of row . childNodes ) {
442+ const children = ( row . firstCell ? row . childNodes . slice ( row . firstCell ) : row . childNodes ) ;
443+ for ( const cell of children ) {
405444 //
406445 // Get the left and right-hand spacing
407446 //
@@ -412,10 +451,10 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
412451 // default already set in the mtd styles
413452 //
414453 const styleNode = ( cell ? cell . chtml : this . adaptor . childNodes ( row . chtml ) [ i ] as N ) ;
415- if ( ( i > 1 || frame ) && lspace !== '.5em' ) {
454+ if ( ( i > 1 && lspace !== '0.4em' ) || ( frame && i === 1 ) ) {
416455 this . adaptor . setStyle ( styleNode , 'paddingLeft' , lspace ) ;
417456 }
418- if ( ( i < this . numCols || frame ) && rspace !== '.5em' ) {
457+ if ( ( i < this . numCols && rspace !== '0.4em' ) || ( frame && i === this . numCols ) ) {
419458 this . adaptor . setStyle ( styleNode , 'paddingRight' , rspace ) ;
420459 }
421460 }
@@ -491,10 +530,10 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
491530 // Set the style for the spacing, if it is needed, and isn't the
492531 // default already set in the mtd styles
493532 //
494- if ( ( i > 1 || frame ) && tspace !== '.125em' ) {
533+ if ( ( i > 1 && tspace !== '0.215em' ) || ( frame && i === 1 ) ) {
495534 this . adaptor . setStyle ( cell . chtml , 'paddingTop' , tspace ) ;
496535 }
497- if ( ( i < this . numRows || frame ) && bspace !== '.125em' ) {
536+ if ( ( i < this . numRows && bspace !== '0.215em' ) || ( frame && i === this . numRows ) ) {
498537 this . adaptor . setStyle ( cell . chtml , 'paddingBottom' , bspace ) ;
499538 }
500539 }
@@ -533,25 +572,39 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
533572 //
534573 // Loop through the rows and set their heights
535574 //
536- for ( const i of Array . from ( this . childNodes . keys ( ) ) ) {
575+ for ( let i = 0 ; i < this . numRows ; i ++ ) {
537576 const row = this . childNodes [ i ] ;
538577 if ( HD !== H [ i ] + D [ i ] ) {
539- this . adaptor . setStyle ( row . chtml , 'height' , this . em ( space [ i ] + HD + space [ i + 1 ] ) ) ;
540- const ralign = row . node . attributes . get ( 'rowalign' ) ;
541- //
542- // Loop through the cells and set the strut height and depth to spread
543- // the extra height equally above and below the baseline. The strut
544- // is the last element in the cell.
545- //
546- for ( const cell of row . childNodes ) {
547- const calign = cell . node . attributes . get ( 'rowalign' ) ;
548- if ( calign === 'baseline' || calign === 'axis' ) {
549- const child = this . adaptor . lastChild ( cell . chtml ) as N ;
550- this . adaptor . setStyle ( child , 'height' , HDem ) ;
551- this . adaptor . setStyle ( child , 'verticalAlign' , this . em ( - ( ( HD - H [ i ] + D [ i ] ) / 2 ) ) ) ;
552- if ( ralign === 'baseline' || ralign === 'axis' ) break ;
553- }
554- }
578+ this . setRowHeight ( row , HD , ( HD - H [ i ] + D [ i ] ) / 2 , space [ i ] + space [ i + 1 ] ) ;
579+ }
580+ }
581+ }
582+
583+ /*
584+ * Set the height of the row, and make sure that the baseline is in the right position for cells
585+ * that are row aligned to baseline ot axis
586+ *
587+ * @param {CHTMLWrapper } row The row to be set
588+ * @param {number } HD The total height+depth for the row
589+ * @param {number] D The new depth for the row
590+ * @param {number } space The total spacing above and below the row
591+ */
592+ protected setRowHeight ( row : CHTMLWrapper < N , T , D > , HD : number , D : number , space : number ) {
593+ const adaptor = this . adaptor ;
594+ adaptor . setStyle ( row . chtml , 'height' , this . em ( HD + space ) ) ;
595+ const ralign = row . node . attributes . get ( 'rowalign' ) ;
596+ //
597+ // Loop through the cells and set the strut height and depth.
598+ // The strut is the last element in the cell.
599+ //
600+ for ( const cell of row . childNodes ) {
601+ const calign = cell . node . attributes . get ( 'rowalign' ) ;
602+ if ( calign === 'baseline' || calign === 'axis' ) {
603+ const child = adaptor . lastChild ( cell . chtml ) as N ;
604+ adaptor . setStyle ( child , 'height' , this . em ( HD ) ) ;
605+ adaptor . setStyle ( child , 'verticalAlign' , this . em ( - D ) ) ;
606+ if ( ( row . kind !== 'mlabeledtr' || cell !== row . childNodes [ 0 ] ) &&
607+ ( ralign === 'baseline' || ralign === 'axis' ) ) break ;
555608 }
556609 }
557610 }
@@ -571,14 +624,16 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
571624 */
572625 protected handleWidth ( ) {
573626 let w = this . node . attributes . get ( 'width' ) as string ;
574- if ( w === 'auto' ) return ;
575- if ( isPercent ( w ) ) {
576- this . bbox . pwidth = w ;
627+ const hasLabels = ( this . adaptor . childNodes ( this . labels ) . length > 0 ) ;
628+ if ( isPercent ( w ) || hasLabels ) {
629+ this . bbox . pwidth = ( hasLabels ? '100%' : w ) ;
630+ this . adaptor . setStyle ( this . chtml , 'width' , '100%' ) ;
577631 } else {
632+ if ( w === 'auto' ) return ;
578633 w = this . em ( this . length2em ( w ) + ( this . frame ? .14 : 0 ) ) ;
579634 }
580- this . adaptor . setStyle ( this . chtml , 'minWidth' , w ) ;
581- this . adaptor . setAttribute ( this . chtml , 'width ' , w ) ;
635+ const table = this . adaptor . firstChild ( this . chtml ) as N ;
636+ this . adaptor . setStyle ( table , 'minWidth ' , w ) ;
582637 }
583638
584639 /*
@@ -599,6 +654,87 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
599654
600655 /******************************************************************/
601656
657+ /*
658+ * Handle addition of labels to the table
659+ */
660+ protected handleLabels ( ) {
661+ const labels = this . labels ;
662+ const adaptor = this . adaptor ;
663+ if ( adaptor . childNodes ( labels ) . length === 0 ) return ;
664+ //
665+ // Set the side for the labels
666+ //
667+ const side = this . node . attributes . get ( 'side' ) as string ;
668+ adaptor . setAttribute ( labels , 'side' , side ) ;
669+ adaptor . setStyle ( labels , side , '0' ) ;
670+ //
671+ // Make sure labels don't overlap table
672+ //
673+ const { L} = this . getTableData ( ) ;
674+ const sep = this . length2em ( this . node . attributes . get ( 'minlabelspacing' ) ) ;
675+ const table = adaptor . firstChild ( this . chtml ) as N ;
676+ adaptor . setStyle ( table , 'margin' , '0 ' + this . em ( L + sep ) ) ; // FIXME, handle indentalign values
677+ //
678+ // Add the labels to the table
679+ //
680+ this . updateRowHeights ( ) ;
681+ this . addLabelSpacing ( ) ;
682+ adaptor . append ( this . chtml , labels ) ;
683+ }
684+
685+ /*
686+ * Update any rows that are not naturally tall enough for the labels
687+ */
688+ protected updateRowHeights ( ) {
689+ if ( this . node . attributes . get ( 'equalrows' ) as boolean ) return ;
690+ let { H, D, NH , ND } = this . getTableData ( ) ;
691+ const space = this . rSpace . map ( x => x / 2 ) ;
692+ space . unshift ( this . fSpace [ 1 ] ) ;
693+ space . push ( this . fSpace [ 1 ] ) ;
694+ for ( let i = 0 ; i < this . numRows ; i ++ ) {
695+ if ( H [ i ] !== NH [ i ] || D [ i ] !== ND [ i ] ) {
696+ this . setRowHeight ( this . childNodes [ i ] , H [ i ] + D [ i ] , D [ i ] , space [ i ] + space [ i + 1 ] ) ;
697+ }
698+ }
699+ }
700+
701+ /*
702+ * Add spacing elements between the label rows so align them with the rest of the table
703+ */
704+ protected addLabelSpacing ( ) {
705+ const adaptor = this . adaptor ;
706+ const equal = this . node . attributes . get ( 'equalrows' ) as boolean ;
707+ const { H, D} = this . getTableData ( ) ;
708+ const HD = ( equal ? this . getEqualRowHeight ( ) : 0 ) ;
709+ //
710+ // Use half spaces in each row, with frame spacing at top and bottom
711+ //
712+ const space = this . rSpace . map ( x => x / 2 ) ;
713+ space . unshift ( this . fSpace [ 1 ] ) ;
714+ space . push ( this . fSpace [ 1 ] ) ;
715+ //
716+ // Start with frame size and add in spacing, height and depth,
717+ // and line thickness for each non-labeled row.
718+ //
719+ let h = ( this . frame ? .07 : 0 ) ;
720+ let current = adaptor . firstChild ( this . labels ) as N ;
721+ for ( let i = 0 ; i < this . numRows ; i ++ ) {
722+ const row = this . childNodes [ i ] ;
723+ if ( row . kind === 'mlabeledtr' ) {
724+ if ( h ) {
725+ adaptor . insert ( this . html ( 'mjx-mtr' , { style : { height : this . em ( h ) } } ) , current ) ;
726+ adaptor . setStyle ( current , 'height' , this . em ( ( equal ? HD : H [ i ] + D [ i ] ) + space [ i ] + space [ i + 1 ] ) ) ;
727+ current = adaptor . next ( current ) as N ;
728+ h = 0 ;
729+ }
730+ } else {
731+ h += space [ i ] + ( equal ? HD : H [ i ] + D [ i ] ) + space [ i + 1 ] + this . rLines [ i ] ;
732+ }
733+ }
734+ }
735+
736+ /******************************************************************/
737+
602738 /*
603739 * Get the maximum height of a row
604740 */
0 commit comments