/*
 * 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 BreakingContextInlineHeaders_h
#define BreakingContextInlineHeaders_h

#include "core/layout/TextRunConstructor.h"
#include "core/layout/api/LineLayoutBox.h"
#include "core/layout/api/LineLayoutListMarker.h"
#include "core/layout/api/LineLayoutRubyRun.h"
#include "core/layout/api/LineLayoutSVGInlineText.h"
#include "core/layout/api/LineLayoutText.h"
#include "core/layout/api/LineLayoutTextCombine.h"
#include "core/layout/line/InlineIterator.h"
#include "core/layout/line/InlineTextBox.h"
#include "core/layout/line/LayoutTextInfo.h"
#include "core/layout/line/LineBreaker.h"
#include "core/layout/line/LineInfo.h"
#include "core/layout/line/LineWidth.h"
#include "core/layout/line/TrailingObjects.h"
#include "core/layout/line/WordMeasurement.h"
#include "core/paint/PaintLayer.h"
#include "platform/fonts/CharacterRange.h"
#include "platform/text/Hyphenation.h"
#include "platform/text/TextBreakIterator.h"
#include "platform/wtf/Allocator.h"
#include "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()),
        allow_images_to_break_(
            !block.GetDocument().InQuirksMode() || !block.IsTableCell() ||
            !block_style_->LogicalWidth().IsIntrinsicOrAuto()),
        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);
  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&,
                          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&,
                                 bool break_all,
                                 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 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 allow_images_to_break_;
  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(0, 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.Style()->VerticalAlign() != parent.Style()->VerticalAlign() ||
       !parent.Style()->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().Style()->WhiteSpace();
  last_ws_ = last_object_.IsLayoutInline()
                 ? last_object_.Style()->WhiteSpace()
                 : last_object_.Parent().Style()->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;
}

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 = current_style_->Clear();
  }
  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();
}

inline bool ShouldAddBorderPaddingMargin(LineLayoutItem child,
                                         bool& check_side) {
  if (!child || (child.IsText() && !LineLayoutText(child).TextLength()))
    return true;
  check_side = false;
  return check_side;
}

inline LayoutUnit InlineLogicalWidthFromAncestorsIfNeeded(LineLayoutItem child,
                                                          bool start = true,
                                                          bool end = true) {
  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))
        extra_width += BorderPaddingMarginStart(parent_as_layout_inline);
      if (end && ShouldAddBorderPaddingMargin(child.NextSibling(), end))
        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.Style()->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.
    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;
    }
  }
  // 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.Style()->IsCollapsibleWhiteSpace(next_char)) {
      line_midpoint_state.StartIgnoringSpaces(InlineIterator(0, 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().IsImage() || allow_images_to_break_) &&
      (!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(0, 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 (size_t i = 0; i < word_measurements.size(); ++i) {
    if (word_measurements[i].width > 0)
      return word_measurements[i].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.Style()->HasTextCombine())
    return text.Width(from, len, font, LayoutUnit(x_pos),
                      text.Style()->Direction(), fallback_fonts, glyph_bounds);

  TextRun run = ConstructTextRun(font, text, from, len, text.StyleRef());
  run.SetTabSize(!collapse_white_space, text.Style()->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,
    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;
  bool mid_word_break_is_before_surrogate_pair =
      U16_IS_LEAD(c) && current_.Offset() + 1 < layout_text.TextLength() &&
      U16_IS_TRAIL(layout_text.UncheckedCharacterAt(current_.Offset() + 1));
  char_width =
      TextWidth(layout_text, current_.Offset(),
                mid_word_break_is_before_surrogate_pair ? 2 : 1, 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 int LastBreakablePositionForBreakAll(LineLayoutText text,
                                                   const ComputedStyle& style,
                                                   int start,
                                                   int end) {
  LazyLineBreakIterator line_break_iterator(text.GetText(),
                                            style.LocaleForLineBreakIterator());
  int last_breakable_position = 0, next_breakable_position = -1;
  for (int i = start;; i = next_breakable_position + 1) {
    line_break_iterator.IsBreakable(i, next_breakable_position,
                                    LineBreakType::kBreakAll);
    if (next_breakable_position == end)
      return end;
    if (next_breakable_position < 0 || next_breakable_position > end)
      return last_breakable_position;
    last_breakable_position = next_breakable_position;
  }
}

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,
    bool break_all,
    WordMeasurement& word_measurement) {
  int start = word_measurement.start_offset;
  int end = CanMidWordBreakBefore(text) ? start : start + 1;
  if (break_all) {
    LazyLineBreakIterator line_break_iterator(
        text.GetText(), style.LocaleForLineBreakIterator());
    int next_breakable = -1;
    line_break_iterator.IsBreakable(end, next_breakable,
                                    LineBreakType::kBreakAll);
    if (next_breakable < 0)
      return false;
    end = next_breakable;
  }
  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())
    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;
  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_all,
                                     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, false);
  if (!len) {
    // No characters can fit in the available space.
    return RewindToFirstMidWordBreak(text, style, font, break_all,
                                     word_measurement);
  }

  int end = start + len;
  if (break_all) {
    end = LastBreakablePositionForBreakAll(text, style, start, end);
    if (!end)
      return false;
    len = end - start;
  }
  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 <= Hyphenation::MinimumPrefixWidth(font))
    return false;

  TextRun run = ConstructTextRun(font, text, start, len, style);
  run.SetTabSize(!collapse_white_space_, style.GetTabSize());
  run.SetXPos(width_.CurrentWidth());
  unsigned max_prefix_length =
      font.OffsetForPosition(run, max_prefix_width, false);
  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();
  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()) {
    bool previous_character_is_space = current_character_is_space_;
    UChar c = current_.Current();
    SetCurrentCharacterIsSpace(c);

    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 && !U16_IS_TRAIL(c))
      mid_word_break =
          ShouldMidWordBreak(c, layout_text, font, 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 (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(0, 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 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() && !can_break_mid_word) {
    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)) {
      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 (width_.FitsOnLine(0, kExcludeWhitespace)) {
      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_.Style()->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_);
      }
    }
  }

  SECURITY_DCHECK(current_style_->RefCount() > 0);
  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_.IsImage() || allow_images_to_break_) &&
        (!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  // BreakingContextInlineHeaders_h
