255 lines
6.9 KiB
JavaScript
255 lines
6.9 KiB
JavaScript
const { warn, debug } = require('./debug');
|
|
const Cell = require('./cell');
|
|
const { ColSpanCell, RowSpanCell } = Cell;
|
|
|
|
(function () {
|
|
function next(alloc, col) {
|
|
if (alloc[col] > 0) {
|
|
return next(alloc, col + 1);
|
|
}
|
|
return col;
|
|
}
|
|
|
|
function layoutTable(table) {
|
|
let alloc = {};
|
|
table.forEach(function (row, rowIndex) {
|
|
let col = 0;
|
|
row.forEach(function (cell) {
|
|
cell.y = rowIndex;
|
|
// Avoid erroneous call to next() on first row
|
|
cell.x = rowIndex ? next(alloc, col) : col;
|
|
const rowSpan = cell.rowSpan || 1;
|
|
const colSpan = cell.colSpan || 1;
|
|
if (rowSpan > 1) {
|
|
for (let cs = 0; cs < colSpan; cs++) {
|
|
alloc[cell.x + cs] = rowSpan;
|
|
}
|
|
}
|
|
col = cell.x + colSpan;
|
|
});
|
|
Object.keys(alloc).forEach((idx) => {
|
|
alloc[idx]--;
|
|
if (alloc[idx] < 1) delete alloc[idx];
|
|
});
|
|
});
|
|
}
|
|
|
|
function maxWidth(table) {
|
|
let mw = 0;
|
|
table.forEach(function (row) {
|
|
row.forEach(function (cell) {
|
|
mw = Math.max(mw, cell.x + (cell.colSpan || 1));
|
|
});
|
|
});
|
|
return mw;
|
|
}
|
|
|
|
function maxHeight(table) {
|
|
return table.length;
|
|
}
|
|
|
|
function cellsConflict(cell1, cell2) {
|
|
let yMin1 = cell1.y;
|
|
let yMax1 = cell1.y - 1 + (cell1.rowSpan || 1);
|
|
let yMin2 = cell2.y;
|
|
let yMax2 = cell2.y - 1 + (cell2.rowSpan || 1);
|
|
let yConflict = !(yMin1 > yMax2 || yMin2 > yMax1);
|
|
|
|
let xMin1 = cell1.x;
|
|
let xMax1 = cell1.x - 1 + (cell1.colSpan || 1);
|
|
let xMin2 = cell2.x;
|
|
let xMax2 = cell2.x - 1 + (cell2.colSpan || 1);
|
|
let xConflict = !(xMin1 > xMax2 || xMin2 > xMax1);
|
|
|
|
return yConflict && xConflict;
|
|
}
|
|
|
|
function conflictExists(rows, x, y) {
|
|
let i_max = Math.min(rows.length - 1, y);
|
|
let cell = { x: x, y: y };
|
|
for (let i = 0; i <= i_max; i++) {
|
|
let row = rows[i];
|
|
for (let j = 0; j < row.length; j++) {
|
|
if (cellsConflict(cell, row[j])) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function allBlank(rows, y, xMin, xMax) {
|
|
for (let x = xMin; x < xMax; x++) {
|
|
if (conflictExists(rows, x, y)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function addRowSpanCells(table) {
|
|
table.forEach(function (row, rowIndex) {
|
|
row.forEach(function (cell) {
|
|
for (let i = 1; i < cell.rowSpan; i++) {
|
|
let rowSpanCell = new RowSpanCell(cell);
|
|
rowSpanCell.x = cell.x;
|
|
rowSpanCell.y = cell.y + i;
|
|
rowSpanCell.colSpan = cell.colSpan;
|
|
insertCell(rowSpanCell, table[rowIndex + i]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function addColSpanCells(cellRows) {
|
|
for (let rowIndex = cellRows.length - 1; rowIndex >= 0; rowIndex--) {
|
|
let cellColumns = cellRows[rowIndex];
|
|
for (let columnIndex = 0; columnIndex < cellColumns.length; columnIndex++) {
|
|
let cell = cellColumns[columnIndex];
|
|
for (let k = 1; k < cell.colSpan; k++) {
|
|
let colSpanCell = new ColSpanCell();
|
|
colSpanCell.x = cell.x + k;
|
|
colSpanCell.y = cell.y;
|
|
cellColumns.splice(columnIndex + 1, 0, colSpanCell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertCell(cell, row) {
|
|
let x = 0;
|
|
while (x < row.length && row[x].x < cell.x) {
|
|
x++;
|
|
}
|
|
row.splice(x, 0, cell);
|
|
}
|
|
|
|
function fillInTable(table) {
|
|
let h_max = maxHeight(table);
|
|
let w_max = maxWidth(table);
|
|
debug(`Max rows: ${h_max}; Max cols: ${w_max}`);
|
|
for (let y = 0; y < h_max; y++) {
|
|
for (let x = 0; x < w_max; x++) {
|
|
if (!conflictExists(table, x, y)) {
|
|
let opts = { x: x, y: y, colSpan: 1, rowSpan: 1 };
|
|
x++;
|
|
while (x < w_max && !conflictExists(table, x, y)) {
|
|
opts.colSpan++;
|
|
x++;
|
|
}
|
|
let y2 = y + 1;
|
|
while (y2 < h_max && allBlank(table, y2, opts.x, opts.x + opts.colSpan)) {
|
|
opts.rowSpan++;
|
|
y2++;
|
|
}
|
|
let cell = new Cell(opts);
|
|
cell.x = opts.x;
|
|
cell.y = opts.y;
|
|
warn(`Missing cell at ${cell.y}-${cell.x}.`);
|
|
insertCell(cell, table[y]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function generateCells(rows) {
|
|
return rows.map(function (row) {
|
|
if (!Array.isArray(row)) {
|
|
let key = Object.keys(row)[0];
|
|
row = row[key];
|
|
if (Array.isArray(row)) {
|
|
row = row.slice();
|
|
row.unshift(key);
|
|
} else {
|
|
row = [key, row];
|
|
}
|
|
}
|
|
return row.map(function (cell) {
|
|
return new Cell(cell);
|
|
});
|
|
});
|
|
}
|
|
|
|
function makeTableLayout(rows) {
|
|
let cellRows = generateCells(rows);
|
|
layoutTable(cellRows);
|
|
fillInTable(cellRows);
|
|
addRowSpanCells(cellRows);
|
|
addColSpanCells(cellRows);
|
|
return cellRows;
|
|
}
|
|
|
|
module.exports = {
|
|
makeTableLayout: makeTableLayout,
|
|
layoutTable: layoutTable,
|
|
addRowSpanCells: addRowSpanCells,
|
|
maxWidth: maxWidth,
|
|
fillInTable: fillInTable,
|
|
computeWidths: makeComputeWidths('colSpan', 'desiredWidth', 'x', 1),
|
|
computeHeights: makeComputeWidths('rowSpan', 'desiredHeight', 'y', 1),
|
|
};
|
|
})();
|
|
|
|
function makeComputeWidths(colSpan, desiredWidth, x, forcedMin) {
|
|
return function (vals, table) {
|
|
let result = [];
|
|
let spanners = [];
|
|
let auto = {};
|
|
table.forEach(function (row) {
|
|
row.forEach(function (cell) {
|
|
if ((cell[colSpan] || 1) > 1) {
|
|
spanners.push(cell);
|
|
} else {
|
|
result[cell[x]] = Math.max(result[cell[x]] || 0, cell[desiredWidth] || 0, forcedMin);
|
|
}
|
|
});
|
|
});
|
|
|
|
vals.forEach(function (val, index) {
|
|
if (typeof val === 'number') {
|
|
result[index] = val;
|
|
}
|
|
});
|
|
|
|
//spanners.forEach(function(cell){
|
|
for (let k = spanners.length - 1; k >= 0; k--) {
|
|
let cell = spanners[k];
|
|
let span = cell[colSpan];
|
|
let col = cell[x];
|
|
let existingWidth = result[col];
|
|
let editableCols = typeof vals[col] === 'number' ? 0 : 1;
|
|
if (typeof existingWidth === 'number') {
|
|
for (let i = 1; i < span; i++) {
|
|
existingWidth += 1 + result[col + i];
|
|
if (typeof vals[col + i] !== 'number') {
|
|
editableCols++;
|
|
}
|
|
}
|
|
} else {
|
|
existingWidth = desiredWidth === 'desiredWidth' ? cell.desiredWidth - 1 : 1;
|
|
if (!auto[col] || auto[col] < existingWidth) {
|
|
auto[col] = existingWidth;
|
|
}
|
|
}
|
|
|
|
if (cell[desiredWidth] > existingWidth) {
|
|
let i = 0;
|
|
while (editableCols > 0 && cell[desiredWidth] > existingWidth) {
|
|
if (typeof vals[col + i] !== 'number') {
|
|
let dif = Math.round((cell[desiredWidth] - existingWidth) / editableCols);
|
|
existingWidth += dif;
|
|
result[col + i] += dif;
|
|
editableCols--;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Object.assign(vals, result, auto);
|
|
for (let j = 0; j < vals.length; j++) {
|
|
vals[j] = Math.max(forcedMin, vals[j] || 0);
|
|
}
|
|
};
|
|
}
|