Skip to content

Commit 6f177ca

Browse files
committed
Add support for the columnwidth attribute, and remove unused wrapper arguments to getAttribute calls
1 parent 46d5f1c commit 6f177ca

File tree

1 file changed

+175
-38
lines changed

1 file changed

+175
-38
lines changed

mathjax3-ts/output/chtml/Wrappers/mtable.ts

Lines changed: 175 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
3837
export 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

Comments
 (0)