KHtml

table_layout.cpp
1 /*
2  * This file is part of the HTML rendering engine for KDE.
3  *
4  * Copyright (C) 2002 Lars Knoll ([email protected])
5  * (C) 2002 Dirk Mueller ([email protected])
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB. If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23 #include "table_layout.h"
24 #include "render_table.h"
25 
26 using namespace khtml;
27 
28 // #define DEBUG_LAYOUT
29 
30 /*
31  The text below is from the CSS 2.1 specs.
32 
33  Fixed table layout
34  ------------------
35 
36  With this (fast) algorithm, the horizontal layout of the table does
37  not depend on the contents of the cells; it only depends on the
38  table's width, the width of the columns, and borders or cell
39  spacing.
40 
41  The table's width may be specified explicitly with the 'width'
42  property. A value of 'auto' (for both 'display: table' and 'display:
43  inline-table') means use the automatic table layout algorithm.
44 
45  In the fixed table layout algorithm, the width of each column is
46  determined as follows:
47 
48  1. A column element with a value other than 'auto' for the 'width'
49  property sets the width for that column.
50 
51  2. Otherwise, a cell in the first row with a value other than
52  'auto' for the 'width' property sets the width for that column. If
53  the cell spans more than one column, the width is divided over the
54  columns.
55 
56  3. Any remaining columns equally divide the remaining horizontal
57  table space (minus borders or cell spacing).
58 
59  The width of the table is then the greater of the value of the
60  'width' property for the table element and the sum of the column
61  widths (plus cell spacing or borders). If the table is wider than
62  the columns, the extra space should be distributed over the columns.
63 
64  In this manner, the user agent can begin to lay out the table once
65  the entire first row has been received. Cells in subsequent rows do
66  not affect column widths. Any cell that has content that overflows
67  uses the 'overflow' property to determine whether to clip the
68  overflow content.
69 
70 _____________________________________________________
71 
72  This is not quite true when comparing to IE. IE always honors
73  table-layout:fixed and treats a variable table width as 100%. Makes
74  a lot of sense, and is implemented here the same way.
75 
76 */
77 
78 FixedTableLayout::FixedTableLayout(RenderTable *table)
79  : TableLayout(table)
80 {
81 }
82 
83 FixedTableLayout::~FixedTableLayout()
84 {
85 }
86 
87 int FixedTableLayout::calcWidthArray()
88 {
89  int usedWidth = 0;
90 
91  // iterate over all <col> elements
92  RenderObject *child = table->firstChild();
93  int nEffCols = table->numEffCols();
94  width.resize(nEffCols);
95  width.fill(Length(Auto));
96 
97 #ifdef DEBUG_LAYOUT
98  qDebug("FixedTableLayout::calcWidthArray()");
99  qDebug(" col elements:");
100 #endif
101 
102  int currentEffectiveColumn = 0;
103  Length grpWidth;
104  while (child) {
105  if (child->isTableCol()) {
106  RenderTableCol *col = static_cast<RenderTableCol *>(child);
107  if (col->firstChild()) {
108  grpWidth = col->style()->width();
109  } else {
110  Length w = col->style()->width();
111  if (w.isAuto()) {
112  w = grpWidth;
113  }
114  int effWidth = 0;
115  if (w.isFixed() && w.isPositive()) {
116  effWidth = w.value();
117  effWidth = qMin(effWidth, 32760);
118  }
119 #ifdef DEBUG_LAYOUT
120  qDebug(" col element: effCol=%d, span=%d: %d w=%d type=%d",
121  cCol, span, effWidth, w.rawValue(), w.type());
122 #endif
123  int span = col->span();
124  while (span) {
125  int spanInCurrentEffectiveColumn;
126  if (currentEffectiveColumn >= nEffCols) {
127  table->appendColumn(span);
128  nEffCols++;
129  width.append(Length());
130  spanInCurrentEffectiveColumn = span;
131  } else {
132  if (span < table->spanOfEffCol(currentEffectiveColumn)) {
133  table->splitColumn(currentEffectiveColumn, span);
134  nEffCols++;
135  width.append(Length());
136  }
137  spanInCurrentEffectiveColumn = table->spanOfEffCol(currentEffectiveColumn);
138  }
139  if ((w.isFixed() || w.isPercent()) && w.isPositive()) {
140  width[currentEffectiveColumn].setRawValue(w.type(), w.rawValue() * spanInCurrentEffectiveColumn);
141  usedWidth += effWidth * spanInCurrentEffectiveColumn;
142 #ifdef DEBUG_LAYOUT
143  qDebug(" setting effCol %d (span=%d) to width %d(type=%d)",
144  cCol + i, eSpan, width[cCol + i].rawValue(), width[cCol + i].type());
145 #endif
146  }
147  span -= spanInCurrentEffectiveColumn;
148  currentEffectiveColumn++;
149  }
150  }
151  } else {
152  break;
153  }
154 
155  RenderObject *next = child->firstChild();
156  if (!next) {
157  next = child->nextSibling();
158  }
159  if (!next && child->parent()->isTableCol()) {
160  next = child->parent()->nextSibling();
161  grpWidth = Length();
162  }
163  child = next;
164  }
165 
166 #ifdef DEBUG_LAYOUT
167  qDebug(" first row:");
168 #endif
169  // iterate over the first row in case some are unspecified.
170  RenderTableSection *section = table->head;
171  if (!section) {
172  section = table->firstBody;
173  }
174  if (!section) {
175  section = table->foot;
176  }
177  if (section && section->firstChild()) {
178  int cCol = 0;
179  // get the first cell in the first row
180  child = section->firstChild()->firstChild();
181  while (child) {
182  if (child->isTableCell()) {
183  RenderTableCell *cell = static_cast<RenderTableCell *>(child);
184  Length w = cell->styleOrColWidth();
185  int span = cell->colSpan();
186  int effWidth = 0;
187  // FIXME: This does not make sense (mixing percentages with absolute length)
188  if ((w.isFixed() || w.isPercent()) && w.isPositive()) {
189  effWidth = w.isPercent() ? w.rawValue() / PERCENT_SCALE_FACTOR : w.value();
190  effWidth = qMin(effWidth, 32760);
191  }
192 #ifdef DEBUG_LAYOUT
193  qDebug(" table cell: effCol=%d, span=%d: %d", cCol, span, effWidth);
194 #endif
195  int usedSpan = 0;
196  int i = 0;
197  while (usedSpan < span) {
198  Q_ASSERT(cCol + i < nEffCols);
199  int eSpan = table->spanOfEffCol(cCol + i);
200  // only set if no col element has already set it.
201  if (width[cCol + i].isAuto() && !w.isAuto()) {
202  width[cCol + i].setRawValue(w.type(), w.rawValue() * eSpan);
203  usedWidth += effWidth * eSpan;
204 #ifdef DEBUG_LAYOUT
205  qDebug(" setting effCol %d (span=%d) to width %d(type=%d)",
206  cCol + i, eSpan, width[cCol + i].rawValue(), width[cCol + i].type());
207 #endif
208  }
209 #ifdef DEBUG_LAYOUT
210  else {
211  qDebug(" width of col %d already defined (span=%d)", cCol, table->spanOfEffCol(cCol));
212  }
213 #endif
214  usedSpan += eSpan;
215  i++;
216  }
217  cCol += i;
218  } else {
219  Q_ASSERT(false);
220  }
221  child = child->nextSibling();
222  }
223  }
224 
225  return usedWidth;
226 
227 }
228 
229 void FixedTableLayout::calcMinMaxWidth()
230 {
231  // we might want to wait until we have all of the first row before
232  // layouting for the first time.
233 
234  // only need to calculate the minimum width as the sum of the
235  // cols/cells with a fixed width.
236  //
237  // The maximum width is qMax( minWidth, tableWidth ) if table
238  // width is fixed. If table width is percent, we set maxWidth to
239  // unlimited.
240 
241  int bs = table->bordersPaddingAndSpacing();
242  int tableWidth = 0;
243  if (table->style()->width().isFixed()) {
244  tableWidth = table->calcBoxWidth(table->style()->width().value());
245  }
246 
247  int mw = calcWidthArray() + bs;
248  table->m_minWidth = qMin(qMax(mw, tableWidth), 0x7fff);
249  table->m_maxWidth = table->m_minWidth;
250 
251  if (!tableWidth) {
252  bool haveNonFixed = false;
253  for (int i = 0; i < width.size(); i++) {
254  if (!width[i].isFixed()) {
255  haveNonFixed = true;
256  break;
257  }
258  }
259  if (haveNonFixed) {
260  table->m_maxWidth = 0x7fff;
261  }
262  }
263 #ifdef DEBUG_LAYOUT
264  qDebug("FixedTableLayout::calcMinMaxWidth: minWidth=%d, maxWidth=%d", table->m_minWidth, table->m_maxWidth);
265 #endif
266 }
267 
268 void FixedTableLayout::layout()
269 {
270  int tableWidth = table->width() - table->bordersPaddingAndSpacing();
271  int available = tableWidth;
272  int nEffCols = table->numEffCols();
273 #ifdef DEBUG_LAYOUT
274  qDebug("FixedTableLayout::layout: tableWidth=%d, numEffCols=%d", tableWidth, nEffCols);
275 #endif
276 
277  QVector<int> calcWidth;
278  calcWidth.resize(nEffCols);
279  calcWidth.fill(-1);
280 
281  // first assign fixed width
282  for (int i = 0; i < nEffCols; i++) {
283  if (width[i].isFixed()) {
284  calcWidth[i] = width[i].value();
285  available -= width[i].value();
286  }
287  }
288 
289  // assign percent width
290  if (available > 0) {
291  int totalPercent = 0;
292  for (int i = 0; i < nEffCols; i++)
293  if (width[i].isPercent()) {
294  totalPercent += width[i].rawValue();
295  }
296 
297  // calculate how much to distribute to percent cells.
298  int base = tableWidth * totalPercent / (100 * PERCENT_SCALE_FACTOR);
299  if (base > available) {
300  base = available;
301  }
302 
303 #ifdef DEBUG_LAYOUT
304  qDebug("FixedTableLayout::layout: assigning percent width, base=%d, totalPercent=%d", base, totalPercent);
305 #endif
306  for (int i = 0; available > 0 && i < nEffCols; i++) {
307  if (width[i].isPercent()) {
308  // totalPercent may be 0 below if all %-width specified are 0%. (#172557)
309  int w = totalPercent ? base * width[i].rawValue() / totalPercent : 0;
310  available -= w;
311  calcWidth[i] = w;
312  }
313  }
314  }
315 
316  // assign variable width
317  if (available > 0) {
318  int totalAuto = 0;
319  for (int i = 0; i < nEffCols; i++)
320  if (width[i].isAuto()) {
321  totalAuto++;
322  }
323 
324  for (int i = 0; available > 0 && i < nEffCols; i++) {
325  if (width[i].isAuto()) {
326  // totalAuto may be 0 below if all the variable widths specified are 0.
327  int w = totalAuto ? available / totalAuto : 0;
328  available -= w;
329  calcWidth[i] = w;
330  totalAuto--;
331  }
332  }
333  }
334 
335  for (int i = 0; i < nEffCols; i++)
336  if (calcWidth[i] < 0) {
337  calcWidth[i] = 0; // IE gives min 1 px...
338  }
339 
340  // spread extra space over columns
341  if (available > 0) {
342  int total = nEffCols;
343  // still have some width to spread
344  int i = nEffCols;
345  while (i--) {
346  int w = available / total;
347  available -= w;
348  total--;
349  calcWidth[i] += w;
350  }
351  }
352 
353  int pos = 0;
354  int hspacing = table->borderHSpacing();
355  for (int i = 0; i < nEffCols; i++) {
356 #ifdef DEBUG_LAYOUT
357  qDebug("col %d: %d (width %d)", i, pos, calcWidth[i]);
358 #endif
359  table->columnPos[i] = pos;
360  pos += calcWidth[i] + hspacing;
361  }
362  table->columnPos[table->columnPos.size() - 1] = pos;
363 }
364 
365 // -------------------------------------------------------------------------
366 // -------------------------------------------------------------------------
367 
368 AutoTableLayout::AutoTableLayout(RenderTable *table)
369  : TableLayout(table)
370 {
371  effWidthDirty = true;
372  hasPercent = false;
373 }
374 
375 AutoTableLayout::~AutoTableLayout()
376 {
377 }
378 
379 /* recalculates the full structure needed to do layouting and minmax calculations.
380  This is usually calculated on the fly, but needs to be done fully when table cells change
381  dynamically
382 */
383 void AutoTableLayout::recalcColumn(int effCol)
384 {
385  Layout &l = layoutStruct[effCol];
386 
387  RenderObject *child = table->firstChild();
388  // first we iterate over all rows.
389 
390  RenderTableCell *fixedContributor = nullptr;
391  RenderTableCell *maxContributor = nullptr;
392 
393  while (child) {
394  if (child->isTableSection()) {
395  RenderTableSection *section = static_cast<RenderTableSection *>(child);
396  int numRows = section->numRows();
397  //RenderTableCell *last = 0;
398  for (int i = 0; i < numRows; i++) {
399  RenderTableCell *cell = section->cellAt(i, effCol);
400  if (cell == (RenderTableCell *) - 1) {
401  continue;
402  }
403  bool cellHasContent = cell && (cell->firstChild() || cell->style()->hasBorder() || cell->style()->hasPadding());
404  if (cellHasContent) {
405  l.emptyCellsOnly = false;
406  }
407  if (cell && cell->colSpan() == 1) {
408  // A cell originates in this column. Ensure we have
409  // a min/max width of at least 1px for this column now.
410  l.minWidth = qMax(int(l.minWidth), 1);
411  l.maxWidth = qMax(int(l.maxWidth), cellHasContent ? 1 : 0);
412 
413  if (!cell->minMaxKnown()) {
414  cell->calcMinMaxWidth();
415  }
416  if (cell->minWidth() > l.minWidth) {
417  l.minWidth = cell->minWidth();
418  }
419  if (cell->maxWidth() > l.maxWidth) {
420  l.maxWidth = cell->maxWidth();
421  maxContributor = cell;
422  }
423 
424  Length w = cell->styleOrColWidth();
425  if (w.rawValue() > 32767) {
426  w.setRawValue(32767);
427  }
428  if (w.isNegative()) {
429  w.setValue(0);
430  }
431  switch (w.type()) {
432  case Fixed:
433  // ignore width=0
434  if (w.isPositive() && !l.width.isPercent()) {
435  int wval = cell->calcBoxWidth(w.value());
436  if (l.width.isFixed()) {
437  // Nav/IE weirdness
438  if ((wval > l.width.value()) ||
439  ((l.width.value() == wval) && (maxContributor == cell))) {
440  l.width.setValue(wval);
441  fixedContributor = cell;
442  }
443  } else {
444  l.width = Length(wval, Fixed);
445  fixedContributor = cell;
446  }
447  }
448  break;
449  case Percent:
450  hasPercent = true;
451  if (w.isPositive() && (!l.width.isPercent() || w.rawValue() > l.width.rawValue())) {
452  l.width = w;
453  }
454  break;
455  case Relative:
456  if (w.isAuto() || (w.isRelative() && w.value() > l.width.rawValue())) {
457  l.width = w;
458  }
459  default:
460  break;
461  }
462  } else {
463  if (cell && (!effCol || section->cellAt(i, effCol - 1) != cell)) {
464  // This spanning cell originates in this column. Ensure we have
465  // a min/max width of at least 1px for this column now.
466  l.minWidth = qMax(int(l.minWidth), cellHasContent ? 1 : 0);
467  l.maxWidth = qMax(int(l.maxWidth), 1);
468  insertSpanCell(cell);
469  }
470  //last = cell;
471  }
472  }
473  }
474  child = child->nextSibling();
475  }
476 
477  // Nav/IE weirdness
478  if (l.width.isFixed()) {
479  if (table->style()->htmlHacks()
480  && (l.maxWidth > l.width.value()) && (fixedContributor != maxContributor)) {
481  l.width = Length();
482  fixedContributor = nullptr;
483  }
484  }
485 
486  l.maxWidth = qMax(l.maxWidth, int(l.minWidth));
487 #ifdef DEBUG_LAYOUT
488  qDebug("col %d, final min=%d, max=%d, width=%d(%d)", effCol, l.minWidth, l.maxWidth, l.width.rawValue(), l.width.type());
489 #endif
490 
491  // ### we need to add col elements aswell
492 }
493 
494 void AutoTableLayout::fullRecalc()
495 {
496  hasPercent = false;
497  effWidthDirty = true;
498 
499  int nEffCols = table->numEffCols();
500  layoutStruct.resize(nEffCols);
501  layoutStruct.fill(Layout());
502  spanCells.fill(nullptr);
503 
504  RenderObject *child = table->firstChild();
505  Length grpWidth;
506  int cCol = 0;
507  while (child) {
508  if (child->isTableCol()) {
509  RenderTableCol *col = static_cast<RenderTableCol *>(child);
510  int span = col->span();
511  if (col->firstChild()) {
512  grpWidth = col->style()->width();
513  } else {
514  Length w = col->style()->width();
515  if (w.isAuto()) {
516  w = grpWidth;
517  }
518  if ((w.isFixed() || w.isPercent()) && w.isZero()) {
519  w = Length();
520  }
521  int cEffCol = table->colToEffCol(cCol);
522 #ifdef DEBUG_LAYOUT
523  qDebug(" col element %d (eff=%d): Length=%d(%d), span=%d, effColSpan=%d", cCol, cEffCol, w.rawValue(), w.type(), span, table->spanOfEffCol(cEffCol));
524 #endif
525  if (!w.isAuto() && span == 1 && cEffCol < nEffCols) {
526  if (table->spanOfEffCol(cEffCol) == 1) {
527  layoutStruct[cEffCol].width = w;
528  if (w.isFixed() && layoutStruct[cEffCol].maxWidth < w.value()) {
529  layoutStruct[cEffCol].maxWidth = w.value();
530  }
531  }
532  }
533  cCol += span;
534  }
535  } else {
536  break;
537  }
538 
539  RenderObject *next = child->firstChild();
540  if (!next) {
541  next = child->nextSibling();
542  }
543  if (!next && child->parent()->isTableCol()) {
544  next = child->parent()->nextSibling();
545  grpWidth = Length();
546  }
547  child = next;
548  }
549 
550  for (int i = 0; i < nEffCols; i++) {
551  recalcColumn(i);
552  }
553 }
554 
555 static bool shouldScaleColumns(RenderTable *table)
556 {
557  // A special case. If this table is not fixed width and contained inside
558  // a cell, then don't bloat the maxwidth by examining percentage growth.
559  bool scale = true;
560  while (table) {
561  Length tw = table->style()->width();
562  if ((tw.isAuto() || tw.isPercent()) && !table->isPositioned()) {
563  RenderBlock *cb = table->containingBlock();
564  while (cb && !cb->isCanvas() && !cb->isTableCell() &&
565  cb->style()->width().isAuto() && !cb->isPositioned()) {
566  cb = cb->containingBlock();
567  }
568 
569  table = nullptr;
570  if (cb && cb->isTableCell() &&
571  (cb->style()->width().isAuto() || cb->style()->width().isPercent())) {
572  if (tw.isPercent()) {
573  scale = false;
574  } else {
575  RenderTableCell *cell = static_cast<RenderTableCell *>(cb);
576  if (cell->colSpan() > 1 || cell->table()->style()->width().isAuto()) {
577  scale = false;
578  } else {
579  table = cell->table();
580  }
581  }
582  }
583  } else {
584  table = nullptr;
585  }
586  }
587  return scale;
588 }
589 
590 void AutoTableLayout::calcMinMaxWidth()
591 {
592 #ifdef DEBUG_LAYOUT
593  qDebug("AutoTableLayout::calcMinMaxWidth");
594 #endif
595  fullRecalc();
596 
597  int spanMaxWidth = calcEffectiveWidth();
598  int minWidth = 0;
599  int maxWidth = 0;
600  int maxPercent = 0;
601  int maxNonPercent = 0;
602 
603  int remainingPercent = 100 * PERCENT_SCALE_FACTOR;
604  for (int i = 0; i < layoutStruct.size(); i++) {
605  minWidth += layoutStruct[i].effMinWidth;
606  maxWidth += layoutStruct[i].effMaxWidth;
607  if (layoutStruct[i].effWidth.isPercent()) {
608  int percent = qMin(layoutStruct[i].effWidth.rawValue(), remainingPercent);
609  int pw = (layoutStruct[i].effMaxWidth * 100 * PERCENT_SCALE_FACTOR) / qMax(percent, 1);
610  remainingPercent -= percent;
611  maxPercent = qMax(pw, maxPercent);
612  } else {
613  maxNonPercent += layoutStruct[i].effMaxWidth;
614  }
615  }
616 
617  if (shouldScaleColumns(table)) {
618  maxNonPercent = (maxNonPercent * 100 * PERCENT_SCALE_FACTOR) / qMax(remainingPercent, 1);
619  maxWidth = qMax(maxNonPercent, maxWidth);
620  maxWidth = qMax(maxWidth, maxPercent);
621  }
622 
623  maxWidth = qMax(maxWidth, spanMaxWidth);
624 
625  int bs = table->bordersPaddingAndSpacing();
626  minWidth += bs;
627  maxWidth += bs;
628 
629  Length tw = table->style()->width();
630  if (tw.isFixed() && tw.isPositive()) {
631  int width = table->calcBoxWidth(tw.value());
632  minWidth = qMax(minWidth, width);
633  maxWidth = minWidth;
634  }
635 
636  table->m_maxWidth = qMin(maxWidth, 0x7fff);
637  table->m_minWidth = qMin(minWidth, 0x7fff);
638 #ifdef DEBUG_LAYOUT
639  qDebug(" minWidth=%d, maxWidth=%d", table->m_minWidth, table->m_maxWidth);
640 #endif
641 }
642 
643 /*
644  This method takes care of colspans.
645  effWidth is the same as width for cells without colspans. If we have colspans, they get modified.
646  */
647 int AutoTableLayout::calcEffectiveWidth()
648 {
649  int tMaxWidth = 0;
650 
651  unsigned int nEffCols = layoutStruct.size();
652  int hspacing = table->borderHSpacing();
653 #ifdef DEBUG_LAYOUT
654  qDebug("AutoTableLayout::calcEffectiveWidth for %d cols", nEffCols);
655 #endif
656  for (unsigned int i = 0; i < nEffCols; i++) {
657  layoutStruct[i].effWidth = layoutStruct[i].width;
658  layoutStruct[i].effMinWidth = layoutStruct[i].minWidth;
659  layoutStruct[i].effMaxWidth = layoutStruct[i].maxWidth;
660  }
661 
662  for (int i = 0; i < spanCells.size(); i++) {
663  RenderTableCell *cell = spanCells[i];
664  if (!cell || cell == (RenderTableCell *) - 1) {
665  break;
666  }
667  int span = cell->colSpan();
668 
669  Length w = cell->styleOrColWidth();
670  if (!w.isRelative() && w.isZero()) {
671  w = Length(); // make it Auto
672  }
673 
674  int col = table->colToEffCol(cell->col());
675  unsigned int lastCol = col;
676  int cMinWidth = cell->minWidth() + hspacing;
677  int cMaxWidth = cell->maxWidth() + hspacing;
678  int totalPercent = 0;
679  int minWidth = 0;
680  int maxWidth = 0;
681  bool allColsArePercent = true;
682  bool allColsAreFixed = true;
683  bool haveAuto = false;
684  bool spanHasEmptyCellsOnly = true;
685  int fixedWidth = 0;
686 #ifdef DEBUG_LAYOUT
687  int cSpan = span;
688 #endif
689  while (lastCol < nEffCols && span > 0) {
690  switch (layoutStruct[lastCol].width.type()) {
691  case Percent:
692  totalPercent += layoutStruct[lastCol].width.rawValue();
693  allColsAreFixed = false;
694  break;
695  case Fixed:
696  if (layoutStruct[lastCol].width.isPositive()) {
697  fixedWidth += layoutStruct[lastCol].width.value();
698  allColsArePercent = false;
699  // IE resets effWidth to Auto here, but this breaks the konqueror about page and seems to be some bad
700  // legacy behavior anyway. mozilla doesn't do this so I decided we don't either.
701  break;
702  }
703  // fall through
704  case Auto:
705  haveAuto = true;
706  // fall through
707  default:
708  // If the column is a percentage width, do not let the spanning cell overwrite the
709  // width value. This caused a mis-rendering on amazon.com.
710  // Sample snippet:
711  // <table border=2 width=100%><
712  // <tr><td>1</td><td colspan=2>2-3</tr>
713  // <tr><td>1</td><td colspan=2 width=100%>2-3</td></tr>
714  // </table>
715  if (!layoutStruct[lastCol].effWidth.isPercent()) {
716  layoutStruct[lastCol].effWidth = Length();
717  allColsArePercent = false;
718  } else {
719  totalPercent += layoutStruct[lastCol].effWidth.rawValue();
720  }
721  allColsAreFixed = false;
722  }
723  if (!layoutStruct[lastCol].emptyCellsOnly) {
724  spanHasEmptyCellsOnly = false;
725  }
726  span -= table->spanOfEffCol(lastCol);
727  minWidth += layoutStruct[lastCol].effMinWidth;
728  maxWidth += layoutStruct[lastCol].effMaxWidth;
729  lastCol++;
730  cMinWidth -= hspacing;
731  cMaxWidth -= hspacing;
732  }
733 #ifdef DEBUG_LAYOUT
734  qDebug(" colspan cell %p at effCol %d, span %d, type %d, value %d cmin=%d min=%d fixedwidth=%d", cell, col, cSpan, w.type(), w.rawValue(), cMinWidth, minWidth, fixedWidth);
735 #endif
736 
737  // adjust table max width if needed
738  if (w.isPercent()) {
739  if (totalPercent > w.rawValue() || allColsArePercent) {
740  // can't satify this condition, treat as variable
741  w = Length();
742  } else {
743  int spanMax = qMax(maxWidth, cMaxWidth);
744 #ifdef DEBUG_LAYOUT
745  qDebug(" adjusting tMaxWidth (%d): spanMax=%d, value=%d, totalPercent=%d", tMaxWidth, spanMax, w.rawValue(), totalPercent);
746 #endif
747  tMaxWidth = qMax(tMaxWidth, spanMax * 100 * PERCENT_SCALE_FACTOR / w.rawValue());
748 
749  // all non percent columns in the span get percent values to sum up correctly.
750  int percentMissing = w.rawValue() - totalPercent;
751  int totalWidth = 0;
752  for (unsigned int pos = col; pos < lastCol; pos++) {
753  if (!(layoutStruct[pos].width.isPercent())) {
754  totalWidth += layoutStruct[pos].effMaxWidth;
755  }
756  }
757 
758  for (unsigned int pos = col; pos < lastCol && totalWidth > 0; pos++) {
759  if (!(layoutStruct[pos].width.isPercent())) {
760  int percent = percentMissing * layoutStruct[pos].effMaxWidth / totalWidth;
761 #ifdef DEBUG_LAYOUT
762  qDebug(" col %d: setting percent value %d effMaxWidth=%d totalWidth=%d", pos, percent, layoutStruct[pos].effMaxWidth, totalWidth);
763 #endif
764  totalWidth -= layoutStruct[pos].effMaxWidth;
765  percentMissing -= percent;
766  if (percent > 0) {
767  layoutStruct[pos].effWidth.setRawValue(Percent, percent);
768  } else {
769  layoutStruct[pos].effWidth = Length();
770  }
771  }
772  }
773 
774  }
775  }
776 
777  // make sure minWidth and maxWidth of the spanning cell are honoured
778  if (cMinWidth > minWidth) {
779  if (allColsAreFixed) {
780 #ifdef DEBUG_LAYOUT
781  qDebug("extending minWidth of cols %d-%d to %dpx currentMin=%d accroding to fixed sum %d", col, lastCol - 1, cMinWidth, minWidth, fixedWidth);
782 #endif
783  for (unsigned int pos = col; fixedWidth > 0 && pos < lastCol; pos++) {
784  int w = qMax(int(layoutStruct[pos].effMinWidth), cMinWidth * layoutStruct[pos].width.value() / fixedWidth);
785 #ifdef DEBUG_LAYOUT
786  qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w);
787 #endif
788  fixedWidth -= layoutStruct[pos].width.value();
789  cMinWidth -= w;
790  layoutStruct[pos].effMinWidth = w;
791  }
792 
793  } else if (allColsArePercent) {
794  int maxw = maxWidth;
795  int minw = minWidth;
796  int cminw = cMinWidth;
797 
798  for (unsigned int pos = col; maxw > 0 && pos < lastCol; pos++) {
799  if (layoutStruct[pos].effWidth.isPercent() && layoutStruct[pos].effWidth.isPositive() && fixedWidth <= cMinWidth) {
800  int w = layoutStruct[pos].effMinWidth;
801  w = qMax(w, cminw * layoutStruct[pos].effWidth.rawValue() / totalPercent);
802  w = qMin(layoutStruct[pos].effMinWidth + (cMinWidth - minw), w);
803 #ifdef DEBUG_LAYOUT
804  qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w);
805 #endif
806  maxw -= layoutStruct[pos].effMaxWidth;
807  minw -= layoutStruct[pos].effMinWidth;
808  cMinWidth -= w;
809  layoutStruct[pos].effMinWidth = w;
810  }
811  }
812  } else {
813 #ifdef DEBUG_LAYOUT
814  qDebug("extending minWidth of cols %d-%d to %dpx currentMin=%d", col, lastCol - 1, cMinWidth, minWidth);
815 #endif
816  int maxw = maxWidth;
817  int minw = minWidth;
818 
819  // Give min to variable first, to fixed second, and to others third.
820  for (unsigned int pos = col; maxw > 0 && pos < lastCol; pos++) {
821  if (layoutStruct[pos].width.isFixed() && haveAuto && fixedWidth <= cMinWidth) {
822  int w = qMax(int(layoutStruct[pos].effMinWidth), layoutStruct[pos].width.value());
823  fixedWidth -= layoutStruct[pos].width.value();
824  minw -= layoutStruct[pos].effMinWidth;
825 #ifdef DEBUG_LAYOUT
826  qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w);
827 #endif
828  maxw -= layoutStruct[pos].effMaxWidth;
829  cMinWidth -= w;
830  layoutStruct[pos].effMinWidth = w;
831  }
832  }
833 
834  for (unsigned int pos = col; maxw > 0 && pos < lastCol && minw < cMinWidth; pos++) {
835  if (!(layoutStruct[pos].width.isFixed() && haveAuto && fixedWidth <= cMinWidth)) {
836  int w = qMax(int(layoutStruct[pos].effMinWidth), cMinWidth * layoutStruct[pos].effMaxWidth / maxw);
837  w = qMin(layoutStruct[pos].effMinWidth + (cMinWidth - minw), w);
838 
839 #ifdef DEBUG_LAYOUT
840  qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w);
841 #endif
842  maxw -= layoutStruct[pos].effMaxWidth;
843  minw -= layoutStruct[pos].effMinWidth;
844  cMinWidth -= w;
845  layoutStruct[pos].effMinWidth = w;
846  }
847  }
848  }
849  }
850  if (!w.isPercent()) {
851  if (cMaxWidth > maxWidth) {
852 #ifdef DEBUG_LAYOUT
853  qDebug("extending maxWidth of cols %d-%d to %dpx", col, lastCol - 1, cMaxWidth);
854 #endif
855  for (unsigned int pos = col; maxWidth > 0 && pos < lastCol; pos++) {
856  int w = qMax(int(layoutStruct[pos].effMaxWidth), cMaxWidth * layoutStruct[pos].effMaxWidth / maxWidth);
857 #ifdef DEBUG_LAYOUT
858  qDebug(" col %d: max=%d, effMax=%d, new=%d", pos, layoutStruct[pos].effMaxWidth, layoutStruct[pos].effMaxWidth, w);
859 #endif
860  maxWidth -= layoutStruct[pos].effMaxWidth;
861  cMaxWidth -= w;
862  layoutStruct[pos].effMaxWidth = w;
863  }
864  }
865  } else {
866  for (unsigned int pos = col; pos < lastCol; pos++) {
867  layoutStruct[pos].maxWidth = qMax(layoutStruct[pos].maxWidth, int(layoutStruct[pos].minWidth));
868  }
869  }
870  // treat span ranges consisting of empty cells only as if they had content
871  if (spanHasEmptyCellsOnly)
872  for (unsigned int pos = col; pos < lastCol; pos++) {
873  layoutStruct[pos].emptyCellsOnly = false;
874  }
875  }
876  effWidthDirty = false;
877 
878 // qDebug("calcEffectiveWidth: tMaxWidth=%d", tMaxWidth );
879  return tMaxWidth;
880 }
881 
882 /* gets all cells that originate in a column and have a cellspan > 1
883  Sorts them by increasing cellspan
884 */
885 void AutoTableLayout::insertSpanCell(RenderTableCell *cell)
886 {
887  if (!cell || cell == (RenderTableCell *) - 1 || cell->colSpan() == 1) {
888  return;
889  }
890 
891 // qDebug("inserting span cell %p with span %d", cell, cell->colSpan() );
892  int size = spanCells.size();
893  if (!size || spanCells[size - 1] != nullptr) {
894  spanCells.resize(size + 10);
895  for (int i = 0; i < 10; i++) {
896  spanCells[size + i] = nullptr;
897  }
898  size += 10;
899  }
900 
901  // add them in sort. This is a slow algorithm, and a binary search or a fast sorting after collection would be better
902  int pos = 0;
903  int span = cell->colSpan();
904  while (pos < spanCells.size() && spanCells[pos] && span > spanCells[pos]->colSpan()) {
905  pos++;
906  }
907  memmove(spanCells.data() + pos + 1, spanCells.data() + pos, (size - pos - 1)*sizeof(RenderTableCell *));
908  spanCells[pos] = cell;
909 }
910 
911 void AutoTableLayout::layout()
912 {
913  // table layout based on the values collected in the layout structure.
914  int tableWidth = table->width() - table->bordersPaddingAndSpacing();
915  int available = tableWidth;
916  int nEffCols = table->numEffCols();
917 
918  if (nEffCols != layoutStruct.size()) {
919  qWarning("WARNING: nEffCols is not equal to layoutstruct!");
920  fullRecalc();
921  nEffCols = table->numEffCols();
922  }
923 #ifdef DEBUG_LAYOUT
924  qDebug("AutoTableLayout::layout()");
925 #endif
926 
927  if (effWidthDirty) {
928  calcEffectiveWidth();
929  }
930 
931 #ifdef DEBUG_LAYOUT
932  qDebug(" tableWidth=%d, nEffCols=%d", tableWidth, nEffCols);
933  for (int i = 0; i < nEffCols; i++) {
934  qDebug(" effcol %d is of type %d value %d, minWidth=%d, maxWidth=%d",
935  i, layoutStruct[i].width.type(), layoutStruct[i].width.rawValue(),
936  layoutStruct[i].minWidth, layoutStruct[i].maxWidth);
937  qDebug(" effective: type %d value %d, minWidth=%d, maxWidth=%d",
938  layoutStruct[i].effWidth.type(), layoutStruct[i].effWidth.rawValue(),
939  layoutStruct[i].effMinWidth, layoutStruct[i].effMaxWidth);
940  }
941 #endif
942 
943  bool havePercent = false;
944  bool haveRelative = false;
945  int totalRelative = 0;
946  int numAuto = 0;
947  int numFixed = 0;
948  int totalAuto = 0;
949  int totalFixed = 0;
950  int totalPercent = 0;
951  int allocAuto = 0;
952  int numAutoEmptyCellsOnly = 0;
953 
954  // fill up every cell with it's minWidth
955  for (int i = 0; i < nEffCols; i++) {
956  int w = layoutStruct[i].effMinWidth;
957  layoutStruct[i].calcWidth = w;
958  available -= w;
959  Length &width = layoutStruct[i].effWidth;
960  switch (width.type()) {
961  case Percent:
962  havePercent = true;
963  totalPercent += width.rawValue();
964  break;
965  case Relative:
966  haveRelative = true;
967  totalRelative += width.value();
968  break;
969  case Fixed:
970  numFixed++;
971  totalFixed += layoutStruct[i].effMaxWidth;
972  // fall through
973  break;
974  case Auto:
975  case Static:
976  if (layoutStruct[i].emptyCellsOnly) {
977  numAutoEmptyCellsOnly++;
978  } else {
979  numAuto++;
980  totalAuto += layoutStruct[i].effMaxWidth;
981  allocAuto += w;
982  }
983  break;
984  }
985  }
986 
987  // allocate width to percent cols
988  if (available > 0 && havePercent) {
989  for (int i = 0; i < nEffCols; i++) {
990  const Length &width = layoutStruct[i].effWidth;
991  if (width.isPercent()) {
992  int w = qMax(int(layoutStruct[i].effMinWidth), width.minWidth(tableWidth));
993  available += layoutStruct[i].calcWidth - w;
994  layoutStruct[i].calcWidth = w;
995  }
996  }
997  if (totalPercent > 100 * PERCENT_SCALE_FACTOR) {
998  // remove overallocated space from the last columns
999  int excess = tableWidth * (totalPercent - (100 * PERCENT_SCALE_FACTOR)) / (100 * PERCENT_SCALE_FACTOR);
1000  for (int i = nEffCols - 1; i >= 0; i--) {
1001  if (layoutStruct[i].effWidth.isPercent()) {
1002  int w = layoutStruct[i].calcWidth;
1003  int reduction = qMin(w, excess);
1004  // the lines below might look inconsistent, but that's the way it's handled in mozilla
1005  excess -= reduction;
1006  int newWidth = qMax(int (layoutStruct[i].effMinWidth), w - reduction);
1007  available += w - newWidth;
1008  layoutStruct[i].calcWidth = newWidth;
1009  //qDebug("col %d: reducing to %d px (reduction=%d)", i, newWidth, reduction );
1010  }
1011  }
1012  }
1013  }
1014 #ifdef DEBUG_LAYOUT
1015  qDebug("percent satisfied: available is %d", available);
1016 #endif
1017 
1018  // then allocate width to fixed cols
1019  if (available > 0 && numFixed) {
1020  for (int i = 0; i < nEffCols; ++i) {
1021  const Length &width = layoutStruct[i].effWidth;
1022  if (width.isFixed() && width.value() > layoutStruct[i].calcWidth) {
1023  available += layoutStruct[i].calcWidth - width.value();
1024  layoutStruct[i].calcWidth = width.value();
1025  }
1026  }
1027  }
1028 #ifdef DEBUG_LAYOUT
1029  qDebug("fixed satisfied: available is %d", available);
1030 #endif
1031 
1032  // now satisfy relative
1033  if (available > 0 && haveRelative) {
1034  for (int i = 0; i < nEffCols; i++) {
1035  const Length &width = layoutStruct[i].effWidth;
1036  if (width.isRelative() && width.value()) {
1037  // width=0* gets effMinWidth.
1038  int w = width.value() * tableWidth / totalRelative;
1039  available += layoutStruct[i].calcWidth - w;
1040  layoutStruct[i].calcWidth = w;
1041  }
1042  }
1043  }
1044 
1045  // now satisfy variable
1046  if (available > 0 && numAuto) {
1047  available += allocAuto; // this gets redistributed
1048  //qDebug("redistributing %dpx to %d variable columns. totalAuto=%d", available, numAuto, totalAuto );
1049  for (int i = 0; i < nEffCols; i++) {
1050  const Length &width = layoutStruct[i].effWidth;
1051  if (width.isAuto() && totalAuto != 0 && !layoutStruct[i].emptyCellsOnly) {
1052  int w = qMax(int (layoutStruct[i].calcWidth),
1053  available * layoutStruct[i].effMaxWidth / totalAuto);
1054  available -= w;
1055  totalAuto -= layoutStruct[i].effMaxWidth;
1056  layoutStruct[i].calcWidth = w;
1057  }
1058  }
1059  }
1060 #ifdef DEBUG_LAYOUT
1061  qDebug("variable satisfied: available is %d", available);
1062 #endif
1063 
1064  // spread over fixed columns
1065  if (available > 0 && numFixed) {
1066  // still have some width to spread, distribute to fixed columns
1067  for (int i = 0; i < nEffCols; i++) {
1068  const Length &width = layoutStruct[i].effWidth;
1069  if (width.isFixed()) {
1070  int w = available * layoutStruct[i].effMaxWidth / totalFixed;
1071  available -= w;
1072  totalFixed -= layoutStruct[i].effMaxWidth;
1073  layoutStruct[i].calcWidth += w;
1074  }
1075  }
1076  }
1077 
1078 #ifdef DEBUG_LAYOUT
1079  qDebug("after fixed distribution: available=%d", available);
1080 #endif
1081 
1082  // spread over percent columns
1083  if (available > 0 && hasPercent && totalPercent < 100 * PERCENT_SCALE_FACTOR) {
1084  // still have some width to spread, distribute weighted to percent columns
1085  for (int i = 0; i < nEffCols; i++) {
1086  const Length &width = layoutStruct[i].effWidth;
1087  if (width.isPercent()) {
1088  int w = available * width.rawValue() / totalPercent;
1089  available -= w;
1090  totalPercent -= width.rawValue();
1091  layoutStruct[i].calcWidth += w;
1092  if (!available || !totalPercent) {
1093  break;
1094  }
1095  }
1096  }
1097  }
1098 
1099 #ifdef DEBUG_LAYOUT
1100  qDebug("after percent distribution: available=%d", available);
1101 #endif
1102 
1103  // spread over the rest
1104  if (available > 0 && nEffCols > numAutoEmptyCellsOnly) {
1105  int total = nEffCols;
1106  // still have some width to spread
1107  int i = nEffCols;
1108  while (i--) {
1109  // variable columns with empty cells only don't get any width
1110  if (layoutStruct[i].width.isAuto() && layoutStruct[i].emptyCellsOnly) {
1111  continue;
1112  }
1113  int w = available / total;
1114  available -= w;
1115  total--;
1116  layoutStruct[i].calcWidth += w;
1117  }
1118  }
1119 
1120 #ifdef DEBUG_LAYOUT
1121  qDebug("after equal distribution: available=%d", available);
1122 #endif
1123  // if we have overallocated, reduce every cell according to the difference between desired width and minwidth
1124  // this seems to produce to the pixel exaxt results with IE. Wonder is some of this also holds for width distributing.
1125  if (available < 0) {
1126  // Need to reduce cells with the following prioritization:
1127  // (1) Auto
1128  // (2) Relative
1129  // (3) Fixed
1130  // (4) Percent
1131  // This is basically the reverse of how we grew the cells.
1132  if (available < 0) {
1133  int mw = 0;
1134  for (int i = nEffCols - 1; i >= 0; i--) {
1135  Length &width = layoutStruct[i].effWidth;
1136  if (width.isAuto()) {
1137  mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1138  }
1139  }
1140 
1141  for (int i = nEffCols - 1; i >= 0 && mw > 0; i--) {
1142  Length &width = layoutStruct[i].effWidth;
1143  if (width.isAuto()) {
1144  int minMaxDiff = layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1145  int reduce = available * minMaxDiff / mw;
1146  layoutStruct[i].calcWidth += reduce;
1147  available -= reduce;
1148  mw -= minMaxDiff;
1149  if (available >= 0) {
1150  break;
1151  }
1152  }
1153  }
1154  }
1155 
1156  if (available < 0 && haveRelative) {
1157  int mw = 0;
1158  for (int i = nEffCols - 1; i >= 0; i--) {
1159  Length &width = layoutStruct[i].effWidth;
1160  if (width.isRelative()) {
1161  mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1162  }
1163  }
1164 
1165  for (int i = nEffCols - 1; i >= 0 && mw > 0; i--) {
1166  Length &width = layoutStruct[i].effWidth;
1167  if (width.isRelative()) {
1168  int minMaxDiff = layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1169  int reduce = available * minMaxDiff / mw;
1170  layoutStruct[i].calcWidth += reduce;
1171  available -= reduce;
1172  mw -= minMaxDiff;
1173  if (available >= 0) {
1174  break;
1175  }
1176  }
1177  }
1178  }
1179 
1180  if (available < 0 && numFixed) {
1181  int mw = 0;
1182  for (int i = nEffCols - 1; i >= 0; i--) {
1183  Length &width = layoutStruct[i].effWidth;
1184  if (width.isFixed()) {
1185  mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1186  }
1187  }
1188 
1189  for (int i = nEffCols - 1; i >= 0 && mw > 0; i--) {
1190  Length &width = layoutStruct[i].effWidth;
1191  if (width.isFixed()) {
1192  int minMaxDiff = layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1193  int reduce = available * minMaxDiff / mw;
1194  layoutStruct[i].calcWidth += reduce;
1195  available -= reduce;
1196  mw -= minMaxDiff;
1197  if (available >= 0) {
1198  break;
1199  }
1200  }
1201  }
1202  }
1203 
1204  if (available < 0 && havePercent) {
1205  int mw = 0;
1206  for (int i = nEffCols - 1; i >= 0; i--) {
1207  Length &width = layoutStruct[i].effWidth;
1208  if (width.isPercent()) {
1209  mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1210  }
1211  }
1212 
1213  for (int i = nEffCols - 1; i >= 0 && mw > 0; i--) {
1214  Length &width = layoutStruct[i].effWidth;
1215  if (width.isPercent()) {
1216  int minMaxDiff = layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth;
1217  int reduce = available * minMaxDiff / mw;
1218  layoutStruct[i].calcWidth += reduce;
1219  available -= reduce;
1220  mw -= minMaxDiff;
1221  if (available >= 0) {
1222  break;
1223  }
1224  }
1225  }
1226  }
1227  }
1228 
1229  //qDebug( " final available=%d", available );
1230 
1231  int pos = 0;
1232  for (int i = 0; i < nEffCols; i++) {
1233 #ifdef DEBUG_LAYOUT
1234  qDebug("col %d: %d (width %d)", i, pos, layoutStruct[i].calcWidth);
1235 #endif
1236  table->columnPos[i] = pos;
1237  pos += layoutStruct[i].calcWidth + table->borderHSpacing();
1238  }
1239  table->columnPos[table->columnPos.size() - 1] = pos;
1240 
1241 }
1242 
1243 #undef DEBUG_LAYOUT
This file is part of the HTML rendering engine for KDE.
QVector< T > & fill(const T &value, int size)
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
void resize(int size)
Type type(const QSqlDatabase &db)
Base Class for all rendering tree objects.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:10 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.