Commit ec0c017a authored by kojii's avatar kojii Committed by Commit Bot

[LayoutNG] Inline margin/border/padding, inter-item breaking, and tests

This patch implements inline margin/border/padding. NGTextFragment is
placed according to these properties. The border is not reflected to
fragment tree yet, adding NGBoxFragments will be in following patches.

This patch also supports when inter-item is not breakable. Inline
margin/border/padding belong to different NGInlineItem than its
text, but between them are not breakable.

Includes a fix for LazyLineBreakIterator where offset 0 should not be
breakable.

This patch also adds tests when inter-items are not breakable.

BUG=636993
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:linux_layout_tests_layout_ng

Review-Url: https://codereview.chromium.org/2865903002
Cr-Commit-Position: refs/heads/master@{#475799}
parent f14ac82c
...@@ -435,9 +435,6 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/margin-col ...@@ -435,9 +435,6 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/margin-col
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/margin-collapse-clear-014.xht [ Failure ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/margin-collapse-clear-014.xht [ Failure ]
### virtual/layout_ng/external/wpt/css/CSS2/linebox ### virtual/layout_ng/external/wpt/css/CSS2/linebox
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/border-padding-bleed-001.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/border-padding-bleed-002.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/border-padding-bleed-003.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/empty-inline-002.xht [ Crash Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/empty-inline-002.xht [ Crash Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-001.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-001.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-002.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-002.xht [ Failure ]
...@@ -449,7 +446,6 @@ crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatti ...@@ -449,7 +446,6 @@ crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatti
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-010b.xht [ Skip ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-010b.xht [ Skip ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-012.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-012.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-013.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-013.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-022.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-023.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/inline-formatting-context-023.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-002.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-002.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-004.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-004.xht [ Failure ]
...@@ -485,8 +481,6 @@ crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-125 ...@@ -485,8 +481,6 @@ crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-125
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-129.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-129.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-bleed-001.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-bleed-001.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-bleed-002.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/line-height-bleed-002.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-117a.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-118a.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-121.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-121.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-baseline-004a.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-baseline-004a.xht [ Failure ]
crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-baseline-005a.xht [ Failure ] crbug.com/636993 virtual/layout_ng/external/wpt/css/CSS2/linebox/vertical-align-baseline-005a.xht [ Failure ]
......
...@@ -1338,6 +1338,7 @@ source_set("unit_tests") { ...@@ -1338,6 +1338,7 @@ source_set("unit_tests") {
"layout/ng/inline/ng_inline_items_builder_test.cc", "layout/ng/inline/ng_inline_items_builder_test.cc",
"layout/ng/inline/ng_inline_layout_algorithm_test.cc", "layout/ng/inline/ng_inline_layout_algorithm_test.cc",
"layout/ng/inline/ng_inline_node_test.cc", "layout/ng/inline/ng_inline_node_test.cc",
"layout/ng/inline/ng_line_breaker_test.cc",
"layout/ng/ng_absolute_utils_test.cc", "layout/ng/ng_absolute_utils_test.cc",
"layout/ng/ng_base_layout_algorithm_test.cc", "layout/ng/ng_base_layout_algorithm_test.cc",
"layout/ng/ng_base_layout_algorithm_test.h", "layout/ng/ng_base_layout_algorithm_test.h",
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "core/layout/ng/inline/ng_inline_item.h" #include "core/layout/ng/inline/ng_inline_item.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutObject.h" #include "core/layout/LayoutObject.h"
#include "platform/fonts/CharacterRange.h" #include "platform/fonts/CharacterRange.h"
#include "platform/fonts/shaping/ShapeResultBuffer.h" #include "platform/fonts/shaping/ShapeResultBuffer.h"
...@@ -123,6 +124,18 @@ void NGInlineItem::GetFallbackFonts( ...@@ -123,6 +124,18 @@ void NGInlineItem::GetFallbackFonts(
shape_result_->FallbackFonts(fallback_fonts); shape_result_->FallbackFonts(fallback_fonts);
} }
bool NGInlineItem::HasStartEdge() const {
DCHECK(Type() == kOpenTag || Type() == kCloseTag);
// TODO(kojii): Should use break token when NG has its own tree building.
return !GetLayoutObject()->IsInlineElementContinuation();
}
bool NGInlineItem::HasEndEdge() const {
DCHECK(Type() == kOpenTag || Type() == kCloseTag);
// TODO(kojii): Should use break token when NG has its own tree building.
return !ToLayoutInline(GetLayoutObject())->Continuation();
}
NGInlineItemRange::NGInlineItemRange(Vector<NGInlineItem>* items, NGInlineItemRange::NGInlineItemRange(Vector<NGInlineItem>* items,
unsigned start_index, unsigned start_index,
unsigned end_index) unsigned end_index)
......
...@@ -93,6 +93,9 @@ class NGInlineItem { ...@@ -93,6 +93,9 @@ class NGInlineItem {
unsigned start, unsigned start,
unsigned end) const; unsigned end) const;
bool HasStartEdge() const;
bool HasEndEdge() const;
static void Split(Vector<NGInlineItem>&, unsigned index, unsigned offset); static void Split(Vector<NGInlineItem>&, unsigned index, unsigned offset);
static unsigned SetBidiLevel(Vector<NGInlineItem>&, static unsigned SetBidiLevel(Vector<NGInlineItem>&,
unsigned index, unsigned index,
......
...@@ -11,6 +11,10 @@ NGInlineItemResult::NGInlineItemResult() {} ...@@ -11,6 +11,10 @@ NGInlineItemResult::NGInlineItemResult() {}
NGInlineItemResult::NGInlineItemResult(unsigned index, NGInlineItemResult::NGInlineItemResult(unsigned index,
unsigned start, unsigned start,
unsigned end) unsigned end)
: item_index(index), start_offset(start), end_offset(end) {} : item_index(index),
start_offset(start),
end_offset(end),
no_break_opportunities_inside(false),
prohibit_break_after(false) {}
} // namespace blink } // namespace blink
...@@ -42,6 +42,14 @@ struct CORE_EXPORT NGInlineItemResult { ...@@ -42,6 +42,14 @@ struct CORE_EXPORT NGInlineItemResult {
// NGBoxStrut for atomic inline items. // NGBoxStrut for atomic inline items.
NGBoxStrut margins; NGBoxStrut margins;
// Inside of this is not breakable.
// Used only during line breaking.
unsigned no_break_opportunities_inside : 1;
// Lines must not break after this.
// Used only during line breaking.
unsigned prohibit_break_after : 1;
NGInlineItemResult(); NGInlineItemResult();
NGInlineItemResult(unsigned index, unsigned start, unsigned end); NGInlineItemResult(unsigned index, unsigned start, unsigned end);
}; };
......
...@@ -271,8 +271,10 @@ bool NGInlineLayoutAlgorithm::PlaceItems( ...@@ -271,8 +271,10 @@ bool NGInlineLayoutAlgorithm::PlaceItems(
borders.BlockSum() + paddings.BlockSum()); borders.BlockSum() + paddings.BlockSum());
} }
} else if (item.Type() == NGInlineItem::kCloseTag) { } else if (item.Type() == NGInlineItem::kCloseTag) {
position += item_result.inline_size;
box = box_states_.OnCloseTag(item, &line_box, box, baseline_type_, box = box_states_.OnCloseTag(item, &line_box, box, baseline_type_,
position); position);
continue;
} else if (item.Type() == NGInlineItem::kAtomicInline) { } else if (item.Type() == NGInlineItem::kAtomicInline) {
box = PlaceAtomicInline(item, &item_result, position, &line_box, box = PlaceAtomicInline(item, &item_result, position, &line_box,
&text_builder); &text_builder);
......
...@@ -93,6 +93,8 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode { ...@@ -93,6 +93,8 @@ class CORE_EXPORT NGInlineNode : public NGLayoutInputNode {
LayoutObject* start_inline_; LayoutObject* start_inline_;
LayoutNGBlockFlow* block_; LayoutNGBlockFlow* block_;
Member<NGLayoutInputNode> next_sibling_; Member<NGLayoutInputNode> next_sibling_;
friend class NGLineBreakerTest;
}; };
inline void NGInlineNode::AssertOffset(unsigned index, unsigned offset) const { inline void NGInlineNode::AssertOffset(unsigned index, unsigned offset) const {
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
#include "core/style/ComputedStyle.h" #include "core/style/ComputedStyle.h"
#include "platform/fonts/shaping/HarfBuzzShaper.h" #include "platform/fonts/shaping/HarfBuzzShaper.h"
#include "platform/fonts/shaping/ShapingLineBreaker.h" #include "platform/fonts/shaping/ShapingLineBreaker.h"
#include "platform/text/TextBreakIterator.h"
namespace blink { namespace blink {
...@@ -28,19 +27,7 @@ namespace { ...@@ -28,19 +27,7 @@ namespace {
#if defined(MOCK_SHAPE_LINE) #if defined(MOCK_SHAPE_LINE)
// The mock for ShapingLineBreaker::ShapeLine(). // The mock for ShapingLineBreaker::ShapeLine().
// Given the design of ShapingLineBreaker, expected semantics are: // See BreakText() for the expected semantics.
// - The returned offset is always > item.StartOffset().
// - offset < item.EndOffset():
// - width <= available_width: the break opportunity to fit is found.
// - width > available_width: the first break opportunity did not fit.
// - offset == item.EndOffset():
// - width <= available_width: the break opportunity at the end of the item
// fits.
// - width > available_width: the first break opportunity is at the end of
// the item and it does not fit.
// - offset > item.EndOffset():, the first break opportunity is beyond the
// end of item and thus cannot measure. In this case, inline_size shows the
// width until the end of the item. It may fit or may not.
std::pair<unsigned, LayoutUnit> ShapeLineMock( std::pair<unsigned, LayoutUnit> ShapeLineMock(
const NGInlineItem& item, const NGInlineItem& item,
unsigned offset, unsigned offset,
...@@ -67,23 +54,16 @@ std::pair<unsigned, LayoutUnit> ShapeLineMock( ...@@ -67,23 +54,16 @@ std::pair<unsigned, LayoutUnit> ShapeLineMock(
} }
#endif #endif
LineBreakType GetLineBreakType(const ComputedStyle& style) {
if (style.AutoWrap()) {
if (style.WordBreak() == EWordBreak::kBreakAll ||
style.WordBreak() == EWordBreak::kBreakWord)
return LineBreakType::kBreakAll;
if (style.WordBreak() == EWordBreak::kKeepAll)
return LineBreakType::kKeepAll;
}
return LineBreakType::kNormal;
}
} // namespace } // namespace
NGLineBreaker::NGLineBreaker(NGInlineNode* node, NGLineBreaker::NGLineBreaker(NGInlineNode* node,
const NGConstraintSpace* space, const NGConstraintSpace* space,
NGInlineBreakToken* break_token) NGInlineBreakToken* break_token)
: node_(node), constraint_space_(space), item_index_(0), offset_(0) { : node_(node),
constraint_space_(space),
item_index_(0),
offset_(0),
break_iterator_(node->Text()) {
if (break_token) { if (break_token) {
item_index_ = break_token->ItemIndex(); item_index_ = break_token->ItemIndex();
offset_ = break_token->TextOffset(); offset_ = break_token->TextOffset();
...@@ -106,130 +86,169 @@ void NGLineBreaker::BreakLine(NGInlineItemResults* item_results, ...@@ -106,130 +86,169 @@ void NGLineBreaker::BreakLine(NGInlineItemResults* item_results,
NGInlineLayoutAlgorithm* algorithm) { NGInlineLayoutAlgorithm* algorithm) {
DCHECK(item_results->IsEmpty()); DCHECK(item_results->IsEmpty());
const Vector<NGInlineItem>& items = node_->Items(); const Vector<NGInlineItem>& items = node_->Items();
const String& text = node_->Text();
const ComputedStyle& style = node_->Style(); const ComputedStyle& style = node_->Style();
LazyLineBreakIterator break_iterator(text, style.LocaleForLineBreakIterator(), UpdateBreakIterator(style);
GetLineBreakType(style));
#if !defined(MOCK_SHAPE_LINE) #if !defined(MOCK_SHAPE_LINE)
// TODO(kojii): Instantiate in the constructor.
HarfBuzzShaper shaper(text.Characters16(), text.length()); HarfBuzzShaper shaper(text.Characters16(), text.length());
#endif #endif
LayoutUnit available_width = algorithm->AvailableWidth(); available_width_ = algorithm->AvailableWidth();
LayoutUnit position; position_ = LayoutUnit(0);
LineBreakState state = LineBreakState::kNotBreakable;
while (item_index_ < items.size()) { while (item_index_ < items.size()) {
// CloseTag prohibits to break before.
const NGInlineItem& item = items[item_index_]; const NGInlineItem& item = items[item_index_];
if (item.Type() == NGInlineItem::kCloseTag) {
item_results->push_back(
NGInlineItemResult(item_index_, offset_, item.EndOffset()));
HandleCloseTag(item, &item_results->back());
continue;
}
if (state == LineBreakState::kBreakAfterTrailings)
return;
if (state == LineBreakState::kIsBreakable && position_ > available_width_)
return HandleOverflow(item_results);
item_results->push_back( item_results->push_back(
NGInlineItemResult(item_index_, offset_, item.EndOffset())); NGInlineItemResult(item_index_, offset_, item.EndOffset()));
NGInlineItemResult* item_result = &item_results->back(); NGInlineItemResult* item_result = &item_results->back();
if (item.Type() == NGInlineItem::kText) {
state = HandleText(item, item_result);
} else if (item.Type() == NGInlineItem::kAtomicInline) {
state = HandleAtomicInline(item, item_result);
} else if (item.Type() == NGInlineItem::kControl) {
state = HandleControlItem(item, item_result);
if (state == LineBreakState::kForcedBreak)
return;
} else if (item.Type() == NGInlineItem::kOpenTag) {
HandleOpenTag(item, item_result);
state = LineBreakState::kNotBreakable;
} else if (item.Type() == NGInlineItem::kFloating) {
HandleFloat(item, item_results, algorithm);
} else {
MoveToNextOf(item);
}
}
if (state == LineBreakState::kIsBreakable && position_ > available_width_)
return HandleOverflow(item_results);
}
// If the start offset is at the item boundary, try to add the entire item. NGLineBreaker::LineBreakState NGLineBreaker::HandleText(
if (offset_ == item.StartOffset()) { const NGInlineItem& item,
if (item.Type() == NGInlineItem::kText) { NGInlineItemResult* item_result) {
item_result->inline_size = item.InlineSize(); DCHECK_EQ(item.Type(), NGInlineItem::kText);
} else if (item.Type() == NGInlineItem::kAtomicInline) {
LayoutAtomicInline(item, item_result); // If the start offset is at the item boundary, try to add the entire item.
} else if (item.Type() == NGInlineItem::kControl) { if (offset_ == item.StartOffset()) {
if (HandleControlItem(item, text, item_result, position)) { item_result->inline_size = item.InlineSize();
MoveToNextOf(item); LayoutUnit next_position = position_ + item_result->inline_size;
break; if (!auto_wrap_ || next_position <= available_width_) {
} position_ = next_position;
} else if (item.Type() == NGInlineItem::kFloating) { MoveToNextOf(item);
algorithm->LayoutAndPositionFloat(position, item.GetLayoutObject()); if (auto_wrap_ && break_iterator_.IsBreakable(item.EndOffset()))
// Floats may change the available width if they fit. return LineBreakState::kIsBreakable;
available_width = algorithm->AvailableWidth(); item_result->prohibit_break_after = true;
// Floats are already positioned in the container_builder. return LineBreakState::kNotBreakable;
item_results->pop_back(); }
MoveToNextOf(item); }
continue;
} else {
MoveToNextOf(item);
continue;
}
LayoutUnit next_position = position + item_result->inline_size;
if (next_position <= available_width) {
MoveToNextOf(item);
position = next_position;
continue;
}
// The entire item does not fit. Handle non-text items as overflow, if (auto_wrap_) {
// since only text item is breakable. // Try to break inside of this text item.
if (item.Type() != NGInlineItem::kText) { BreakText(item_result, item, available_width_ - position_);
MoveToNextOf(item); position_ += item_result->inline_size;
return HandleOverflow(item_results, break_iterator);
} bool is_overflow = position_ > available_width_;
item_result->no_break_opportunities_inside = is_overflow;
if (item_result->end_offset < item.EndOffset()) {
offset_ = item_result->end_offset;
return is_overflow ? LineBreakState::kIsBreakable
: LineBreakState::kBreakAfterTrailings;
} }
MoveToNextOf(item);
return item_result->prohibit_break_after ? LineBreakState::kNotBreakable
: LineBreakState::kIsBreakable;
}
// Add the rest of the item if !auto_wrap.
// Because the start position may need to reshape, run ShapingLineBreaker
// with max available width.
DCHECK_NE(offset_, item.StartOffset());
BreakText(item_result, item, LayoutUnit::Max());
DCHECK_EQ(item_result->end_offset, item.EndOffset());
item_result->no_break_opportunities_inside = true;
item_result->prohibit_break_after = true;
position_ += item_result->inline_size;
MoveToNextOf(item);
return LineBreakState::kNotBreakable;
}
void NGLineBreaker::BreakText(NGInlineItemResult* item_result,
const NGInlineItem& item,
LayoutUnit available_width) {
DCHECK_EQ(item.Type(), NGInlineItem::kText);
item.AssertOffset(item_result->start_offset);
// Either the start or the break is in the mid of a text item.
DCHECK_EQ(item.Type(), NGInlineItem::kText);
DCHECK_LT(offset_, item.EndOffset());
break_iterator.SetLocale(item.Style()->LocaleForLineBreakIterator());
break_iterator.SetBreakType(GetLineBreakType(*item.Style()));
#if defined(MOCK_SHAPE_LINE) #if defined(MOCK_SHAPE_LINE)
unsigned break_offset; std::tie(item_result->end_offset, item_result->inline_size) = ShapeLineMock(
std::tie(break_offset, item_result->inline_size) = ShapeLineMock( item, item_result->start_offset, available_width, break_iterator_);
item, offset_, available_width - position, break_iterator);
#else #else
// TODO(kojii): We need to instantiate ShapingLineBreaker here because it // TODO(kojii): We need to instantiate ShapingLineBreaker here because it
// has item-specific info as context. Should they be part of ShapeLine() to // has item-specific info as context. Should they be part of ShapeLine() to
// instantiate once, or is this just fine since instatiation is not // instantiate once, or is this just fine since instatiation is not
// expensive? // expensive?
DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(), DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(), item.StartOffset());
item.StartOffset()); DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset());
DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset()); ShapingLineBreaker breaker(&shaper, &item.Style()->GetFont(),
ShapingLineBreaker breaker(&shaper, &item.Style()->GetFont(), item.TextShapeResult(), break_iterator_);
item.TextShapeResult(), &break_iterator); item_result->shape_result = breaker.ShapeLine(
unsigned break_offset; item_result->start_offset, available_width, &item_result->end_offset);
item_result->shape_result = item_result->inline_size = item_result->shape_result->SnappedWidth();
breaker.ShapeLine(offset_, available_width - position, &break_offset);
item_result->inline_size = item_result->shape_result->SnappedWidth();
#endif #endif
DCHECK_GT(break_offset, offset_); DCHECK_GT(item_result->end_offset, item_result->start_offset);
position += item_result->inline_size; // * If width <= available_width:
// * If offset < item.EndOffset(): the break opportunity to fit is found.
// If the break found within the item, break here. // * If offset == item.EndOffset(): the break opportunity at the end fits.
if (break_offset < item.EndOffset()) { // There may be room for more characters.
offset_ = item_result->end_offset = break_offset; // * If offset > item.EndOffset(): the first break opportunity is beyond
if (position <= available_width) // the end. There may be room for more characters.
break; // * If width > available_width: The first break opporunity does not fit.
// The first break opportunity of the item does not fit. // offset is the first break opportunity, either inside, at the end, or
} else { // beyond the end.
// No break opporunity in the item, or the first break opportunity is at if (item_result->end_offset <= item.EndOffset()) {
// the end of the item. If it fits, continue to the next item. item_result->prohibit_break_after = false;
item_result->end_offset = item.EndOffset(); } else {
MoveToNextOf(item); item_result->prohibit_break_after = true;
if (position <= available_width) item_result->end_offset = item.EndOffset();
continue;
}
// We need to look at next item if we're overflowing, and the break
// opportunity is beyond this item.
if (break_offset > item.EndOffset())
continue;
return HandleOverflow(item_results, break_iterator);
} }
} }
// Measure control items; new lines and tab, that are similar to text, affect // Measure control items; new lines and tab, that are similar to text, affect
// layout, but do not need shaping/painting. // layout, but do not need shaping/painting.
bool NGLineBreaker::HandleControlItem(const NGInlineItem& item, NGLineBreaker::LineBreakState NGLineBreaker::HandleControlItem(
const String& text, const NGInlineItem& item,
NGInlineItemResult* item_result, NGInlineItemResult* item_result) {
LayoutUnit position) {
DCHECK_EQ(item.Length(), 1u); DCHECK_EQ(item.Length(), 1u);
UChar character = text[item.StartOffset()]; UChar character = node_->Text()[item.StartOffset()];
if (character == kNewlineCharacter) if (character == kNewlineCharacter) {
return true; MoveToNextOf(item);
return LineBreakState::kForcedBreak;
}
DCHECK_EQ(character, kTabulationCharacter); DCHECK_EQ(character, kTabulationCharacter);
DCHECK(item.Style()); DCHECK(item.Style());
const ComputedStyle& style = *item.Style(); const ComputedStyle& style = *item.Style();
const Font& font = style.GetFont(); const Font& font = style.GetFont();
item_result->inline_size = font.TabWidth(style.GetTabSize(), position); item_result->inline_size = font.TabWidth(style.GetTabSize(), position_);
return false; position_ += item_result->inline_size;
MoveToNextOf(item);
// TODO(kojii): Implement break around the tab character.
return LineBreakState::kIsBreakable;
} }
void NGLineBreaker::LayoutAtomicInline(const NGInlineItem& item, NGLineBreaker::LineBreakState NGLineBreaker::HandleAtomicInline(
NGInlineItemResult* item_result) { const NGInlineItem& item,
NGInlineItemResult* item_result) {
DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline); DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline);
NGBlockNode* node = new NGBlockNode(item.GetLayoutObject()); NGBlockNode* node = new NGBlockNode(item.GetLayoutObject());
const ComputedStyle& style = node->Style(); const ComputedStyle& style = node->Style();
...@@ -251,65 +270,158 @@ void NGLineBreaker::LayoutAtomicInline(const NGInlineItem& item, ...@@ -251,65 +270,158 @@ void NGLineBreaker::LayoutAtomicInline(const NGInlineItem& item,
ComputeMargins(*constraint_space_, style, ComputeMargins(*constraint_space_, style,
constraint_space_->WritingMode(), style.Direction()); constraint_space_->WritingMode(), style.Direction());
item_result->inline_size += item_result->margins.InlineSum(); item_result->inline_size += item_result->margins.InlineSum();
position_ += item_result->inline_size;
MoveToNextOf(item);
if (auto_wrap_)
return LineBreakState::kIsBreakable;
item_result->prohibit_break_after = true;
return LineBreakState::kNotBreakable;
}
void NGLineBreaker::HandleFloat(const NGInlineItem& item,
NGInlineItemResults* item_results,
NGInlineLayoutAlgorithm* algorithm) {
algorithm->LayoutAndPositionFloat(position_, item.GetLayoutObject());
// Floats may change the available width if they fit.
available_width_ = algorithm->AvailableWidth();
// Floats are already positioned in the container_builder.
item_results->pop_back();
MoveToNextOf(item);
}
void NGLineBreaker::HandleOpenTag(const NGInlineItem& item,
NGInlineItemResult* item_result) {
if (item.HasStartEdge()) {
DCHECK(item.Style());
// TODO(kojii): We compute 16 values and discard 12 out of that, and do it 3
// times per element. We may want to cache this. crrev.com/2865903002/#msg14
NGBoxStrut margins = ComputeMargins(*constraint_space_, *item.Style(),
constraint_space_->WritingMode(),
constraint_space_->Direction());
NGBoxStrut borders = ComputeBorders(*constraint_space_, *item.Style());
NGBoxStrut paddings = ComputePadding(*constraint_space_, *item.Style());
item_result->inline_size =
margins.inline_start + borders.inline_start + paddings.inline_start;
position_ += item_result->inline_size;
}
UpdateBreakIterator(*item.Style());
MoveToNextOf(item);
}
void NGLineBreaker::HandleCloseTag(const NGInlineItem& item,
NGInlineItemResult* item_result) {
if (item.HasEndEdge()) {
DCHECK(item.Style());
NGBoxStrut margins = ComputeMargins(*constraint_space_, *item.Style(),
constraint_space_->WritingMode(),
constraint_space_->Direction());
NGBoxStrut borders = ComputeBorders(*constraint_space_, *item.Style());
NGBoxStrut paddings = ComputePadding(*constraint_space_, *item.Style());
item_result->inline_size =
margins.inline_end + borders.inline_end + paddings.inline_end;
position_ += item_result->inline_size;
}
DCHECK(item.GetLayoutObject() && item.GetLayoutObject()->Parent());
UpdateBreakIterator(item.GetLayoutObject()->Parent()->StyleRef());
MoveToNextOf(item);
} }
// Handles when the last item overflows. // Handles when the last item overflows.
// At this point, item_results does not fit into the current line, and there // At this point, item_results does not fit into the current line, and there
// are no break opportunities in item_results.back(). // are no break opportunities in item_results.back().
void NGLineBreaker::HandleOverflow( void NGLineBreaker::HandleOverflow(NGInlineItemResults* item_results) {
NGInlineItemResults* item_results,
const LazyLineBreakIterator& break_iterator) {
DCHECK_GT(offset_, 0u);
// Find the last break opportunity. If none, let this line overflow.
unsigned line_start_offset = item_results->front().start_offset;
unsigned break_offset =
break_iterator.PreviousBreakOpportunity(offset_ - 1, line_start_offset);
if (!break_offset || break_offset <= line_start_offset) {
AppendCloseTags(item_results);
return;
}
// Truncate the end of the line to the break opportunity.
const Vector<NGInlineItem>& items = node_->Items(); const Vector<NGInlineItem>& items = node_->Items();
unsigned new_end = item_results->size(); LayoutUnit rewind_width = available_width_ - position_;
while (true) { DCHECK_LT(rewind_width, 0);
NGInlineItemResult* item_result = &(*item_results)[--new_end];
if (item_result->start_offset < break_offset) { // Search for a break opportunity that can fit.
// The break is at the mid of the item. Adjust the end_offset to the new // Also keep track of the first break opportunity in case of overflow.
// break offset. unsigned break_before = 0;
const NGInlineItem& item = items[item_result->item_index]; unsigned break_before_if_before_allow = 0;
item.AssertEndOffset(break_offset); LayoutUnit rewind_width_if_before_allow;
DCHECK_EQ(item.Type(), NGInlineItem::kText); bool last_item_prohibits_break_before = true;
DCHECK_NE(item_result->end_offset, break_offset); for (unsigned i = item_results->size(); i;) {
item_result->end_offset = break_offset; NGInlineItemResult* item_result = &(*item_results)[--i];
item_result->inline_size = const NGInlineItem& item = items[item_result->item_index];
item.InlineSize(item_result->start_offset, item_result->end_offset); rewind_width += item_result->inline_size;
// TODO(kojii): May need to reshape. Add to ShapingLineBreaker? if (item.Type() == NGInlineItem::kText ||
new_end++; item.Type() == NGInlineItem::kAtomicInline) {
break; // Try to break inside of this item.
} if (item.Type() == NGInlineItem::kText && rewind_width >= 0 &&
if (item_result->start_offset == break_offset) { !item_result->no_break_opportunities_inside) {
// The new break offset is at the item boundary. Remove items up to the // When the text fits but its right margin does not, the break point
// new break offset. // must not be at the end.
// TODO(kojii): Remove open tags as well. LayoutUnit item_available_width =
break; std::min(rewind_width, item_result->inline_size - 1);
BreakText(item_result, item, item_available_width);
if (item_result->inline_size <= item_available_width) {
DCHECK_LT(item_result->end_offset, item.EndOffset());
DCHECK(!item_result->prohibit_break_after);
return Rewind(item_results, i + 1);
}
if (!item_result->prohibit_break_after &&
!last_item_prohibits_break_before) {
break_before = i + 1;
}
}
// Try to break after this item.
if (break_before_if_before_allow && !item_result->prohibit_break_after) {
if (rewind_width_if_before_allow >= 0)
return Rewind(item_results, break_before_if_before_allow);
break_before = break_before_if_before_allow;
}
// Otherwise, before this item is a possible break point.
break_before_if_before_allow = i;
rewind_width_if_before_allow = rewind_width;
last_item_prohibits_break_before = false;
} else if (item.Type() == NGInlineItem::kCloseTag) {
last_item_prohibits_break_before = true;
} else {
if (i + 1 == break_before_if_before_allow) {
break_before_if_before_allow = i;
rewind_width_if_before_allow = rewind_width;
}
last_item_prohibits_break_before = false;
} }
} }
DCHECK_GT(new_end, 0u);
// The rewind point did not found, let this line overflow.
// If there was a break opporunity, the overflow should stop there.
if (break_before)
Rewind(item_results, break_before);
}
void NGLineBreaker::Rewind(NGInlineItemResults* item_results,
unsigned new_end) {
// TODO(kojii): Should we keep results for the next line? We don't need to // TODO(kojii): Should we keep results for the next line? We don't need to
// re-layout atomic inlines. // re-layout atomic inlines.
// TODO(kojii): Removing processed floats is likely a problematic. Keep // TODO(kojii): Removing processed floats is likely a problematic. Keep
// floats in this line, or keep it for the next line. // floats in this line, or keep it for the next line.
item_results->Shrink(new_end); item_results->Shrink(new_end);
// Update the current item index and offset to the new break point. MoveToNextOf(item_results->back());
const NGInlineItemResult& last_item_result = item_results->back(); }
offset_ = last_item_result.end_offset;
item_index_ = last_item_result.item_index; void NGLineBreaker::UpdateBreakIterator(const ComputedStyle& style) {
if (items[item_index_].EndOffset() == offset_) auto_wrap_ = style.AutoWrap();
item_index_++;
if (auto_wrap_) {
break_iterator_.SetLocale(style.LocaleForLineBreakIterator());
if (style.WordBreak() == EWordBreak::kBreakAll ||
style.WordBreak() == EWordBreak::kBreakWord) {
break_iterator_.SetBreakType(LineBreakType::kBreakAll);
} else if (style.WordBreak() == EWordBreak::kKeepAll) {
break_iterator_.SetBreakType(LineBreakType::kKeepAll);
} else {
break_iterator_.SetBreakType(LineBreakType::kNormal);
}
// TODO(kojii): Implement word-wrap/overflow-wrap property
}
} }
void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) { void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) {
...@@ -318,6 +430,14 @@ void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) { ...@@ -318,6 +430,14 @@ void NGLineBreaker::MoveToNextOf(const NGInlineItem& item) {
item_index_++; item_index_++;
} }
void NGLineBreaker::MoveToNextOf(const NGInlineItemResult& item_result) {
offset_ = item_result.end_offset;
item_index_ = item_result.item_index;
const NGInlineItem& item = node_->Items()[item_result.item_index];
if (offset_ == item.EndOffset())
item_index_++;
}
void NGLineBreaker::SkipCollapsibleWhitespaces() { void NGLineBreaker::SkipCollapsibleWhitespaces() {
const Vector<NGInlineItem>& items = node_->Items(); const Vector<NGInlineItem>& items = node_->Items();
if (item_index_ >= items.size()) if (item_index_ >= items.size())
...@@ -336,17 +456,6 @@ void NGLineBreaker::SkipCollapsibleWhitespaces() { ...@@ -336,17 +456,6 @@ void NGLineBreaker::SkipCollapsibleWhitespaces() {
} }
} }
void NGLineBreaker::AppendCloseTags(NGInlineItemResults* item_results) {
const Vector<NGInlineItem>& items = node_->Items();
for (; item_index_ < items.size(); item_index_++) {
const NGInlineItem& item = items[item_index_];
if (item.Type() != NGInlineItem::kCloseTag)
break;
DCHECK_EQ(offset_, item.EndOffset());
item_results->push_back(NGInlineItemResult(item_index_, offset_, offset_));
}
}
RefPtr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken() const { RefPtr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken() const {
const Vector<NGInlineItem>& items = node_->Items(); const Vector<NGInlineItem>& items = node_->Items();
if (item_index_ >= items.size()) if (item_index_ >= items.size())
......
...@@ -8,11 +8,12 @@ ...@@ -8,11 +8,12 @@
#include "core/CoreExport.h" #include "core/CoreExport.h"
#include "core/layout/ng/inline/ng_inline_item_result.h" #include "core/layout/ng/inline/ng_inline_item_result.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "platform/text/TextBreakIterator.h"
#include "platform/wtf/Allocator.h"
#include "platform/wtf/text/AtomicString.h" #include "platform/wtf/text/AtomicString.h"
namespace blink { namespace blink {
class LazyLineBreakIterator;
class NGInlineBreakToken; class NGInlineBreakToken;
class NGInlineItem; class NGInlineItem;
class NGInlineNode; class NGInlineNode;
...@@ -43,24 +44,50 @@ class CORE_EXPORT NGLineBreaker { ...@@ -43,24 +44,50 @@ class CORE_EXPORT NGLineBreaker {
private: private:
void BreakLine(NGInlineItemResults*, NGInlineLayoutAlgorithm*); void BreakLine(NGInlineItemResults*, NGInlineLayoutAlgorithm*);
bool HandleControlItem(const NGInlineItem&, enum class LineBreakState {
const String& text, // The current position is not breakable.
NGInlineItemResult*, kNotBreakable,
LayoutUnit position); // The current position is breakable.
void LayoutAtomicInline(const NGInlineItem&, NGInlineItemResult*); kIsBreakable,
// Break by including trailing items (CloseTag).
kBreakAfterTrailings,
// Break immediately.
kForcedBreak
};
void HandleOverflow(NGInlineItemResults*, const LazyLineBreakIterator&); LineBreakState HandleText(const NGInlineItem&, NGInlineItemResult*);
void BreakText(NGInlineItemResult*,
const NGInlineItem&,
LayoutUnit available_width);
LineBreakState HandleControlItem(const NGInlineItem&, NGInlineItemResult*);
LineBreakState HandleAtomicInline(const NGInlineItem&, NGInlineItemResult*);
void HandleFloat(const NGInlineItem&,
NGInlineItemResults*,
NGInlineLayoutAlgorithm*);
void HandleOpenTag(const NGInlineItem&, NGInlineItemResult*);
void HandleCloseTag(const NGInlineItem&, NGInlineItemResult*);
void HandleOverflow(NGInlineItemResults*);
void Rewind(NGInlineItemResults*, unsigned new_end);
void UpdateBreakIterator(const ComputedStyle&);
void MoveToNextOf(const NGInlineItem&); void MoveToNextOf(const NGInlineItem&);
void MoveToNextOf(const NGInlineItemResult&);
void SkipCollapsibleWhitespaces(); void SkipCollapsibleWhitespaces();
void AppendCloseTags(NGInlineItemResults*);
Persistent<NGInlineNode> node_; Persistent<NGInlineNode> node_;
const NGConstraintSpace* constraint_space_; const NGConstraintSpace* constraint_space_;
const AtomicString locale_; const AtomicString locale_;
unsigned item_index_; unsigned item_index_;
unsigned offset_; unsigned offset_;
LayoutUnit available_width_;
LayoutUnit position_;
LazyLineBreakIterator break_iterator_;
unsigned auto_wrap_ : 1;
}; };
} // namespace blink } // namespace blink
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/layout/ng/ng_base_layout_algorithm_test.h"
#include "core/layout/ng/inline/ng_inline_layout_algorithm.h"
#include "core/layout/ng/inline/ng_inline_node.h"
#include "core/layout/ng/inline/ng_line_breaker.h"
#include "core/layout/ng/layout_ng_block_flow.h"
#include "core/layout/ng/ng_constraint_space_builder.h"
#include "platform/wtf/text/StringBuilder.h"
namespace blink {
class NGLineBreakerTest : public NGBaseLayoutAlgorithmTest {
protected:
NGInlineNode* CreateInlineNode(const String& html_content) {
SetBodyInnerHTML(html_content);
LayoutNGBlockFlow* block_flow =
ToLayoutNGBlockFlow(GetLayoutObjectByElementId("container"));
NGInlineNode* inline_node =
new NGInlineNode(block_flow->FirstChild(), block_flow);
return inline_node;
}
// Break lines using the specified available width.
Vector<NGInlineItemResults> BreakLines(NGInlineNode* node,
LayoutUnit available_width) {
if (!node->IsPrepareLayoutFinished())
node->PrepareLayout();
RefPtr<NGConstraintSpace> space =
NGConstraintSpaceBuilder(NGWritingMode::kHorizontalTopBottom)
.SetAvailableSize({available_width, NGSizeIndefinite})
.ToConstraintSpace(NGWritingMode::kHorizontalTopBottom);
NGLineBreaker line_breaker(node, space.Get());
NGInlineLayoutAlgorithm algorithm(node, space.Get());
Vector<NGInlineItemResults> lines;
while (true) {
NGInlineItemResults item_results;
line_breaker.NextLine(&item_results, &algorithm);
if (item_results.IsEmpty())
break;
lines.push_back(item_results);
}
return lines;
}
};
namespace {
String ToString(NGInlineItemResults line, NGInlineNode* node) {
StringBuilder builder;
for (const auto& item_result : line) {
builder.Append(
node->Text(item_result.start_offset, item_result.end_offset));
}
return builder.ToString();
}
TEST_F(NGLineBreakerTest, SingleNode) {
LoadAhem();
NGInlineNode* node = CreateInlineNode(R"HTML(
<!DOCTYPE html>
<style>
#container {
font: 10px/1 Ahem;
}
</style>
<div id=container>123 456 789</div>
)HTML");
Vector<NGInlineItemResults> lines;
lines = BreakLines(node, LayoutUnit(80));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("123 456", ToString(lines[0], node));
EXPECT_EQ("789", ToString(lines[1], node));
lines = BreakLines(node, LayoutUnit(60));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("123", ToString(lines[0], node));
EXPECT_EQ("456", ToString(lines[1], node));
EXPECT_EQ("789", ToString(lines[2], node));
}
TEST_F(NGLineBreakerTest, OverflowWord) {
LoadAhem();
NGInlineNode* node = CreateInlineNode(R"HTML(
<!DOCTYPE html>
<style>
#container {
font: 10px/1 Ahem;
}
</style>
<div id=container>12345 678</div>
)HTML");
// The first line overflows, but the last line does not.
Vector<NGInlineItemResults> lines;
lines = BreakLines(node, LayoutUnit(40));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("12345", ToString(lines[0], node));
EXPECT_EQ("678", ToString(lines[1], node));
// Both lines overflow.
lines = BreakLines(node, LayoutUnit(20));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("12345", ToString(lines[0], node));
EXPECT_EQ("678", ToString(lines[1], node));
}
TEST_F(NGLineBreakerTest, OverflowAtomicInline) {
LoadAhem();
NGInlineNode* node = CreateInlineNode(R"HTML(
<!DOCTYPE html>
<style>
#container {
font: 10px/1 Ahem;
}
span {
display: inline-block;
width: 30px;
height: 10px;
}
</style>
<div id=container>12345<span></span>678</div>
)HTML");
Vector<NGInlineItemResults> lines;
lines = BreakLines(node, LayoutUnit(80));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ(String(u"12345\uFFFC"), ToString(lines[0], node));
EXPECT_EQ("678", ToString(lines[1], node));
lines = BreakLines(node, LayoutUnit(70));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("12345", ToString(lines[0], node));
EXPECT_EQ(String(u"\uFFFC678"), ToString(lines[1], node));
lines = BreakLines(node, LayoutUnit(40));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("12345", ToString(lines[0], node));
EXPECT_EQ(String(u"\uFFFC"), ToString(lines[1], node));
EXPECT_EQ("678", ToString(lines[2], node));
lines = BreakLines(node, LayoutUnit(20));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("12345", ToString(lines[0], node));
EXPECT_EQ(String(u"\uFFFC"), ToString(lines[1], node));
EXPECT_EQ("678", ToString(lines[2], node));
}
TEST_F(NGLineBreakerTest, OverflowMargin) {
LoadAhem();
NGInlineNode* node = CreateInlineNode(R"HTML(
<!DOCTYPE html>
<style>
#container {
font: 10px/1 Ahem;
}
span {
margin-right: 4em;
}
</style>
<div id=container><span>123 456</span> 789</div>
)HTML");
const Vector<NGInlineItem>& items = node->Items();
// While "123 456" can fit in a line, "456" has a right margin that cannot
// fit. Since "456" and its right margin is not breakable, "456" should be on
// the next line.
Vector<NGInlineItemResults> lines;
lines = BreakLines(node, LayoutUnit(80));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("123", ToString(lines[0], node));
EXPECT_EQ("456", ToString(lines[1], node));
DCHECK_EQ(NGInlineItem::kCloseTag, items[lines[1].back().item_index].Type());
EXPECT_EQ("789", ToString(lines[2], node));
// Same as above, but this time "456" overflows the line because it is 70px.
lines = BreakLines(node, LayoutUnit(60));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("123", ToString(lines[0], node));
EXPECT_EQ("456", ToString(lines[1], node));
DCHECK_EQ(NGInlineItem::kCloseTag, items[lines[1].back().item_index].Type());
EXPECT_EQ("789", ToString(lines[2], node));
}
TEST_F(NGLineBreakerTest, BoundaryInWord) {
LoadAhem();
NGInlineNode* node = CreateInlineNode(R"HTML(
<!DOCTYPE html>
<style>
#container {
font: 10px/1 Ahem;
}
</style>
<div id=container><span>123 456</span>789 abc</div>
)HTML");
// The element boundary within "456789" should not cause a break.
// Since "789" does not fit, it should go to the next line along with "456".
Vector<NGInlineItemResults> lines;
lines = BreakLines(node, LayoutUnit(80));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("123", ToString(lines[0], node));
EXPECT_EQ("456789", ToString(lines[1], node));
EXPECT_EQ("abc", ToString(lines[2], node));
// Same as above, but this time "456789" overflows the line because it is
// 60px.
lines = BreakLines(node, LayoutUnit(50));
EXPECT_EQ(3u, lines.size());
EXPECT_EQ("123", ToString(lines[0], node));
EXPECT_EQ("456789", ToString(lines[1], node));
EXPECT_EQ("abc", ToString(lines[2], node));
}
TEST_F(NGLineBreakerTest, BoundaryInFirstWord) {
LoadAhem();
NGInlineNode* node = CreateInlineNode(R"HTML(
<!DOCTYPE html>
<style>
#container {
font: 10px/1 Ahem;
}
</style>
<div id=container><span>123</span>456 789</div>
)HTML");
Vector<NGInlineItemResults> lines;
lines = BreakLines(node, LayoutUnit(80));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("123456", ToString(lines[0], node));
EXPECT_EQ("789", ToString(lines[1], node));
lines = BreakLines(node, LayoutUnit(50));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("123456", ToString(lines[0], node));
EXPECT_EQ("789", ToString(lines[1], node));
lines = BreakLines(node, LayoutUnit(20));
EXPECT_EQ(2u, lines.size());
EXPECT_EQ("123456", ToString(lines[0], node));
EXPECT_EQ("789", ToString(lines[1], node));
}
} // namespace
} // namespace blink
...@@ -391,7 +391,7 @@ int LazyLineBreakIterator::NextBreakablePositionKeepAll(int pos) const { ...@@ -391,7 +391,7 @@ int LazyLineBreakIterator::NextBreakablePositionKeepAll(int pos) const {
} }
unsigned LazyLineBreakIterator::NextBreakOpportunity(unsigned offset) const { unsigned LazyLineBreakIterator::NextBreakOpportunity(unsigned offset) const {
int next_break = 0; int next_break = -1;
IsBreakable(offset, next_break); IsBreakable(offset, next_break);
return next_break; return next_break;
} }
...@@ -400,8 +400,7 @@ unsigned LazyLineBreakIterator::PreviousBreakOpportunity(unsigned offset, ...@@ -400,8 +400,7 @@ unsigned LazyLineBreakIterator::PreviousBreakOpportunity(unsigned offset,
unsigned min) const { unsigned min) const {
unsigned pos = std::min(offset, string_.length()); unsigned pos = std::min(offset, string_.length());
for (; pos > min; pos--) { for (; pos > min; pos--) {
int next_break = 0; if (IsBreakable(pos))
if (IsBreakable(pos, next_break))
return pos; return pos;
} }
return min; return min;
......
...@@ -217,6 +217,11 @@ class PLATFORM_EXPORT LazyLineBreakIterator final { ...@@ -217,6 +217,11 @@ class PLATFORM_EXPORT LazyLineBreakIterator final {
return IsBreakable(pos, next_breakable, break_type_); return IsBreakable(pos, next_breakable, break_type_);
} }
inline bool IsBreakable(int pos) const {
int next_breakable = -1;
return IsBreakable(pos, next_breakable, break_type_);
}
// Returns the break opportunity at or after |offset|. // Returns the break opportunity at or after |offset|.
unsigned NextBreakOpportunity(unsigned offset) const; unsigned NextBreakOpportunity(unsigned offset) const;
......
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