Issue #1914 - Implement white-space: break-spaces

This also simplifies GetCSSWhitespaceToCompressionMode (FFS with the function
names, Mozilla!) to be less fragile.
This commit is contained in:
Moonchild 2022-06-10 07:35:22 +00:00 • committed by roytam1
commit afbfbcb1e1
9 changed files with 57 additions and 37 deletions

View file

@ -9519,6 +9519,7 @@ exports.CSS_PROPERTIES = {
"supports": [], "supports": [],
"values": [ "values": [
"-moz-pre-space", "-moz-pre-space",
"break-spaces",
"inherit", "inherit",
"initial", "initial",
"normal", "normal",

View file

@ -851,6 +851,7 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
bool *aUsedHyphenation, bool *aUsedHyphenation,
uint32_t *aLastBreak, uint32_t *aLastBreak,
bool aCanWordWrap, bool aCanWordWrap,
bool aCanWhitespaceWrap,
gfxBreakPriority *aBreakPriority) gfxBreakPriority *aBreakPriority)
{ {
aMaxLength = std::min(aMaxLength, GetLength() - aStart); aMaxLength = std::min(aMaxLength, GetLength() - aStart);
@ -918,7 +919,15 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() && aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() &&
*aBreakPriority <= gfxBreakPriority::eWordWrapBreak; *aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
if (atBreak || wordWrapping) { bool whitespaceWrapping = false;
if (i > aStart) {
// The spec says the breaking opportunity is *after* whitespace.
auto const& g = mCharacterGlyphs[i - 1];
whitespaceWrapping = aCanWhitespaceWrap &&
(g.CharIsSpace() || g.CharIsTab() || g.CharIsNewline());
}
if (atBreak || wordWrapping || whitespaceWrapping) {
gfxFloat hyphenatedAdvance = advance; gfxFloat hyphenatedAdvance = advance;
if (atHyphenationBreak) { if (atHyphenationBreak) {
hyphenatedAdvance += aProvider->GetHyphenWidth(); hyphenatedAdvance += aProvider->GetHyphenWidth();
@ -930,8 +939,9 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
lastBreakTrimmableChars = trimmableChars; lastBreakTrimmableChars = trimmableChars;
lastBreakTrimmableAdvance = trimmableAdvance; lastBreakTrimmableAdvance = trimmableAdvance;
lastBreakUsedHyphenation = atHyphenationBreak; lastBreakUsedHyphenation = atHyphenationBreak;
*aBreakPriority = atBreak ? gfxBreakPriority::eNormalBreak *aBreakPriority = (atBreak || whitespaceWrapping)
: gfxBreakPriority::eWordWrapBreak; ? gfxBreakPriority::eNormalBreak
: gfxBreakPriority::eWordWrapBreak;
} }
width += advance; width += advance;

View file

@ -409,6 +409,7 @@ public:
bool *aUsedHyphenation, bool *aUsedHyphenation,
uint32_t *aLastBreak, uint32_t *aLastBreak,
bool aCanWordWrap, bool aCanWordWrap,
bool aCanWhitespaceWrap,
gfxBreakPriority *aBreakPriority); gfxBreakPriority *aBreakPriority);
// Utility getters // Utility getters

View file

@ -1988,35 +1988,31 @@ GetHyphenTextRun(const gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
MakeHyphenTextRun(dt, aTextRun->GetAppUnitsPerDevUnit()); MakeHyphenTextRun(dt, aTextRun->GetAppUnitsPerDevUnit());
} }
static_assert(NS_STYLE_WHITESPACE_NORMAL == 0, "Convention: NS_STYLE_WHITESPACE_NORMAL should be 0");
static_assert(NS_STYLE_WHITESPACE_PRE == 1, "Convention: NS_STYLE_WHITESPACE_PRE should be 1");
static_assert(NS_STYLE_WHITESPACE_NOWRAP == 2, "Convention: NS_STYLE_WHITESPACE_NOWRAP should be 2");
static_assert(NS_STYLE_WHITESPACE_PRE_WRAP == 3, "Convention: NS_STYLE_WHITESPACE_PRE_WRAP should be 3");
static_assert(NS_STYLE_WHITESPACE_PRE_LINE == 4, "Convention: NS_STYLE_WHITESPACE_PRE_LINE should be 4");
static_assert(NS_STYLE_WHITESPACE_PRE_SPACE == 5, "Convention: NS_STYLE_WHITESPACE_PRE_SPACE should be 5");
static nsTextFrameUtils::CompressionMode static nsTextFrameUtils::CompressionMode
GetCSSWhitespaceToCompressionMode(nsTextFrame* aFrame, GetCSSWhitespaceToCompressionMode(nsTextFrame* aFrame,
const nsStyleText* aStyleText) const nsStyleText* aStyleText)
{ {
static const nsTextFrameUtils::CompressionMode sModes[] = switch (aStyleText->mWhiteSpace) {
{ case NS_STYLE_WHITESPACE_NORMAL:
nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal case NS_STYLE_WHITESPACE_NOWRAP:
nsTextFrameUtils::COMPRESS_NONE, // pre return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap case NS_STYLE_WHITESPACE_PRE:
nsTextFrameUtils::COMPRESS_NONE, // pre-wrap case NS_STYLE_WHITESPACE_PRE_WRAP:
nsTextFrameUtils::COMPRESS_WHITESPACE, // pre-line case NS_STYLE_WHITESPACE_BREAK_SPACES:
nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE // -moz-pre-space if (!aStyleText->NewlineIsSignificant(aFrame)) {
}; // If newline is set to be preserved, but then suppressed,
// transform newline to space.
auto compression = sModes[aStyleText->mWhiteSpace]; return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
if (compression == nsTextFrameUtils::COMPRESS_NONE && }
!aStyleText->NewlineIsSignificant(aFrame)) { return nsTextFrameUtils::COMPRESS_NONE;
// If newline is set to be preserved, but then suppressed, case NS_STYLE_WHITESPACE_PRE_SPACE:
// transform newline to space. return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
compression = nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE; case NS_STYLE_WHITESPACE_PRE_LINE:
return nsTextFrameUtils::COMPRESS_WHITESPACE;
default:
MOZ_ASSERT_UNREACHABLE("Unknown white-space value");
return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
} }
return compression;
} }
already_AddRefed<gfxTextRun> already_AddRefed<gfxTextRun>
@ -9206,8 +9202,10 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
} }
bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() || bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML); (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
bool isBreakSpaces = textStyle->mWhiteSpace == NS_STYLE_WHITESPACE_BREAK_SPACES;
// allow whitespace to overflow the container // allow whitespace to overflow the container
bool whitespaceCanHang = textStyle->WhiteSpaceCanWrapStyle() && bool whitespaceCanHang = !isBreakSpaces &&
textStyle->WhiteSpaceCanWrapStyle() &&
textStyle->WhiteSpaceIsSignificant(); textStyle->WhiteSpaceIsSignificant();
gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority(); gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak; gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
@ -9227,7 +9225,9 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
&textMetrics, boundingBoxType, &textMetrics, boundingBoxType,
aDrawTarget, aDrawTarget,
&usedHyphenation, &transformedLastBreak, &usedHyphenation, &transformedLastBreak,
textStyle->WordCanWrap(this), &breakPriority); textStyle->WordCanWrap(this),
isBreakSpaces,
&breakPriority);
if (!length && !textMetrics.mAscent && !textMetrics.mDescent) { if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
// If we're measuring a zero-length piece of text, update // If we're measuring a zero-length piece of text, update
// the height manually. // the height manually.

View file

@ -171,6 +171,7 @@ CSS_KEY(both, both)
CSS_KEY(bottom, bottom) CSS_KEY(bottom, bottom)
CSS_KEY(bottom-outside, bottom_outside) CSS_KEY(bottom-outside, bottom_outside)
CSS_KEY(break-all, break_all) CSS_KEY(break-all, break_all)
CSS_KEY(break-spaces, break_spaces)
CSS_KEY(break-word, break_word) CSS_KEY(break-word, break_word)
CSS_KEY(brightness, brightness) CSS_KEY(brightness, brightness)
CSS_KEY(browser, browser) CSS_KEY(browser, browser)

View file

@ -2253,6 +2253,7 @@ const KTableEntry nsCSSProps::kWhitespaceKTable[] = {
{ eCSSKeyword_pre_wrap, NS_STYLE_WHITESPACE_PRE_WRAP }, { eCSSKeyword_pre_wrap, NS_STYLE_WHITESPACE_PRE_WRAP },
{ eCSSKeyword_pre_line, NS_STYLE_WHITESPACE_PRE_LINE }, { eCSSKeyword_pre_line, NS_STYLE_WHITESPACE_PRE_LINE },
{ eCSSKeyword__moz_pre_space, NS_STYLE_WHITESPACE_PRE_SPACE }, { eCSSKeyword__moz_pre_space, NS_STYLE_WHITESPACE_PRE_SPACE },
{ eCSSKeyword_break_spaces, NS_STYLE_WHITESPACE_BREAK_SPACES },
{ eCSSKeyword_UNKNOWN, -1 } { eCSSKeyword_UNKNOWN, -1 }
}; };

View file

@ -995,12 +995,13 @@ enum class StyleDisplay : uint8_t {
#define NS_STYLE_TABSIZE_INITIAL 8 #define NS_STYLE_TABSIZE_INITIAL 8
// See nsStyleText // See nsStyleText
#define NS_STYLE_WHITESPACE_NORMAL 0 #define NS_STYLE_WHITESPACE_NORMAL 0
#define NS_STYLE_WHITESPACE_PRE 1 #define NS_STYLE_WHITESPACE_PRE 1
#define NS_STYLE_WHITESPACE_NOWRAP 2 #define NS_STYLE_WHITESPACE_NOWRAP 2
#define NS_STYLE_WHITESPACE_PRE_WRAP 3 #define NS_STYLE_WHITESPACE_PRE_WRAP 3
#define NS_STYLE_WHITESPACE_PRE_LINE 4 #define NS_STYLE_WHITESPACE_PRE_LINE 4
#define NS_STYLE_WHITESPACE_PRE_SPACE 5 #define NS_STYLE_WHITESPACE_PRE_SPACE 5
#define NS_STYLE_WHITESPACE_BREAK_SPACES 6
// See nsStyleText // See nsStyleText
#define NS_STYLE_WORDBREAK_NORMAL 0 #define NS_STYLE_WORDBREAK_NORMAL 0

View file

@ -2105,30 +2105,35 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText
bool WhiteSpaceIsSignificant() const { bool WhiteSpaceIsSignificant() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_PRE || return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP || mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_BREAK_SPACES ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE; mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE;
} }
bool NewlineIsSignificantStyle() const { bool NewlineIsSignificantStyle() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_PRE || return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP || mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_BREAK_SPACES ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE; mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE;
} }
bool WhiteSpaceOrNewlineIsSignificant() const { bool WhiteSpaceOrNewlineIsSignificant() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_PRE || return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP || mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_BREAK_SPACES ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE || mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE; mWhiteSpace == NS_STYLE_WHITESPACE_PRE_SPACE;
} }
bool TabIsSignificant() const { bool TabIsSignificant() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_PRE || return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP; mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_BREAK_SPACES;
} }
bool WhiteSpaceCanWrapStyle() const { bool WhiteSpaceCanWrapStyle() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_NORMAL || return mWhiteSpace == NS_STYLE_WHITESPACE_NORMAL ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP || mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_BREAK_SPACES ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE; mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE;
} }

View file

@ -4052,7 +4052,7 @@ var gCSSProperties = {
inherited: true, inherited: true,
type: CSS_TYPE_LONGHAND, type: CSS_TYPE_LONGHAND,
initial_values: [ "normal" ], initial_values: [ "normal" ],
other_values: [ "pre", "nowrap", "pre-wrap", "pre-line", "-moz-pre-space" ], other_values: [ "pre", "nowrap", "pre-wrap", "pre-line", "-moz-pre-space", "break-spaces" ],
invalid_values: [] invalid_values: []
}, },
"width": { "width": {