| 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); |
| } |
| }; |
| } |