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