@@ -32,15 +32,12 @@ import {DIRECTION} from '../FontData.js';
3232
3333
3434/*
35- * The heights, depths, and widths of the rows and columns, and the
36- * natural width and height of the table
35+ * The heights, depths, and widths of the rows and columns
3736 */
3837export type TableData = {
3938 H : number [ ] ;
4039 D : number [ ] ;
4140 W : number [ ] ;
42- width : number ;
43- height : number ;
4441} ;
4542
4643/*****************************************************************/
@@ -85,7 +82,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
8582 protected rSpace : number [ ] ;
8683 protected cLines : number [ ] ;
8784 protected rLines : number [ ] ;
88- protected cWidths : string [ ] ;
85+ protected cWidths : ( number | string ) [ ] ;
8986
9087 /*
9188 * The bounding box information for the table rows and columns
@@ -117,7 +114,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
117114 this . rSpace = this . convertLengths ( this . getRowAttributes ( 'rowspacing' ) ) ;
118115 this . cLines = this . getColumnAttributes ( 'columnlines' ) . map ( x => ( x === 'none' ? 0 : .07 ) ) ;
119116 this . rLines = this . getColumnAttributes ( 'rowlines' ) . map ( x => ( x === 'none' ? 0 : .07 ) ) ;
120- this . cWidths = this . getColumnAttributes ( 'columnwidth' ) ;
117+ this . cWidths = this . getColumnWidths ( ) ;
121118 //
122119 // Stretch the columns (rows are already taken care of in the CHTMLmtr wrapper)
123120 //
@@ -197,6 +194,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
197194 this . padRows ( ) ;
198195 this . handleColumnSpacing ( ) ;
199196 this . handleColumnLines ( ) ;
197+ this . handleColumnWidths ( ) ;
200198 this . handleRowSpacing ( ) ;
201199 this . handleRowLines ( ) ;
202200 this . handleEqualRows ( ) ;
@@ -230,33 +228,53 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
230228 }
231229 }
232230 const w = this . node . attributes . get ( 'width' ) as string ;
233- const height = H . concat ( D , this . rLines , this . rSpace ) . reduce ( ( a , b ) => a + b , 0 )
234- + ( this . frame ? .14 : 0 )
235- + 2 * this . fSpace [ 1 ] ;
236- let width ;
237- if ( w === 'auto' || w . match ( / % $ / ) ) {
238- width = W . concat ( this . cLines , this . cSpace ) . reduce ( ( a , b ) => a + b , 0 )
239- + ( this . frame ? .14 : 0 )
240- + 2 * this . fSpace [ 0 ] ;
241- } else {
242- const cwidth = this . metrics . containerWidth / this . metrics . em ;
243- width = this . length2em ( w , cwidth ) + ( this . frame ? .14 : 0 ) ;
244- }
245- this . data = { H, D, W, width, height} ;
231+ this . data = { H, D, W} ;
246232 return this . data ;
247233 }
248234
249235 /*
250236 * @override
251237 */
252238 public computeBBox ( bbox : BBox ) {
253- let { width, height} = this . getTableData ( ) ;
239+ const { H, D, W} = this . getTableData ( ) ;
240+ let height , width ;
241+ //
242+ // For equal rows, use the common height and depth for all rows
243+ // Otherwise, use the height and depths for each row separately.
244+ // Add in the spacing, line widths, and frame size.
245+ //
254246 if ( this . node . attributes . get ( 'equalrows' ) ) {
255247 const HD = this . getEqualRowHeight ( ) ;
256248 height = [ ] . concat ( this . rLines , this . rSpace ) . reduce ( ( a , b ) => a + b , 0 )
257- + HD * this . numRows
258- + 2 * this . fSpace [ 1 ] ;
249+ + HD * this . numRows ;
250+ } else {
251+ height = H . concat ( D , this . rLines , this . rSpace ) . reduce ( ( a , b ) => a + b , 0 ) ;
252+ }
253+ height += ( this . frame ? .14 : 0 ) + 2 * this . fSpace [ 1 ] ;
254+ //
255+ // Get the widths of all columns (explicit width or computed width)
256+ //
257+ const CW = Array . from ( W . keys ( ) ) . map ( i => {
258+ return ( typeof this . cWidths [ i ] === 'number' ? this . cWidths [ i ] as number : W [ i ] ) ;
259+ } ) ;
260+ //
261+ // Get the expected width of the table
262+ //
263+ width = CW . concat ( this . cLines , this . cSpace ) . reduce ( ( a , b ) => a + b , 0 )
264+ + ( this . frame ? .14 : 0 )
265+ + 2 * this . fSpace [ 0 ] ;
266+ //
267+ // If the table width is not 'auto', determine the specified width
268+ // and pick the larger of the specified and computed widths.
269+ //
270+ const w = this . node . attributes . get ( 'width' ) as string ;
271+ if ( w !== 'auto' ) {
272+ const cwidth = this . metrics . containerWidth / this . metrics . em ;
273+ width = Math . max ( this . length2em ( w , cwidth ) + ( this . frame ? .14 : 0 ) , width ) ;
259274 }
275+ //
276+ // Return the bbounding box information
277+ //
260278 const a = this . font . params . axis_height ;
261279 bbox . h = height / 2 + a ;
262280 bbox . d = height / 2 - a ;
@@ -338,6 +356,24 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
338356 }
339357 }
340358
359+ /*
360+ * Add widths to the cells for the column widths
361+ */
362+ protected handleColumnWidths ( ) {
363+ for ( const row of this . childNodes ) {
364+ let i = 0 ;
365+ for ( const cell of this . adaptor . childNodes ( row . chtml ) as N [ ] ) {
366+ const w = this . cWidths [ i ++ ] ;
367+ if ( w !== null ) {
368+ const width = ( typeof w === 'number' ? this . em ( w ) : w ) ;
369+ this . adaptor . setStyle ( cell , 'width' , width ) ;
370+ this . adaptor . setStyle ( cell , 'maxWidth' , width ) ;
371+ this . adaptor . setStyle ( cell , 'minWidth' , width ) ;
372+ }
373+ }
374+ }
375+ }
376+
341377 /*
342378 * Set the inter-row spacing for all rows
343379 * (Use frame spacing on the outsides, if needed, and use half the row spacing on each
@@ -444,7 +480,7 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
444480 } else {
445481 w = this . em ( this . length2em ( w ) + ( this . frame ? .14 : 0 ) ) ;
446482 }
447- this . adaptor . setStyle ( this . chtml , 'width ' , w ) ;
483+ this . adaptor . setStyle ( this . chtml , 'minWidth ' , w ) ;
448484 this . adaptor . setAttribute ( this . chtml , 'width' , w ) ;
449485 }
450486
@@ -459,51 +495,152 @@ export class CHTMLmtable<N, T, D> extends CHTMLWrapper<N, T, D> {
459495 return Math . max . apply ( Math , HD ) ;
460496 }
461497
498+ /*
499+ * Determine the column widths that can be computed (and need to be set).
500+ * The resulting arrays will have numbers for fixed-size arrays,
501+ * strings for percentage sizes that can't be determined now,
502+ * and null for stretchy columns tht will expand to fill the extra space.
503+ * Depending on the width specified for the table, different column
504+ * values can be determined.
505+ *
506+ * @return {(string|number|null)[] } The array of widths
507+ */
508+ protected getColumnWidths ( ) {
509+ const width = this . node . attributes . get ( 'width' ) as string ;
510+ const swidths = this . getColumnAttributes ( 'columnwidth' , 0 ) ;
511+ if ( width === 'auto' ) {
512+ return this . getColumnWidthsAuto ( swidths ) ;
513+ }
514+ if ( width . match ( / % $ / ) ) {
515+ return this . getColumnWidthsPercent ( swidths , width ) ;
516+ }
517+ return this . getColumnWidthsFixed ( swidths , this . length2em ( width ) ) ;
518+ }
519+
520+ /*
521+ * For tables with width="auto", auto and fit columns
522+ * will end up being natural width, so don't need to
523+ * set those explicitly.
524+ *
525+ * @return {(string|number|null)[] } The array of widths
526+ */
527+ protected getColumnWidthsAuto ( swidths : string [ ] ) {
528+ return swidths . map ( x => {
529+ if ( x === 'auto' || x === 'fit' ) return null ;
530+ if ( x . match ( / % $ / ) ) return x ;
531+ return this . length2em ( x ) ;
532+ } ) ;
533+ }
534+
535+ /*
536+ * For tables with percentage widths, let 'fit' columns (or 'auto'
537+ * columns if there are not 'fit' ones) will stretch automatically,
538+ * but for 'auto' columns (when there are 'fit' ones), set the size
539+ * to the natural size of the column.
540+ *
541+ * @return {(string|number|null)[] } The array of widths
542+ */
543+ protected getColumnWidthsPercent ( swidths : string [ ] , width : string ) {
544+ const hasFit = swidths . indexOf ( 'fit' ) >= 0 ;
545+ const { W} = ( hasFit ? this . getTableData ( ) : { W : null } ) ;
546+ return Array . from ( swidths . keys ( ) ) . map ( i => {
547+ const x = swidths [ i ] ;
548+ if ( x === 'fit' ) return null ;
549+ if ( x === 'auto' ) return ( hasFit ? W [ i ] : null ) ;
550+ if ( x . match ( / % $ / ) ) return x ;
551+ return this . length2em ( x ) ;
552+ } ) ;
553+ }
554+
555+ /*
556+ * For fixed-width tables, compute the column widths of all columns.
557+ *
558+ * @return {(string|number|null)[] } The array of widths
559+ */
560+ protected getColumnWidthsFixed ( swidths : string [ ] , width : number ) {
561+ //
562+ // Get the indices of the fit and auto columns, and the number of fit or auto entries.
563+ // If there are fit or auto columns, get the column widths.
564+ //
565+ const indices = Array . from ( swidths . keys ( ) ) ;
566+ const fit = indices . filter ( i => swidths [ i ] === 'fit' ) ;
567+ const auto = indices . filter ( i => swidths [ i ] === 'auto' ) ;
568+ const n = fit . length || auto . length ;
569+ const { W} = ( n ? this . getTableData ( ) : { W : null } ) ;
570+ //
571+ // Determine the space remaining from the fixed width after the
572+ // separation and lines have been removed (cwidth), and
573+ // after the width of the columns have been removed (dw).
574+ //
575+ const cwidth = width - [ ] . concat ( this . cLines , this . cSpace ) . reduce ( ( a , b ) => a + b , 0 ) - 2 * this . fSpace [ 0 ] ;
576+ let dw = cwidth ;
577+ indices . forEach ( i => {
578+ const x = swidths [ i ] ;
579+ dw -= ( x === 'fit' || x === 'auto' ? W [ i ] : this . length2em ( x , width ) ) ;
580+ } ) ;
581+ //
582+ // Get the amount of extra space per column, or 0 (fw)
583+ //
584+ const fw = ( n && dw > 0 ? dw / n : 0 ) ;
585+ //
586+ // Return the column widths (plus extr space for those that are stretching
587+ //
588+ return indices . map ( i => {
589+ const x = swidths [ i ] ;
590+ if ( x === 'fit' ) return W [ i ] + fw ;
591+ if ( x === 'auto' ) return W [ i ] + ( fit . length === 0 ? fw : 0 ) ;
592+ return this . length2em ( x , cwidth ) ;
593+ } ) ;
594+ }
595+
596+ /******************************************************************/
597+
462598 /*
463599 * @param {string } name The name of the attribute to get as an array
464- * @param {CHTMLWrapper } wrapper The wrapper whose attribute is to be used
600+ * @param {number } i Return this many fewer than numCols entries
465601 * @return {string[] } The array of values in the given attribute, split at spaces,
466602 * padded to the number of table columns (minus 1) by repeating the last entry
467603 */
468- protected getColumnAttributes ( name : string , wrapper : CHTMLWrapper < N , T , D > = null ) {
469- const columns = this . getAttributeArray ( name , wrapper ) ;
604+ protected getColumnAttributes ( name : string , i : number = 1 ) {
605+ const n = this . numCols - i ;
606+ const columns = this . getAttributeArray ( name ) ;
470607 if ( columns . length === 0 ) return ;
471- while ( columns . length < this . numCols - 1 ) {
608+ while ( columns . length < n ) {
472609 columns . push ( columns [ columns . length - 1 ] ) ;
473610 }
474- if ( columns . length >= this . numCols ) {
475- columns . splice ( this . numCols - 1 ) ;
611+ if ( columns . length > n ) {
612+ columns . splice ( n ) ;
476613 }
477614 return columns ;
478615 }
479616
480617 /*
481618 * @param {string } name The name of the attribute to get as an array
482- * @param {CHTMLWrapper } wrapper The wrapper whose attribute is to be used
619+ * @param {number } i Return this many fewer than numRows entries
483620 * @return {string[] } The array of values in the given attribute, split at spaces,
484621 * padded to the number of table rows (minus 1) by repeating the last entry
485622 */
486- protected getRowAttributes ( name : string , wrapper : CHTMLWrapper < N , T , D > = null ) {
487- const rows = this . getAttributeArray ( name , wrapper ) ;
623+ protected getRowAttributes ( name : string , i : number = 1 ) {
624+ const n = this . numRows - i ;
625+ const rows = this . getAttributeArray ( name ) ;
488626 if ( rows . length === 0 ) return ;
489- while ( rows . length < this . numRows - 1 ) {
627+ while ( rows . length < n ) {
490628 rows . push ( rows [ rows . length - 1 ] ) ;
491629 }
492- if ( rows . length >= this . numRows ) {
493- rows . splice ( this . numRows - 1 ) ;
630+ if ( rows . length > n ) {
631+ rows . splice ( n ) ;
494632 }
495633 return rows ;
496634 }
497635
498636 /*
499637 * @param {string } name The name of the attribute to get as an array
500- * @param {CHTMLWrapper } wrapper The wrapper whose attribute is to be used
501638 * @return {string[] } The array of values in the given attribute, split at spaces
502639 * (after leading and trailing spaces are removed, and multiple
503640 * spaces have been collapsed to one).
504641 */
505- protected getAttributeArray ( name : string , wrapper : CHTMLWrapper < N , T , D > = null ) {
506- const value = ( ( wrapper || this ) . node . attributes . get ( name ) as string ) ;
642+ protected getAttributeArray ( name : string ) {
643+ const value = this . node . attributes . get ( name ) as string ;
507644 if ( ! value ) return [ ] ;
508645 return value . replace ( / ^ \s + / , '' ) . replace ( / \s + $ / , '' ) . replace ( / \s + / g, ' ' ) . split ( / / ) ;
509646 }
0 commit comments