1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177 |
- /*************************************************************
- *
- * Copyright (c) 2017-2022 The MathJax Consortium
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Implements the CommonMtable wrapper mixin for the MmlMtable object
- *
- * @author dpvc@mathjax.org (Davide Cervone)
- */
- import {AnyWrapper, WrapperConstructor, Constructor} from '../Wrapper.js';
- import {CommonMtr} from './mtr.js';
- import {CommonMo} from './mo.js';
- import {BBox} from '../../../util/BBox.js';
- import {DIRECTION} from '../FontData.js';
- import {split, isPercent} from '../../../util/string.js';
- import {sum, max} from '../../../util/numeric.js';
- /*****************************************************************/
- /**
- * The heights, depths, and widths of the rows and columns
- * Plus the natural height and depth (i.e., without the labels)
- * Plus the label column width
- */
- export type TableData = {
- H: number[];
- D: number[];
- W: number[];
- NH: number[];
- ND: number[];
- L: number;
- };
- /**
- * An array of table dimensions
- */
- export type ColumnWidths = (string | number | null)[];
- /*****************************************************************/
- /**
- * The CommonMtable interface
- *
- * @template C The class for table cells
- * @template R The class for table rows
- */
- export interface CommonMtable<C extends AnyWrapper, R extends CommonMtr<C>> extends AnyWrapper {
- /**
- * The number of columns and rows in the table
- */
- numCols: number;
- numRows: number;
- /**
- * True if there are labeled rows
- */
- hasLabels: boolean;
- /**
- * True if this mtable is the top element, or in a top-most mrow
- */
- isTop: boolean;
- /**
- * The parent node of this table (skipping non-parents and mrows)
- * and the position of the table as a child node
- */
- container: AnyWrapper;
- containerI: number;
- /**
- * The spacing and line data
- */
- frame: boolean;
- fLine: number;
- fSpace: number[];
- cSpace: number[];
- rSpace: number[];
- cLines: number[];
- rLines: number[];
- cWidths: (number | string)[];
- /**
- * The bounding box information for the table rows and columns
- */
- data: TableData;
- /**
- * The table cells that have percentage-width content
- */
- pwidthCells: [C, number][];
- /**
- * The full width of a percentage-width table
- */
- pWidth: number;
- /**
- * The rows of the table
- */
- readonly tableRows: R[];
- /**
- * @override
- */
- childNodes: R[];
- /**
- * Find the container and the child position of the table
- */
- findContainer(): void;
- /**
- * If the table has a precentage width or has labels, set the pwidth of the bounding box
- */
- getPercentageWidth(): void;
- /**
- * Stretch the rows to the equal height or natural height
- */
- stretchRows(): void;
- /**
- * Stretch the columns to their proper widths
- */
- stretchColumns(): void;
- /**
- * Handle horizontal stretching within the ith column
- *
- * @param {number} i The column number
- * @param {number} W The computed width of the column (or null of not computed)
- */
- stretchColumn(i: number, W: number): void;
- /**
- * Determine the row heights and depths, the column widths,
- * and the natural width and height of the table.
- *
- * @return {TableData} The dimensions of the rows and columns
- */
- getTableData(): TableData;
- /**
- * @param {C} cell The cell whose height, depth, and width are to be added into the H, D, W arrays
- * @param {number} i The column number for the cell
- * @param {number} j The row number for the cell
- * @param {string} align The row alignment
- * @param {number[]} H The maximum height for each of the rows
- * @param {number[]} D The maximum depth for each of the rows
- * @param {number[]} W The maximum width for each column
- * @param {number} M The current height for items aligned top and bottom
- * @return {number} The updated value for M
- */
- updateHDW(cell: C, i: number, j: number, align: string, H: number[], D: number[], W: number[], M: number): number;
- /**
- * Extend the H and D of a row to cover the maximum height needed by top/bottom aligned items
- *
- * @param {number} i The row whose hight and depth should be adjusted
- * @param {number[]} H The row heights
- * @param {number[]} D The row depths
- * @param {number} M The maximum height of top/bottom aligned items
- */
- extendHD(i: number, H: number[], D: number[], M: number): void;
- /**
- * Set cell widths for columns with percentage width children
- */
- setColumnPWidths(): void;
- /**
- * @param {number} height The total height of the table
- * @return {number[]} The [height, depth] for the aligned table
- */
- getBBoxHD(height: number): number[];
- /**
- * Get bbox left and right amounts to cover labels
- */
- getBBoxLR(): number[];
- /**
- * @param {string} side The side for the labels
- * @return {[number, string, number]} The padding, alignment, and shift amounts
- */
- getPadAlignShift(side: string): [number, string, number];
- /**
- * @return {number} The true width of the table (without labels)
- */
- getWidth(): number;
- /**
- * @return {number} The maximum height of a row
- */
- getEqualRowHeight(): number;
- /**
- * @return {number[]} The array of computed widths
- */
- getComputedWidths(): number[];
- /**
- * Determine the column widths that can be computed (and need to be set).
- * The resulting arrays will have numbers for fixed-size arrays,
- * strings for percentage sizes that can't be determined now,
- * and null for stretchy columns tht will expand to fill the extra space.
- * Depending on the width specified for the table, different column
- * values can be determined.
- *
- * @return {ColumnWidths} The array of widths
- */
- getColumnWidths(): ColumnWidths;
- /**
- * For tables with equal columns, get the proper amount per row.
- *
- * @return {ColumnWidths} The array of widths
- */
- getEqualColumns(width: string): ColumnWidths;
- /**
- * For tables with width="auto", auto and fit columns
- * will end up being natural width, so don't need to
- * set those explicitly.
- *
- * @return {ColumnWidths} The array of widths
- */
- getColumnWidthsAuto(swidths: string[]): ColumnWidths;
- /**
- * For tables with percentage widths, let 'fit' columns (or 'auto'
- * columns if there are not 'fit' ones) will stretch automatically,
- * but for 'auto' columns (when there are 'fit' ones), set the size
- * to the natural size of the column.
- *
- * @param {string[]} widths Strings giving the widths
- * @return {ColumnWidths} The array of widths
- */
- getColumnWidthsPercent(widths: string[]): ColumnWidths;
- /**
- * For fixed-width tables, compute the column widths of all columns.
- *
- * @return {ColumnWidths} The array of widths
- */
- getColumnWidthsFixed(swidths: string[], width: number): ColumnWidths;
- /**
- * @param {number} i The row number (starting at 0)
- * @param {string} align The alignment on that row
- * @return {number} The offest of the alignment position from the top of the table
- */
- getVerticalPosition(i: number, align: string): number;
- /**
- * @param {number} fspace The frame spacing to use
- * @param {number[]} space The array of spacing values to convert to strings
- * @param {number} scale A scaling factor to use for the sizes
- * @return {string[]} The half-spacing as stings with units of "em"
- * with frame spacing at the beginning and end
- */
- getEmHalfSpacing(fspace: number, space: number[], scale?: number): string[];
- /**
- * @return {number[]} The half-spacing for rows with frame spacing at the ends
- */
- getRowHalfSpacing(): number[];
- /**
- * @return {number[]} The half-spacing for columns with frame spacing at the ends
- */
- getColumnHalfSpacing(): number[];
- /**
- * @return {[string,number|null]} The alignment and row number (based at 0) or null
- */
- getAlignmentRow(): [string, number | null];
- /**
- * @param {string} name The name of the attribute to get as an array
- * @param {number=} i Return this many fewer than numCols entries
- * @return {string[]} The array of values in the given attribute, split at spaces,
- * padded to the number of table columns (minus 1) by repeating the last entry
- */
- getColumnAttributes(name: string, i?: number): string[];
- /**
- * @param {string} name The name of the attribute to get as an array
- * @param {number=} i Return this many fewer than numRows entries
- * @return {string[]} The array of values in the given attribute, split at spaces,
- * padded to the number of table rows (minus 1) by repeating the last entry
- */
- getRowAttributes(name: string, i?: number): string[];
- /**
- * @param {string} name The name of the attribute to get as an array
- * @return {string[]} The array of values in the given attribute, split at spaces
- * (after leading and trailing spaces are removed, and multiple
- * spaces have been collapsed to one).
- */
- getAttributeArray(name: string): string[];
- /**
- * Adds "em" to a list of dimensions, after dividing by n (defaults to 1).
- *
- * @param {string[]} list The array of dimensions (in em's)
- * @param {nunber=} n The number to divide each dimension by after converted
- * @return {string[]} The array of values with "em" added
- */
- addEm(list: number[], n?: number): string[];
- /**
- * Converts an array of dimensions (with arbitrary units) to an array of numbers
- * representing the dimensions in units of em's.
- *
- * @param {string[]} list The array of dimensions to be turned into em's
- * @return {number[]} The array of values converted to em's
- */
- convertLengths(list: string[]): number[];
- }
- /**
- * Shorthand for the CommonMtable constructor
- */
- export type MtableConstructor<C extends AnyWrapper, R extends CommonMtr<C>> = Constructor<CommonMtable<C, R>>;
- /*****************************************************************/
- /**
- * The CommonMtable wrapper mixin for the MmlMtable object
- *
- * @template C The table cell class
- * @temlpate R the table row class
- * @template T The Wrapper class constructor type
- */
- export function CommonMtableMixin<
- C extends AnyWrapper,
- R extends CommonMtr<C>,
- T extends WrapperConstructor
- >(Base: T): MtableConstructor<C, R> & T {
- return class extends Base {
- /**
- * The number of columns in the table
- */
- public numCols: number = 0;
- /**
- * The number of rows in the table
- */
- public numRows: number = 0;
- /**
- * True if there are labeled rows
- */
- public hasLabels: boolean;
- /**
- * True if this mtable is the top element, or in a top-most mrow
- */
- public isTop: boolean;
- /**
- * The parent node of this table (skipping non-parents and mrows)
- */
- public container: AnyWrapper;
- /**
- * The position of the table as a child node of its container
- */
- public containerI: number;
- /**
- * True if there is a frame
- */
- public frame: boolean;
- /**
- * The size of the frame line (or 0 if none)
- */
- public fLine: number;
- /**
- * frame spacing on the left and right
- */
- public fSpace: number[];
- /**
- * The spacing between columns
- */
- public cSpace: number[];
- /**
- * The spacing between rows
- */
- public rSpace: number[];
- /**
- * The width of columns lines (or 0 if no line for the column)
- */
- public cLines: number[];
- /**
- * The width of row lines (or 0 if no lone for that row)
- */
- public rLines: number[];
- /**
- * The column widths (or percentages, etc.)
- */
- public cWidths: (number | string)[];
- /**
- * The bounding box information for the table rows and columns
- */
- public data: TableData = null;
- /**
- * The table cells that have percentage-width content
- */
- public pwidthCells: [C, number][] = [];
- /**
- * The full width of a percentage-width table
- */
- public pWidth: number = 0;
- /**
- * @return {R[]} The rows of the table
- */
- get tableRows(): R[] {
- return this.childNodes;
- }
- /******************************************************************/
- /**
- * @override
- * @constructor
- */
- constructor(...args: any[]) {
- super(...args);
- //
- // Determine the number of columns and rows, and whether the table is stretchy
- //
- this.numCols = max(this.tableRows.map(row => row.numCells));
- this.numRows = this.childNodes.length;
- this.hasLabels = this.childNodes.reduce((value, row) => value || row.node.isKind('mlabeledtr'), false);
- this.findContainer();
- this.isTop = !this.container || (this.container.node.isKind('math') && !this.container.parent);
- if (this.isTop) {
- this.jax.table = this;
- }
- this.getPercentageWidth();
- //
- // Get the frame, row, and column parameters
- //
- const attributes = this.node.attributes;
- this.frame = attributes.get('frame') !== 'none';
- this.fLine = (this.frame && attributes.get('frame') ? .07 : 0);
- this.fSpace = (this.frame ? this.convertLengths(this.getAttributeArray('framespacing')) : [0, 0]);
- this.cSpace = this.convertLengths(this.getColumnAttributes('columnspacing'));
- this.rSpace = this.convertLengths(this.getRowAttributes('rowspacing'));
- this.cLines = this.getColumnAttributes('columnlines').map(x => (x === 'none' ? 0 : .07));
- this.rLines = this.getRowAttributes('rowlines').map(x => (x === 'none' ? 0 : .07));
- this.cWidths = this.getColumnWidths();
- //
- // Stretch the rows and columns
- //
- this.stretchRows();
- this.stretchColumns();
- }
- /**
- * Find the container and the child position of the table
- */
- public findContainer() {
- let node = this as AnyWrapper;
- let parent = node.parent as AnyWrapper;
- while (parent && (parent.node.notParent || parent.node.isKind('mrow'))) {
- node = parent;
- parent = parent.parent;
- }
- this.container = parent;
- this.containerI = node.node.childPosition();
- }
- /**
- * If the table has a precentage width or has labels, set the pwidth of the bounding box
- */
- public getPercentageWidth() {
- if (this.hasLabels) {
- this.bbox.pwidth = BBox.fullWidth;
- } else {
- const width = this.node.attributes.get('width') as string;
- if (isPercent(width)) {
- this.bbox.pwidth = width;
- }
- }
- }
- /**
- * Stretch the rows to the equal height or natural height
- */
- public stretchRows() {
- const equal = this.node.attributes.get('equalrows') as boolean;
- const HD = (equal ? this.getEqualRowHeight() : 0);
- const {H, D} = (equal ? this.getTableData() : {H: [0], D: [0]});
- const rows = this.tableRows;
- for (let i = 0; i < this.numRows; i++) {
- const hd = (equal ? [(HD + H[i] - D[i]) / 2, (HD - H[i] + D[i]) / 2] : null);
- rows[i].stretchChildren(hd);
- }
- }
- /**
- * Stretch the columns to their proper widths
- */
- public stretchColumns() {
- for (let i = 0; i < this.numCols; i++) {
- const width = (typeof this.cWidths[i] === 'number' ? this.cWidths[i] as number : null);
- this.stretchColumn(i, width);
- }
- }
- /**
- * Handle horizontal stretching within the ith column
- *
- * @param {number} i The column number
- * @param {number} W The computed width of the column (or null of not computed)
- */
- public stretchColumn(i: number, W: number) {
- let stretchy: AnyWrapper[] = [];
- //
- // Locate and count the stretchy children
- //
- for (const row of this.tableRows) {
- const cell = row.getChild(i);
- if (cell) {
- const child = cell.childNodes[0];
- if (child.stretch.dir === DIRECTION.None &&
- child.canStretch(DIRECTION.Horizontal)) {
- stretchy.push(child);
- }
- }
- }
- let count = stretchy.length;
- let nodeCount = this.childNodes.length;
- if (count && nodeCount > 1) {
- if (W === null) {
- W = 0;
- //
- // If all the children are stretchy, find the largest one,
- // otherwise, find the width of the non-stretchy children.
- //
- let all = (count > 1 && count === nodeCount);
- for (const row of this.tableRows) {
- const cell = row.getChild(i);
- if (cell) {
- const child = cell.childNodes[0];
- const noStretch = (child.stretch.dir === DIRECTION.None);
- if (all || noStretch) {
- const {w} = child.getBBox(noStretch);
- if (w > W) {
- W = w;
- }
- }
- }
- }
- }
- //
- // Stretch the stretchable children
- //
- for (const child of stretchy) {
- (child.coreMO() as CommonMo).getStretchedVariant([W]);
- }
- }
- }
- /******************************************************************/
- /**
- * Determine the row heights and depths, the column widths,
- * and the natural width and height of the table.
- *
- * @return {TableData} The dimensions of the rows and columns
- */
- public getTableData(): TableData {
- if (this.data) {
- return this.data;
- }
- const H = new Array(this.numRows).fill(0);
- const D = new Array(this.numRows).fill(0);
- const W = new Array(this.numCols).fill(0);
- const NH = new Array(this.numRows);
- const ND = new Array(this.numRows);
- const LW = [0];
- const rows = this.tableRows;
- for (let j = 0; j < rows.length; j++) {
- let M = 0;
- const row = rows[j];
- const align = row.node.attributes.get('rowalign') as string;
- for (let i = 0; i < row.numCells; i++) {
- const cell = row.getChild(i);
- M = this.updateHDW(cell, i, j, align, H, D, W, M);
- this.recordPWidthCell(cell, i);
- }
- NH[j] = H[j];
- ND[j] = D[j];
- if (row.labeled) {
- M = this.updateHDW(row.childNodes[0], 0, j, align, H, D, LW, M);
- }
- this.extendHD(j, H, D, M);
- this.extendHD(j, NH, ND, M);
- }
- const L = LW[0];
- this.data = {H, D, W, NH, ND, L};
- return this.data;
- }
- /**
- * @override
- */
- public updateHDW(
- cell: C, i: number, j: number, align: string, H: number[], D: number[], W: number[], M: number
- ): number {
- let {h, d, w} = cell.getBBox();
- const scale = cell.parent.bbox.rscale;
- if (cell.parent.bbox.rscale !== 1) {
- h *= scale;
- d *= scale;
- w *= scale;
- }
- if (this.node.getProperty('useHeight')) {
- if (h < .75) h = .75;
- if (d < .25) d = .25;
- }
- let m = 0;
- align = cell.node.attributes.get('rowalign') as string || align;
- if (align !== 'baseline' && align !== 'axis') {
- m = h + d;
- h = d = 0;
- }
- if (h > H[j]) H[j] = h;
- if (d > D[j]) D[j] = d;
- if (m > M) M = m;
- if (W && w > W[i]) W[i] = w;
- return M;
- }
- /**
- * @override
- */
- public extendHD(i: number, H: number[], D: number[], M: number) {
- const d = (M - (H[i] + D[i])) / 2;
- if (d < .00001) return;
- H[i] += d;
- D[i] += d;
- }
- /**
- * @param {C} cell The cell to check for percentage widths
- * @param {number} i The column index of the cell
- */
- public recordPWidthCell(cell: C, i: number) {
- if (cell.childNodes[0] && cell.childNodes[0].getBBox().pwidth) {
- this.pwidthCells.push([cell, i]);
- }
- }
- /**
- * @override
- */
- public computeBBox(bbox: BBox, _recompute: boolean = false) {
- const {H, D} = this.getTableData();
- let height, width;
- //
- // For equal rows, use the common height and depth for all rows
- // Otherwise, use the height and depths for each row separately.
- // Add in the spacing, line widths, and frame size.
- //
- if (this.node.attributes.get('equalrows') as boolean) {
- const HD = this.getEqualRowHeight();
- height = sum([].concat(this.rLines, this.rSpace)) + HD * this.numRows;
- } else {
- height = sum(H.concat(D, this.rLines, this.rSpace));
- }
- height += 2 * (this.fLine + this.fSpace[1]);
- //
- // Get the widths of all columns
- //
- const CW = this.getComputedWidths();
- //
- // Get the expected width of the table
- //
- width = sum(CW.concat(this.cLines, this.cSpace)) + 2 * (this.fLine + this.fSpace[0]);
- //
- // If the table width is not 'auto', determine the specified width
- // and pick the larger of the specified and computed widths.
- //
- const w = this.node.attributes.get('width') as string;
- if (w !== 'auto') {
- width = Math.max(this.length2em(w, 0) + 2 * this.fLine, width);
- }
- //
- // Return the bounding box information
- //
- let [h, d] = this.getBBoxHD(height);
- bbox.h = h;
- bbox.d = d;
- bbox.w = width;
- let [L, R] = this.getBBoxLR();
- bbox.L = L;
- bbox.R = R;
- //
- // Handle cell widths if width is not a percentage
- //
- if (!isPercent(w)) {
- this.setColumnPWidths();
- }
- }
- /**
- * @override
- */
- public setChildPWidths(_recompute: boolean, cwidth: number, _clear: boolean) {
- const width = this.node.attributes.get('width') as string;
- if (!isPercent(width)) return false;
- if (!this.hasLabels) {
- this.bbox.pwidth = '';
- this.container.bbox.pwidth = '';
- }
- const {w, L, R} = this.bbox;
- const labelInWidth = this.node.attributes.get('data-width-includes-label') as boolean;
- const W = Math.max(w, this.length2em(width, Math.max(cwidth, L + w + R))) - (labelInWidth ? L + R : 0);
- const cols = (this.node.attributes.get('equalcolumns') as boolean ?
- Array(this.numCols).fill(this.percent(1 / Math.max(1, this.numCols))) :
- this.getColumnAttributes('columnwidth', 0));
- this.cWidths = this.getColumnWidthsFixed(cols, W);
- const CW = this.getComputedWidths();
- this.pWidth = sum(CW.concat(this.cLines, this.cSpace)) + 2 * (this.fLine + this.fSpace[0]);
- if (this.isTop) {
- this.bbox.w = this.pWidth;
- }
- this.setColumnPWidths();
- if (this.pWidth !== w) {
- this.parent.invalidateBBox();
- }
- return this.pWidth !== w;
- }
- /**
- * Finalize any cells that have percentage-width content
- */
- public setColumnPWidths() {
- const W = this.cWidths as number[];
- for (const [cell, i] of this.pwidthCells) {
- if (cell.setChildPWidths(false, W[i])) {
- cell.invalidateBBox();
- cell.getBBox();
- }
- }
- }
- /**
- * @param {number} height The total height of the table
- * @return {[number, number]} The [height, depth] for the aligned table
- */
- public getBBoxHD(height: number): [number, number] {
- const [align, row] = this.getAlignmentRow();
- if (row === null) {
- const a = this.font.params.axis_height;
- const h2 = height / 2;
- const HD: {[key: string]: [number, number]} = {
- top: [0, height],
- center: [h2, h2],
- bottom: [height, 0],
- baseline: [h2, h2],
- axis: [h2 + a, h2 - a]
- };
- return HD[align] || [h2, h2];
- } else {
- const y = this.getVerticalPosition(row, align);
- return [y, height - y];
- }
- }
- /**
- * Get bbox left and right amounts to cover labels
- */
- public getBBoxLR() {
- if (this.hasLabels) {
- const attributes = this.node.attributes;
- const side = attributes.get('side') as string;
- let [pad, align] = this.getPadAlignShift(side);
- //
- // If labels are included in the width,
- // remove the frame spacing if there is no frame line (added by multline)
- // and use left or right justification rather than centering so that
- // there is no extra space reserved for the label on the opposite side,
- // (as there usually is to center the equation).
- //
- const labels = this.hasLabels && !!attributes.get('data-width-includes-label');
- if (labels && this.frame && this.fSpace[0]) {
- pad -= this.fSpace[0];
- }
- return (align === 'center' && !labels ? [pad, pad] :
- side === 'left' ? [pad, 0] : [0, pad]);
- }
- return [0, 0];
- }
- /**
- * @param {string} side The side for the labels
- * @return {[number, string, number]} The padding, alignment, and shift amounts
- */
- public getPadAlignShift(side: string): [number, string, number] {
- //
- // Make sure labels don't overlap table
- //
- const {L} = this.getTableData();
- const sep = this.length2em(this.node.attributes.get('minlabelspacing'));
- let pad = L + sep;
- const [lpad, rpad] = (this.styles == null ? ['', ''] :
- [this.styles.get('padding-left'), this.styles.get('padding-right')]);
- if (lpad || rpad) {
- pad = Math.max(pad, this.length2em(lpad || '0'), this.length2em(rpad || '0'));
- }
- //
- // Handle indentation
- //
- let [align, shift] = this.getAlignShift();
- if (align === side) {
- shift = (side === 'left' ? Math.max(pad, shift) - pad : Math.min(-pad, shift) + pad);
- }
- return [pad, align, shift] as [number, string, number];
- }
- /**
- * @override
- */
- public getAlignShift() {
- return (this.isTop ? super.getAlignShift() :
- [this.container.getChildAlign(this.containerI), 0] as [string, number]);
- }
- /**
- * @return {number} The true width of the table (without labels)
- */
- public getWidth(): number {
- return this.pWidth || this.getBBox().w;
- }
- /******************************************************************/
- /**
- * @return {number} The maximum height of a row
- */
- public getEqualRowHeight(): number {
- const {H, D} = this.getTableData();
- const HD = Array.from(H.keys()).map(i => H[i] + D[i]);
- return Math.max.apply(Math, HD);
- }
- /**
- * @return {number[]} The array of computed widths
- */
- public getComputedWidths(): number[] {
- const W = this.getTableData().W;
- let CW = Array.from(W.keys()).map(i => {
- return (typeof this.cWidths[i] === 'number' ? this.cWidths[i] as number : W[i]);
- });
- if (this.node.attributes.get('equalcolumns') as boolean) {
- CW = Array(CW.length).fill(max(CW));
- }
- return CW;
- }
- /**
- * Determine the column widths that can be computed (and need to be set).
- * The resulting arrays will have numbers for fixed-size arrays,
- * strings for percentage sizes that can't be determined now,
- * and null for stretchy columns that will expand to fill the extra space.
- * Depending on the width specified for the table, different column
- * values can be determined.
- *
- * @return {(string|number|null)[]} The array of widths
- */
- public getColumnWidths(): (string | number | null)[] {
- const width = this.node.attributes.get('width') as string;
- if (this.node.attributes.get('equalcolumns') as boolean) {
- return this.getEqualColumns(width);
- }
- const swidths = this.getColumnAttributes('columnwidth', 0);
- if (width === 'auto') {
- return this.getColumnWidthsAuto(swidths);
- }
- if (isPercent(width)) {
- return this.getColumnWidthsPercent(swidths);
- }
- return this.getColumnWidthsFixed(swidths, this.length2em(width));
- }
- /**
- * For tables with equal columns, get the proper amount per column.
- *
- * @param {string} width The width attribute of the table
- * @return {(string|number|null)[]} The array of widths
- */
- public getEqualColumns(width: string): (string | number | null)[] {
- const n = Math.max(1, this.numCols);
- let cwidth;
- if (width === 'auto') {
- const {W} = this.getTableData();
- cwidth = max(W);
- } else if (isPercent(width)) {
- cwidth = this.percent(1 / n);
- } else {
- const w = sum([].concat(this.cLines, this.cSpace)) + 2 * this.fSpace[0];
- cwidth = Math.max(0, this.length2em(width) - w) / n;
- }
- return Array(this.numCols).fill(cwidth);
- }
- /**
- * For tables with width="auto", auto and fit columns
- * will end up being natural width, so don't need to
- * set those explicitly.
- *
- * @param {string[]} swidths The split and padded columnwidths attribute
- * @return {ColumnWidths} The array of widths
- */
- public getColumnWidthsAuto(swidths: string[]): ColumnWidths {
- return swidths.map(x => {
- if (x === 'auto' || x === 'fit') return null;
- if (isPercent(x)) return x;
- return this.length2em(x);
- });
- }
- /**
- * For tables with percentage widths, the 'fit' columns (or 'auto'
- * columns if there are not 'fit' ones) will stretch automatically,
- * but for 'auto' columns (when there are 'fit' ones), set the size
- * to the natural size of the column.
- *
- * @param {string[]} swidths The split and padded columnwidths attribute
- * @return {ColumnWidths} The array of widths
- */
- public getColumnWidthsPercent(swidths: string[]): ColumnWidths {
- const hasFit = swidths.indexOf('fit') >= 0;
- const {W} = (hasFit ? this.getTableData() : {W: null});
- return Array.from(swidths.keys()).map(i => {
- const x = swidths[i];
- if (x === 'fit') return null;
- if (x === 'auto') return (hasFit ? W[i] : null);
- if (isPercent(x)) return x;
- return this.length2em(x);
- });
- }
- /**
- * For fixed-width tables, compute the column widths of all columns.
- *
- * @param {string[]} swidths The split and padded columnwidths attribute
- * @param {number} width The width of the table
- * @return {ColumnWidths} The array of widths
- */
- public getColumnWidthsFixed(swidths: string[], width: number): ColumnWidths {
- //
- // Get the indices of the fit and auto columns, and the number of fit or auto entries.
- // If there are fit or auto columns, get the column widths.
- //
- const indices = Array.from(swidths.keys());
- const fit = indices.filter(i => swidths[i] === 'fit');
- const auto = indices.filter(i => swidths[i] === 'auto');
- const n = fit.length || auto.length;
- const {W} = (n ? this.getTableData() : {W: null});
- //
- // Determine the space remaining from the fixed width after the
- // separation and lines have been removed (cwidth), and
- // after the width of the columns have been removed (dw).
- //
- const cwidth = width - sum([].concat(this.cLines, this.cSpace)) - 2 * this.fSpace[0];
- let dw = cwidth;
- indices.forEach(i => {
- const x = swidths[i];
- dw -= (x === 'fit' || x === 'auto' ? W[i] : this.length2em(x, cwidth));
- });
- //
- // Get the amount of extra space per column, or 0 (fw)
- //
- const fw = (n && dw > 0 ? dw / n : 0);
- //
- // Return the column widths (plus extra space for those that are stretching
- //
- return indices.map(i => {
- const x = swidths[i];
- if (x === 'fit') return W[i] + fw;
- if (x === 'auto') return W[i] + (fit.length === 0 ? fw : 0);
- return this.length2em(x, cwidth);
- });
- }
- /**
- * @param {number} i The row number (starting at 0)
- * @param {string} align The alignment on that row
- * @return {number} The offest of the alignment position from the top of the table
- */
- public getVerticalPosition(i: number, align: string): number {
- const equal = this.node.attributes.get('equalrows') as boolean;
- const {H, D} = this.getTableData();
- const HD = (equal ? this.getEqualRowHeight() : 0);
- const space = this.getRowHalfSpacing();
- //
- // Start with frame size and add in spacing, height and depth,
- // and line thickness for each row.
- //
- let y = this.fLine;
- for (let j = 0; j < i; j++) {
- y += space[j] + (equal ? HD : H[j] + D[j]) + space[j + 1] + this.rLines[j];
- }
- //
- // For equal rows, get updated height and depth
- //
- const [h, d] = (equal ? [(HD + H[i] - D[i]) / 2, (HD - H[i] + D[i]) / 2] : [H[i], D[i]]);
- //
- // Add the offset into the specified row
- //
- const offset: {[name: string]: number} = {
- top: 0,
- center: space[i] + (h + d) / 2,
- bottom: space[i] + h + d + space[i + 1],
- baseline: space[i] + h,
- axis: space[i] + h - .25
- };
- y += offset[align] || 0;
- //
- // Return the final result
- //
- return y;
- }
- /******************************************************************/
- /**
- * @param {number} fspace The frame spacing to use
- * @param {number[]} space The array of spacing values to convert to strings
- * @param {number} scale A scaling factor to use for the sizes
- * @return {string[]} The half-spacing as stings with units of "em"
- * with frame spacing at the beginning and end
- */
- public getEmHalfSpacing(fspace: number, space: number[], scale: number = 1): string[] {
- //
- // Get the column spacing values, and add the frame spacing values at the left and right
- //
- const fspaceEm = this.em(fspace * scale);
- const spaceEm = this.addEm(space, 2 / scale);
- spaceEm.unshift(fspaceEm);
- spaceEm.push(fspaceEm);
- return spaceEm;
- }
- /**
- * @return {number[]} The half-spacing for rows with frame spacing at the ends
- */
- public getRowHalfSpacing(): number[] {
- const space = this.rSpace.map(x => x / 2);
- space.unshift(this.fSpace[1]);
- space.push(this.fSpace[1]);
- return space;
- }
- /**
- * @return {number[]} The half-spacing for columns with frame spacing at the ends
- */
- public getColumnHalfSpacing(): number[] {
- const space = this.cSpace.map(x => x / 2);
- space.unshift(this.fSpace[0]);
- space.push(this.fSpace[0]);
- return space;
- }
- /**
- * @return {[string,number|null]} The alignment and row number (based at 0) or null
- */
- public getAlignmentRow(): [string, number] {
- const [align, row] = split(this.node.attributes.get('align') as string);
- if (row == null) return [align, null];
- let i = parseInt(row);
- if (i < 0) i += this.numRows + 1;
- return [align, i < 1 || i > this.numRows ? null : i - 1];
- }
- /**
- * @param {string} name The name of the attribute to get as an array
- * @param {number=} i Return this many fewer than numCols entries
- * @return {string[]} The array of values in the given attribute, split at spaces,
- * padded to the number of table columns (minus 1) by repeating the last entry
- */
- public getColumnAttributes(name: string, i: number = 1): string[] | null {
- const n = this.numCols - i;
- const columns = this.getAttributeArray(name);
- if (columns.length === 0) return null;
- while (columns.length < n) {
- columns.push(columns[columns.length - 1]);
- }
- if (columns.length > n) {
- columns.splice(n);
- }
- return columns;
- }
- /**
- * @param {string} name The name of the attribute to get as an array
- * @param {number=} i Return this many fewer than numRows entries
- * @return {string[]} The array of values in the given attribute, split at spaces,
- * padded to the number of table rows (minus 1) by repeating the last entry
- */
- public getRowAttributes(name: string, i: number = 1): string[] | null {
- const n = this.numRows - i;
- const rows = this.getAttributeArray(name);
- if (rows.length === 0) return null;
- while (rows.length < n) {
- rows.push(rows[rows.length - 1]);
- }
- if (rows.length > n) {
- rows.splice(n);
- }
- return rows;
- }
- /**
- * @param {string} name The name of the attribute to get as an array
- * @return {string[]} The array of values in the given attribute, split at spaces
- * (after leading and trailing spaces are removed, and multiple
- * spaces have been collapsed to one).
- */
- public getAttributeArray(name: string): string[] {
- const value = this.node.attributes.get(name) as string;
- if (!value) return [this.node.attributes.getDefault(name) as string];
- return split(value);
- }
- /**
- * Adds "em" to a list of dimensions, after dividing by n (defaults to 1).
- *
- * @param {string[]} list The array of dimensions (in em's)
- * @param {nunber=} n The number to divide each dimension by after converted
- * @return {string[]} The array of values with "em" added
- */
- public addEm(list: number[], n: number = 1): string[] | null {
- if (!list) return null;
- return list.map(x => this.em(x / n));
- }
- /**
- * Converts an array of dimensions (with arbitrary units) to an array of numbers
- * representing the dimensions in units of em's.
- *
- * @param {string[]} list The array of dimensions to be turned into em's
- * @return {number[]} The array of values converted to em's
- */
- public convertLengths(list: string[]): number[] | null {
- if (!list) return null;
- return list.map(x => this.length2em(x));
- }
- };
- }
|