Skip to content

Commit 31f5614

Browse files
committed
Bug 1731541 - When line-clamp is in effect, make text-wrap:balance consider only the lines up to the clamp limit. r=emilio
This corresponds to how Chrome behaves, and passes the test they included in WPT. It's unclear to me whether this behavior actually follows from the current spec (see w3c/csswg-drafts#9310), but it seems to be the desired result. (I've put it behind a (default-enabled) pref for now, so that it's possible to experiment with the two possible interpretations, but we can remove the pref once the spec question is clarified/confirmed.) This patch also disables balancing for fragmented/overflowing blocks, as that will not currently work well. We may want to address that as a followup issue (though it won't matter to the primary balance use-cases such as titles). Depends on D188139 Differential Revision: https://phabricator.services.mozilla.com/D188220
1 parent d62a0b5 commit 31f5614

File tree

3 files changed

+77
-15
lines changed

3 files changed

+77
-15
lines changed

layout/generic/nsBlockFrame.cpp

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,9 +1449,25 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
14491449
bool tryBalance = StyleText()->mTextWrap == StyleTextWrap::Balance &&
14501450
!GetPrevContinuation();
14511451

1452-
// Target number of lines in the block while balancing; negative if no
1453-
// balancing is being done.
1454-
int32_t balanceTarget = -1;
1452+
// Struct used to hold the "target" number of lines or clamp position to
1453+
// maintain when doing text-wrap: balance.
1454+
struct BalanceTarget {
1455+
// If line-clamp is in effect, mContent and mOffset indicate the starting
1456+
// position of the first line after the clamp limit. If line-clamp is not
1457+
// in use, mContent is null and mOffset is the total number of lines that
1458+
// the block must contain.
1459+
nsIContent* mContent = nullptr;
1460+
int32_t mOffset = -1;
1461+
1462+
bool operator==(const BalanceTarget& aOther) const {
1463+
return mContent == aOther.mContent && mOffset == aOther.mOffset;
1464+
}
1465+
bool operator!=(const BalanceTarget& aOther) const {
1466+
return !(*this == aOther);
1467+
}
1468+
};
1469+
1470+
BalanceTarget balanceTarget;
14551471

14561472
// Helpers for text-wrap: balance implementation:
14571473

@@ -1467,6 +1483,24 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
14671483
return n;
14681484
};
14691485

1486+
// Return a BalanceTarget record representing the position at which line-clamp
1487+
// will take effect for the current line list. Only to be used when there are
1488+
// enough lines that the clamp will apply.
1489+
auto getClampPosition = [&](uint32_t aClampCount) -> BalanceTarget {
1490+
MOZ_ASSERT(aClampCount < mLines.size());
1491+
auto iter = mLines.begin();
1492+
for (uint32_t i = 0; i < aClampCount; i++) {
1493+
++iter;
1494+
}
1495+
nsIContent* content = iter->mFirstChild->GetContent();
1496+
int32_t offset = 0;
1497+
if (content && iter->mFirstChild->IsTextFrame()) {
1498+
auto* textFrame = static_cast(iter->mFirstChild);
1499+
offset = textFrame->GetContentOffset();
1500+
}
1501+
return BalanceTarget{content, offset};
1502+
};
1503+
14701504
// "balancing" is implemented by shortening the effective inline-size of the
14711505
// lines, so that content will tend to be pushed down to fill later lines of
14721506
// the block. `balanceInset` is the current amount of "inset" to apply, and
@@ -1495,30 +1529,57 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
14951529
if (!reflowStatus.IsFullyComplete()) {
14961530
break;
14971531
}
1498-
balanceTarget =
1532+
balanceTarget.mOffset =
14991533
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1500-
if (balanceTarget < 2) {
1534+
if (balanceTarget.mOffset < 2) {
15011535
// If there are less than 2 lines, or the number exceeds the limit,
15021536
// no balancing is needed; just break from the balance loop.
15031537
break;
15041538
}
15051539
// Initialize the amount of inset to try, and the iteration step size.
1506-
balanceStep = aReflowInput.ComputedISize() / balanceTarget;
1540+
balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset;
15071541
trialState.ResetForBalance(balanceStep);
15081542
balanceStep /= 2;
15091543

1544+
// If -webkit-line-clamp is in effect, then we need to maintain the
1545+
// content location at which clamping occurs, rather than the total
1546+
// number of lines in the block.
1547+
if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
1548+
IsLineClampRoot(this)) {
1549+
uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp;
1550+
if (uint32_t(balanceTarget.mOffset) > lineClampCount) {
1551+
auto t = getClampPosition(lineClampCount);
1552+
if (t.mContent) {
1553+
balanceTarget = t;
1554+
}
1555+
}
1556+
}
1557+
15101558
// Restore initial floatManager state for a new trial with updated inset.
15111559
aReflowInput.mFloatManager->PopState(&floatManagerState);
15121560
continue;
15131561
}
15141562

1563+
// Helper to determine whether the current trial succeeded (i.e. was able
1564+
// to fit the content into the expected number of lines).
1565+
auto trialSucceeded = [&]() -> bool {
1566+
if (!reflowStatus.IsFullyComplete()) {
1567+
return false;
1568+
}
1569+
if (balanceTarget.mContent) {
1570+
auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
1571+
return t == balanceTarget;
1572+
}
1573+
int32_t numLines =
1574+
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1575+
return numLines == balanceTarget.mOffset;
1576+
};
1577+
15151578
// If we're in the process of a balance operation, check whether we've
15161579
// inset by too much and either increase or reduce the inset for the next
15171580
// iteration.
15181581
if (balanceStep > 0) {
1519-
int32_t numLines =
1520-
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1521-
if (reflowStatus.IsFullyComplete() && numLines == balanceTarget) {
1582+
if (trialSucceeded()) {
15221583
trialState.ResetForBalance(balanceStep);
15231584
} else {
15241585
trialState.ResetForBalance(-balanceStep);
@@ -1531,10 +1592,8 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
15311592

15321593
// If we were attempting to balance, check whether the final iteration was
15331594
// successful, and if not, back up by one step.
1534-
if (balanceTarget >= 0) {
1535-
int32_t numLines =
1536-
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
1537-
if (reflowStatus.IsFullyComplete() && numLines == balanceTarget) {
1595+
if (balanceTarget.mOffset >= 0) {
1596+
if (trialSucceeded()) {
15381597
break;
15391598
}
15401599
trialState.ResetForBalance(-1);

modules/libpref/init/StaticPrefList.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8775,6 +8775,11 @@
87758775
value: 10
87768776
mirror: always
87778777

8778+
- name: layout.css.text-wrap-balance-after-clamp.enabled
8779+
type: bool
8780+
value: true
8781+
mirror: always
8782+
87788783
# Support for the css Zoom property.
87798784
- name: layout.css.zoom.enabled
87808785
type: RelaxedAtomicBool

testing/web-platform/meta/css/css-text/white-space/text-wrap-balance-line-clamp-001.html.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)