@@ -61,6 +61,13 @@ function isPercent(x: string) {
6161 return x . match ( / % \s * $ / ) ;
6262}
6363
64+ /*
65+ * Split a space-separated string of values
66+ */
67+ function SPLIT ( x : string ) {
68+ return x . trim ( ) . split ( / \s + / ) ;
69+ }
70+
6471/*****************************************************************/
6572/*
6673 * The CHTMLmtable wrapper for the MmlMtable object
@@ -84,6 +91,21 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
8491 } ,
8592 'mjx-mtable[width] > mjx-itable' : {
8693 width : '100%'
94+ } ,
95+ 'mjx-mtable[align]' : {
96+ 'vertical-align' : 'baseline'
97+ } ,
98+ 'mjx-mtable[align="top"] > mjx-itable' : {
99+ 'vertical-align' : 'top'
100+ } ,
101+ 'mjx-mtable[align="bottom"] > mjx-itable' : {
102+ 'vertical-align' : 'bottom'
103+ } ,
104+ 'mjx-mtable[align="center"] > mjx-itable' : {
105+ 'vertical-align' : 'middle'
106+ } ,
107+ 'mjx-mtable[align="baseline"] > mjx-itable' : {
108+ 'vertical-align' : 'middle'
87109 }
88110 } ;
89111
@@ -131,7 +153,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
131153 this . cSpace = this . convertLengths ( this . getColumnAttributes ( 'columnspacing' ) ) ;
132154 this . rSpace = this . convertLengths ( this . getRowAttributes ( 'rowspacing' ) ) ;
133155 this . cLines = this . getColumnAttributes ( 'columnlines' ) . map ( x => ( x === 'none' ? 0 : .07 ) ) ;
134- this . rLines = this . getColumnAttributes ( 'rowlines' ) . map ( x => ( x === 'none' ? 0 : .07 ) ) ;
156+ this . rLines = this . getRowAttributes ( 'rowlines' ) . map ( x => ( x === 'none' ? 0 : .07 ) ) ;
135157 this . cWidths = this . getColumnWidths ( ) ;
136158 //
137159 // Stretch the rows and columns
@@ -242,6 +264,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
242264 this . handleEqualRows ( ) ;
243265 this . handleFrame ( ) ;
244266 this . handleWidth ( ) ;
267+ this . handleAlign ( ) ;
245268 this . drawBBox ( ) ;
246269 }
247270
@@ -285,7 +308,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
285308 // Otherwise, use the height and depths for each row separately.
286309 // Add in the spacing, line widths, and frame size.
287310 //
288- if ( this . node . attributes . get ( 'equalrows' ) ) {
311+ if ( this . node . attributes . get ( 'equalrows' ) as boolean ) {
289312 const HD = this . getEqualRowHeight ( ) ;
290313 height = SUM ( [ ] . concat ( this . rLines , this . rSpace ) ) + HD * this . numRows ;
291314 } else {
@@ -312,14 +335,36 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
312335 width = Math . max ( this . length2em ( w , cwidth ) + ( this . frame ? .14 : 0 ) , width ) ;
313336 }
314337 //
315- // Return the bbounding box information
338+ // Return the bounding box information
316339 //
317- const a = this . font . params . axis_height ;
318- bbox . h = height / 2 + a ;
319- bbox . d = height / 2 - a ;
340+ let [ h , d ] = this . getBBoxHD ( height ) ;
341+ bbox . h = h ;
342+ bbox . d = d ;
320343 bbox . w = width ;
321344 }
322345
346+ /*
347+ * @param {number } height The total height of the table
348+ * @return {number[] } The [height, depth] for the aligned table
349+ */
350+ protected getBBoxHD ( height : number ) {
351+ const [ align , row ] = this . getAlignmentRow ( ) ;
352+ if ( row === null ) {
353+ const a = this . font . params . axis_height ;
354+ const h2 = height / 2 ;
355+ return ( {
356+ top : [ 0 , height ] ,
357+ center : [ h2 , h2 ] ,
358+ bottom : [ height , 0 ] ,
359+ baseline : [ h2 , h2 ] ,
360+ axis : [ h2 + a , h2 - a ]
361+ } as { [ key : string ] : number [ ] } ) [ align ] || [ h2 , h2 ] ;
362+ } else {
363+ const y = this . getVerticalPosition ( row , align ) ;
364+ return [ y , height - y ] ;
365+ }
366+ }
367+
323368 /******************************************************************/
324369
325370 /*
@@ -537,6 +582,22 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
537582 this . adaptor . setAttribute ( this . chtml , 'width' , w ) ;
538583 }
539584
585+ /*
586+ * Handle alignment of table to surrounding baseline
587+ */
588+ protected handleAlign ( ) {
589+ const [ align , row ] = this . getAlignmentRow ( ) ;
590+ if ( row === null ) {
591+ if ( align !== 'axis' ) {
592+ this . adaptor . setAttribute ( this . chtml , 'align' , align ) ;
593+ }
594+ } else {
595+ const y = this . getVerticalPosition ( row , align ) ;
596+ this . adaptor . setAttribute ( this . chtml , 'align' , 'top' ) ;
597+ this . adaptor . setStyle ( this . chtml , 'verticalAlign' , this . em ( y ) ) ;
598+ }
599+ }
600+
540601 /******************************************************************/
541602
542603 /*
@@ -560,7 +621,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
560621 */
561622 protected getColumnWidths ( ) {
562623 const width = this . node . attributes . get ( 'width' ) as string ;
563- if ( this . node . attributes . get ( 'equalcolumns' ) ) {
624+ if ( this . node . attributes . get ( 'equalcolumns' ) as boolean ) {
564625 return this . getEqualColumns ( width ) ;
565626 }
566627 const swidths = this . getColumnAttributes ( 'columnwidth' , 0 ) ;
@@ -669,8 +730,62 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
669730 } ) ;
670731 }
671732
733+ /*
734+ * @param {number } i The row number (starting at 0)
735+ * @param {string } align The alignment on that row
736+ * @return {number } The offest of the alignment position from the top of the table
737+ */
738+ protected getVerticalPosition ( i : number , align : string ) {
739+ const equal = this . node . attributes . get ( 'equalrows' ) as boolean ;
740+ const { H, D} = this . getTableData ( ) ;
741+ const HD = ( equal ? this . getEqualRowHeight ( ) : 0 ) ;
742+ //
743+ // Use half spaces in each row, with frame spacing at top and bottom
744+ //
745+ const space = this . rSpace . map ( x => x / 2 ) ;
746+ space . unshift ( this . fSpace [ 1 ] ) ;
747+ space . push ( this . fSpace [ 1 ] ) ;
748+ //
749+ // Start with frame size and add in spacing, height and depth,
750+ // and line thickness for each row.
751+ //
752+ let y = ( this . frame ? .07 : 0 ) ;
753+ for ( let j = 0 ; j < i ; j ++ ) {
754+ y += space [ j ] + ( equal ? HD : H [ j ] + D [ j ] ) + space [ j + 1 ] + this . rLines [ j ] ;
755+ }
756+ //
757+ // For equal rows, get updated height and depth
758+ //
759+ const [ h , d ] = ( equal ? [ ( HD + H [ i ] - D [ i ] ) / 2 , ( HD - H [ i ] + D [ i ] ) / 2 ] : [ H [ i ] , D [ i ] ] ) ;
760+ //
761+ // Add the offset into the specified row
762+ //
763+ y += ( {
764+ top : 0 ,
765+ center : space [ i ] + ( h + d ) / 2 ,
766+ bottom : space [ i ] + h + d + space [ i + 1 ] ,
767+ baseline : space [ i ] + h ,
768+ axis : space [ i ] + h - .25
769+ } as { [ name : string ] : number } ) [ align ] || 0 ;
770+ //
771+ // Return the final result
772+ //
773+ return y ;
774+ }
775+
672776 /******************************************************************/
673777
778+ /*
779+ * @return {[string,number|null] } The alignment and row number (based at 0) or null
780+ */
781+ protected getAlignmentRow ( ) : [ string , number ] {
782+ const [ align , row ] = SPLIT ( this . node . attributes . get ( 'align' ) as string ) ;
783+ if ( row == null ) return [ align , null ] ;
784+ let i = parseInt ( row ) ;
785+ if ( i < 0 ) i += this . numRows ;
786+ return [ align , i < 1 || i > this . numRows ? null : i - 1 ] ;
787+ }
788+
674789 /*
675790 * @param {string } name The name of the attribute to get as an array
676791 * @param {number } i Return this many fewer than numCols entries
@@ -718,7 +833,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
718833 protected getAttributeArray ( name : string ) {
719834 const value = this . node . attributes . get ( name ) as string ;
720835 if ( ! value ) return [ ] ;
721- return value . replace ( / ^ \s + / , '' ) . replace ( / \s + $ / , '' ) . replace ( / \s + / g , ' ' ) . split ( / / ) ;
836+ return SPLIT ( value ) ;
722837 }
723838
724839 /*
0 commit comments