FixedTableLayoutStrategy.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. // vim:cindent:ts=2:et:sw=2:
  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. * Algorithms that determine column and table inline sizes used for
  8. * CSS2's 'table-layout: fixed'.
  9. */
  10. #include "FixedTableLayoutStrategy.h"
  11. #include "nsTableFrame.h"
  12. #include "nsTableColFrame.h"
  13. #include "nsTableCellFrame.h"
  14. #include <algorithm>
  15. #include "mozilla/WritingModes.h"
  16. using namespace mozilla;
  17. FixedTableLayoutStrategy::FixedTableLayoutStrategy(nsTableFrame *aTableFrame)
  18. : nsITableLayoutStrategy(nsITableLayoutStrategy::Fixed)
  19. , mTableFrame(aTableFrame)
  20. {
  21. MarkIntrinsicISizesDirty();
  22. }
  23. /* virtual */
  24. FixedTableLayoutStrategy::~FixedTableLayoutStrategy()
  25. {
  26. }
  27. /* virtual */ nscoord
  28. FixedTableLayoutStrategy::GetMinISize(nsRenderingContext* aRenderingContext)
  29. {
  30. DISPLAY_MIN_WIDTH(mTableFrame, mMinISize);
  31. if (mMinISize != NS_INTRINSIC_WIDTH_UNKNOWN) {
  32. return mMinISize;
  33. }
  34. // It's theoretically possible to do something much better here that
  35. // depends only on the columns and the first row (where we look at
  36. // intrinsic inline sizes inside the first row and then reverse the
  37. // algorithm to find the narrowest inline size that would hold all of
  38. // those intrinsic inline sizes), but it wouldn't be compatible with
  39. // other browsers, or with the use of GetMinISize by
  40. // nsTableFrame::ComputeSize to determine the inline size of a fixed
  41. // layout table, since CSS2.1 says:
  42. // The width of the table is then the greater of the value of the
  43. // 'width' property for the table element and the sum of the column
  44. // widths (plus cell spacing or borders).
  45. // XXX Should we really ignore 'min-width' and 'max-width'?
  46. // XXX Should we really ignore widths on column groups?
  47. nsTableCellMap *cellMap = mTableFrame->GetCellMap();
  48. int32_t colCount = cellMap->GetColCount();
  49. nscoord result = 0;
  50. if (colCount > 0) {
  51. result += mTableFrame->GetColSpacing(-1, colCount);
  52. }
  53. WritingMode wm = mTableFrame->GetWritingMode();
  54. for (int32_t col = 0; col < colCount; ++col) {
  55. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  56. if (!colFrame) {
  57. NS_ERROR("column frames out of sync with cell map");
  58. continue;
  59. }
  60. nscoord spacing = mTableFrame->GetColSpacing(col);
  61. const nsStyleCoord *styleISize = &colFrame->StylePosition()->ISize(wm);
  62. if (styleISize->ConvertsToLength()) {
  63. result += colFrame->ComputeISizeValue(aRenderingContext,
  64. 0, 0, 0, *styleISize);
  65. } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
  66. // do nothing
  67. } else {
  68. NS_ASSERTION(styleISize->GetUnit() == eStyleUnit_Auto ||
  69. styleISize->GetUnit() == eStyleUnit_Enumerated ||
  70. (styleISize->IsCalcUnit() && styleISize->CalcHasPercent()),
  71. "bad inline size");
  72. // The 'table-layout: fixed' algorithm considers only cells in the
  73. // first row.
  74. bool originates;
  75. int32_t colSpan;
  76. nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(0, col, &originates,
  77. &colSpan);
  78. if (cellFrame) {
  79. styleISize = &cellFrame->StylePosition()->ISize(wm);
  80. if (styleISize->ConvertsToLength() ||
  81. (styleISize->GetUnit() == eStyleUnit_Enumerated &&
  82. (styleISize->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
  83. styleISize->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
  84. nscoord cellISize =
  85. nsLayoutUtils::IntrinsicForContainer(aRenderingContext, cellFrame,
  86. nsLayoutUtils::MIN_ISIZE);
  87. if (colSpan > 1) {
  88. // If a column-spanning cell is in the first row, split up
  89. // the space evenly. (XXX This isn't quite right if some of
  90. // the columns it's in have specified inline sizes. Should
  91. // we care?)
  92. cellISize = ((cellISize + spacing) / colSpan) - spacing;
  93. }
  94. result += cellISize;
  95. } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
  96. if (colSpan > 1) {
  97. // XXX Can this force columns to negative inline sizes?
  98. result -= spacing * (colSpan - 1);
  99. }
  100. }
  101. // else, for 'auto', '-moz-available', '-moz-fit-content',
  102. // and 'calc()' with percentages, do nothing
  103. }
  104. }
  105. }
  106. return (mMinISize = result);
  107. }
  108. /* virtual */ nscoord
  109. FixedTableLayoutStrategy::GetPrefISize(nsRenderingContext* aRenderingContext,
  110. bool aComputingSize)
  111. {
  112. // It's theoretically possible to do something much better here that
  113. // depends only on the columns and the first row (where we look at
  114. // intrinsic inline sizes inside the first row and then reverse the
  115. // algorithm to find the narrowest inline size that would hold all of
  116. // those intrinsic inline sizes), but it wouldn't be compatible with
  117. // other browsers.
  118. nscoord result = nscoord_MAX;
  119. DISPLAY_PREF_WIDTH(mTableFrame, result);
  120. return result;
  121. }
  122. /* virtual */ void
  123. FixedTableLayoutStrategy::MarkIntrinsicISizesDirty()
  124. {
  125. mMinISize = NS_INTRINSIC_WIDTH_UNKNOWN;
  126. mLastCalcISize = nscoord_MIN;
  127. }
  128. static inline nscoord
  129. AllocateUnassigned(nscoord aUnassignedSpace, float aShare)
  130. {
  131. if (aShare == 1.0f) {
  132. // This happens when the numbers we're dividing to get aShare are
  133. // equal. We want to return unassignedSpace exactly, even if it
  134. // can't be precisely round-tripped through float.
  135. return aUnassignedSpace;
  136. }
  137. return NSToCoordRound(float(aUnassignedSpace) * aShare);
  138. }
  139. /* virtual */ void
  140. FixedTableLayoutStrategy::ComputeColumnISizes(const ReflowInput& aReflowInput)
  141. {
  142. nscoord tableISize = aReflowInput.ComputedISize();
  143. if (mLastCalcISize == tableISize) {
  144. return;
  145. }
  146. mLastCalcISize = tableISize;
  147. nsTableCellMap *cellMap = mTableFrame->GetCellMap();
  148. int32_t colCount = cellMap->GetColCount();
  149. if (colCount == 0) {
  150. // No Columns - nothing to compute
  151. return;
  152. }
  153. // border-spacing isn't part of the basis for percentages.
  154. tableISize -= mTableFrame->GetColSpacing(-1, colCount);
  155. // store the old column inline sizes. We might call SetFinalISize
  156. // multiple times on the columns, due to this we can't compare at the
  157. // last call that the inline size has changed with respect to the last
  158. // call to ComputeColumnISizes. In order to overcome this we store the
  159. // old values in this array. A single call to SetFinalISize would make
  160. // it possible to call GetFinalISize before and to compare when
  161. // setting the final inline size.
  162. nsTArray<nscoord> oldColISizes;
  163. // XXX This ignores the 'min-width' and 'max-width' properties
  164. // throughout. Then again, that's what the CSS spec says to do.
  165. // XXX Should we really ignore widths on column groups?
  166. uint32_t unassignedCount = 0;
  167. nscoord unassignedSpace = tableISize;
  168. const nscoord unassignedMarker = nscoord_MIN;
  169. // We use the PrefPercent on the columns to store the percentages
  170. // used to compute column inline sizes in case we need to shrink or
  171. // expand the columns.
  172. float pctTotal = 0.0f;
  173. // Accumulate the total specified (non-percent) on the columns for
  174. // distributing excess inline size to the columns.
  175. nscoord specTotal = 0;
  176. WritingMode wm = mTableFrame->GetWritingMode();
  177. for (int32_t col = 0; col < colCount; ++col) {
  178. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  179. if (!colFrame) {
  180. oldColISizes.AppendElement(0);
  181. NS_ERROR("column frames out of sync with cell map");
  182. continue;
  183. }
  184. oldColISizes.AppendElement(colFrame->GetFinalISize());
  185. colFrame->ResetPrefPercent();
  186. const nsStyleCoord *styleISize = &colFrame->StylePosition()->ISize(wm);
  187. nscoord colISize;
  188. if (styleISize->ConvertsToLength()) {
  189. colISize = colFrame->ComputeISizeValue(aReflowInput.mRenderingContext,
  190. 0, 0, 0, *styleISize);
  191. specTotal += colISize;
  192. } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
  193. float pct = styleISize->GetPercentValue();
  194. colISize = NSToCoordFloor(pct * float(tableISize));
  195. colFrame->AddPrefPercent(pct);
  196. pctTotal += pct;
  197. } else {
  198. NS_ASSERTION(styleISize->GetUnit() == eStyleUnit_Auto ||
  199. styleISize->GetUnit() == eStyleUnit_Enumerated ||
  200. (styleISize->IsCalcUnit() && styleISize->CalcHasPercent()),
  201. "bad inline size");
  202. // The 'table-layout: fixed' algorithm considers only cells in the
  203. // first row.
  204. bool originates;
  205. int32_t colSpan;
  206. nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(0, col, &originates,
  207. &colSpan);
  208. if (cellFrame) {
  209. const nsStylePosition* cellStylePos = cellFrame->StylePosition();
  210. styleISize = &cellStylePos->ISize(wm);
  211. if (styleISize->ConvertsToLength() ||
  212. (styleISize->GetUnit() == eStyleUnit_Enumerated &&
  213. (styleISize->GetIntValue() == NS_STYLE_WIDTH_MAX_CONTENT ||
  214. styleISize->GetIntValue() == NS_STYLE_WIDTH_MIN_CONTENT))) {
  215. // XXX This should use real percentage padding
  216. // Note that the difference between MIN_ISIZE and PREF_ISIZE
  217. // shouldn't matter for any of these values of styleISize; use
  218. // MIN_ISIZE for symmetry with GetMinISize above, just in case
  219. // there is a difference.
  220. colISize =
  221. nsLayoutUtils::IntrinsicForContainer(aReflowInput.mRenderingContext,
  222. cellFrame,
  223. nsLayoutUtils::MIN_ISIZE);
  224. } else if (styleISize->GetUnit() == eStyleUnit_Percent) {
  225. // XXX This should use real percentage padding
  226. float pct = styleISize->GetPercentValue();
  227. colISize = NSToCoordFloor(pct * float(tableISize));
  228. if (cellStylePos->mBoxSizing == StyleBoxSizing::Content) {
  229. nsIFrame::IntrinsicISizeOffsetData offsets =
  230. cellFrame->IntrinsicISizeOffsets();
  231. colISize += offsets.hPadding + offsets.hBorder;
  232. }
  233. pct /= float(colSpan);
  234. colFrame->AddPrefPercent(pct);
  235. pctTotal += pct;
  236. } else {
  237. // 'auto', '-moz-available', '-moz-fit-content', and 'calc()'
  238. // with percentages
  239. colISize = unassignedMarker;
  240. }
  241. if (colISize != unassignedMarker) {
  242. if (colSpan > 1) {
  243. // If a column-spanning cell is in the first row, split up
  244. // the space evenly. (XXX This isn't quite right if some of
  245. // the columns it's in have specified iSizes. Should we
  246. // care?)
  247. nscoord spacing = mTableFrame->GetColSpacing(col);
  248. colISize = ((colISize + spacing) / colSpan) - spacing;
  249. if (colISize < 0) {
  250. colISize = 0;
  251. }
  252. }
  253. if (styleISize->GetUnit() != eStyleUnit_Percent) {
  254. specTotal += colISize;
  255. }
  256. }
  257. } else {
  258. colISize = unassignedMarker;
  259. }
  260. }
  261. colFrame->SetFinalISize(colISize);
  262. if (colISize == unassignedMarker) {
  263. ++unassignedCount;
  264. } else {
  265. unassignedSpace -= colISize;
  266. }
  267. }
  268. if (unassignedSpace < 0) {
  269. if (pctTotal > 0) {
  270. // If the columns took up too much space, reduce those that had
  271. // percentage inline sizes. The spec doesn't say to do this, but
  272. // we've always done it in the past, and so does WinIE6.
  273. nscoord pctUsed = NSToCoordFloor(pctTotal * float(tableISize));
  274. nscoord reduce = std::min(pctUsed, -unassignedSpace);
  275. float reduceRatio = float(reduce) / pctTotal;
  276. for (int32_t col = 0; col < colCount; ++col) {
  277. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  278. if (!colFrame) {
  279. NS_ERROR("column frames out of sync with cell map");
  280. continue;
  281. }
  282. nscoord colISize = colFrame->GetFinalISize();
  283. colISize -= NSToCoordFloor(colFrame->GetPrefPercent() * reduceRatio);
  284. if (colISize < 0) {
  285. colISize = 0;
  286. }
  287. colFrame->SetFinalISize(colISize);
  288. }
  289. }
  290. unassignedSpace = 0;
  291. }
  292. if (unassignedCount > 0) {
  293. // The spec says to distribute the remaining space evenly among
  294. // the columns.
  295. nscoord toAssign = unassignedSpace / unassignedCount;
  296. for (int32_t col = 0; col < colCount; ++col) {
  297. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  298. if (!colFrame) {
  299. NS_ERROR("column frames out of sync with cell map");
  300. continue;
  301. }
  302. if (colFrame->GetFinalISize() == unassignedMarker) {
  303. colFrame->SetFinalISize(toAssign);
  304. }
  305. }
  306. } else if (unassignedSpace > 0) {
  307. // The spec doesn't say how to distribute the unassigned space.
  308. if (specTotal > 0) {
  309. // Distribute proportionally to non-percentage columns.
  310. nscoord specUndist = specTotal;
  311. for (int32_t col = 0; col < colCount; ++col) {
  312. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  313. if (!colFrame) {
  314. NS_ERROR("column frames out of sync with cell map");
  315. continue;
  316. }
  317. if (colFrame->GetPrefPercent() == 0.0f) {
  318. NS_ASSERTION(colFrame->GetFinalISize() <= specUndist,
  319. "inline sizes don't add up");
  320. nscoord toAdd = AllocateUnassigned(unassignedSpace,
  321. float(colFrame->GetFinalISize()) /
  322. float(specUndist));
  323. specUndist -= colFrame->GetFinalISize();
  324. colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
  325. unassignedSpace -= toAdd;
  326. if (specUndist <= 0) {
  327. NS_ASSERTION(specUndist == 0, "math should be exact");
  328. break;
  329. }
  330. }
  331. }
  332. NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
  333. } else if (pctTotal > 0) {
  334. // Distribute proportionally to percentage columns.
  335. float pctUndist = pctTotal;
  336. for (int32_t col = 0; col < colCount; ++col) {
  337. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  338. if (!colFrame) {
  339. NS_ERROR("column frames out of sync with cell map");
  340. continue;
  341. }
  342. if (pctUndist < colFrame->GetPrefPercent()) {
  343. // This can happen with floating-point math.
  344. NS_ASSERTION(colFrame->GetPrefPercent() - pctUndist < 0.0001,
  345. "inline sizes don't add up");
  346. pctUndist = colFrame->GetPrefPercent();
  347. }
  348. nscoord toAdd = AllocateUnassigned(unassignedSpace,
  349. colFrame->GetPrefPercent() /
  350. pctUndist);
  351. colFrame->SetFinalISize(colFrame->GetFinalISize() + toAdd);
  352. unassignedSpace -= toAdd;
  353. pctUndist -= colFrame->GetPrefPercent();
  354. if (pctUndist <= 0.0f) {
  355. break;
  356. }
  357. }
  358. NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
  359. } else {
  360. // Distribute equally to the zero-iSize columns.
  361. int32_t colsRemaining = colCount;
  362. for (int32_t col = 0; col < colCount; ++col) {
  363. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  364. if (!colFrame) {
  365. NS_ERROR("column frames out of sync with cell map");
  366. continue;
  367. }
  368. NS_ASSERTION(colFrame->GetFinalISize() == 0, "yikes");
  369. nscoord toAdd = AllocateUnassigned(unassignedSpace,
  370. 1.0f / float(colsRemaining));
  371. colFrame->SetFinalISize(toAdd);
  372. unassignedSpace -= toAdd;
  373. --colsRemaining;
  374. }
  375. NS_ASSERTION(unassignedSpace == 0, "failed to redistribute");
  376. }
  377. }
  378. for (int32_t col = 0; col < colCount; ++col) {
  379. nsTableColFrame *colFrame = mTableFrame->GetColFrame(col);
  380. if (!colFrame) {
  381. NS_ERROR("column frames out of sync with cell map");
  382. continue;
  383. }
  384. if (oldColISizes.ElementAt(col) != colFrame->GetFinalISize()) {
  385. mTableFrame->DidResizeColumns();
  386. break;
  387. }
  388. }
  389. }