blob: e0a65b1a502cb0ba1d86b5b82b370350efa8aacc [file] [log] [blame]
/*
* Copyright (C) 2000 Lars Knoll (knoll@kde.org)
* Copyright (C) 2003, 2004, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc.
* All right reserved.
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2013 Adobe Systems Incorporated.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_BREAKING_CONTEXT_INLINE_HEADERS_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_BREAKING_CONTEXT_INLINE_HEADERS_H_
#include "third_party/blink/renderer/core/layout/api/line_layout_box.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_list_marker.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_ruby_run.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_svg_inline_text.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_text.h"
#include "third_party/blink/renderer/core/layout/api/line_layout_text_combine.h"
#include "third_party/blink/renderer/core/layout/line/inline_iterator.h"
#include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
#include "third_party/blink/renderer/core/layout/line/layout_text_info.h"
#include "third_party/blink/renderer/core/layout/line/line_breaker.h"
#include "third_party/blink/renderer/core/layout/line/line_info.h"
#include "third_party/blink/renderer/core/layout/line/line_width.h"
#include "third_party/blink/renderer/core/layout/line/trailing_objects.h"
#include "third_party/blink/renderer/core/layout/line/word_measurement.h"
#include "third_party/blink/renderer/core/layout/logical_values.h"
#include "third_party/blink/renderer/core/layout/text_run_constructor.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/platform/fonts/character_range.h"
#include "third_party/blink/renderer/platform/text/hyphenation.h"
#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
#include "third_party/blink/renderer/platform/wtf/allocator.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
// We don't let our line box tree for a single line get any deeper than this.
const unsigned kCMaxLineDepth = 200;
class BreakingContext {
STACK_ALLOCATED();
public:
BreakingContext(InlineBidiResolver& resolver,
LineInfo& in_line_info,
LineWidth& line_width,
LayoutTextInfo& in_layout_text_info,
bool applied_start_width,
LineLayoutBlockFlow block)
: resolver_(resolver),
current_(resolver.GetPosition()),
line_break_(resolver.GetPosition()),
block_(block),
last_object_(current_.GetLineLayoutItem()),
next_object_(nullptr),
current_style_(nullptr),
block_style_(block.Style()),
line_info_(in_line_info),
layout_text_info_(in_layout_text_info),
width_(line_width),
curr_ws_(EWhiteSpace::kNormal),
last_ws_(EWhiteSpace::kNormal),
preserves_newline_(false),
at_start_(true),
ignoring_spaces_(false),
current_character_is_space_(false),
applied_start_width_(applied_start_width),
include_end_width_(true),
auto_wrap_(false),
auto_wrap_was_ever_true_on_line_(false),
floats_fit_on_line_(true),
collapse_white_space_(false),
starting_new_paragraph_(line_info_.PreviousLineBrokeCleanly()),
at_end_(false),
line_midpoint_state_(resolver.GetMidpointState()) {
line_info_.SetPreviousLineBrokeCleanly(false);
}
LineLayoutItem CurrentItem() { return current_.GetLineLayoutItem(); }
InlineIterator LineBreak() { return line_break_; }
bool AtEnd() { return at_end_; }
void InitializeForCurrentObject();
void Increment();
void HandleBR(EClear&);
void HandleOutOfFlowPositioned(Vector<LineLayoutBox>& positioned_objects);
void HandleFloat();
void HandleEmptyInline();
void HandleReplaced();
bool HandleText(WordMeasurements&, bool& hyphenated);
void PrepareForNextCharacter(const LineLayoutText&,
bool& prohibit_break_inside,
bool previous_character_is_space);
bool CanBreakAtWhitespace(bool break_words,
WordMeasurement&,
bool stopped_ignoring_spaces,
float char_width,
bool& hyphenated,
bool disable_soft_hyphen,
float& hyphen_width,
bool between_words,
bool mid_word_break,
bool can_break_mid_word,
bool previous_character_is_space,
float last_width_measurement,
const LineLayoutText&,
const Font&,
bool apply_word_spacing,
float word_spacing);
void TrailingSpacesHang(bool can_break_mid_word);
bool TrailingSpaceExceedsAvailableWidth(bool can_break_mid_word,
const LineLayoutText&,
WordMeasurement&,
bool apply_word_spacing,
bool word_spacing,
const Font&);
WordMeasurement& CalculateWordWidth(WordMeasurements&,
LineLayoutText&,
unsigned last_space,
float& last_width_measurement,
float word_spacing_for_word_measurement,
const Font&,
float word_trailing_space_width,
UChar);
void StopIgnoringSpaces(unsigned& last_space);
void CommitAndUpdateLineBreakIfNeeded();
InlineIterator HandleEndOfLine();
void ClearLineBreakIfFitsOnLine() {
if (width_.FitsOnLine() || last_ws_ == EWhiteSpace::kNowrap)
line_break_.Clear();
}
private:
void SetCurrentCharacterIsSpace(UChar);
void SkipTrailingWhitespace(InlineIterator&, const LineInfo&);
bool ShouldMidWordBreak(UChar,
LineLayoutText,
const Font&,
unsigned& next_grapheme_cluster,
float& char_width,
float& width_from_last_breaking_opportunity,
bool break_all,
int& next_breakable_position_for_break_all);
bool RewindToMidWordBreak(WordMeasurement&, int end, float width);
bool CanMidWordBreakBefore(LineLayoutText);
bool RewindToFirstMidWordBreak(LineLayoutText,
const ComputedStyle&,
const Font&,
const LazyLineBreakIterator&,
WordMeasurement&);
bool RewindToMidWordBreak(LineLayoutText,
const ComputedStyle&,
const Font&,
bool break_all,
WordMeasurement&);
bool Hyphenate(LineLayoutText,
const ComputedStyle&,
const Font&,
const Hyphenation&,
float last_space_word_spacing,
WordMeasurement&);
bool IsBreakAtSoftHyphen() const;
InlineBidiResolver& resolver_;
InlineIterator current_;
InlineIterator line_break_;
InlineIterator start_of_ignored_spaces_;
LineLayoutBlockFlow block_;
LineLayoutItem last_object_;
LineLayoutItem next_object_;
const ComputedStyle* current_style_;
const ComputedStyle* block_style_;
LineInfo& line_info_;
LayoutTextInfo& layout_text_info_;
LineWidth width_;
EWhiteSpace curr_ws_;
EWhiteSpace last_ws_;
bool preserves_newline_;
bool at_start_;
bool ignoring_spaces_;
bool current_character_is_space_;
bool previous_character_is_space_;
bool single_leading_space_;
unsigned current_start_offset_; // initial offset for the current text
bool applied_start_width_;
bool include_end_width_;
bool auto_wrap_;
bool auto_wrap_was_ever_true_on_line_;
bool floats_fit_on_line_;
bool collapse_white_space_;
bool starting_new_paragraph_;
bool at_end_;
LineMidpointState& line_midpoint_state_;
TrailingObjects trailing_objects_;
};
// When ignoring spaces, this needs to be called for objects that need line
// boxes such as LayoutInlines or hard line breaks to ensure that they're not
// ignored.
inline void EnsureLineBoxInsideIgnoredSpaces(LineMidpointState* midpoint_state,
LineLayoutItem item) {
InlineIterator midpoint(nullptr, item, 0);
midpoint_state->StopIgnoringSpaces(midpoint);
midpoint_state->StartIgnoringSpaces(midpoint);
}
inline bool ShouldCollapseWhiteSpace(const ComputedStyle& style,
const LineInfo& line_info,
WhitespacePosition whitespace_position) {
// CSS2 16.6.1
// If a space (U+0020) at the beginning of a line has 'white-space' set to
// 'normal', 'nowrap', or 'pre-line', it is removed.
// If a space (U+0020) at the end of a line has 'white-space' set to 'normal',
// 'nowrap', or 'pre-line', it is also removed.
// If spaces (U+0020) or tabs (U+0009) at the end of a line have 'white-space'
// set to 'pre-wrap', UAs may visually collapse them.
return style.CollapseWhiteSpace() ||
(whitespace_position == kTrailingWhitespace &&
style.WhiteSpace() == EWhiteSpace::kPreWrap &&
(!line_info.IsEmpty() || !line_info.PreviousLineBrokeCleanly()));
}
inline bool RequiresLineBoxForContent(LineLayoutInline flow,
const LineInfo& line_info) {
LineLayoutItem parent = flow.Parent();
if (flow.GetDocument().InNoQuirksMode() &&
(flow.Style(line_info.IsFirstLine())->LineHeight() !=
parent.Style(line_info.IsFirstLine())->LineHeight() ||
flow.StyleRef().VerticalAlign() != parent.StyleRef().VerticalAlign() ||
!parent.StyleRef().HasIdenticalAscentDescentAndLineGap(flow.StyleRef())))
return true;
return false;
}
inline bool AlwaysRequiresLineBox(LineLayoutItem flow) {
// FIXME: Right now, we only allow line boxes for inlines that are truly
// empty. We need to fix this, though, because at the very least, inlines
// containing only ignorable whitespace should should also have line boxes.
return IsEmptyInline(flow) &&
LineLayoutInline(flow).HasInlineDirectionBordersPaddingOrMargin();
}
inline bool RequiresLineBox(
const InlineIterator& it,
const LineInfo& line_info = LineInfo(),
WhitespacePosition whitespace_position = kLeadingWhitespace) {
if (it.GetLineLayoutItem().IsEmptyText())
return false;
if (it.GetLineLayoutItem().IsFloatingOrOutOfFlowPositioned())
return false;
if (it.GetLineLayoutItem().IsLayoutInline() &&
!AlwaysRequiresLineBox(it.GetLineLayoutItem()) &&
!RequiresLineBoxForContent(LineLayoutInline(it.GetLineLayoutItem()),
line_info))
return false;
if (!ShouldCollapseWhiteSpace(it.GetLineLayoutItem().StyleRef(), line_info,
whitespace_position) ||
it.GetLineLayoutItem().IsBR())
return true;
UChar current = it.Current();
bool not_just_whitespace = current != kSpaceCharacter &&
current != kTabulationCharacter &&
current != kSoftHyphenCharacter &&
(current != kNewlineCharacter ||
it.GetLineLayoutItem().PreservesNewline());
return not_just_whitespace || IsEmptyInline(it.GetLineLayoutItem());
}
inline void SetStaticPositions(LineLayoutBlockFlow block,
LineLayoutBox child,
IndentTextOrNot indent_text) {
DCHECK(child.IsOutOfFlowPositioned());
// FIXME: The math here is actually not really right. It's a best-guess
// approximation that will work for the common cases
LineLayoutItem container_block = child.Container();
LayoutUnit block_height = block.LogicalHeight();
if (container_block.IsLayoutInline()) {
// A relative positioned inline encloses us. In this case, we also have to
// determine our position as though we were an inline.
// Set |staticInlinePosition| and |staticBlockPosition| on the relative
// positioned inline so that we can obtain the value later.
LineLayoutInline(container_block)
.Layer()
->SetStaticInlinePosition(
block.StartAlignedOffsetForLine(block_height, indent_text));
LineLayoutInline(container_block)
.Layer()
->SetStaticBlockPosition(block_height);
// If |child| is a leading or trailing positioned object this is its only
// opportunity to ensure it moves with an inline container changing width.
child.MoveWithEdgeOfInlineContainerIfNecessary(
child.IsHorizontalWritingMode());
}
block.UpdateStaticInlinePositionForChild(child, block_height, indent_text);
child.Layer()->SetStaticBlockPosition(block_height);
}
// FIXME: The entire concept of the skipTrailingWhitespace function is flawed,
// since we really need to be building line boxes even for containers that may
// ultimately collapse away. Otherwise we'll never get positioned elements quite
// right. In other words, we need to build this function's work into the normal
// line object iteration process. NB. this function will insert any floating
// elements that would otherwise be skipped but it will not position them.
inline void BreakingContext::SkipTrailingWhitespace(InlineIterator& iterator,
const LineInfo& line_info) {
while (!iterator.AtEnd() &&
!RequiresLineBox(iterator, line_info, kTrailingWhitespace)) {
LineLayoutItem item = iterator.GetLineLayoutItem();
if (item.IsOutOfFlowPositioned())
SetStaticPositions(block_, LineLayoutBox(item), kDoNotIndentText);
else if (item.IsFloating())
block_.InsertFloatingObject(LineLayoutBox(item));
iterator.Increment();
}
}
inline void BreakingContext::InitializeForCurrentObject() {
current_style_ = current_.GetLineLayoutItem().Style();
next_object_ =
BidiNextSkippingEmptyInlines(block_, current_.GetLineLayoutItem());
if (next_object_ && next_object_.Parent() &&
!next_object_.Parent().IsDescendantOf(
current_.GetLineLayoutItem().Parent()))
include_end_width_ = true;
curr_ws_ =
current_.GetLineLayoutItem().IsLayoutInline()
? current_style_->WhiteSpace()
: current_.GetLineLayoutItem().Parent().StyleRef().WhiteSpace();
last_ws_ = last_object_.IsLayoutInline()
? last_object_.StyleRef().WhiteSpace()
: last_object_.Parent().StyleRef().WhiteSpace();
bool is_svg_text = current_.GetLineLayoutItem().IsSVGInlineText();
auto_wrap_ = !is_svg_text && ComputedStyle::AutoWrap(curr_ws_);
auto_wrap_was_ever_true_on_line_ =
auto_wrap_was_ever_true_on_line_ || auto_wrap_;
preserves_newline_ = !is_svg_text && ComputedStyle::PreserveNewline(curr_ws_);
collapse_white_space_ = ComputedStyle::CollapseWhiteSpace(curr_ws_);
// Ensure the whitespace in constructions like '<span style="white-space:
// pre-wrap">text <span><span> text</span>' does not collapse.
if (collapse_white_space_ && !ComputedStyle::CollapseWhiteSpace(last_ws_))
current_character_is_space_ = false;
// Since current_ iterates all along the text's length, we need to store the
// initial offset of the current handle text so that we can then identify
// a single leading white-space as potential breaking opportunities.
current_start_offset_ = current_.Offset();
single_leading_space_ = false;
}
inline void BreakingContext::Increment() {
current_.MoveToStartOf(next_object_);
// When the line box tree is created, this position in the line will be
// snapped to LayoutUnit's, and those measurements will be used by the paint
// code. Do the equivalent snapping here, to get consistent line measurements.
width_.SnapAtNodeBoundary();
at_start_ = false;
}
inline void BreakingContext::HandleBR(EClear& clear) {
if (width_.FitsOnLine()) {
LineLayoutItem br = current_.GetLineLayoutItem();
line_break_.MoveToStartOf(br);
line_break_.Increment();
// A <br> always breaks a line, so don't let the line be collapsed
// away. Also, the space at the end of a line with a <br> does not
// get collapsed away. It only does this if the previous line broke
// cleanly. Otherwise the <br> has no effect on whether the line is
// empty or not.
if (starting_new_paragraph_)
line_info_.SetEmpty(false);
trailing_objects_.Clear();
line_info_.SetPreviousLineBrokeCleanly(true);
// A <br> with clearance always needs a linebox in case the lines below it
// get dirtied later and need to check for floats to clear - so if we're
// ignoring spaces, stop ignoring them and add a run for this object.
if (ignoring_spaces_ && current_style_->Clear() != EClear::kNone)
EnsureLineBoxInsideIgnoredSpaces(&line_midpoint_state_, br);
if (!line_info_.IsEmpty())
clear = ResolvedClear(*current_style_, block_.StyleRef());
}
at_end_ = true;
}
inline LayoutUnit BorderPaddingMarginStart(LineLayoutInline child) {
return child.MarginStart() + child.PaddingStart() + child.BorderStart();
}
inline LayoutUnit BorderPaddingMarginEnd(LineLayoutInline child) {
return child.MarginEnd() + child.PaddingEnd() + child.BorderEnd();
}
enum CollapsibleWhiteSpace {
IgnoreCollapsibleWhiteSpace,
UseCollapsibleWhiteSpace
};
inline bool ShouldAddBorderPaddingMargin(LineLayoutItem child,
bool& check_side,
CollapsibleWhiteSpace white_space) {
if (!child)
return true;
if (child.IsText()) {
// A caller will only be interested in adding BPM from objects separated by
// collapsible whitespace if they haven't already been added to the line's
// width, such as when adding end-BPM when about to place a float after a
// linebox.
if (white_space == UseCollapsibleWhiteSpace &&
LineLayoutText(child).IsAllCollapsibleWhitespace())
return true;
if (!LineLayoutText(child).TextLength())
return true;
}
check_side = false;
return check_side;
}
inline LayoutUnit InlineLogicalWidthFromAncestorsIfNeeded(
LineLayoutItem child,
bool start = true,
bool end = true,
CollapsibleWhiteSpace white_space = IgnoreCollapsibleWhiteSpace) {
unsigned line_depth = 1;
LayoutUnit extra_width;
LineLayoutItem parent = child.Parent();
while (parent.IsLayoutInline() && line_depth++ < kCMaxLineDepth) {
LineLayoutInline parent_as_layout_inline(parent);
if (!IsEmptyInline(parent_as_layout_inline)) {
if (start && ShouldAddBorderPaddingMargin(child.PreviousSibling(), start,
white_space))
extra_width += BorderPaddingMarginStart(parent_as_layout_inline);
if (end &&
ShouldAddBorderPaddingMargin(child.NextSibling(), end, white_space))
extra_width += BorderPaddingMarginEnd(parent_as_layout_inline);
if (!start && !end)
return extra_width;
}
child = parent;
parent = child.Parent();
}
return extra_width;
}
inline void BreakingContext::HandleOutOfFlowPositioned(
Vector<LineLayoutBox>& positioned_objects) {
// If our original display wasn't an inline type, then we can
// go ahead and determine our static inline position now.
LineLayoutBox box(current_.GetLineLayoutItem());
bool is_inline_type = box.StyleRef().IsOriginalDisplayInlineType();
if (!is_inline_type) {
block_.SetStaticInlinePositionForChild(box, block_.StartOffsetForContent());
} else {
// If our original display was an INLINE type, then we can determine our
// static y position now. Note, however, that if we're paginated, we may
// have to update this position after the line has been laid out, since the
// line may be pushed by a pagination strut.
box.Layer()->SetStaticBlockPosition(block_.LogicalHeight());
}
// If we're ignoring spaces, we have to stop and include this object and
// then start ignoring spaces again.
bool container_is_inline = box.Container().IsLayoutInline();
if (is_inline_type || container_is_inline) {
if (ignoring_spaces_)
EnsureLineBoxInsideIgnoredSpaces(&line_midpoint_state_, box);
trailing_objects_.AppendObjectIfNeeded(box);
}
if (!container_is_inline)
positioned_objects.push_back(box);
width_.AddUncommittedWidth(
InlineLogicalWidthFromAncestorsIfNeeded(box).ToFloat());
// Reset prior line break context characters.
layout_text_info_.line_break_iterator_.ResetPriorContext();
}
inline void BreakingContext::HandleFloat() {
LineLayoutBox float_box(current_.GetLineLayoutItem());
FloatingObject* floating_object = block_.InsertFloatingObject(float_box);
if (floats_fit_on_line_) {
// We need to calculate the logical width of the float before we can tell
// whether it's going to fit on the line. That means that we need to
// position and lay it out. Note that we have to avoid positioning floats
// that have been placed prematurely: Sometimes, floats are inserted too
// early by skipTrailingWhitespace(), and later on they all get placed by
// the first float here in handleFloat(). Their position may then be wrong,
// but it's too late to do anything about that now. See crbug.com/671577
if (!floating_object->IsPlaced())
block_.PositionAndLayoutFloat(*floating_object, block_.LogicalHeight());
// Check if it fits in the current line; if it does, place it now,
// otherwise, place it after moving to next line (in newLine() func).
// FIXME: Bug 110372: Properly position multiple stacked floats with
// non-rectangular shape outside.
// When fitting the float on the line we need to treat the width on the line
// so far as though end-border, -padding and -margin from
// inline ancestors has been applied to the end of the previous inline box.
float width_from_ancestors =
InlineLogicalWidthFromAncestorsIfNeeded(float_box, false, true,
UseCollapsibleWhiteSpace)
.ToFloat();
width_.AddUncommittedWidth(width_from_ancestors);
if (width_.FitsOnLine(
block_.LogicalWidthForFloat(*floating_object).ToFloat(),
kExcludeWhitespace)) {
block_.PlaceNewFloats(block_.LogicalHeight(), &width_);
if (line_break_.GetLineLayoutItem() == current_.GetLineLayoutItem()) {
DCHECK(!line_break_.Offset());
line_break_.Increment();
}
} else {
floats_fit_on_line_ = false;
}
width_.AddUncommittedWidth(-width_from_ancestors);
}
// Update prior line break context characters, using U+FFFD (OBJECT
// REPLACEMENT CHARACTER) for floating element.
layout_text_info_.line_break_iterator_.UpdatePriorContext(
kReplacementCharacter);
}
// This is currently just used for list markers and inline flows that have line
// boxes. Neither should have an effect on whitespace at the start of the line.
inline bool ShouldSkipWhitespaceAfterStartObject(
LineLayoutBlockFlow block,
LineLayoutItem o,
LineMidpointState& line_midpoint_state) {
LineLayoutItem next = BidiNextSkippingEmptyInlines(block, o);
while (next && next.IsFloatingOrOutOfFlowPositioned())
next = BidiNextSkippingEmptyInlines(block, next);
while (next && IsEmptyInline(next)) {
LineLayoutItem child = LineLayoutInline(next).FirstChild();
next = child ? child : BidiNextSkippingEmptyInlines(block, next);
}
if (next && !next.IsBR() && next.IsText() &&
LineLayoutText(next).TextLength() > 0) {
LineLayoutText next_text(next);
UChar next_char = next_text.CharacterAt(0);
if (next_text.StyleRef().IsCollapsibleWhiteSpace(next_char)) {
line_midpoint_state.StartIgnoringSpaces(InlineIterator(nullptr, o, 0));
return true;
}
}
return false;
}
inline void BreakingContext::HandleEmptyInline() {
// This should only end up being called on empty inlines
DCHECK(current_.GetLineLayoutItem());
LineLayoutInline flow_box(current_.GetLineLayoutItem());
bool requires_line_box = AlwaysRequiresLineBox(current_.GetLineLayoutItem());
if (requires_line_box || RequiresLineBoxForContent(flow_box, line_info_)) {
// An empty inline that only has line-height, vertical-align or font-metrics
// will not force linebox creation (and thus affect the height of the line)
// if the rest of the line is empty.
if (requires_line_box)
line_info_.SetEmpty(false);
if (ignoring_spaces_) {
// If we are in a run of ignored spaces then ensure we get a linebox if
// lineboxes are eventually created for the line...
trailing_objects_.Clear();
EnsureLineBoxInsideIgnoredSpaces(&line_midpoint_state_,
current_.GetLineLayoutItem());
} else if (block_style_->CollapseWhiteSpace() &&
resolver_.GetPosition().GetLineLayoutItem() ==
current_.GetLineLayoutItem() &&
ShouldSkipWhitespaceAfterStartObject(
block_, current_.GetLineLayoutItem(),
line_midpoint_state_)) {
// If this object is at the start of the line, we need to behave like list
// markers and start ignoring spaces.
current_character_is_space_ = true;
ignoring_spaces_ = true;
} else {
// If we are after a trailing space but aren't ignoring spaces yet then
// ensure we get a linebox if we encounter collapsible whitepace.
trailing_objects_.AppendObjectIfNeeded(current_.GetLineLayoutItem());
}
}
width_.AddUncommittedWidth(
(InlineLogicalWidthFromAncestorsIfNeeded(current_.GetLineLayoutItem()) +
BorderPaddingMarginStart(flow_box) + BorderPaddingMarginEnd(flow_box))
.ToFloat());
}
inline void BreakingContext::HandleReplaced() {
LineLayoutBox replaced_box(current_.GetLineLayoutItem());
if (at_start_)
width_.UpdateAvailableWidth(replaced_box.LogicalHeight());
// Break on replaced elements if either has normal white-space,
// or if the replaced element is ruby that can break before.
if ((auto_wrap_ || ComputedStyle::AutoWrap(last_ws_)) &&
(!current_.GetLineLayoutItem().IsRubyRun() ||
LineLayoutRubyRun(current_.GetLineLayoutItem())
.CanBreakBefore(layout_text_info_.line_break_iterator_))) {
width_.Commit();
line_break_.MoveToStartOf(current_.GetLineLayoutItem());
}
if (ignoring_spaces_) {
line_midpoint_state_.StopIgnoringSpaces(
InlineIterator(nullptr, current_.GetLineLayoutItem(), 0));
}
line_info_.SetEmpty(false);
ignoring_spaces_ = false;
current_character_is_space_ = false;
trailing_objects_.Clear();
// Optimize for a common case. If we can't find whitespace after the list
// item, then this is all moot.
LayoutUnit replaced_logical_width =
block_.LogicalWidthForChild(replaced_box) +
block_.MarginStartForChild(replaced_box) +
block_.MarginEndForChild(replaced_box) +
InlineLogicalWidthFromAncestorsIfNeeded(current_.GetLineLayoutItem());
if (current_.GetLineLayoutItem().IsListMarker()) {
if (block_style_->CollapseWhiteSpace() &&
ShouldSkipWhitespaceAfterStartObject(
block_, current_.GetLineLayoutItem(), line_midpoint_state_)) {
// Like with inline flows, we start ignoring spaces to make sure that any
// additional spaces we see will be discarded.
current_character_is_space_ = true;
ignoring_spaces_ = true;
}
if (LineLayoutListMarker(current_.GetLineLayoutItem()).IsInside())
width_.AddUncommittedWidth(replaced_logical_width.ToFloat());
} else {
width_.AddUncommittedWidth(replaced_logical_width.ToFloat());
}
if (current_.GetLineLayoutItem().IsRubyRun())
width_.ApplyOverhang(LineLayoutRubyRun(current_.GetLineLayoutItem()),
last_object_, next_object_);
// Update prior line break context characters, using U+FFFD (OBJECT
// REPLACEMENT CHARACTER) for replaced element.
layout_text_info_.line_break_iterator_.UpdatePriorContext(
kReplacementCharacter);
}
inline void NextCharacter(UChar& current_character,
UChar& last_character,
UChar& second_to_last_character) {
second_to_last_character = last_character;
last_character = current_character;
}
ALWAYS_INLINE void BreakingContext::SetCurrentCharacterIsSpace(UChar c) {
current_character_is_space_ =
c == kSpaceCharacter || c == kTabulationCharacter ||
(!preserves_newline_ && (c == kNewlineCharacter));
}
inline float FirstPositiveWidth(const WordMeasurements& word_measurements) {
for (const WordMeasurement& word_measurement : word_measurements) {
if (word_measurement.width > 0)
return word_measurement.width;
}
return 0;
}
ALWAYS_INLINE TextDirection
TextDirectionFromUnicode(WTF::unicode::CharDirection direction) {
return direction == WTF::unicode::kRightToLeft ||
direction == WTF::unicode::kRightToLeftArabic
? TextDirection::kRtl
: TextDirection::kLtr;
}
ALWAYS_INLINE float TextWidth(
LineLayoutText text,
unsigned from,
unsigned len,
const Font& font,
float x_pos,
bool collapse_white_space,
HashSet<const SimpleFontData*>* fallback_fonts = nullptr,
FloatRect* glyph_bounds = nullptr) {
if ((!from && len == text.TextLength()) || text.StyleRef().HasTextCombine()) {
return text.Width(from, len, font, LayoutUnit(x_pos),
text.StyleRef().Direction(), fallback_fonts,
glyph_bounds);
}
TextRun run = ConstructTextRun(font, text, from, len, text.StyleRef());
run.SetTabSize(!collapse_white_space, text.StyleRef().GetTabSize());
run.SetXPos(x_pos);
return font.Width(run, fallback_fonts, glyph_bounds);
}
ALWAYS_INLINE bool BreakingContext::ShouldMidWordBreak(
UChar c,
LineLayoutText layout_text,
const Font& font,
unsigned& next_grapheme_cluster,
float& char_width,
float& width_from_last_breaking_opportunity,
bool break_all,
int& next_breakable_position_for_break_all) {
// For breakWords/breakAll, we need to measure up to normal break
// opportunity and then rewindToMidWordBreak() because ligatures/kerning can
// shorten the width as we add more characters.
// However, doing so can hit the performance when a "word" is really long,
// such as minimized JS, because the next line will re-shape the rest of the
// word in the current architecture.
// This function is a heuristic optimization to stop at 2em overflow.
float overflow_allowance = 2 * font.GetFontDescription().ComputedSize();
width_from_last_breaking_opportunity += char_width;
unsigned char_len;
if (layout_text.Is8Bit()) {
// For 8-bit string, code unit == grapheme cluster.
char_len = 1;
} else {
NonSharedCharacterBreakIterator iterator(layout_text.GetText());
int next = iterator.Following(current_.Offset());
next_grapheme_cluster =
next != kTextBreakDone ? next : layout_text.TextLength();
char_len = next_grapheme_cluster - current_.Offset();
}
char_width =
TextWidth(layout_text, current_.Offset(), char_len, font,
width_.CommittedWidth() + width_from_last_breaking_opportunity,
collapse_white_space_);
if (width_.CommittedWidth() + width_from_last_breaking_opportunity +
char_width <=
width_.AvailableWidth() + overflow_allowance)
return false;
// breakAll has different break opportunities. Ensure we break only at
// breakAll allows to break.
if (break_all && !layout_text_info_.line_break_iterator_.IsBreakable(
current_.Offset(), next_breakable_position_for_break_all,
LineBreakType::kBreakAll)) {
return false;
}
return true;
}
ALWAYS_INLINE bool BreakingContext::RewindToMidWordBreak(
WordMeasurement& word_measurement,
int end,
float width) {
word_measurement.end_offset = end;
word_measurement.width = width;
current_.MoveTo(current_.GetLineLayoutItem(), end,
current_.NextBreakablePosition());
SetCurrentCharacterIsSpace(current_.Current());
line_break_.MoveTo(current_.GetLineLayoutItem(), end,
current_.NextBreakablePosition());
return true;
}
ALWAYS_INLINE bool BreakingContext::CanMidWordBreakBefore(LineLayoutText text) {
// No break opportunities at the beginning of a line.
if (!width_.CurrentWidth())
return false;
// If this text node is not the first child of an inline, the parent and the
// preivous have the same style.
if (text.PreviousSibling())
return true;
// Element boundaries belong to the parent of the element.
// TODO(kojii): Not all cases of element boundaries are supported yet.
// crbug.com/282134
if (LineLayoutItem element = text.Parent()) {
if (LineLayoutItem parent = element.Parent()) {
const ComputedStyle& parent_style = parent.StyleRef();
return parent_style.AutoWrap() &&
((parent_style.BreakWords() && !width_.CommittedWidth()) ||
parent_style.WordBreak() == EWordBreak::kBreakAll);
}
}
return false;
}
ALWAYS_INLINE bool BreakingContext::RewindToFirstMidWordBreak(
LineLayoutText text,
const ComputedStyle& style,
const Font& font,
const LazyLineBreakIterator& break_iterator,
WordMeasurement& word_measurement) {
int start = word_measurement.start_offset;
int end = CanMidWordBreakBefore(text)
? start
: break_iterator.NextBreakOpportunity(start + 1);
if (end >= word_measurement.end_offset)
return false;
float width = TextWidth(text, start, end - start, font, width_.CurrentWidth(),
collapse_white_space_);
// If the first break opportunity doesn't fit, and if there's a break
// opportunity in previous runs, break at the opportunity.
if (!width_.FitsOnLine(width) &&
(width_.CommittedWidth() || single_leading_space_))
return false;
return RewindToMidWordBreak(word_measurement, end, width);
}
ALWAYS_INLINE bool BreakingContext::RewindToMidWordBreak(
LineLayoutText text,
const ComputedStyle& style,
const Font& font,
bool break_all,
WordMeasurement& word_measurement) {
int start = word_measurement.start_offset;
int len = word_measurement.end_offset - start;
if (!len)
return false;
LazyLineBreakIterator break_iterator(
text.GetText(), style.LocaleForLineBreakIterator(),
break_all ? LineBreakType::kBreakAll : LineBreakType::kBreakCharacter);
float x_pos_to_break = width_.AvailableWidth() - width_.CurrentWidth();
if (x_pos_to_break <= LayoutUnit::Epsilon()) {
// There were no space left. Skip computing how many characters can fit.
return RewindToFirstMidWordBreak(text, style, font, break_iterator,
word_measurement);
}
TextRun run = ConstructTextRun(font, text, start, len, style);
run.SetTabSize(!collapse_white_space_, style.GetTabSize());
run.SetXPos(width_.CurrentWidth());
// TODO(kojii): should be replaced with safe-to-break when hb is ready.
x_pos_to_break += LayoutUnit::Epsilon();
if (run.Rtl())
x_pos_to_break = word_measurement.width - x_pos_to_break;
len = font.OffsetForPosition(run, x_pos_to_break, OnlyFullGlyphs,
DontBreakGlyphs);
int end = start + len;
if (len) {
end = break_iterator.PreviousBreakOpportunity(end, start);
len = end - start;
}
if (!len) {
// No characters can fit in the available space.
// Break at the first mid-word break opportunity and let the line overflow.
return RewindToFirstMidWordBreak(text, style, font, break_iterator,
word_measurement);
}
FloatRect rect = font.SelectionRectForText(run, FloatPoint(), 0, 0, len);
return RewindToMidWordBreak(word_measurement, end, rect.Width());
}
ALWAYS_INLINE bool BreakingContext::Hyphenate(
LineLayoutText text,
const ComputedStyle& style,
const Font& font,
const Hyphenation& hyphenation,
float last_space_word_spacing,
WordMeasurement& word_measurement) {
unsigned start = word_measurement.start_offset;
unsigned len = word_measurement.end_offset - start;
if (len <= Hyphenation::kMinimumSuffixLength)
return false;
float hyphen_width = text.HyphenWidth(
font, TextDirectionFromUnicode(resolver_.GetPosition().Direction()));
float max_prefix_width = width_.AvailableWidth() - width_.CurrentWidth() -
hyphen_width - last_space_word_spacing;
if (max_prefix_width <=
font.GetFontDescription().MinimumPrefixWidthToHyphenate()) {
return false;
}
TextRun run = ConstructTextRun(font, text, start, len, style);
run.SetTabSize(!collapse_white_space_, style.GetTabSize());
run.SetXPos(width_.CurrentWidth());
// TODO(fserb): Check if this need to be BreakGlyphs.
unsigned max_prefix_length = font.OffsetForPosition(
run, max_prefix_width, OnlyFullGlyphs, DontBreakGlyphs);
if (max_prefix_length < Hyphenation::kMinimumPrefixLength)
return false;
unsigned prefix_length = hyphenation.LastHyphenLocation(
StringView(text.GetText(), start, len),
std::min(max_prefix_length, len - Hyphenation::kMinimumSuffixLength) + 1);
DCHECK_LE(prefix_length, max_prefix_length);
if (!prefix_length || prefix_length < Hyphenation::kMinimumPrefixLength)
return false;
// TODO(kojii): getCharacterRange() measures as if the word were not broken
// as defined in the spec, and is faster than measuring each fragment, but
// ignores the kerning between the last letter and the hyphen.
return RewindToMidWordBreak(
word_measurement, start + prefix_length,
font.GetCharacterRange(run, 0, prefix_length).Width());
}
ALWAYS_INLINE bool BreakingContext::IsBreakAtSoftHyphen() const {
return line_break_ != resolver_.GetPosition()
? line_break_.PreviousInSameNode() == kSoftHyphenCharacter
: current_.PreviousInSameNode() == kSoftHyphenCharacter;
}
static ALWAYS_INLINE bool HasVisibleText(LineLayoutText layout_text,
unsigned offset) {
return !layout_text.ContainsOnlyWhitespace(offset,
layout_text.TextLength() - offset);
}
inline bool BreakingContext::HandleText(WordMeasurements& word_measurements,
bool& hyphenated) {
if (!current_.Offset())
applied_start_width_ = false;
LineLayoutText layout_text(current_.GetLineLayoutItem());
// If we have left a no-wrap inline and entered an autowrap inline while
// ignoring spaces then we need to mark the start of the autowrap inline as a
// potential linebreak now.
if (auto_wrap_ && !ComputedStyle::AutoWrap(last_ws_) && ignoring_spaces_) {
width_.Commit();
line_break_.MoveToStartOf(current_.GetLineLayoutItem());
}
const ComputedStyle& style = layout_text.StyleRef(line_info_.IsFirstLine());
const Font& font = style.GetFont();
unsigned last_space = current_.Offset();
unsigned next_grapheme_cluster = 0;
float word_spacing = current_style_->WordSpacing();
float last_space_word_spacing = 0;
float word_spacing_for_word_measurement = 0;
float width_from_last_breaking_opportunity = width_.UncommittedWidth();
float char_width = 0;
// Auto-wrapping text should wrap in the middle of a word only if it could not
// wrap before the word, which is only possible if the word is the first thing
// on the line, that is, if |w| is zero.
bool break_words = current_style_->BreakWords() &&
((auto_wrap_ && !width_.CommittedWidth()) ||
curr_ws_ == EWhiteSpace::kPre);
bool mid_word_break = false;
bool break_all =
current_style_->WordBreak() == EWordBreak::kBreakAll && auto_wrap_;
bool keep_all =
current_style_->WordBreak() == EWordBreak::kKeepAll && auto_wrap_;
bool prohibit_break_inside = current_style_->HasTextCombine() &&
layout_text.IsCombineText() &&
LineLayoutTextCombine(layout_text).IsCombined();
// This is currently only used for word-break: break-all, specifically for the
// case where we have a break opportunity within a word, then a string of non-
// breakable content that ends up making our word wider than the current line.
// See: fast/css3-text/css3-word-break/word-break-all-wrap-with-floats.html
float width_measurement_at_last_break_opportunity = 0;
Hyphenation* hyphenation = auto_wrap_ ? style.GetHyphenation() : nullptr;
bool disable_soft_hyphen = style.GetHyphens() == Hyphens::kNone;
float hyphen_width = 0;
bool is_line_empty = line_info_.IsEmpty();
if (layout_text.IsSVGInlineText()) {
break_words = false;
break_all = false;
keep_all = false;
}
// Use LineBreakType::Normal for break-all. When a word does not fit,
// rewindToMidWordBreak() finds the mid-word break point.
LineBreakType line_break_type =
keep_all ? LineBreakType::kKeepAll : LineBreakType::kNormal;
bool can_break_mid_word = break_all || break_words;
int next_breakable_position_for_break_all = -1;
if (layout_text.IsWordBreak()) {
width_.Commit();
line_break_.MoveToStartOf(current_.GetLineLayoutItem());
DCHECK_EQ(current_.Offset(), layout_text.TextLength());
}
if (layout_text_info_.text_ != layout_text) {
layout_text_info_.text_ = layout_text;
layout_text_info_.font_ = &font;
layout_text_info_.line_break_iterator_.ResetStringAndReleaseIterator(
layout_text.GetText(), style.LocaleForLineBreakIterator());
} else if (layout_text_info_.font_ != &font) {
layout_text_info_.font_ = &font;
}
// Non-zero only when kerning is enabled, in which case we measure
// words with their trailing space, then subtract its width.
float word_trailing_space_width =
(font.GetFontDescription().GetTypesettingFeatures() & kKerning)
? font.Width(ConstructTextRun(font, &kSpaceCharacter, 1, style,
style.Direction())) +
word_spacing
: 0;
UChar last_character = layout_text_info_.line_break_iterator_.LastCharacter();
UChar second_to_last_character =
layout_text_info_.line_break_iterator_.SecondToLastCharacter();
for (; current_.Offset() < layout_text.TextLength();
current_.FastIncrementInTextNode()) {
previous_character_is_space_ = current_character_is_space_;
UChar c = current_.Current();
SetCurrentCharacterIsSpace(c);
// Auto-wrapping text should not wrap in the middle of a word if it has
// an opportunity to break at a leading white-space.
// TODO (jfernandez): This change is questionable, but it's required to
// achieve the expected behavior for 'break-word' (cases 2.1 and 2.2), while
// keeping current behavior for 'break-all' (cases 4.1 and 4.2)
// https://github.com/w3c/csswg-drafts/issues/2907
if (single_leading_space_)
can_break_mid_word = break_all;
if (!collapse_white_space_ || !current_character_is_space_) {
line_info_.SetEmpty(false);
width_.SetTrailingWhitespaceWidth(0);
}
if (c == kSoftHyphenCharacter && auto_wrap_ && !hyphen_width &&
!disable_soft_hyphen) {
hyphen_width = layout_text.HyphenWidth(
font, TextDirectionFromUnicode(resolver_.GetPosition().Direction()));
width_.AddUncommittedWidth(hyphen_width);
}
bool apply_word_spacing = false;
// Determine if we should try breaking in the middle of a word.
if (can_break_mid_word && !mid_word_break &&
current_.Offset() >= next_grapheme_cluster) {
mid_word_break =
ShouldMidWordBreak(c, layout_text, font, next_grapheme_cluster,
char_width, width_from_last_breaking_opportunity,
break_all, next_breakable_position_for_break_all);
}
// Determine if we are in the whitespace between words.
int next_breakable_position = current_.NextBreakablePosition();
bool between_words =
c == kNewlineCharacter ||
(curr_ws_ != EWhiteSpace::kPre && !at_start_ &&
layout_text_info_.line_break_iterator_.IsBreakable(
current_.Offset(), next_breakable_position, line_break_type) &&
(!disable_soft_hyphen ||
current_.PreviousInSameNode() != kSoftHyphenCharacter));
current_.SetNextBreakablePosition(next_breakable_position);
// If we're in the middle of a word or at the start of a new one and can't
// break there, then continue to the next character.
if (!between_words && !mid_word_break) {
if (ignoring_spaces_) {
// Stop ignoring spaces and begin at this
// new point.
last_space_word_spacing = apply_word_spacing ? word_spacing : 0;
word_spacing_for_word_measurement =
(apply_word_spacing && word_measurements.back().width)
? word_spacing
: 0;
StopIgnoringSpaces(last_space);
}
PrepareForNextCharacter(layout_text, prohibit_break_inside,
previous_character_is_space_);
at_start_ = false;
NextCharacter(c, last_character, second_to_last_character);
continue;
}
// If we're collapsing space and we're at a collapsible space such as a
// space or tab, continue to the next character.
if (ignoring_spaces_ && current_character_is_space_) {
last_space_word_spacing = 0;
// Just keep ignoring these spaces.
NextCharacter(c, last_character, second_to_last_character);
continue;
}
// We're in the first whitespace after a word or in whitespace that we don't
// collapse, which means we may have a breaking opportunity here.
// If we're here and we're collapsing space then the current character isn't
// a form of whitespace we can collapse. Stop ignoring spaces.
bool stopped_ignoring_spaces = false;
if (ignoring_spaces_) {
last_space_word_spacing = 0;
word_spacing_for_word_measurement = 0;
stopped_ignoring_spaces = true;
StopIgnoringSpaces(last_space);
}
// Update our tally of the width since the last breakable position with the
// width of the word we're now at the end of.
float last_width_measurement;
WordMeasurement& word_measurement = CalculateWordWidth(
word_measurements, layout_text, last_space, last_width_measurement,
word_spacing_for_word_measurement, font, word_trailing_space_width, c);
last_width_measurement += last_space_word_spacing;
width_.AddUncommittedWidth(last_width_measurement);
// We keep track of the total width contributed by trailing space as we
// often want to exclude it when determining
// if a run fits on a line.
if (collapse_white_space_ && previous_character_is_space_ &&
current_character_is_space_ && last_width_measurement)
width_.SetTrailingWhitespaceWidth(last_width_measurement);
// If this is the end of the first word in run of text then make sure we
// apply the width from any leading inlines.
// For example: '<span style="margin-left: 5px;"><span style="margin-left:
// 10px;">FirstWord</span></span>' would apply a width of 15px from the two
// span ancestors.
if (!applied_start_width_) {
width_.AddUncommittedWidth(InlineLogicalWidthFromAncestorsIfNeeded(
current_.GetLineLayoutItem(), true, false)
.ToFloat());
applied_start_width_ = true;
}
mid_word_break = false;
if (!width_.FitsOnLine()) {
if (hyphenation && (next_object_ || is_line_empty ||
HasVisibleText(layout_text, current_.Offset()))) {
width_.AddUncommittedWidth(-word_measurement.width);
DCHECK_EQ(last_space,
static_cast<unsigned>(word_measurement.start_offset));
DCHECK_EQ(current_.Offset(),
static_cast<unsigned>(word_measurement.end_offset));
if (Hyphenate(layout_text, style, font, *hyphenation,
last_space_word_spacing, word_measurement)) {
width_.AddUncommittedWidth(word_measurement.width);
hyphenated = true;
at_end_ = true;
return false;
}
width_.AddUncommittedWidth(word_measurement.width);
}
if (can_break_mid_word) {
width_.AddUncommittedWidth(-word_measurement.width);
if (RewindToMidWordBreak(layout_text, style, font, break_all,
word_measurement)) {
last_width_measurement =
word_measurement.width + last_space_word_spacing;
mid_word_break = true;
}
width_.AddUncommittedWidth(word_measurement.width);
}
}
// If we haven't hit a breakable position yet and already don't fit on the
// line try to move below any floats.
if (!width_.CommittedWidth() && auto_wrap_ && !width_.FitsOnLine() &&
!width_measurement_at_last_break_opportunity) {
float available_width_before = width_.AvailableWidth();
width_.FitBelowFloats(line_info_.IsFirstLine());
// If availableWidth changes by moving the line below floats, needs to
// measure midWordBreak again.
if (mid_word_break && available_width_before != width_.AvailableWidth())
mid_word_break = false;
}
// If there is a soft-break available at this whitespace position then take
// it.
apply_word_spacing = word_spacing && current_character_is_space_;
if (CanBreakAtWhitespace(
break_words, word_measurement, stopped_ignoring_spaces, char_width,
hyphenated, disable_soft_hyphen, hyphen_width, between_words,
mid_word_break, can_break_mid_word, previous_character_is_space_,
last_width_measurement, layout_text, font, apply_word_spacing,
word_spacing))
return false;
// If there is a hard-break available at this whitespace position then take
// it.
if (c == kNewlineCharacter && preserves_newline_) {
if (!stopped_ignoring_spaces && current_.Offset())
line_midpoint_state_.EnsureCharacterGetsLineBox(current_);
line_break_.MoveTo(current_.GetLineLayoutItem(), current_.Offset(),
current_.NextBreakablePosition());
line_break_.Increment();
line_info_.SetPreviousLineBrokeCleanly(true);
return true;
}
// Auto-wrapping text should not wrap in the middle of a word once it has
// had an opportunity to break after a word.
if (auto_wrap_ && between_words) {
width_.Commit();
width_from_last_breaking_opportunity = 0;
line_break_.MoveTo(current_.GetLineLayoutItem(), current_.Offset(),
current_.NextBreakablePosition());
break_words = false;
can_break_mid_word = break_all;
width_measurement_at_last_break_opportunity = last_width_measurement;
}
if (between_words) {
last_space_word_spacing = apply_word_spacing ? word_spacing : 0;
word_spacing_for_word_measurement =
(apply_word_spacing && word_measurement.width) ? word_spacing : 0;
last_space = current_.Offset();
}
// If we encounter a newline, or if we encounter a second space, we need to
// go ahead and break up this run and enter a mode where we start collapsing
// spaces.
if (!ignoring_spaces_ && current_style_->CollapseWhiteSpace()) {
if (current_character_is_space_ && previous_character_is_space_) {
ignoring_spaces_ = true;
// We just entered a mode where we are ignoring spaces. Create a
// midpoint to terminate the run before the second space.
line_midpoint_state_.StartIgnoringSpaces(start_of_ignored_spaces_);
trailing_objects_.UpdateMidpointsForTrailingObjects(
line_midpoint_state_, InlineIterator(),
TrailingObjects::kDoNotCollapseFirstSpace);
}
}
PrepareForNextCharacter(layout_text, prohibit_break_inside,
previous_character_is_space_);
at_start_ = false;
is_line_empty = line_info_.IsEmpty();
NextCharacter(c, last_character, second_to_last_character);
}
layout_text_info_.line_break_iterator_.SetPriorContext(
last_character, second_to_last_character);
word_measurements.Grow(word_measurements.size() + 1);
WordMeasurement& word_measurement = word_measurements.back();
word_measurement.layout_text = layout_text;
// IMPORTANT: current.offset() is > layoutText.textLength() here!
float last_width_measurement = 0;
word_measurement.start_offset = last_space;
word_measurement.end_offset = current_.Offset();
if (!ignoring_spaces_) {
last_width_measurement = TextWidth(
layout_text, last_space, current_.Offset() - last_space, font,
width_.CurrentWidth(), collapse_white_space_,
&word_measurement.fallback_fonts, &word_measurement.glyph_bounds);
word_measurement.width =
last_width_measurement + word_spacing_for_word_measurement;
word_measurement.glyph_bounds.Move(word_spacing_for_word_measurement, 0);
}
last_width_measurement += last_space_word_spacing;
LayoutUnit additional_width_from_ancestors =
InlineLogicalWidthFromAncestorsIfNeeded(current_.GetLineLayoutItem(),
!applied_start_width_,
include_end_width_);
width_.AddUncommittedWidth(last_width_measurement +
additional_width_from_ancestors);
if (collapse_white_space_ && current_character_is_space_ &&
last_width_measurement)
width_.SetTrailingWhitespaceWidth(last_width_measurement +
additional_width_from_ancestors);
include_end_width_ = false;
if (!ignoring_spaces_ && !width_.FitsOnLine()) {
if (hyphenation && (next_object_ || is_line_empty)) {
width_.AddUncommittedWidth(-word_measurement.width);
DCHECK_EQ(last_space,
static_cast<unsigned>(word_measurement.start_offset));
DCHECK_EQ(current_.Offset(),
static_cast<unsigned>(word_measurement.end_offset));
if (Hyphenate(layout_text, style, font, *hyphenation,
last_space_word_spacing, word_measurement)) {
hyphenated = true;
at_end_ = true;
}
width_.AddUncommittedWidth(word_measurement.width);
}
if (!hyphenated && IsBreakAtSoftHyphen() && !disable_soft_hyphen) {
hyphenated = true;
at_end_ = true;
}
if (!hyphenated && can_break_mid_word) {
width_.AddUncommittedWidth(-word_measurement.width);
if (RewindToMidWordBreak(layout_text, style, font, break_all,
word_measurement)) {
width_.AddUncommittedWidth(word_measurement.width);
width_.Commit();
at_end_ = true;
} else {
width_.AddUncommittedWidth(word_measurement.width);
if (width_.CommittedWidth())
at_end_ = true;
}
}
}
return false;
}
inline void BreakingContext::PrepareForNextCharacter(
const LineLayoutText& layout_text,
bool& prohibit_break_inside,
bool previous_character_is_space) {
if (layout_text.IsSVGInlineText() && current_.Offset()) {
// Force creation of new InlineBoxes for each absolute positioned character
// (those that start new text chunks).
if (LineLayoutSVGInlineText(layout_text)
.CharacterStartsNewTextChunk(current_.Offset()))
line_midpoint_state_.EnsureCharacterGetsLineBox(current_);
}
if (prohibit_break_inside) {
current_.SetNextBreakablePosition(layout_text.TextLength());
prohibit_break_inside = false;
}
if (current_character_is_space_ && !previous_character_is_space) {
start_of_ignored_spaces_.SetLineLayoutItem(current_.GetLineLayoutItem());
start_of_ignored_spaces_.SetOffset(current_.Offset());
}
if (!current_character_is_space_ && previous_character_is_space) {
if (auto_wrap_ && current_style_->BreakOnlyAfterWhiteSpace()) {
line_break_.MoveTo(current_.GetLineLayoutItem(), current_.Offset(),
current_.NextBreakablePosition());
if (current_.Offset() == current_start_offset_ + 1)
single_leading_space_ = true;
}
}
if (collapse_white_space_ && current_character_is_space_ && !ignoring_spaces_)
trailing_objects_.SetTrailingWhitespace(
LineLayoutText(current_.GetLineLayoutItem()));
else if (!current_style_->CollapseWhiteSpace() ||
!current_character_is_space_)
trailing_objects_.Clear();
}
inline void BreakingContext::StopIgnoringSpaces(unsigned& last_space) {
ignoring_spaces_ = false;
// e.g., "Foo goo", don't add in any of the ignored spaces.
last_space = current_.Offset();
line_midpoint_state_.StopIgnoringSpaces(
InlineIterator(nullptr, current_.GetLineLayoutItem(), current_.Offset()));
}
inline WordMeasurement& BreakingContext::CalculateWordWidth(
WordMeasurements& word_measurements,
LineLayoutText& layout_text,
unsigned last_space,
float& last_width_measurement,
float word_spacing_for_word_measurement,
const Font& font,
float word_trailing_space_width,
UChar c) {
word_measurements.Grow(word_measurements.size() + 1);
WordMeasurement& word_measurement = word_measurements.back();
word_measurement.layout_text = layout_text;
word_measurement.end_offset = current_.Offset();
word_measurement.start_offset = last_space;
if (word_trailing_space_width && c == kSpaceCharacter)
last_width_measurement =
TextWidth(layout_text, last_space, current_.Offset() + 1 - last_space,
font, width_.CurrentWidth(), collapse_white_space_,
&word_measurement.fallback_fonts,
&word_measurement.glyph_bounds) -
word_trailing_space_width;
else
last_width_measurement = TextWidth(
layout_text, last_space, current_.Offset() - last_space, font,
width_.CurrentWidth(), collapse_white_space_,
&word_measurement.fallback_fonts, &word_measurement.glyph_bounds);
word_measurement.width =
last_width_measurement + word_spacing_for_word_measurement;
word_measurement.glyph_bounds.Move(word_spacing_for_word_measurement, 0);
return word_measurement;
}
inline void BreakingContext::TrailingSpacesHang(bool can_break_mid_word) {
DCHECK(curr_ws_ == EWhiteSpace::kBreakSpaces);
// Avoid breaking before the first white-space after a word if there is a
// breaking opportunity before.
if (single_leading_space_ && !previous_character_is_space_)
return;
line_break_.MoveTo(current_.GetLineLayoutItem(), current_.Offset(),
current_.NextBreakablePosition());
// Avoid breaking before the first white-space after a word, unless
// overflow-wrap or word-break allow to.
if (!previous_character_is_space_ && !can_break_mid_word)
line_break_.Increment();
}
inline bool BreakingContext::TrailingSpaceExceedsAvailableWidth(
bool can_break_mid_word,
const LineLayoutText& layout_text,
WordMeasurement& word_measurement,
bool apply_word_spacing,
bool word_spacing,
const Font& font) {
// If we break only after white-space, consider the current character
// as candidate width for this line.
if (width_.FitsOnLine() && current_character_is_space_ &&
current_style_->BreakOnlyAfterWhiteSpace()) {
float char_width = TextWidth(layout_text, current_.Offset(), 1, font,
width_.CurrentWidth(), collapse_white_space_,
&word_measurement.fallback_fonts,
&word_measurement.glyph_bounds) +
(apply_word_spacing ? word_spacing : 0);
// Check if line is too big even without the extra space
// at the end of the line. If it is not, do nothing.
// If the line needs the extra whitespace to be too long,
// then move the line break to the space and skip all
// additional whitespace.
if (!width_.FitsOnLine(char_width)) {
if (curr_ws_ == EWhiteSpace::kBreakSpaces) {
TrailingSpacesHang(can_break_mid_word);
return true;
}
line_break_.MoveTo(current_.GetLineLayoutItem(), current_.Offset(),
current_.NextBreakablePosition());
SkipTrailingWhitespace(line_break_, line_info_);
return true;
}
}
return false;
}
inline bool BreakingContext::CanBreakAtWhitespace(
bool break_words,
WordMeasurement& word_measurement,
bool stopped_ignoring_spaces,
float char_width,
bool& hyphenated,
bool disable_soft_hyphen,
float& hyphen_width,
bool between_words,
bool mid_word_break,
bool can_break_mid_word,
bool previous_character_is_space,
float last_width_measurement,
const LineLayoutText& layout_text,
const Font& font,
bool apply_word_spacing,
float word_spacing) {
if (!auto_wrap_ && !break_words)
return false;
// If we break only after white-space, consider the current character
// as candidate width for this line.
if (mid_word_break ||
TrailingSpaceExceedsAvailableWidth(can_break_mid_word, layout_text,
word_measurement, apply_word_spacing,
word_spacing, font) ||
!width_.FitsOnLine()) {
if (line_break_.AtTextParagraphSeparator()) {
if (!stopped_ignoring_spaces && current_.Offset() > 0)
line_midpoint_state_.EnsureCharacterGetsLineBox(current_);
line_break_.Increment();
line_info_.SetPreviousLineBrokeCleanly(true);
word_measurement.end_offset = line_break_.Offset();
}
if (IsBreakAtSoftHyphen() && !disable_soft_hyphen)
hyphenated = true;
if (line_break_.Offset() &&
line_break_.Offset() != (unsigned)word_measurement.end_offset &&
!word_measurement.width) {
if (char_width) {
word_measurement.end_offset = line_break_.Offset();
word_measurement.width = char_width;
}
}
// Didn't fit. Jump to the end unless there's still an opportunity to
// collapse whitespace.
if (ignoring_spaces_ || !collapse_white_space_ ||
!current_character_is_space_ || !previous_character_is_space) {
at_end_ = true;
return true;
}
} else {
if (!between_words || (mid_word_break && !auto_wrap_))
width_.AddUncommittedWidth(-last_width_measurement);
if (hyphen_width) {
// Subtract the width of the soft hyphen out since we fit on a line.
width_.AddUncommittedWidth(-hyphen_width);
hyphen_width = 0;
}
}
return false;
}
inline void BreakingContext::CommitAndUpdateLineBreakIfNeeded() {
bool check_for_break = auto_wrap_;
if (width_.CommittedWidth() && !width_.FitsOnLine() &&
line_break_.GetLineLayoutItem() && curr_ws_ == EWhiteSpace::kNowrap) {
// If this nowrap item fits but its trailing spaces does not, and if the
// next item is auto-wrap, break before the next item.
// TODO(kojii): This case should be handled when we read next item.
if (width_.FitsOnLine(0, kExcludeWhitespace) &&
(!next_object_ || next_object_.StyleRef().AutoWrap())) {
width_.Commit();
line_break_.MoveToStartOf(next_object_);
}
check_for_break = true;
} else if (next_object_ && current_.GetLineLayoutItem().IsText() &&
next_object_.IsText() && !next_object_.IsBR() &&
(auto_wrap_ || next_object_.StyleRef().AutoWrap())) {
if (auto_wrap_ && current_character_is_space_) {
check_for_break = true;
} else {
LineLayoutText next_text(next_object_);
if (next_text.TextLength()) {
UChar c = next_text.CharacterAt(0);
// If the next item on the line is text, and if we did not end with
// a space, then the next text run continues our word (and so it needs
// to keep adding to the uncommitted width. Just update and continue.
check_for_break =
!current_character_is_space_ &&
(c == kSpaceCharacter || c == kTabulationCharacter ||
(c == kNewlineCharacter && !next_object_.PreservesNewline()));
} else if (next_text.IsWordBreak()) {
check_for_break = true;
}
if (!width_.FitsOnLine() && !width_.CommittedWidth())
width_.FitBelowFloats(line_info_.IsFirstLine());
bool can_place_on_line =
width_.FitsOnLine() || !auto_wrap_was_ever_true_on_line_;
if (can_place_on_line && check_for_break) {
width_.Commit();
line_break_.MoveToStartOf(next_object_);
}
}
}
if (check_for_break && !width_.FitsOnLine()) {
// if we have floats, try to get below them.
if (current_character_is_space_ && !ignoring_spaces_ &&
current_style_->CollapseWhiteSpace())
trailing_objects_.Clear();
// If we are going to break before a float on an object that is just
// collapsible white space then make sure the line break is moved to
// the float.
if (width_.FitsOnLine(0, kExcludeWhitespace) && next_object_ &&
next_object_.IsFloating() && current_.GetLineLayoutItem().IsText() &&
LineLayoutText(current_.GetLineLayoutItem())
.IsAllCollapsibleWhitespace()) {
width_.Commit();
line_break_.MoveToStartOf(next_object_);
}
if (width_.CommittedWidth()) {
at_end_ = true;
return;
}
width_.FitBelowFloats(line_info_.IsFirstLine());
// |width| may have been adjusted because we got shoved down past a float
// (thus giving us more room), so we need to retest, and only jump to the
// the end label if we still don't fit on the line. -dwh
if (!width_.FitsOnLine()) {
at_end_ = true;
return;
}
} else if (block_style_->AutoWrap() && !width_.FitsOnLine() &&
!width_.CommittedWidth()) {
// If the container autowraps but the current child does not then we still
// need to ensure that it wraps and moves below any floats.
width_.FitBelowFloats(line_info_.IsFirstLine());
}
if (!current_.GetLineLayoutItem().IsFloatingOrOutOfFlowPositioned()) {
last_object_ = current_.GetLineLayoutItem();
if (last_object_.IsAtomicInlineLevel() && auto_wrap_ &&
(!last_object_.IsListMarker() ||
LineLayoutListMarker(last_object_).IsInside()) &&
!last_object_.IsRubyRun()) {
width_.Commit();
line_break_.MoveToStartOf(next_object_);
}
}
}
inline IndentTextOrNot RequiresIndent(bool is_first_line,
bool is_after_hard_line_break,
const ComputedStyle& style) {
IndentTextOrNot indent_text = kDoNotIndentText;
if (is_first_line ||
(is_after_hard_line_break &&
style.GetTextIndentLine() != TextIndentLine::kFirstLine))
indent_text = kIndentText;
if (style.GetTextIndentType() == TextIndentType::kHanging)
indent_text = indent_text == kIndentText ? kDoNotIndentText : kIndentText;
return indent_text;
}
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_BREAKING_CONTEXT_INLINE_HEADERS_H_