const objectAssign = require('object-assign'); const Cell = require('./cell'); const { ColSpanCell, RowSpanCell } = Cell; (function() { function layoutTable(table) { table.forEach(function(row, rowIndex) { row.forEach(function(cell, columnIndex) { cell.y = rowIndex; cell.x = columnIndex; for (let y = rowIndex; y >= 0; y--) { let row2 = table[y]; let xMax = y === rowIndex ? columnIndex : row2.length; for (let x = 0; x < xMax; x++) { let cell2 = row2[x]; while (cellsConflict(cell, cell2)) { cell.x++; } } } }); }); } 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); 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; 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 = []; 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; for (let i = 1; i < span; i++) { existingWidth += 1 + result[col + i]; if (typeof vals[col + i] !== 'number') { editableCols++; } } 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++; } } } objectAssign(vals, result); for (let j = 0; j < vals.length; j++) { vals[j] = Math.max(forcedMin, vals[j] || 0); } }; }