1const objectAssign = require('object-assign'); 2const Cell = require('./cell'); 3const { ColSpanCell, RowSpanCell } = Cell; 4 5(function() { 6 function layoutTable(table) { 7 table.forEach(function(row, rowIndex) { 8 row.forEach(function(cell, columnIndex) { 9 cell.y = rowIndex; 10 cell.x = columnIndex; 11 for (let y = rowIndex; y >= 0; y--) { 12 let row2 = table[y]; 13 let xMax = y === rowIndex ? columnIndex : row2.length; 14 for (let x = 0; x < xMax; x++) { 15 let cell2 = row2[x]; 16 while (cellsConflict(cell, cell2)) { 17 cell.x++; 18 } 19 } 20 } 21 }); 22 }); 23 } 24 25 function maxWidth(table) { 26 let mw = 0; 27 table.forEach(function(row) { 28 row.forEach(function(cell) { 29 mw = Math.max(mw, cell.x + (cell.colSpan || 1)); 30 }); 31 }); 32 return mw; 33 } 34 35 function maxHeight(table) { 36 return table.length; 37 } 38 39 function cellsConflict(cell1, cell2) { 40 let yMin1 = cell1.y; 41 let yMax1 = cell1.y - 1 + (cell1.rowSpan || 1); 42 let yMin2 = cell2.y; 43 let yMax2 = cell2.y - 1 + (cell2.rowSpan || 1); 44 let yConflict = !(yMin1 > yMax2 || yMin2 > yMax1); 45 46 let xMin1 = cell1.x; 47 let xMax1 = cell1.x - 1 + (cell1.colSpan || 1); 48 let xMin2 = cell2.x; 49 let xMax2 = cell2.x - 1 + (cell2.colSpan || 1); 50 let xConflict = !(xMin1 > xMax2 || xMin2 > xMax1); 51 52 return yConflict && xConflict; 53 } 54 55 function conflictExists(rows, x, y) { 56 let i_max = Math.min(rows.length - 1, y); 57 let cell = { x: x, y: y }; 58 for (let i = 0; i <= i_max; i++) { 59 let row = rows[i]; 60 for (let j = 0; j < row.length; j++) { 61 if (cellsConflict(cell, row[j])) { 62 return true; 63 } 64 } 65 } 66 return false; 67 } 68 69 function allBlank(rows, y, xMin, xMax) { 70 for (let x = xMin; x < xMax; x++) { 71 if (conflictExists(rows, x, y)) { 72 return false; 73 } 74 } 75 return true; 76 } 77 78 function addRowSpanCells(table) { 79 table.forEach(function(row, rowIndex) { 80 row.forEach(function(cell) { 81 for (let i = 1; i < cell.rowSpan; i++) { 82 let rowSpanCell = new RowSpanCell(cell); 83 rowSpanCell.x = cell.x; 84 rowSpanCell.y = cell.y + i; 85 rowSpanCell.colSpan = cell.colSpan; 86 insertCell(rowSpanCell, table[rowIndex + i]); 87 } 88 }); 89 }); 90 } 91 92 function addColSpanCells(cellRows) { 93 for (let rowIndex = cellRows.length - 1; rowIndex >= 0; rowIndex--) { 94 let cellColumns = cellRows[rowIndex]; 95 for (let columnIndex = 0; columnIndex < cellColumns.length; columnIndex++) { 96 let cell = cellColumns[columnIndex]; 97 for (let k = 1; k < cell.colSpan; k++) { 98 let colSpanCell = new ColSpanCell(); 99 colSpanCell.x = cell.x + k; 100 colSpanCell.y = cell.y; 101 cellColumns.splice(columnIndex + 1, 0, colSpanCell); 102 } 103 } 104 } 105 } 106 107 function insertCell(cell, row) { 108 let x = 0; 109 while (x < row.length && row[x].x < cell.x) { 110 x++; 111 } 112 row.splice(x, 0, cell); 113 } 114 115 function fillInTable(table) { 116 let h_max = maxHeight(table); 117 let w_max = maxWidth(table); 118 for (let y = 0; y < h_max; y++) { 119 for (let x = 0; x < w_max; x++) { 120 if (!conflictExists(table, x, y)) { 121 let opts = { x: x, y: y, colSpan: 1, rowSpan: 1 }; 122 x++; 123 while (x < w_max && !conflictExists(table, x, y)) { 124 opts.colSpan++; 125 x++; 126 } 127 let y2 = y + 1; 128 while (y2 < h_max && allBlank(table, y2, opts.x, opts.x + opts.colSpan)) { 129 opts.rowSpan++; 130 y2++; 131 } 132 133 let cell = new Cell(opts); 134 cell.x = opts.x; 135 cell.y = opts.y; 136 insertCell(cell, table[y]); 137 } 138 } 139 } 140 } 141 142 function generateCells(rows) { 143 return rows.map(function(row) { 144 if (!Array.isArray(row)) { 145 let key = Object.keys(row)[0]; 146 row = row[key]; 147 if (Array.isArray(row)) { 148 row = row.slice(); 149 row.unshift(key); 150 } else { 151 row = [key, row]; 152 } 153 } 154 return row.map(function(cell) { 155 return new Cell(cell); 156 }); 157 }); 158 } 159 160 function makeTableLayout(rows) { 161 let cellRows = generateCells(rows); 162 layoutTable(cellRows); 163 fillInTable(cellRows); 164 addRowSpanCells(cellRows); 165 addColSpanCells(cellRows); 166 return cellRows; 167 } 168 169 module.exports = { 170 makeTableLayout: makeTableLayout, 171 layoutTable: layoutTable, 172 addRowSpanCells: addRowSpanCells, 173 maxWidth: maxWidth, 174 fillInTable: fillInTable, 175 computeWidths: makeComputeWidths('colSpan', 'desiredWidth', 'x', 1), 176 computeHeights: makeComputeWidths('rowSpan', 'desiredHeight', 'y', 1), 177 }; 178})(); 179 180function makeComputeWidths(colSpan, desiredWidth, x, forcedMin) { 181 return function(vals, table) { 182 let result = []; 183 let spanners = []; 184 table.forEach(function(row) { 185 row.forEach(function(cell) { 186 if ((cell[colSpan] || 1) > 1) { 187 spanners.push(cell); 188 } else { 189 result[cell[x]] = Math.max(result[cell[x]] || 0, cell[desiredWidth] || 0, forcedMin); 190 } 191 }); 192 }); 193 194 vals.forEach(function(val, index) { 195 if (typeof val === 'number') { 196 result[index] = val; 197 } 198 }); 199 200 //spanners.forEach(function(cell){ 201 for (let k = spanners.length - 1; k >= 0; k--) { 202 let cell = spanners[k]; 203 let span = cell[colSpan]; 204 let col = cell[x]; 205 let existingWidth = result[col]; 206 let editableCols = typeof vals[col] === 'number' ? 0 : 1; 207 for (let i = 1; i < span; i++) { 208 existingWidth += 1 + result[col + i]; 209 if (typeof vals[col + i] !== 'number') { 210 editableCols++; 211 } 212 } 213 if (cell[desiredWidth] > existingWidth) { 214 let i = 0; 215 while (editableCols > 0 && cell[desiredWidth] > existingWidth) { 216 if (typeof vals[col + i] !== 'number') { 217 let dif = Math.round((cell[desiredWidth] - existingWidth) / editableCols); 218 existingWidth += dif; 219 result[col + i] += dif; 220 editableCols--; 221 } 222 i++; 223 } 224 } 225 } 226 227 objectAssign(vals, result); 228 for (let j = 0; j < vals.length; j++) { 229 vals[j] = Math.max(forcedMin, vals[j] || 0); 230 } 231 }; 232} 233