Commit 12d7f5b1 authored by Javier Fernández García-Boente's avatar Javier Fernández García-Boente Committed by Commit Bot

Reland "New TextBreakIterator's default behavior breaks after space run"

This is a reland of 1cb4c9a7

Rebaselined a few tests for mac 10.14 and 10.15.

Original change's description:
> New TextBreakIterator's default behavior breaks after space run
>
> Before this change, we were considering breaking opportunities before
> space runs. This approach allowed us to avoid re-shaping in many cases,
> which has an important advantage in terms of performance.
>
> However, the Unicode spec (UAX#14) state that breaking before a space
> character is not allowed [1], so we had to implement this logic after
> our TextBreakIterator had already determined the best breaking
> opportunity. This approach has been working fine so far for regular
> spaces (white-space, tabs, ...), but it doesn't work correctly for
> other BA [2] class characters; in the CSS Text specification, these are
> known as "other space separators" [3].
>
> In order to implement the correct behavior for any kind of space, we
> would need to change our TextBreakIterator implementation so that
> matches the Unicode rules, considering breaking opportunity after
> space runs. This change should also consider the performance impact
> of the extra re-shaping operations required to deal with trailing
> spaces.
>
> In order to prevent performance regressions, we'll store the position
> of the 'end of non-hangable run', which will be used in case of items
> with styles dictating rules to collapse trailing spaces.
>
> [1] https://unicode-org.atlassian.net/browse/ICU-20843
> [2] https://www.unicode.org/reports/tr14/tr14-39.html#BA
> [3] https://drafts.csswg.org/css-text-3/#other-space-separators
>
> Change-Id: Ie4a3890c75a3faff1a0155d4a40bcaa85bc6ac06
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2047943
> Commit-Queue: Javier Fernandez <jfernandez@igalia.com>
> Reviewed-by: Koji Ishii <kojii@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#806928}

Change-Id: I9b5204f0b2e49367ab7d7a2107900763844fd2bb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2412307
Commit-Queue: Javier Fernandez <jfernandez@igalia.com>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807457}
parent 807acabd
...@@ -53,6 +53,9 @@ struct CORE_EXPORT NGInlineItemResult { ...@@ -53,6 +53,9 @@ struct CORE_EXPORT NGInlineItemResult {
// The range of text content for this item. // The range of text content for this item.
NGTextOffset text_offset; NGTextOffset text_offset;
// Indicates the limits of the trailing space run.
base::Optional<unsigned> non_hangable_run_end;
// Inline size of this item. // Inline size of this item.
LayoutUnit inline_size; LayoutUnit inline_size;
......
...@@ -198,7 +198,7 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node, ...@@ -198,7 +198,7 @@ NGLineBreaker::NGLineBreaker(NGInlineNode node,
handled_leading_floats_index_(handled_leading_floats_index), handled_leading_floats_index_(handled_leading_floats_index),
base_direction_(node_.BaseDirection()) { base_direction_(node_.BaseDirection()) {
available_width_ = ComputeAvailableWidth(); available_width_ = ComputeAvailableWidth();
break_iterator_.SetBreakSpace(BreakSpaceType::kBeforeSpaceRun); break_iterator_.SetBreakSpace(BreakSpaceType::kAfterSpaceRun);
if (!break_token) if (!break_token)
return; return;
...@@ -622,6 +622,17 @@ void NGLineBreaker::HandleText(const NGInlineItem& item, ...@@ -622,6 +622,17 @@ void NGLineBreaker::HandleText(const NGInlineItem& item,
return; return;
} }
// Hanging trailing spaces may resolve the overflow.
if (item_result->has_only_trailing_spaces) {
if (item_result->item->Style()->WhiteSpace() == EWhiteSpace::kPreWrap &&
IsBreakableSpace(Text()[item_result->EndOffset() - 1])) {
unsigned end_index = item_result - line_info->Results().begin();
Rewind(end_index, line_info);
}
state_ = LineBreakState::kTrailing;
return;
}
// If this is all trailable spaces, this item is trailable, and next item // If this is all trailable spaces, this item is trailable, and next item
// maybe too. Don't go to |HandleOverflow()| yet. // maybe too. Don't go to |HandleOverflow()| yet.
if (IsAllBreakableSpaces(Text(), item_result->StartOffset(), if (IsAllBreakableSpaces(Text(), item_result->StartOffset(),
...@@ -760,6 +771,8 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText( ...@@ -760,6 +771,8 @@ NGLineBreaker::BreakResult NGLineBreaker::BreakText(
item_result->inline_size = inline_size; item_result->inline_size = inline_size;
item_result->text_offset.end = result.break_offset; item_result->text_offset.end = result.break_offset;
item_result->text_offset.AssertNotEmpty(); item_result->text_offset.AssertNotEmpty();
item_result->non_hangable_run_end = result.non_hangable_run_end;
item_result->has_only_trailing_spaces = result.has_trailing_spaces;
item_result->shape_result = std::move(shape_result); item_result->shape_result = std::move(shape_result);
break; break;
} }
...@@ -884,20 +897,31 @@ bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result, ...@@ -884,20 +897,31 @@ bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result,
if (end_offset >= item.EndOffset()) if (end_offset >= item.EndOffset())
break; break;
unsigned non_hangable_run_end = end_offset;
if (item.Style()->WhiteSpace() != EWhiteSpace::kBreakSpaces) {
while (non_hangable_run_end > item.StartOffset() &&
IsBreakableSpace(text[non_hangable_run_end - 1])) {
--non_hangable_run_end;
}
}
// Inserting a hyphenation character is not supported yet. // Inserting a hyphenation character is not supported yet.
if (text[end_offset - 1] == kSoftHyphenCharacter) // TODO (jfernandez): Maybe we need to use 'end_offset' here ?
if (text[non_hangable_run_end - 1] == kSoftHyphenCharacter)
return false; return false;
float start_position = shape_result.CachedPositionForOffset( float start_position = shape_result.CachedPositionForOffset(
start_offset - shape_result.StartIndex()); start_offset - shape_result.StartIndex());
float end_position = shape_result.CachedPositionForOffset( float end_position = shape_result.CachedPositionForOffset(
end_offset - shape_result.StartIndex()); non_hangable_run_end - shape_result.StartIndex());
float word_width = IsLtr(shape_result.Direction()) float word_width = IsLtr(shape_result.Direction())
? end_position - start_position ? end_position - start_position
: start_position - end_position; : start_position - end_position;
min_width = std::max(word_width, min_width); min_width = std::max(word_width, min_width);
last_end_offset = end_offset; last_end_offset = non_hangable_run_end;
// TODO (jfernandez): I think that having the non_hangable_run_end
// would make this loop unnecessary/redudant.
start_offset = end_offset; start_offset = end_offset;
while (start_offset < item.EndOffset() && while (start_offset < item.EndOffset() &&
IsBreakableSpace(text[start_offset])) { IsBreakableSpace(text[start_offset])) {
...@@ -914,7 +938,8 @@ bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result, ...@@ -914,7 +938,8 @@ bool NGLineBreaker::HandleTextForFastMinContent(NGInlineItemResult* item_result,
return false; return false;
// Create an NGInlineItemResult that has the max of widths of all words. // Create an NGInlineItemResult that has the max of widths of all words.
item_result->text_offset.end = last_end_offset; item_result->text_offset.end =
std::max(last_end_offset, item_result->text_offset.start + 1);
item_result->text_offset.AssertNotEmpty(); item_result->text_offset.AssertNotEmpty();
item_result->inline_size = LayoutUnit::FromFloatCeil(min_width); item_result->inline_size = LayoutUnit::FromFloatCeil(min_width);
item_result->can_break_after = true; item_result->can_break_after = true;
...@@ -1026,6 +1051,9 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item, ...@@ -1026,6 +1051,9 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item,
(item.Type() == NGInlineItem::kControl && (item.Type() == NGInlineItem::kControl &&
Text()[item.StartOffset()] == kTabulationCharacter)); Text()[item.StartOffset()] == kTabulationCharacter));
DCHECK(&shape_result); DCHECK(&shape_result);
bool is_control_tab = item.Type() == NGInlineItem::kControl &&
Text()[item.StartOffset()] == kTabulationCharacter;
DCHECK(item.Type() == NGInlineItem::kText || is_control_tab);
DCHECK_GE(offset_, item.StartOffset()); DCHECK_GE(offset_, item.StartOffset());
DCHECK_LT(offset_, item.EndOffset()); DCHECK_LT(offset_, item.EndOffset());
const String& text = Text(); const String& text = Text();
...@@ -1039,6 +1067,8 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item, ...@@ -1039,6 +1067,8 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item,
if (style.CollapseWhiteSpace()) { if (style.CollapseWhiteSpace()) {
if (text[offset_] != kSpaceCharacter) { if (text[offset_] != kSpaceCharacter) {
if (offset_ > 0 && IsBreakableSpace(text[offset_ - 1]))
trailing_whitespace_ = WhitespaceState::kCollapsible;
state_ = LineBreakState::kDone; state_ = LineBreakState::kDone;
return; return;
} }
...@@ -1060,18 +1090,29 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item, ...@@ -1060,18 +1090,29 @@ void NGLineBreaker::HandleTrailingSpaces(const NGInlineItem& item,
while (end < item.EndOffset() && IsBreakableSpace(text[end])) while (end < item.EndOffset() && IsBreakableSpace(text[end]))
end++; end++;
if (end == offset_) { if (end == offset_) {
if (IsBreakableSpace(text[end - 1]))
trailing_whitespace_ = WhitespaceState::kPreserved;
state_ = LineBreakState::kDone; state_ = LineBreakState::kDone;
return; return;
} }
// TODO (jfernandez): Could we just modify the last ItemResult
// instead of creating a new one ?
// Probably we can (koji). We would need to review usage of these
// item results, and change them to use "non_hangable_run_end"
// instead.
NGInlineItemResult* item_result = AddItem(item, end, line_info); NGInlineItemResult* item_result = AddItem(item, end, line_info);
item_result->non_hangable_run_end = offset_;
item_result->has_only_trailing_spaces = true; item_result->has_only_trailing_spaces = true;
item_result->shape_result = ShapeResultView::Create(&shape_result); item_result->shape_result = ShapeResultView::Create(&shape_result);
if (item_result->StartOffset() == item.StartOffset() && if (item_result->StartOffset() == item.StartOffset() &&
item_result->EndOffset() == item.EndOffset()) item_result->EndOffset() == item.EndOffset()) {
item_result->inline_size = item_result->shape_result->SnappedWidth(); item_result->inline_size = item_result->shape_result
else ? item_result->shape_result->SnappedWidth()
: LayoutUnit();
} else {
UpdateShapeResult(*line_info, item_result); UpdateShapeResult(*line_info, item_result);
}
position_ += item_result->inline_size; position_ += item_result->inline_size;
item_result->can_break_after = item_result->can_break_after =
end < text.length() && !IsBreakableSpace(text[end]); end < text.length() && !IsBreakableSpace(text[end]);
...@@ -1662,7 +1703,8 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item, ...@@ -1662,7 +1703,8 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
DCHECK(!item_result->can_break_after); DCHECK(!item_result->can_break_after);
const NGInlineItemResults& item_results = line_info->Results(); const NGInlineItemResults& item_results = line_info->Results();
if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results.size() >= 2)) { if (UNLIKELY(!was_auto_wrap && auto_wrap_ && item_results.size() >= 2)) {
ComputeCanBreakAfter(std::prev(item_result), auto_wrap_, break_iterator_); if (IsPreviousItemOfType(NGInlineItem::kText))
ComputeCanBreakAfter(std::prev(item_result), auto_wrap_, break_iterator_);
} }
} }
...@@ -1691,29 +1733,15 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, ...@@ -1691,29 +1733,15 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
if (item_results.size() >= 2) { if (item_results.size() >= 2) {
NGInlineItemResult* last = std::prev(item_result); NGInlineItemResult* last = std::prev(item_result);
if (was_auto_wrap || last->can_break_after) { if (was_auto_wrap || last->can_break_after) {
item_result->can_break_after = last->can_break_after; item_result->can_break_after =
last->can_break_after ||
IsBreakableSpace(Text()[item_result->EndOffset()]);
last->can_break_after = false; last->can_break_after = false;
return; return;
} }
if (auto_wrap_ && !IsBreakableSpace(Text()[item_result->EndOffset() - 1]))
ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
} }
if (was_auto_wrap)
return;
DCHECK(!item_result->can_break_after);
if (!auto_wrap_)
return;
// When auto-wrap follows no-wrap, the boundary is determined by the break
// iterator. However, when space characters follow the boundary, the break
// iterator cannot compute this because it considers break opportunities are
// before a run of spaces.
const String& text = Text();
if (item_result->EndOffset() < text.length() &&
IsBreakableSpace(text[item_result->EndOffset()])) {
item_result->can_break_after = true;
return;
}
ComputeCanBreakAfter(item_result, auto_wrap_, break_iterator_);
} }
// Handles when the last item overflows. // Handles when the last item overflows.
...@@ -2149,6 +2177,10 @@ void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) { ...@@ -2149,6 +2177,10 @@ void NGLineBreaker::SetCurrentStyle(const ComputedStyle& style) {
spacing_.SetSpacing(style.GetFont()); spacing_.SetSpacing(style.GetFont());
} }
bool NGLineBreaker::IsPreviousItemOfType(NGInlineItem::NGInlineItemType type) {
return item_index_ > 0 ? Items().at(item_index_ - 1).Type() == type : false;
}
void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) { void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) {
offset_ = item.EndOffset(); offset_ = item.EndOffset();
item_index_++; item_index_++;
......
...@@ -194,6 +194,7 @@ class CORE_EXPORT NGLineBreaker { ...@@ -194,6 +194,7 @@ class CORE_EXPORT NGLineBreaker {
NGLineInfo*) const; NGLineInfo*) const;
void SetCurrentStyle(const ComputedStyle&); void SetCurrentStyle(const ComputedStyle&);
bool IsPreviousItemOfType(NGInlineItem::NGInlineItemType);
void MoveToNextOf(const NGInlineItem&); void MoveToNextOf(const NGInlineItem&);
void MoveToNextOf(const NGInlineItemResult&); void MoveToNextOf(const NGInlineItemResult&);
......
...@@ -66,6 +66,19 @@ inline void CheckBreakOffset(unsigned offset, unsigned start, unsigned end) { ...@@ -66,6 +66,19 @@ inline void CheckBreakOffset(unsigned offset, unsigned start, unsigned end) {
CHECK_LE(offset, end); CHECK_LE(offset, end);
} }
unsigned FindNonHangableEnd(const String& text, unsigned candidate) {
DCHECK_LT(candidate, text.length());
DCHECK(IsBreakableSpace(text[candidate]));
// Looking for the non-hangable run end
unsigned non_hangable_end = candidate;
while (non_hangable_end > 0) {
if (!IsBreakableSpace(text[--non_hangable_end]))
return non_hangable_end + 1;
}
return non_hangable_end;
}
} // namespace } // namespace
inline const String& ShapingLineBreaker::GetText() const { inline const String& ShapingLineBreaker::GetText() const {
...@@ -110,8 +123,11 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate( ...@@ -110,8 +123,11 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate(
bool backwards) const { bool backwards) const {
const String& text = GetText(); const String& text = GetText();
unsigned word_end = break_iterator_->NextBreakOpportunity(offset); unsigned word_end = break_iterator_->NextBreakOpportunity(offset);
if (word_end != offset && IsBreakableSpace(text[word_end - 1]))
word_end = std::max(start, FindNonHangableEnd(text, word_end - 1));
if (word_end == offset) { if (word_end == offset) {
DCHECK_EQ(offset, break_iterator_->PreviousBreakOpportunity(offset, start)); DCHECK(IsBreakableSpace(text[offset]) ||
offset == break_iterator_->PreviousBreakOpportunity(offset, start));
return {word_end, false}; return {word_end, false};
} }
unsigned previous_break_opportunity = unsigned previous_break_opportunity =
...@@ -119,6 +135,7 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate( ...@@ -119,6 +135,7 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate(
unsigned word_start = previous_break_opportunity; unsigned word_start = previous_break_opportunity;
// Skip the leading spaces of this word because the break iterator breaks // Skip the leading spaces of this word because the break iterator breaks
// before spaces. // before spaces.
// TODO (jfernandez): This is no longer true, so we should remove this code.
while (word_start < text.length() && while (word_start < text.length() &&
LazyLineBreakIterator::IsBreakableSpace(text[word_start])) LazyLineBreakIterator::IsBreakableSpace(text[word_start]))
word_start++; word_start++;
...@@ -134,8 +151,8 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate( ...@@ -134,8 +151,8 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate(
ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::BreakOpportunity
ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset, ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset,
unsigned start) const { unsigned start) const {
const String& text = GetText();
if (UNLIKELY(!IsSoftHyphenEnabled())) { if (UNLIKELY(!IsSoftHyphenEnabled())) {
const String& text = GetText();
for (;; offset--) { for (;; offset--) {
offset = break_iterator_->PreviousBreakOpportunity(offset, start); offset = break_iterator_->PreviousBreakOpportunity(offset, start);
if (offset <= start || offset >= text.length() || if (offset <= start || offset >= text.length() ||
...@@ -147,15 +164,24 @@ ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset, ...@@ -147,15 +164,24 @@ ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset,
if (UNLIKELY(hyphenation_)) if (UNLIKELY(hyphenation_))
return Hyphenate(offset, start, true); return Hyphenate(offset, start, true);
return {break_iterator_->PreviousBreakOpportunity(offset, start), false}; // If the break opportunity is preceded by trailing spaces, find the
// end of non-hangable character (i.e., start of the space run).
unsigned break_offset =
break_iterator_->PreviousBreakOpportunity(offset, start);
unsigned non_hangable_run_end =
IsBreakableSpace(text[break_offset - 1])
? FindNonHangableEnd(text, break_offset - 1)
: break_offset;
return {break_offset, non_hangable_run_end, false};
} }
ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity( ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity(
unsigned offset, unsigned offset,
unsigned start, unsigned start,
unsigned len) const { unsigned len) const {
const String& text = GetText();
if (UNLIKELY(!IsSoftHyphenEnabled())) { if (UNLIKELY(!IsSoftHyphenEnabled())) {
const String& text = GetText();
for (;; offset++) { for (;; offset++) {
offset = break_iterator_->NextBreakOpportunity(offset); offset = break_iterator_->NextBreakOpportunity(offset);
if (offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter) if (offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter)
...@@ -166,7 +192,16 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity( ...@@ -166,7 +192,16 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity(
if (UNLIKELY(hyphenation_)) if (UNLIKELY(hyphenation_))
return Hyphenate(offset, start, false); return Hyphenate(offset, start, false);
return {break_iterator_->NextBreakOpportunity(offset, len), false}; // We should also find the beginning of the space run to find the
// end of non-hangable character (i.e., start of the space run),
// which may be useful to avoid reshaping.
unsigned break_offset = break_iterator_->NextBreakOpportunity(offset, len);
unsigned non_hangable_run_end =
IsBreakableSpace(text[break_offset - 1])
? FindNonHangableEnd(text, break_offset - 1)
: break_offset;
return {break_offset, non_hangable_run_end, false};
} }
// Shapes a line of text by finding a valid and appropriate break opportunity // Shapes a line of text by finding a valid and appropriate break opportunity
...@@ -210,7 +245,10 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -210,7 +245,10 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
DCHECK_LT(start, range_end); DCHECK_LT(start, range_end);
result_out->is_overflow = false; result_out->is_overflow = false;
result_out->is_hyphenated = false; result_out->is_hyphenated = false;
result_out->has_trailing_spaces = false;
const String& text = GetText(); const String& text = GetText();
const bool is_break_after_any_space =
break_iterator_->BreakSpace() == BreakSpaceType::kAfterEverySpace;
// The start position in the original shape results. // The start position in the original shape results.
float start_position = result_->CachedPositionForOffset(start - range_start); float start_position = result_->CachedPositionForOffset(start - range_start);
...@@ -242,24 +280,59 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -242,24 +280,59 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
// comparing floats. See ShapeLineZeroAvailableWidth on Linux/Mac. // comparing floats. See ShapeLineZeroAvailableWidth on Linux/Mac.
candidate_break = std::max(candidate_break, start); candidate_break = std::max(candidate_break, start);
// If there are no break opportunity before candidate_break, overflow. // If we are in the middle of a trailing space sequence, which are
// Find the next break opportunity after the candidate_break. // defined by the UAX#14 spec as Break After (A) class, we should
// look for breaking opportunityes after the end of the sequence.
// https://www.unicode.org/reports/tr14/#BA
// TODO(jfernandez): if break-spaces, do special handling.
BreakOpportunity break_opportunity = BreakOpportunity break_opportunity =
PreviousBreakOpportunity(candidate_break, start); !IsBreakableSpace(text[candidate_break]) || is_break_after_any_space
? PreviousBreakOpportunity(candidate_break, start)
: NextBreakOpportunity(std::max(candidate_break, start + 1), start,
range_end);
// There are no break opportunity before candidate_break, overflow.
// Find the next break opportunity after the candidate_break.
// TODO: (jfernandez): Maybe also non_hangable_run_end <= start ?
result_out->is_overflow = break_opportunity.offset <= start; result_out->is_overflow = break_opportunity.offset <= start;
if (result_out->is_overflow) { if (result_out->is_overflow) {
DCHECK(is_break_after_any_space ||
!IsBreakableSpace(text[candidate_break]));
if (options & kNoResultIfOverflow) if (options & kNoResultIfOverflow)
return nullptr; return nullptr;
// No need to scan past range_end for a break oppertunity. // No need to scan past range_end for a break opportunity.
break_opportunity = NextBreakOpportunity( break_opportunity = NextBreakOpportunity(
std::max(candidate_break, start + 1), start, range_end); std::max(candidate_break, start + 1), start, range_end);
// |range_end| may not be a break opportunity, but this function cannot }
// measure beyond it.
if (break_opportunity.offset >= range_end) { // We don't care whether this result contains only spaces if we
result_out->break_offset = range_end; // are breaking after any space. We shouldn't early return either
return ShapeToEnd(start, first_safe, range_start, range_end); // in that case.
if (!is_break_after_any_space &&
break_opportunity.non_hangable_run_end <= start) {
// TODO (jfenandez): There may be cases where candidate_break is
// not a breakable space but we also want to early return for
// triggering the trailing spaces handling
if (IsBreakableSpace(text[candidate_break])) {
result_out->has_trailing_spaces = true;
result_out->break_offset = std::min(range_end, break_opportunity.offset);
result_out->non_hangable_run_end = break_opportunity.non_hangable_run_end;
#if DCHECK_IS_ON()
DCHECK(IsAllSpaces(text, start, result_out->break_offset));
#endif
return ShapeResultView::Create(result_.get(), start,
result_out->break_offset);
} }
} }
// |range_end| may not be a break opportunity, but this function cannot
// measure beyond it.
if (break_opportunity.offset >= range_end) {
result_out->break_offset = range_end;
result_out->non_hangable_run_end = break_opportunity.non_hangable_run_end;
if (result_out->is_overflow)
return ShapeToEnd(start, first_safe, range_start, range_end);
}
CheckBreakOffset(break_opportunity.offset, start, range_end); CheckBreakOffset(break_opportunity.offset, start, range_end);
// If the start offset is not at a safe-to-break boundary the content between // If the start offset is not at a safe-to-break boundary the content between
...@@ -270,6 +343,7 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -270,6 +343,7 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
if (first_safe >= break_opportunity.offset) { if (first_safe >= break_opportunity.offset) {
// There is no safe-to-break, reshape the whole range. // There is no safe-to-break, reshape the whole range.
result_out->break_offset = break_opportunity.offset; result_out->break_offset = break_opportunity.offset;
result_out->non_hangable_run_end = break_opportunity.non_hangable_run_end;
CheckBreakOffset(result_out->break_offset, start, range_end); CheckBreakOffset(result_out->break_offset, start, range_end);
return ShapeResultView::Create( return ShapeResultView::Create(
Shape(start, break_opportunity.offset).get()); Shape(start, break_opportunity.offset).get());
...@@ -285,12 +359,21 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -285,12 +359,21 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
DCHECK_LE(first_safe, break_opportunity.offset); DCHECK_LE(first_safe, break_opportunity.offset);
scoped_refptr<const ShapeResult> line_end_result; scoped_refptr<const ShapeResult> line_end_result;
unsigned last_safe = break_opportunity.offset;
bool reshape_line_end = true; bool reshape_line_end = true;
if (options & kDontReshapeEndIfAtSpace) { if (options & kDontReshapeEndIfAtSpace) {
if (IsBreakableSpace(text[break_opportunity.offset])) if (IsBreakableSpace(text[break_opportunity.offset - 1]))
reshape_line_end = false; reshape_line_end = false;
} }
// Avoid re-shape if at the end of the range.
// TODO (jfernandez): Is this even possible ? Shouldn't we just
// early return if offset >= range_end ?
if (break_opportunity.offset == range_end)
reshape_line_end = false;
if (!is_break_after_any_space) {
break_opportunity.offset =
std::max(start + 1, break_opportunity.non_hangable_run_end);
}
unsigned last_safe = break_opportunity.offset;
if (reshape_line_end) { if (reshape_line_end) {
// If the previous valid break opportunity is not at a safe-to-break // If the previous valid break opportunity is not at a safe-to-break
// boundary reshape between the safe-to-break offset and the valid break // boundary reshape between the safe-to-break offset and the valid break
...@@ -298,6 +381,10 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -298,6 +381,10 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
// preceding boundary is tried until the available space is sufficient. // preceding boundary is tried until the available space is sufficient.
while (true) { while (true) {
DCHECK_LE(start, break_opportunity.offset); DCHECK_LE(start, break_opportunity.offset);
if (!is_break_after_any_space) {
break_opportunity.offset =
std::max(start + 1, break_opportunity.non_hangable_run_end);
}
last_safe = last_safe =
result_->CachedPreviousSafeToBreakOffset(break_opportunity.offset); result_->CachedPreviousSafeToBreakOffset(break_opportunity.offset);
// No need to reshape the line end because this opportunity is safe. // No need to reshape the line end because this opportunity is safe.
...@@ -311,7 +398,7 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -311,7 +398,7 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
// Moved the opportunity back enough to require reshaping the whole line. // Moved the opportunity back enough to require reshaping the whole line.
if (UNLIKELY(last_safe < first_safe)) { if (UNLIKELY(last_safe < first_safe)) {
DCHECK_LT(last_safe, start); DCHECK(last_safe == 0 || last_safe < start);
last_safe = start; last_safe = start;
line_start_result = nullptr; line_start_result = nullptr;
} }
...@@ -345,12 +432,17 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -345,12 +432,17 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
// because none can fit. The one after candidate_break is better for // because none can fit. The one after candidate_break is better for
// ligatures, but the one before is better for kernings. // ligatures, but the one before is better for kernings.
result_out->is_overflow = true; result_out->is_overflow = true;
// TODO (jfernandez): Would be possible to refactor this logic
// with the one performed prior tp the reshape
// (FindBreakingOpportuntty() + overflow handling)?
break_opportunity = PreviousBreakOpportunity(candidate_break, start); break_opportunity = PreviousBreakOpportunity(candidate_break, start);
if (break_opportunity.offset <= start) { if (break_opportunity.offset <= start) {
break_opportunity = NextBreakOpportunity( break_opportunity = NextBreakOpportunity(
std::max(candidate_break, start + 1), start, range_end); std::max(candidate_break, start + 1), start, range_end);
if (break_opportunity.offset >= range_end) { if (break_opportunity.offset >= range_end) {
result_out->break_offset = range_end; result_out->break_offset = range_end;
result_out->non_hangable_run_end =
break_opportunity.non_hangable_run_end;
return ShapeToEnd(start, first_safe, range_start, range_end); return ShapeToEnd(start, first_safe, range_start, range_end);
} }
} }
...@@ -380,6 +472,7 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine( ...@@ -380,6 +472,7 @@ scoped_refptr<const ShapeResultView> ShapingLineBreaker::ShapeLine(
DCHECK_EQ(break_opportunity.offset - start, line_result->NumCharacters()); DCHECK_EQ(break_opportunity.offset - start, line_result->NumCharacters());
result_out->break_offset = break_opportunity.offset; result_out->break_offset = break_opportunity.offset;
result_out->non_hangable_run_end = break_opportunity.non_hangable_run_end;
result_out->is_hyphenated = result_out->is_hyphenated =
break_opportunity.is_hyphenated || break_opportunity.is_hyphenated ||
text[break_opportunity.offset - 1] == kSoftHyphenCharacter; text[break_opportunity.offset - 1] == kSoftHyphenCharacter;
......
...@@ -53,9 +53,15 @@ class PLATFORM_EXPORT ShapingLineBreaker final { ...@@ -53,9 +53,15 @@ class PLATFORM_EXPORT ShapingLineBreaker final {
STACK_ALLOCATED(); STACK_ALLOCATED();
public: public:
// Indicates the limits of the space run.
base::Optional<unsigned> non_hangable_run_end;
// Indicates the resulting break offset. // Indicates the resulting break offset.
unsigned break_offset; unsigned break_offset;
// Indicates that the shape result contains trailing spaces
bool has_trailing_spaces;
// True if there were no break opportunities that can fit. When this is // True if there were no break opportunities that can fit. When this is
// false, the result width should be smaller than or equal to the available // false, the result width should be smaller than or equal to the available
// space. // space.
...@@ -107,7 +113,17 @@ class PLATFORM_EXPORT ShapingLineBreaker final { ...@@ -107,7 +113,17 @@ class PLATFORM_EXPORT ShapingLineBreaker final {
STACK_ALLOCATED(); STACK_ALLOCATED();
public: public:
BreakOpportunity(unsigned new_offset, bool hyphenated)
: offset(new_offset),
non_hangable_run_end(new_offset),
is_hyphenated(hyphenated) {}
BreakOpportunity(unsigned new_offset, unsigned run_end, bool hyphenated)
: offset(new_offset),
non_hangable_run_end(run_end),
is_hyphenated(hyphenated) {}
unsigned offset; unsigned offset;
unsigned non_hangable_run_end;
bool is_hyphenated; bool is_hyphenated;
}; };
BreakOpportunity PreviousBreakOpportunity(unsigned offset, BreakOpportunity PreviousBreakOpportunity(unsigned offset,
......
...@@ -334,6 +334,12 @@ inline int LazyLineBreakIterator::NextBreakablePosition( ...@@ -334,6 +334,12 @@ inline int LazyLineBreakIterator::NextBreakablePosition(
continue; continue;
} }
break; break;
case BreakSpaceType::kAfterSpaceRun:
if (is_space)
continue;
if (is_last_space)
return i;
break;
case BreakSpaceType::kAfterEverySpace: case BreakSpaceType::kAfterEverySpace:
if (is_last_space) if (is_last_space)
return i; return i;
...@@ -398,6 +404,10 @@ inline int LazyLineBreakIterator::NextBreakablePosition( ...@@ -398,6 +404,10 @@ inline int LazyLineBreakIterator::NextBreakablePosition(
return NextBreakablePosition<CharacterType, lineBreakType, return NextBreakablePosition<CharacterType, lineBreakType,
BreakSpaceType::kBeforeSpaceRun>(pos, str, BreakSpaceType::kBeforeSpaceRun>(pos, str,
len); len);
case BreakSpaceType::kAfterSpaceRun:
return NextBreakablePosition<CharacterType, lineBreakType,
BreakSpaceType::kAfterSpaceRun>(pos, str,
len);
case BreakSpaceType::kAfterEverySpace: case BreakSpaceType::kAfterEverySpace:
return NextBreakablePosition<CharacterType, lineBreakType, return NextBreakablePosition<CharacterType, lineBreakType,
BreakSpaceType::kAfterEverySpace>(pos, str, BreakSpaceType::kAfterEverySpace>(pos, str,
...@@ -516,6 +526,8 @@ std::ostream& operator<<(std::ostream& ostream, BreakSpaceType break_space) { ...@@ -516,6 +526,8 @@ std::ostream& operator<<(std::ostream& ostream, BreakSpaceType break_space) {
return ostream << "kAfterEverySpace"; return ostream << "kAfterEverySpace";
case BreakSpaceType::kBeforeSpaceRun: case BreakSpaceType::kBeforeSpaceRun:
return ostream << "kBeforeSpaceRun"; return ostream << "kBeforeSpaceRun";
case BreakSpaceType::kAfterSpaceRun:
return ostream << "kAfterSpaceRun";
} }
NOTREACHED(); NOTREACHED();
return ostream << "BreakSpaceType::" << static_cast<int>(break_space); return ostream << "BreakSpaceType::" << static_cast<int>(break_space);
......
...@@ -111,6 +111,11 @@ enum class BreakSpaceType { ...@@ -111,6 +111,11 @@ enum class BreakSpaceType {
// LayoutNG line breaker uses this type. // LayoutNG line breaker uses this type.
kBeforeSpaceRun, kBeforeSpaceRun,
// Break after a run of white space characters.
// This mode enables the LazyLineBreakIterator to completely rely on
// ICU for determining the breaking opportunities.
kAfterSpaceRun,
// white-spaces:break-spaces allows breaking after any preserved white-space, // white-spaces:break-spaces allows breaking after any preserved white-space,
// even when these are leading spaces so that we can avoid breaking // even when these are leading spaces so that we can avoid breaking
// the word in case of overflow. // the word in case of overflow.
......
...@@ -208,6 +208,9 @@ crbug.com/591099 external/wpt/css/css-text/text-transform/text-transform-shaping ...@@ -208,6 +208,9 @@ crbug.com/591099 external/wpt/css/css-text/text-transform/text-transform-shaping
crbug.com/591099 external/wpt/css/css-text/white-space/control-chars-00C.html [ Failure ] crbug.com/591099 external/wpt/css/css-text/white-space/control-chars-00C.html [ Failure ]
crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-001.html [ Failure ] crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-001.html [ Failure ]
crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-002.html [ Failure ] crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-002.html [ Failure ]
crbug.com/591099 external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-012.html [ Failure ]
crbug.com/591099 external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-013.html [ Failure ]
crbug.com/591099 external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-014.html [ Failure ]
### external/wpt/css/css-text/word-break/ ### external/wpt/css/css-text/word-break/
crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-004.html [ Failure ] crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-004.html [ Failure ]
...@@ -342,6 +345,9 @@ crbug.com/1012289 external/wpt/css/css-lists/list-style-type-string-005a.html [ ...@@ -342,6 +345,9 @@ crbug.com/1012289 external/wpt/css/css-lists/list-style-type-string-005a.html [
crbug.com/1012289 external/wpt/css/css-lists/list-style-type-string-006.html [ Failure ] crbug.com/1012289 external/wpt/css/css-lists/list-style-type-string-006.html [ Failure ]
crbug.com/1012294 external/wpt/css/css-lists/list-style-type-string-007.html [ Pass ] crbug.com/1012294 external/wpt/css/css-lists/list-style-type-string-007.html [ Pass ]
### external/wpt/css/css-content/
crbug.com/591099 external/wpt/css/css-content/quotes-033.html [ Failure ]
### external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/ ### external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/
crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-002.xhtml [ Failure ] crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-horiz-002.xhtml [ Failure ]
crbug.com/886592 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-margin-002.html [ Failure ] crbug.com/886592 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/align3/flex-abspos-staticpos-margin-002.html [ Failure ]
...@@ -378,6 +384,7 @@ crbug.com/875235 external/wpt/html/rendering/non-replaced-elements/the-fieldset- ...@@ -378,6 +384,7 @@ crbug.com/875235 external/wpt/html/rendering/non-replaced-elements/the-fieldset-
### virtual/text-antialias/ ### virtual/text-antialias/
crbug.com/591099 virtual/text-antialias/justify-ideograph-simple.html [ Failure ] crbug.com/591099 virtual/text-antialias/justify-ideograph-simple.html [ Failure ]
crbug.com/591099 virtual/text-antialias/apply-start-width-after-skipped-text.html [ Failure ]
### http/tests/ ### http/tests/
crbug.com/591099 http/tests/devtools/elements/highlight/highlight-node-vertical-rl.js [ Failure ] crbug.com/591099 http/tests/devtools/elements/highlight/highlight-node-vertical-rl.js [ Failure ]
......
...@@ -884,6 +884,7 @@ crbug.com/591099 fast/multicol/vertical-lr/nested-columns.html [ Failure ] ...@@ -884,6 +884,7 @@ crbug.com/591099 fast/multicol/vertical-lr/nested-columns.html [ Failure ]
crbug.com/591099 fast/multicol/vertical-lr/unsplittable-inline-block.html [ Failure ] crbug.com/591099 fast/multicol/vertical-lr/unsplittable-inline-block.html [ Failure ]
crbug.com/591099 [ Win ] virtual/text-antialias/ellipsis-with-self-painting-layer.html [ Failure ] crbug.com/591099 [ Win ] virtual/text-antialias/ellipsis-with-self-painting-layer.html [ Failure ]
crbug.com/591099 [ Mac ] fast/multicol/file-upload-as-multicol.html [ Failure ] crbug.com/591099 [ Mac ] fast/multicol/file-upload-as-multicol.html [ Failure ]
crbug.com/1098801 virtual/text-antialias/whitespace/whitespace-in-pre.html [ Failure ]
# LayoutNG failures that needs to be triaged # LayoutNG failures that needs to be triaged
crbug.com/591099 virtual/text-antialias/selection/selection-rect-line-height-too-small.html [ Failure ] crbug.com/591099 virtual/text-antialias/selection/selection-rect-line-height-too-small.html [ Failure ]
......
<!doctype html>
<meta charset=utf-8>
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
div {
font: 20px/1 Ahem;
white-space: pre;
}
span { background: blue; }
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX&#x0020;X<span>&#x0020;<br>X</span></div>
<!doctype html>
<meta charset=utf-8>
<meta http-equiv="content-language" content="en, ja" />
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<style>
div { white-space: pre; }
span { background: blue; }
</style>
<p>This test passes if the line is after the white space, which hangs (blue).
<div>ああ<span>&#x0020;<br>X</span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
div {
font: 20px/1 Ahem;
white-space: pre;
}
span { background: blue; }
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX&#x0020;<span>X&#x0020;<br>XXXX&#x0020;</span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
div {
font: 20px/1 Ahem;
white-space: pre;
}
span { background: blue; }
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX&#x0020;X<span>X&#x0020;<br>X</span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
div {
font: 20px/1 Ahem;
white-space: pre;
}
span { background: blue; }
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX&#x0020;X<span><br>X</span></div>
<!doctype html>
<meta charset=utf-8>
<meta http-equiv="content-language" content="en, ja" />
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<style>
div { white-space: pre; }
span { background: blue; }
</style>
<p>This test passes if the line is after the white space, which hangs (blue).
<div>ああ<span><br>X</span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
div {
font: 20px/1 Ahem;
white-space: pre;
}
span { background: blue; }
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX&#x0020;<span>X<br>XXXX</span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS test Reference</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
<style>
div {
font: 20px/1 Ahem;
white-space: pre;
}
span { background: blue; }
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX&#x0020;X<span>X<br>X</span></div>
...@@ -24,7 +24,5 @@ div { ...@@ -24,7 +24,5 @@ div {
<section class="ideo"> <section class="ideo">
<div><br></div> <div><br></div>
<div><br></div> <div><br></div>
<div><br></div>
<div><br></div>
</section> </section>
</body> </body>
<!doctype html>
<meta charset=utf-8>
<title>CSS Text test: hanging trailing spaces with white-space:pre-wrap</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com">
<link rel="help" title="3. White Space and Wrapping: the white-space property" href="https://drafts.csswg.org/css-text-3/#white-space-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
<link rel="help" title="5.2. Breaking Rules for Letters: the word-break property" href="https://drafts.csswg.org/css-text-3/#word-break-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-word-break-normal">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-012-ref.html">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-alt-012-ref.html">
<meta name="assert" content="Previous breaking opportunities are not considered if the overflow is caused by hanging trailing spaces.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<style>
div {
font: 20px/1 Ahem;
width: 4ch;
white-space: pre-wrap;
}
span { background: blue; } /* If the space is removed instead of hanging, there will be no blue box*/
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX X<span> X</span></div>
<!doctype html>
<meta charset=utf-8>
<meta http-equiv="content-language" content="en, ja" />
<title>CSS Text test: hanging trailing spaces with white-space:pre-wrap</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com">
<link rel="help" title="3. White Space and Wrapping: the white-space property" href="https://drafts.csswg.org/css-text-3/#white-space-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
<link rel="help" title="5.2. Breaking Rules for Letters: the word-break property" href="https://drafts.csswg.org/css-text-3/#word-break-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-word-break-normal">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-013-ref.html">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-alt-013-ref.html">
<meta name="assert" content="Previous breaking opportunities are not considered if the overflow is caused by hanging trailing spaces.">
<style>
div {
width: 2em;
white-space: pre-wrap;
}
span { background: blue; } /* If the space is removed instead of hanging, there will be no blue box*/
</style>
<p>This test passes if the line is after the white space, which hangs (blue).
<div>ああ<span>&#x0020;X<span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS Text test: hanging trailing spaces with white-space:pre-wrap</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com">
<link rel="help" title="3. White Space and Wrapping: the white-space property" href="https://drafts.csswg.org/css-text-3/#white-space-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
<link rel="help" title="5.2. Breaking Rules for Letters: the word-break property" href="https://drafts.csswg.org/css-text-3/#word-break-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-word-break-normal">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-014-ref.html">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-alt-014-ref.html">
<meta name="assert" content="Previous breaking opportunities are not considered if the overflow is caused by hanging trailing spaces.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<style>
div {
font: 20px/1 Ahem;
width: 4ch;
white-space: pre-wrap;
}
span { background: blue; } /* If the space is removed instead of hanging, there will be no blue box */
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX <span>X </span>X<span>XXX </span></div>
<!doctype html>
<meta charset=utf-8>
<title>CSS Text test: hanging trailing spaces with white-space:pre-wrap</title>
<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com">
<link rel="help" title="3. White Space and Wrapping: the white-space property" href="https://drafts.csswg.org/css-text-3/#white-space-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
<link rel="help" title="4.1.3. Phase II: Trimming and Positioning" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
<link rel="help" title="5.2. Breaking Rules for Letters: the word-break property" href="https://drafts.csswg.org/css-text-3/#word-break-property">
<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-word-break-normal">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-015-ref.html">
<link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-alt-015-ref.html">
<meta name="assert" content="Previous breaking opportunities are not considered if the overflow is caused by hanging trailing spaces.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<style>
div {
font: 20px/1 Ahem;
width: 5ch;
white-space: pre-wrap;
}
span { background: blue; } /* If the space is removed instead of hanging, there will be no blue box */
</style>
<p>This test passes if the line is broken after the 2nd white space, which hangs (blue).
<div>XX X<span>X X</span></div>
...@@ -35,7 +35,5 @@ div { ...@@ -35,7 +35,5 @@ div {
<section class="ideo"> <section class="ideo">
<div><span class="nowrap"></span></div> <div><span class="nowrap"></span></div>
<div><span class="nowrap"></span><span class="normal"></span></div> <div><span class="nowrap"></span><span class="normal"></span></div>
<div><span class="nowrap"><span class="normal"></span></span></div>
<div class="nowrap"><span class="normal"></span></div>
</section> </section>
</body> </body>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment