BasicTableLayoutStrategy.cpp 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. // vim:cindent:ts=4:et:sw=4:
  3. /* This Source Code Form is subject to the terms of the Mozilla Public
  4. * License, v. 2.0. If a copy of the MPL was not distributed with this
  5. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6. /*
  7. * Web-compatible algorithms that determine column and table widths,
  8. * used for CSS2's 'table-layout: auto'.
  9. */
  10. #include "BasicTableLayoutStrategy.h"
  11. #include <algorithm>
  12. #include "nsTableFrame.h"
  13. #include "nsTableColFrame.h"
  14. #include "nsTableCellFrame.h"
  15. #include "nsLayoutUtils.h"
  16. #include "nsGkAtoms.h"
  17. #include "SpanningCellSorter.h"
  18. #include "nsIContent.h"
  19. using namespace mozilla;
  20. using namespace mozilla::layout;
  21. namespace css = mozilla::css;
  22. #undef DEBUG_TABLE_STRATEGY
  23. BasicTableLayoutStrategy::BasicTableLayoutStrategy(nsTableFrame *aTableFrame)
  24. : nsITableLayoutStrategy(nsITableLayoutStrategy::Auto)
  25. , mTableFrame(aTableFrame)
  26. {
  27. MarkIntrinsicISizesDirty();
  28. }
  29. /* virtual */
  30. BasicTableLayoutStrategy::~BasicTableLayoutStrategy()
  31. {
  32. }
  33. /* virtual */ nscoord
  34. BasicTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext)
  35. {
  36. DISPLAY_MIN_WIDTH(mTableFrame, mMinISize);
  37. if (mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
  38. ComputeIntrinsicISizes(aRenderingContext);
  39. }
  40. return mMinISize;
  41. }
  42. /* virtual */ nscoord
  43. BasicTableLayoutStrategy::GetPrefISize(nsRenderingContext* aRenderingContext,
  44. bool aComputingSize)
  45. {
  46. DISPLAY_PREF_WIDTH(mTableFrame, mPrefISize);
  47. NS_ASSERTION((mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) ==
  48. (mPrefISizePctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
  49. "dirtyness out of sync");
  50. if (mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
  51. ComputeIntrinsicISizes(aRenderingContext);
  52. }
  53. return aComputingSize ? mPrefISizePctExpand : mPrefISize;
  54. }
  55. struct CellISizeInfo {
  56. CellISizeInfo(nscoord aMinCoord, nscoord aPrefCoord,
  57. float aPrefPercent, bool aHasSpecifiedISize)
  58. : hasSpecifiedISize(aHasSpecifiedISize)
  59. , minCoord(aMinCoord)
  60. , prefCoord(aPrefCoord)
  61. , prefPercent(aPrefPercent)
  62. {
  63. }
  64. bool hasSpecifiedISize;
  65. nscoord minCoord;
  66. nscoord prefCoord;
  67. float prefPercent;
  68. };
  69. // Used for both column and cell calculations. The parts needed only
  70. // for cells are skipped when aIsCell is false.
  71. static CellISizeInfo
  72. GetISizeInfo(nsRenderingContext *aRenderingContext,
  73. nsIFrame *aFrame, WritingMode aWM, bool aIsCell)
  74. {
  75. nscoord minCoord, prefCoord;
  76. const nsStylePosition *stylePos = aFrame->StylePosition();
  77. bool isQuirks = aFrame->PresContext()->CompatibilityMode() ==
  78. eCompatibility_NavQuirks;
  79. nscoord boxSizingToBorderEdge = 0;
  80. if (aIsCell) {
  81. // If aFrame is a container for font size inflation, then shrink
  82. // wrapping inside of it should not apply font size inflation.
  83. AutoMaybeDisableFontInflation an(aFrame);
  84. minCoord = aFrame->GetMinISize(aRenderingContext);
  85. prefCoord = aFrame->GetPrefISize(aRenderingContext);
  86. // Until almost the end of this function, minCoord and prefCoord
  87. // represent the box-sizing based isize values (which mean they
  88. // should include inline padding and border width when
  89. // box-sizing is set to border-box).
  90. // Note that this function returns border-box isize, we add the
  91. // outer edges near the end of this function.
  92. // XXX Should we ignore percentage padding?
  93. nsIFrame::IntrinsicISizeOffsetData offsets =
  94. aFrame->IntrinsicISizeOffsets();
  95. // In quirks mode, table cell isize should be content-box,
  96. // but bsize should be border box.
  97. // Because of this historic anomaly, we do not use quirk.css.
  98. // (We can't specify one value of box-sizing for isize and another
  99. // for bsize).
  100. // For this reason, we also do not use box-sizing for just one of
  101. // them, as this may be confusing.
  102. if (isQuirks || stylePos->mBoxSizing == StyleBoxSizing::Content) {
  103. boxSizingToBorderEdge = offsets.hPadding + offsets.hBorder;
  104. }
  105. else {
  106. // StyleBoxSizing::Border and standards-mode
  107. minCoord += offsets.hPadding + offsets.hBorder;
  108. prefCoord += offsets.hPadding + offsets.hBorder;
  109. }
  110. } else {
  111. minCoord = 0;
  112. prefCoord = 0;
  113. }
  114. float prefPercent = 0.0f;
  115. bool hasSpecifiedISize = false;
  116. const nsStyleCoord& iSize = stylePos->ISize(aWM);
  117. nsStyleUnit unit = iSize.GetUnit();
  118. // NOTE: We're ignoring calc() units with percentages here, for lack of a
  119. // sensible idea for what to do with them. This means calc() with
  120. // percentages is basically handled like 'auto' for table cells and
  121. // columns.
  122. if (iSize.ConvertsToLength()) {
  123. hasSpecifiedISize = true;
  124. // Note: since ComputeISizeValue was designed to return content-box
  125. // isize, it will (in some cases) subtract the box-sizing edges.
  126. // We prevent this unwanted behavior by calling it with
  127. // aContentEdgeToBoxSizing and aBoxSizingToMarginEdge set to 0.
  128. nscoord c = aFrame->ComputeISizeValue(aRenderingContext, 0, 0, 0, iSize);
  129. // Quirk: A cell with "nowrap" set and a coord value for the
  130. // isize which is bigger than the intrinsic minimum isize uses
  131. // that coord value as the minimum isize.
  132. // This is kept up-to-date with dynamic changes to nowrap by code in
  133. // nsTableCellFrame::AttributeChanged
  134. if (aIsCell && c > minCoord && isQuirks &&
  135. aFrame->GetContent()->HasAttr(kNameSpaceID_None,
  136. nsGkAtoms::nowrap)) {
  137. minCoord = c;
  138. }
  139. prefCoord = std::max(c, minCoord);
  140. } else if (unit == eStyleUnit_Percent) {
  141. prefPercent = iSize.GetPercentValue();
  142. } else if (unit == eStyleUnit_Enumerated && aIsCell) {
  143. switch (iSize.GetIntValue()) {
  144. case NS_STYLE_WIDTH_MAX_CONTENT:
  145. // 'inline-size' only affects pref isize, not min
  146. // isize, so don't change anything
  147. break;
  148. case NS_STYLE_WIDTH_MIN_CONTENT:
  149. prefCoord = minCoord;
  150. break;
  151. case NS_STYLE_WIDTH_FIT_CONTENT:
  152. case NS_STYLE_WIDTH_AVAILABLE:
  153. // act just like 'inline-size: auto'
  154. break;
  155. default:
  156. NS_NOTREACHED("unexpected enumerated value");
  157. }
  158. }
  159. nsStyleCoord maxISize(stylePos->MaxISize(aWM));
  160. if (maxISize.GetUnit() == eStyleUnit_Enumerated) {
  161. if (!aIsCell || maxISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) {
  162. maxISize.SetNoneValue();
  163. } else if (maxISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) {
  164. // for 'max-inline-size', '-moz-fit-content' is like
  165. // '-moz-max-content'
  166. maxISize.SetIntValue(NS_STYLE_WIDTH_MAX_CONTENT,
  167. eStyleUnit_Enumerated);
  168. }
  169. }
  170. unit = maxISize.GetUnit();
  171. // XXX To really implement 'max-inline-size' well, we'd need to store
  172. // it separately on the columns.
  173. if (maxISize.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
  174. nscoord c = aFrame->ComputeISizeValue(aRenderingContext,
  175. 0, 0, 0, maxISize);
  176. minCoord = std::min(c, minCoord);
  177. prefCoord = std::min(c, prefCoord);
  178. } else if (unit == eStyleUnit_Percent) {
  179. float p = stylePos->MaxISize(aWM).GetPercentValue();
  180. if (p < prefPercent) {
  181. prefPercent = p;
  182. }
  183. }
  184. // treat calc() with percentages on max-inline-size just like 'none'.
  185. nsStyleCoord minISize(stylePos->MinISize(aWM));
  186. if (minISize.GetUnit() == eStyleUnit_Enumerated) {
  187. if (!aIsCell || minISize.GetIntValue() == NS_STYLE_WIDTH_AVAILABLE) {
  188. minISize.SetCoordValue(0);
  189. } else if (minISize.GetIntValue() == NS_STYLE_WIDTH_FIT_CONTENT) {
  190. // for 'min-inline-size', '-moz-fit-content' is like
  191. // '-moz-min-content'
  192. minISize.SetIntValue(NS_STYLE_WIDTH_MIN_CONTENT,
  193. eStyleUnit_Enumerated);
  194. }
  195. }
  196. unit = minISize.GetUnit();
  197. if (minISize.ConvertsToLength() || unit == eStyleUnit_Enumerated) {
  198. nscoord c = aFrame->ComputeISizeValue(aRenderingContext,
  199. 0, 0, 0, minISize);
  200. minCoord = std::max(c, minCoord);
  201. prefCoord = std::max(c, prefCoord);
  202. } else if (unit == eStyleUnit_Percent) {
  203. float p = stylePos->MinISize(aWM).GetPercentValue();
  204. if (p > prefPercent) {
  205. prefPercent = p;
  206. }
  207. }
  208. // treat calc() with percentages on min-inline-size just like '0'.
  209. // XXX Should col frame have border/padding considered?
  210. if (aIsCell) {
  211. minCoord += boxSizingToBorderEdge;
  212. prefCoord = NSCoordSaturatingAdd(prefCoord, boxSizingToBorderEdge);
  213. }
  214. return CellISizeInfo(minCoord, prefCoord, prefPercent, hasSpecifiedISize);
  215. }
  216. static inline CellISizeInfo
  217. GetCellISizeInfo(nsRenderingContext *aRenderingContext,
  218. nsTableCellFrame *aCellFrame, WritingMode aWM)
  219. {
  220. return GetISizeInfo(aRenderingContext, aCellFrame, aWM, true);
  221. }
  222. static inline CellISizeInfo
  223. GetColISizeInfo(nsRenderingContext *aRenderingContext,
  224. nsIFrame *aFrame, WritingMode aWM)
  225. {
  226. return GetISizeInfo(aRenderingContext, aFrame, aWM, false);
  227. }
  228. /**
  229. * The algorithm in this function, in addition to meeting the
  230. * requirements of Web-compatibility, is also invariant under reordering
  231. * of the rows within a table (something that most, but not all, other
  232. * browsers are).
  233. */
  234. void
  235. BasicTableLayoutStrategy::ComputeColumnIntrinsicISizes(nsRenderingContext* aRenderingContext)
  236. {
  237. nsTableFrame *tableFrame = mTableFrame;
  238. nsTableCellMap *cellMap = tableFrame->GetCellMap();
  239. WritingMode wm = tableFrame->GetWritingMode();
  240. mozilla::AutoStackArena arena;
  241. SpanningCellSorter spanningCells;
  242. // Loop over the columns to consider the columns and cells *without*
  243. // a colspan.
  244. int32_t col, col_end;
  245. for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
  246. nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
  247. if (!colFrame) {
  248. NS_ERROR("column frames out of sync with cell map");
  249. continue;
  250. }
  251. colFrame->ResetIntrinsics();
  252. colFrame->ResetSpanIntrinsics();
  253. // Consider the isizes on the column.
  254. CellISizeInfo colInfo = GetColISizeInfo(aRenderingContext,
  255. colFrame, wm);
  256. colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
  257. colInfo.hasSpecifiedISize);
  258. colFrame->AddPrefPercent(colInfo.prefPercent);
  259. // Consider the isizes on the column-group. Note that we follow
  260. // what the HTML spec says here, and make the isize apply to
  261. // each column in the group, not the group as a whole.
  262. // If column has isize, column-group doesn't override isize.
  263. if (colInfo.minCoord == 0 && colInfo.prefCoord == 0 &&
  264. colInfo.prefPercent == 0.0f) {
  265. NS_ASSERTION(colFrame->GetParent()->GetType() ==
  266. nsGkAtoms::tableColGroupFrame,
  267. "expected a column-group");
  268. colInfo = GetColISizeInfo(aRenderingContext,
  269. colFrame->GetParent(), wm);
  270. colFrame->AddCoords(colInfo.minCoord, colInfo.prefCoord,
  271. colInfo.hasSpecifiedISize);
  272. colFrame->AddPrefPercent(colInfo.prefPercent);
  273. }
  274. // Consider the contents of and the isizes on the cells without
  275. // colspans.
  276. nsCellMapColumnIterator columnIter(cellMap, col);
  277. int32_t row, colSpan;
  278. nsTableCellFrame* cellFrame;
  279. while ((cellFrame = columnIter.GetNextFrame(&row, &colSpan))) {
  280. if (colSpan > 1) {
  281. spanningCells.AddCell(colSpan, row, col);
  282. continue;
  283. }
  284. CellISizeInfo info = GetCellISizeInfo(aRenderingContext,
  285. cellFrame, wm);
  286. colFrame->AddCoords(info.minCoord, info.prefCoord,
  287. info.hasSpecifiedISize);
  288. colFrame->AddPrefPercent(info.prefPercent);
  289. }
  290. #ifdef DEBUG_dbaron_off
  291. printf("table %p col %d nonspan: min=%d pref=%d spec=%d pct=%f\n",
  292. mTableFrame, col, colFrame->GetMinCoord(),
  293. colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
  294. colFrame->GetPrefPercent());
  295. #endif
  296. }
  297. #ifdef DEBUG_TABLE_STRATEGY
  298. printf("ComputeColumnIntrinsicISizes single\n");
  299. mTableFrame->Dump(false, true, false);
  300. #endif
  301. // Consider the cells with a colspan that we saved in the loop above
  302. // into the spanning cell sorter. We consider these cells by seeing
  303. // if they require adding to the isizes resulting only from cells
  304. // with a smaller colspan, and therefore we must process them sorted
  305. // in increasing order by colspan. For each colspan group, we
  306. // accumulate new values to accumulate in the column frame's Span*
  307. // members.
  308. //
  309. // Considering things only relative to the isizes resulting from
  310. // cells with smaller colspans (rather than incrementally including
  311. // the results from spanning cells, or doing spanning and
  312. // non-spanning cells in a single pass) means that layout remains
  313. // row-order-invariant and (except for percentage isizes that add to
  314. // more than 100%) column-order invariant.
  315. //
  316. // Starting with smaller colspans makes it more likely that we
  317. // satisfy all the constraints given and don't distribute space to
  318. // columns where we don't need it.
  319. SpanningCellSorter::Item *item;
  320. int32_t colSpan;
  321. while ((item = spanningCells.GetNext(&colSpan))) {
  322. NS_ASSERTION(colSpan > 1,
  323. "cell should not have been put in spanning cell sorter");
  324. do {
  325. int32_t row = item->row;
  326. col = item->col;
  327. CellData *cellData = cellMap->GetDataAt(row, col);
  328. NS_ASSERTION(cellData && cellData->IsOrig(),
  329. "bogus result from spanning cell sorter");
  330. nsTableCellFrame *cellFrame = cellData->GetCellFrame();
  331. NS_ASSERTION(cellFrame, "bogus result from spanning cell sorter");
  332. CellISizeInfo info =
  333. GetCellISizeInfo(aRenderingContext, cellFrame, wm);
  334. if (info.prefPercent > 0.0f) {
  335. DistributePctISizeToColumns(info.prefPercent,
  336. col, colSpan);
  337. }
  338. DistributeISizeToColumns(info.minCoord, col, colSpan,
  339. BTLS_MIN_ISIZE, info.hasSpecifiedISize);
  340. DistributeISizeToColumns(info.prefCoord, col, colSpan,
  341. BTLS_PREF_ISIZE, info.hasSpecifiedISize);
  342. } while ((item = item->next));
  343. // Combine the results of the span analysis into the main results,
  344. // for each increment of colspan.
  345. for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
  346. nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
  347. if (!colFrame) {
  348. NS_ERROR("column frames out of sync with cell map");
  349. continue;
  350. }
  351. colFrame->AccumulateSpanIntrinsics();
  352. colFrame->ResetSpanIntrinsics();
  353. #ifdef DEBUG_dbaron_off
  354. printf("table %p col %d span %d: min=%d pref=%d spec=%d pct=%f\n",
  355. mTableFrame, col, colSpan, colFrame->GetMinCoord(),
  356. colFrame->GetPrefCoord(), colFrame->GetHasSpecifiedCoord(),
  357. colFrame->GetPrefPercent());
  358. #endif
  359. }
  360. }
  361. // Prevent percentages from adding to more than 100% by (to be
  362. // compatible with other browsers) treating any percentages that would
  363. // increase the total percentage to more than 100% as the number that
  364. // would increase it to only 100% (which is 0% if we've already hit
  365. // 100%). This means layout depends on the order of columns.
  366. float pct_used = 0.0f;
  367. for (col = 0, col_end = cellMap->GetColCount(); col < col_end; ++col) {
  368. nsTableColFrame *colFrame = tableFrame->GetColFrame(col);
  369. if (!colFrame) {
  370. NS_ERROR("column frames out of sync with cell map");
  371. continue;
  372. }
  373. colFrame->AdjustPrefPercent(&pct_used);
  374. }
  375. #ifdef DEBUG_TABLE_STRATEGY
  376. printf("ComputeColumnIntrinsicISizes spanning\n");
  377. mTableFrame->Dump(false, true, false);
  378. #endif
  379. }
  380. void
  381. BasicTableLayoutStrategy::ComputeIntrinsicISizes(nsRenderingContext* aRenderingContext)
  382. {
  383. ComputeColumnIntrinsicISizes(aRenderingContext);
  384. nsTableCellMap *cellMap = mTableFrame->GetCellMap();
  385. nscoord min = 0, pref = 0, max_small_pct_pref = 0, nonpct_pref_total = 0;
  386. float pct_total = 0.0f; // always from 0.0f - 1.0f
  387. int32_t colCount = cellMap->GetColCount();
  388. // add a total of (colcount + 1) lots of cellSpacingX for columns where a
  389. // cell originates
  390. nscoord add = mTableFrame->GetColSpacing(colCount);
  391. for (int32_t col = 0; col < colCount; ++col) {
  392. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  393. if (!colFrame) {
  394. NS_ERROR("column frames out of sync with cell map");
  395. continue;
  396. }
  397. if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
  398. add += mTableFrame->GetColSpacing(col - 1);
  399. }
  400. min += colFrame->GetMinCoord();
  401. pref = NSCoordSaturatingAdd(pref, colFrame->GetPrefCoord());
  402. // Percentages are of the table, so we have to reverse them for
  403. // intrinsic isizes.
  404. float p = colFrame->GetPrefPercent();
  405. if (p > 0.0f) {
  406. nscoord colPref = colFrame->GetPrefCoord();
  407. nscoord new_small_pct_expand =
  408. (colPref == nscoord_MAX ?
  409. nscoord_MAX : nscoord(float(colPref) / p));
  410. if (new_small_pct_expand > max_small_pct_pref) {
  411. max_small_pct_pref = new_small_pct_expand;
  412. }
  413. pct_total += p;
  414. } else {
  415. nonpct_pref_total = NSCoordSaturatingAdd(nonpct_pref_total,
  416. colFrame->GetPrefCoord());
  417. }
  418. }
  419. nscoord pref_pct_expand = pref;
  420. // Account for small percentages expanding the preferred isize of
  421. // *other* columns.
  422. if (max_small_pct_pref > pref_pct_expand) {
  423. pref_pct_expand = max_small_pct_pref;
  424. }
  425. // Account for large percentages expanding the preferred isize of
  426. // themselves. There's no need to iterate over the columns multiple
  427. // times, since when there is such a need, the small percentage
  428. // effect is bigger anyway. (I think!)
  429. NS_ASSERTION(0.0f <= pct_total && pct_total <= 1.0f,
  430. "column percentage inline-sizes not adjusted down to 100%");
  431. if (pct_total == 1.0f) {
  432. if (nonpct_pref_total > 0) {
  433. pref_pct_expand = nscoord_MAX;
  434. // XXX Or should I use some smaller value? (Test this using
  435. // nested tables!)
  436. }
  437. } else {
  438. nscoord large_pct_pref =
  439. (nonpct_pref_total == nscoord_MAX ?
  440. nscoord_MAX :
  441. nscoord(float(nonpct_pref_total) / (1.0f - pct_total)));
  442. if (large_pct_pref > pref_pct_expand)
  443. pref_pct_expand = large_pct_pref;
  444. }
  445. // border-spacing isn't part of the basis for percentages
  446. if (colCount > 0) {
  447. min += add;
  448. pref = NSCoordSaturatingAdd(pref, add);
  449. pref_pct_expand = NSCoordSaturatingAdd(pref_pct_expand, add);
  450. }
  451. mMinISize = min;
  452. mPrefISize = pref;
  453. mPrefISizePctExpand = pref_pct_expand;
  454. }
  455. /* virtual */ void
  456. BasicTableLayoutStrategy::MarkIntrinsicISizesDirty()
  457. {
  458. mMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
  459. mPrefISize = NS_INTRINSIC_WIDTH_UNKNOWN;
  460. mPrefISizePctExpand = NS_INTRINSIC_WIDTH_UNKNOWN;
  461. mLastCalcISize = nscoord_MIN;
  462. }
  463. /* virtual */ void
  464. BasicTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput)
  465. {
  466. nscoord iSize = aReflowInput.ComputedISize();
  467. if (mLastCalcISize == iSize) {
  468. return;
  469. }
  470. mLastCalcISize = iSize;
  471. NS_ASSERTION((mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) ==
  472. (mPrefISize == NS_INTRINSIC_WIDTH_UNKNOWN),
  473. "dirtyness out of sync");
  474. NS_ASSERTION((mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) ==
  475. (mPrefISizePctExpand == NS_INTRINSIC_WIDTH_UNKNOWN),
  476. "dirtyness out of sync");
  477. // XXX Is this needed?
  478. if (mMinISize == NS_INTRINSIC_WIDTH_UNKNOWN) {
  479. ComputeIntrinsicISizes(aReflowInput.mRenderingContext);
  480. }
  481. nsTableCellMap *cellMap = mTableFrame->GetCellMap();
  482. int32_t colCount = cellMap->GetColCount();
  483. if (colCount <= 0)
  484. return; // nothing to do
  485. DistributeISizeToColumns(iSize, 0, colCount, BTLS_FINAL_ISIZE, false);
  486. #ifdef DEBUG_TABLE_STRATEGY
  487. printf("ComputeColumnISizes final\n");
  488. mTableFrame->Dump(false, true, false);
  489. #endif
  490. }
  491. void
  492. BasicTableLayoutStrategy::DistributePctISizeToColumns(float aSpanPrefPct,
  493. int32_t aFirstCol,
  494. int32_t aColCount)
  495. {
  496. // First loop to determine:
  497. int32_t nonPctColCount = 0; // number of spanned columns without % isize
  498. nscoord nonPctTotalPrefISize = 0; // total pref isize of those columns
  499. // and to reduce aSpanPrefPct by columns that already have % isize
  500. int32_t scol, scol_end;
  501. nsTableCellMap *cellMap = mTableFrame->GetCellMap();
  502. for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
  503. scol < scol_end; ++scol) {
  504. nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
  505. if (!scolFrame) {
  506. NS_ERROR("column frames out of sync with cell map");
  507. continue;
  508. }
  509. float scolPct = scolFrame->GetPrefPercent();
  510. if (scolPct == 0.0f) {
  511. nonPctTotalPrefISize += scolFrame->GetPrefCoord();
  512. if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
  513. ++nonPctColCount;
  514. }
  515. } else {
  516. aSpanPrefPct -= scolPct;
  517. }
  518. }
  519. if (aSpanPrefPct <= 0.0f || nonPctColCount == 0) {
  520. // There's no %-isize on the colspan left over to distribute,
  521. // or there are no columns to which we could distribute %-isize
  522. return;
  523. }
  524. // Second loop, to distribute what remains of aSpanPrefPct
  525. // between the non-percent-isize spanned columns
  526. const bool spanHasNonPctPref = nonPctTotalPrefISize > 0; // Loop invariant
  527. for (scol = aFirstCol, scol_end = aFirstCol + aColCount;
  528. scol < scol_end; ++scol) {
  529. nsTableColFrame *scolFrame = mTableFrame->GetColFrame(scol);
  530. if (!scolFrame) {
  531. NS_ERROR("column frames out of sync with cell map");
  532. continue;
  533. }
  534. if (scolFrame->GetPrefPercent() == 0.0f) {
  535. NS_ASSERTION((!spanHasNonPctPref ||
  536. nonPctTotalPrefISize != 0) &&
  537. nonPctColCount != 0,
  538. "should not be zero if we haven't allocated "
  539. "all pref percent");
  540. float allocatedPct; // % isize to be given to this column
  541. if (spanHasNonPctPref) {
  542. // Group so we're multiplying by 1.0f when we need
  543. // to use up aSpanPrefPct.
  544. allocatedPct = aSpanPrefPct *
  545. (float(scolFrame->GetPrefCoord()) /
  546. float(nonPctTotalPrefISize));
  547. } else if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
  548. // distribute equally when all pref isizes are 0
  549. allocatedPct = aSpanPrefPct / float(nonPctColCount);
  550. } else {
  551. allocatedPct = 0.0f;
  552. }
  553. // Allocate the percent
  554. scolFrame->AddSpanPrefPercent(allocatedPct);
  555. // To avoid accumulating rounding error from division,
  556. // subtract this column's values from the totals.
  557. aSpanPrefPct -= allocatedPct;
  558. nonPctTotalPrefISize -= scolFrame->GetPrefCoord();
  559. if (cellMap->GetNumCellsOriginatingInCol(scol) > 0) {
  560. --nonPctColCount;
  561. }
  562. if (!aSpanPrefPct) {
  563. // No more span-percent-isize to distribute --> we're done.
  564. NS_ASSERTION(spanHasNonPctPref ?
  565. nonPctTotalPrefISize == 0 :
  566. nonPctColCount == 0,
  567. "No more pct inline-size to distribute, "
  568. "but there are still cols that need some.");
  569. return;
  570. }
  571. }
  572. }
  573. }
  574. void
  575. BasicTableLayoutStrategy::DistributeISizeToColumns(nscoord aISize,
  576. int32_t aFirstCol,
  577. int32_t aColCount,
  578. BtlsISizeType aISizeType,
  579. bool aSpanHasSpecifiedISize)
  580. {
  581. NS_ASSERTION(aISizeType != BTLS_FINAL_ISIZE ||
  582. (aFirstCol == 0 &&
  583. aColCount == mTableFrame->GetCellMap()->GetColCount()),
  584. "Computing final column isizes, but didn't get full column range");
  585. nscoord subtract = 0;
  586. // aISize initially includes border-spacing for the boundaries in between
  587. // each of the columns. We start at aFirstCol + 1 because the first
  588. // in-between boundary would be at the left edge of column aFirstCol + 1
  589. for (int32_t col = aFirstCol + 1; col < aFirstCol + aColCount; ++col) {
  590. if (mTableFrame->ColumnHasCellSpacingBefore(col)) {
  591. // border-spacing isn't part of the basis for percentages.
  592. subtract += mTableFrame->GetColSpacing(col - 1);
  593. }
  594. }
  595. if (aISizeType == BTLS_FINAL_ISIZE) {
  596. // If we're computing final col-isize, then aISize initially includes
  597. // border spacing on the table's far istart + far iend edge, too. Need
  598. // to subtract those out, too.
  599. subtract += (mTableFrame->GetColSpacing(-1) +
  600. mTableFrame->GetColSpacing(aColCount));
  601. }
  602. aISize = NSCoordSaturatingSubtract(aISize, subtract, nscoord_MAX);
  603. /*
  604. * The goal of this function is to distribute |aISize| between the
  605. * columns by making an appropriate AddSpanCoords or SetFinalISize
  606. * call for each column. (We call AddSpanCoords if we're
  607. * distributing a column-spanning cell's minimum or preferred isize
  608. * to its spanned columns. We call SetFinalISize if we're
  609. * distributing a table's final isize to its columns.)
  610. *
  611. * The idea is to either assign one of the following sets of isizes
  612. * or a weighted average of two adjacent sets of isizes. It is not
  613. * possible to assign values smaller than the smallest set of
  614. * isizes. However, see below for handling the case of assigning
  615. * values larger than the largest set of isizes. From smallest to
  616. * largest, these are:
  617. *
  618. * 1. [guess_min] Assign all columns their min isize.
  619. *
  620. * 2. [guess_min_pct] Assign all columns with percentage isizes
  621. * their percentage isize, and all other columns their min isize.
  622. *
  623. * 3. [guess_min_spec] Assign all columns with percentage isizes
  624. * their percentage isize, all columns with specified coordinate
  625. * isizes their pref isize (since it doesn't matter whether it's the
  626. * largest contributor to the pref isize that was the specified
  627. * contributor), and all other columns their min isize.
  628. *
  629. * 4. [guess_pref] Assign all columns with percentage isizes their
  630. * specified isize, and all other columns their pref isize.
  631. *
  632. * If |aISize| is *larger* than what we would assign in (4), then we
  633. * expand the columns:
  634. *
  635. * a. if any columns without a specified coordinate isize or
  636. * percent isize have nonzero pref isize, in proportion to pref
  637. * isize [total_flex_pref]
  638. *
  639. * b. otherwise, if any columns without a specified coordinate
  640. * isize or percent isize, but with cells originating in them,
  641. * have zero pref isize, equally between these
  642. * [numNonSpecZeroISizeCols]
  643. *
  644. * c. otherwise, if any columns without percent isize have nonzero
  645. * pref isize, in proportion to pref isize [total_fixed_pref]
  646. *
  647. * d. otherwise, if any columns have nonzero percentage isizes, in
  648. * proportion to the percentage isizes [total_pct]
  649. *
  650. * e. otherwise, equally.
  651. */
  652. // Loop #1 over the columns, to figure out the four values above so
  653. // we know which case we're dealing with.
  654. nscoord guess_min = 0,
  655. guess_min_pct = 0,
  656. guess_min_spec = 0,
  657. guess_pref = 0,
  658. total_flex_pref = 0,
  659. total_fixed_pref = 0;
  660. float total_pct = 0.0f; // 0.0f to 1.0f
  661. int32_t numInfiniteISizeCols = 0;
  662. int32_t numNonSpecZeroISizeCols = 0;
  663. int32_t col;
  664. nsTableCellMap *cellMap = mTableFrame->GetCellMap();
  665. for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
  666. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  667. if (!colFrame) {
  668. NS_ERROR("column frames out of sync with cell map");
  669. continue;
  670. }
  671. nscoord min_iSize = colFrame->GetMinCoord();
  672. guess_min += min_iSize;
  673. if (colFrame->GetPrefPercent() != 0.0f) {
  674. float pct = colFrame->GetPrefPercent();
  675. total_pct += pct;
  676. nscoord val = nscoord(float(aISize) * pct);
  677. if (val < min_iSize) {
  678. val = min_iSize;
  679. }
  680. guess_min_pct += val;
  681. guess_pref = NSCoordSaturatingAdd(guess_pref, val);
  682. } else {
  683. nscoord pref_iSize = colFrame->GetPrefCoord();
  684. if (pref_iSize == nscoord_MAX) {
  685. ++numInfiniteISizeCols;
  686. }
  687. guess_pref = NSCoordSaturatingAdd(guess_pref, pref_iSize);
  688. guess_min_pct += min_iSize;
  689. if (colFrame->GetHasSpecifiedCoord()) {
  690. // we'll add on the rest of guess_min_spec outside the
  691. // loop
  692. nscoord delta = NSCoordSaturatingSubtract(pref_iSize,
  693. min_iSize, 0);
  694. guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, delta);
  695. total_fixed_pref = NSCoordSaturatingAdd(total_fixed_pref,
  696. pref_iSize);
  697. } else if (pref_iSize == 0) {
  698. if (cellMap->GetNumCellsOriginatingInCol(col) > 0) {
  699. ++numNonSpecZeroISizeCols;
  700. }
  701. } else {
  702. total_flex_pref = NSCoordSaturatingAdd(total_flex_pref,
  703. pref_iSize);
  704. }
  705. }
  706. }
  707. guess_min_spec = NSCoordSaturatingAdd(guess_min_spec, guess_min_pct);
  708. // Determine what we're flexing:
  709. enum Loop2Type {
  710. FLEX_PCT_SMALL, // between (1) and (2) above
  711. FLEX_FIXED_SMALL, // between (2) and (3) above
  712. FLEX_FLEX_SMALL, // between (3) and (4) above
  713. FLEX_FLEX_LARGE, // greater than (4) above, case (a)
  714. FLEX_FLEX_LARGE_ZERO, // greater than (4) above, case (b)
  715. FLEX_FIXED_LARGE, // greater than (4) above, case (c)
  716. FLEX_PCT_LARGE, // greater than (4) above, case (d)
  717. FLEX_ALL_LARGE // greater than (4) above, case (e)
  718. };
  719. Loop2Type l2t;
  720. // These are constants (over columns) for each case's math. We use
  721. // a pair of nscoords rather than a float so that we can subtract
  722. // each column's allocation so we avoid accumulating rounding error.
  723. nscoord space; // the amount of extra isize to allocate
  724. union {
  725. nscoord c;
  726. float f;
  727. } basis; // the sum of the statistic over columns to divide it
  728. if (aISize < guess_pref) {
  729. if (aISizeType != BTLS_FINAL_ISIZE && aISize <= guess_min) {
  730. // Return early -- we don't have any extra space to distribute.
  731. return;
  732. }
  733. NS_ASSERTION(!(aISizeType == BTLS_FINAL_ISIZE && aISize < guess_min),
  734. "Table inline-size is less than the "
  735. "sum of its columns' min inline-sizes");
  736. if (aISize < guess_min_pct) {
  737. l2t = FLEX_PCT_SMALL;
  738. space = aISize - guess_min;
  739. basis.c = guess_min_pct - guess_min;
  740. } else if (aISize < guess_min_spec) {
  741. l2t = FLEX_FIXED_SMALL;
  742. space = aISize - guess_min_pct;
  743. basis.c = NSCoordSaturatingSubtract(guess_min_spec, guess_min_pct,
  744. nscoord_MAX);
  745. } else {
  746. l2t = FLEX_FLEX_SMALL;
  747. space = aISize - guess_min_spec;
  748. basis.c = NSCoordSaturatingSubtract(guess_pref, guess_min_spec,
  749. nscoord_MAX);
  750. }
  751. } else {
  752. space = NSCoordSaturatingSubtract(aISize, guess_pref, nscoord_MAX);
  753. if (total_flex_pref > 0) {
  754. l2t = FLEX_FLEX_LARGE;
  755. basis.c = total_flex_pref;
  756. } else if (numNonSpecZeroISizeCols > 0) {
  757. l2t = FLEX_FLEX_LARGE_ZERO;
  758. basis.c = numNonSpecZeroISizeCols;
  759. } else if (total_fixed_pref > 0) {
  760. l2t = FLEX_FIXED_LARGE;
  761. basis.c = total_fixed_pref;
  762. } else if (total_pct > 0.0f) {
  763. l2t = FLEX_PCT_LARGE;
  764. basis.f = total_pct;
  765. } else {
  766. l2t = FLEX_ALL_LARGE;
  767. basis.c = aColCount;
  768. }
  769. }
  770. #ifdef DEBUG_dbaron_off
  771. printf("ComputeColumnISizes: %d columns in isize %d,\n"
  772. " guesses=[%d,%d,%d,%d], totals=[%d,%d,%f],\n"
  773. " l2t=%d, space=%d, basis.c=%d\n",
  774. aColCount, aISize,
  775. guess_min, guess_min_pct, guess_min_spec, guess_pref,
  776. total_flex_pref, total_fixed_pref, total_pct,
  777. l2t, space, basis.c);
  778. #endif
  779. for (col = aFirstCol; col < aFirstCol + aColCount; ++col) {
  780. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  781. if (!colFrame) {
  782. NS_ERROR("column frames out of sync with cell map");
  783. continue;
  784. }
  785. nscoord col_iSize;
  786. float pct = colFrame->GetPrefPercent();
  787. if (pct != 0.0f) {
  788. col_iSize = nscoord(float(aISize) * pct);
  789. nscoord col_min = colFrame->GetMinCoord();
  790. if (col_iSize < col_min) {
  791. col_iSize = col_min;
  792. }
  793. } else {
  794. col_iSize = colFrame->GetPrefCoord();
  795. }
  796. nscoord col_iSize_before_adjust = col_iSize;
  797. switch (l2t) {
  798. case FLEX_PCT_SMALL:
  799. col_iSize = col_iSize_before_adjust = colFrame->GetMinCoord();
  800. if (pct != 0.0f) {
  801. nscoord pct_minus_min =
  802. nscoord(float(aISize) * pct) - col_iSize;
  803. if (pct_minus_min > 0) {
  804. float c = float(space) / float(basis.c);
  805. basis.c -= pct_minus_min;
  806. col_iSize += NSToCoordRound(float(pct_minus_min) * c);
  807. }
  808. }
  809. break;
  810. case FLEX_FIXED_SMALL:
  811. if (pct == 0.0f) {
  812. NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
  813. "wrong inline-size assigned");
  814. if (colFrame->GetHasSpecifiedCoord()) {
  815. nscoord col_min = colFrame->GetMinCoord();
  816. nscoord pref_minus_min = col_iSize - col_min;
  817. col_iSize = col_iSize_before_adjust = col_min;
  818. if (pref_minus_min != 0) {
  819. float c = float(space) / float(basis.c);
  820. basis.c -= pref_minus_min;
  821. col_iSize += NSToCoordRound(
  822. float(pref_minus_min) * c);
  823. }
  824. } else
  825. col_iSize = col_iSize_before_adjust =
  826. colFrame->GetMinCoord();
  827. }
  828. break;
  829. case FLEX_FLEX_SMALL:
  830. if (pct == 0.0f &&
  831. !colFrame->GetHasSpecifiedCoord()) {
  832. NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
  833. "wrong inline-size assigned");
  834. nscoord col_min = colFrame->GetMinCoord();
  835. nscoord pref_minus_min =
  836. NSCoordSaturatingSubtract(col_iSize, col_min, 0);
  837. col_iSize = col_iSize_before_adjust = col_min;
  838. if (pref_minus_min != 0) {
  839. float c = float(space) / float(basis.c);
  840. // If we have infinite-isize cols, then the standard
  841. // adjustment to col_iSize using 'c' won't work,
  842. // because basis.c and pref_minus_min are both
  843. // nscoord_MAX and will cancel each other out in the
  844. // col_iSize adjustment (making us assign all the
  845. // space to the first inf-isize col). To correct for
  846. // this, we'll also divide by numInfiniteISizeCols to
  847. // spread the space equally among the inf-isize cols.
  848. if (numInfiniteISizeCols) {
  849. if (colFrame->GetPrefCoord() == nscoord_MAX) {
  850. c = c / float(numInfiniteISizeCols);
  851. --numInfiniteISizeCols;
  852. } else {
  853. c = 0.0f;
  854. }
  855. }
  856. basis.c = NSCoordSaturatingSubtract(basis.c,
  857. pref_minus_min,
  858. nscoord_MAX);
  859. col_iSize += NSToCoordRound(
  860. float(pref_minus_min) * c);
  861. }
  862. }
  863. break;
  864. case FLEX_FLEX_LARGE:
  865. if (pct == 0.0f &&
  866. !colFrame->GetHasSpecifiedCoord()) {
  867. NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
  868. "wrong inline-size assigned");
  869. if (col_iSize != 0) {
  870. if (space == nscoord_MAX) {
  871. basis.c -= col_iSize;
  872. col_iSize = nscoord_MAX;
  873. } else {
  874. float c = float(space) / float(basis.c);
  875. basis.c -= col_iSize;
  876. col_iSize += NSToCoordRound(float(col_iSize) * c);
  877. }
  878. }
  879. }
  880. break;
  881. case FLEX_FLEX_LARGE_ZERO:
  882. if (pct == 0.0f &&
  883. !colFrame->GetHasSpecifiedCoord() &&
  884. cellMap->GetNumCellsOriginatingInCol(col) > 0) {
  885. NS_ASSERTION(col_iSize == 0 &&
  886. colFrame->GetPrefCoord() == 0,
  887. "Since we're in FLEX_FLEX_LARGE_ZERO case, "
  888. "all auto-inline-size cols should have zero "
  889. "pref inline-size.");
  890. float c = float(space) / float(basis.c);
  891. col_iSize += NSToCoordRound(c);
  892. --basis.c;
  893. }
  894. break;
  895. case FLEX_FIXED_LARGE:
  896. if (pct == 0.0f) {
  897. NS_ASSERTION(col_iSize == colFrame->GetPrefCoord(),
  898. "wrong inline-size assigned");
  899. NS_ASSERTION(colFrame->GetHasSpecifiedCoord() ||
  900. colFrame->GetPrefCoord() == 0,
  901. "wrong case");
  902. if (col_iSize != 0) {
  903. float c = float(space) / float(basis.c);
  904. basis.c -= col_iSize;
  905. col_iSize += NSToCoordRound(float(col_iSize) * c);
  906. }
  907. }
  908. break;
  909. case FLEX_PCT_LARGE:
  910. NS_ASSERTION(pct != 0.0f || colFrame->GetPrefCoord() == 0,
  911. "wrong case");
  912. if (pct != 0.0f) {
  913. float c = float(space) / basis.f;
  914. col_iSize += NSToCoordRound(pct * c);
  915. basis.f -= pct;
  916. }
  917. break;
  918. case FLEX_ALL_LARGE:
  919. {
  920. float c = float(space) / float(basis.c);
  921. col_iSize += NSToCoordRound(c);
  922. --basis.c;
  923. }
  924. break;
  925. }
  926. // Only subtract from space if it's a real number.
  927. if (space != nscoord_MAX) {
  928. NS_ASSERTION(col_iSize != nscoord_MAX,
  929. "How is col_iSize nscoord_MAX if space isn't?");
  930. NS_ASSERTION(col_iSize_before_adjust != nscoord_MAX,
  931. "How is col_iSize_before_adjust nscoord_MAX if space isn't?");
  932. space -= col_iSize - col_iSize_before_adjust;
  933. }
  934. NS_ASSERTION(col_iSize >= colFrame->GetMinCoord(),
  935. "assigned inline-size smaller than min");
  936. // Apply the new isize
  937. switch (aISizeType) {
  938. case BTLS_MIN_ISIZE:
  939. {
  940. // Note: AddSpanCoords requires both a min and pref isize.
  941. // For the pref isize, we'll just pass in our computed
  942. // min isize, because the real pref isize will be at least
  943. // as big
  944. colFrame->AddSpanCoords(col_iSize, col_iSize,
  945. aSpanHasSpecifiedISize);
  946. }
  947. break;
  948. case BTLS_PREF_ISIZE:
  949. {
  950. // Note: AddSpanCoords requires both a min and pref isize.
  951. // For the min isize, we'll just pass in 0, because
  952. // the real min isize will be at least 0
  953. colFrame->AddSpanCoords(0, col_iSize,
  954. aSpanHasSpecifiedISize);
  955. }
  956. break;
  957. case BTLS_FINAL_ISIZE:
  958. {
  959. nscoord old_final = colFrame->GetFinalISize();
  960. colFrame->SetFinalISize(col_iSize);
  961. if (old_final != col_iSize) {
  962. mTableFrame->DidResizeColumns();
  963. }
  964. }
  965. break;
  966. }
  967. }
  968. NS_ASSERTION((space == 0 || space == nscoord_MAX) &&
  969. ((l2t == FLEX_PCT_LARGE)
  970. ? (-0.001f < basis.f && basis.f < 0.001f)
  971. : (basis.c == 0 || basis.c == nscoord_MAX)),
  972. "didn't subtract all that we added");
  973. }