• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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