| /* |
| * 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. |
| * |
| * 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. |
| * |
| */ |
| |
| #include "build/build_config.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_item.h" |
| #include "third_party/blink/renderer/core/layout/bidi_run_for_line.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/layout/layout_ruby_run.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/line/breaking_context_inline_headers.h" |
| #include "third_party/blink/renderer/core/layout/line/glyph_overflow.h" |
| #include "third_party/blink/renderer/core/layout/line/layout_text_info.h" |
| #include "third_party/blink/renderer/core/layout/line/line_layout_state.h" |
| #include "third_party/blink/renderer/core/layout/line/line_width.h" |
| #include "third_party/blink/renderer/core/layout/line/word_measurement.h" |
| #include "third_party/blink/renderer/core/layout/svg/line/svg_root_inline_box.h" |
| #include "third_party/blink/renderer/core/layout/vertical_position_cache.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" |
| #include "third_party/blink/renderer/platform/text/bidi_resolver.h" |
| #include "third_party/blink/renderer/platform/text/character.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| |
| namespace blink { |
| |
| class ExpansionOpportunities { |
| public: |
| ExpansionOpportunities() : total_opportunities_(0) {} |
| |
| void AddRunWithExpansions(BidiRun& run, |
| bool& is_after_expansion, |
| TextJustify text_justify) { |
| LineLayoutText text = LineLayoutText(run.line_layout_item_); |
| unsigned opportunities_in_run; |
| if (text.Is8Bit()) { |
| opportunities_in_run = Character::ExpansionOpportunityCount( |
| text.Characters8() + run.start_, run.stop_ - run.start_, |
| run.box_->Direction(), is_after_expansion, text_justify); |
| } else if (run.line_layout_item_.IsCombineText()) { |
| // Justfication applies to before and after the combined text as if |
| // it is an ideographic character, and is prohibited inside the |
| // combined text. |
| opportunities_in_run = is_after_expansion ? 1 : 2; |
| is_after_expansion = true; |
| } else { |
| opportunities_in_run = Character::ExpansionOpportunityCount( |
| text.Characters16() + run.start_, run.stop_ - run.start_, |
| run.box_->Direction(), is_after_expansion, text_justify); |
| } |
| runs_with_expansions_.push_back(opportunities_in_run); |
| total_opportunities_ += opportunities_in_run; |
| } |
| void RemoveTrailingExpansion() { |
| if (!total_opportunities_ || !runs_with_expansions_.back()) |
| return; |
| runs_with_expansions_.back()--; |
| total_opportunities_--; |
| } |
| |
| unsigned Count() { return total_opportunities_; } |
| |
| unsigned OpportunitiesInRun(size_t run) { return runs_with_expansions_[run]; } |
| |
| void ComputeExpansionsForJustifiedText(BidiRun* first_run, |
| BidiRun* trailing_space_run, |
| LayoutUnit& total_logical_width, |
| LayoutUnit available_logical_width) { |
| if (!total_opportunities_ || available_logical_width <= total_logical_width) |
| return; |
| |
| size_t i = 0; |
| for (BidiRun* r = first_run; r; r = r->Next()) { |
| if (!r->box_ || r == trailing_space_run) |
| continue; |
| |
| if (r->line_layout_item_.IsText()) { |
| unsigned opportunities_in_run = runs_with_expansions_[i++]; |
| |
| CHECK_LE(opportunities_in_run, total_opportunities_); |
| |
| // Don't justify for white-space: pre. |
| if (r->line_layout_item_.StyleRef().WhiteSpace() != EWhiteSpace::kPre) { |
| InlineTextBox* text_box = ToInlineTextBox(r->box_); |
| CHECK(total_opportunities_); |
| int expansion = ((available_logical_width - total_logical_width) * |
| opportunities_in_run / total_opportunities_) |
| .ToInt(); |
| text_box->SetExpansion(expansion); |
| total_logical_width += expansion; |
| } |
| total_opportunities_ -= opportunities_in_run; |
| if (!total_opportunities_) |
| break; |
| } |
| } |
| } |
| |
| private: |
| Vector<unsigned, 16> runs_with_expansions_; |
| unsigned total_opportunities_; |
| }; |
| |
| static inline InlineBox* CreateInlineBoxForLayoutObject( |
| LineLayoutItem line_layout_item, |
| bool is_root_line_box, |
| bool is_only_run = false) { |
| // Callers should handle text themselves. |
| DCHECK(!line_layout_item.IsText()); |
| |
| if (is_root_line_box) |
| return LineLayoutBlockFlow(line_layout_item).CreateAndAppendRootInlineBox(); |
| |
| if (line_layout_item.IsBox()) |
| return LineLayoutBox(line_layout_item).CreateInlineBox(); |
| |
| return LineLayoutInline(line_layout_item).CreateAndAppendInlineFlowBox(); |
| } |
| |
| static inline InlineTextBox* CreateInlineBoxForText(BidiRun& run, |
| bool is_only_run) { |
| DCHECK(run.line_layout_item_.IsText()); |
| LineLayoutText text = LineLayoutText(run.line_layout_item_); |
| InlineTextBox* text_box = |
| text.CreateInlineTextBox(run.start_, run.stop_ - run.start_); |
| // We only treat a box as text for a <br> if we are on a line by ourself or in |
| // strict mode (Note the use of strict mode. In "almost strict" mode, we |
| // don't treat the box for <br> as text.) |
| if (text.IsBR()) |
| text_box->SetIsText(is_only_run || text.GetDocument().InNoQuirksMode()); |
| text_box->SetDirOverride( |
| run.DirOverride(text.StyleRef().RtlOrdering() == EOrder::kVisual)); |
| if (run.has_hyphen_) |
| text_box->SetHasHyphen(true); |
| return text_box; |
| } |
| |
| static inline void DirtyLineBoxesForObject(LayoutObject* o, bool full_layout) { |
| if (o->IsText()) { |
| LayoutText* layout_text = ToLayoutText(o); |
| layout_text->DirtyOrDeleteLineBoxesIfNeeded(full_layout); |
| } else { |
| ToLayoutInline(o)->DirtyLineBoxes(full_layout); |
| } |
| } |
| |
| static bool ParentIsConstructedOrHaveNext(InlineFlowBox* parent_box) { |
| do { |
| if (parent_box->IsConstructed() || parent_box->NextOnLine()) |
| return true; |
| parent_box = parent_box->Parent(); |
| } while (parent_box); |
| return false; |
| } |
| |
| InlineFlowBox* LayoutBlockFlow::CreateLineBoxes(LineLayoutItem line_layout_item, |
| const LineInfo& line_info, |
| InlineBox* child_box) { |
| // See if we have an unconstructed line box for this object that is also |
| // the last item on the line. |
| unsigned line_depth = 1; |
| InlineFlowBox* parent_box = nullptr; |
| InlineFlowBox* result = nullptr; |
| do { |
| if (line_depth++ >= kCMaxLineDepth || |
| (IsLayoutNGBlockFlow() && line_layout_item.IsLayoutBlockFlow())) { |
| // If we've exceeded our line depth, then jump straight to the root and |
| // skip all the remaining intermediate inline flows. Additionally, if |
| // we're in LayoutNG, abort once we find a block in the ancestry. It may |
| // be that it's not |this|. This happens in multicol, because LayoutNG |
| // doesn't see the flow thread, and treats DOM children of the multicol |
| // container as actual children of the multicol container, without any |
| // intervening flow thread block (although that block does exist in the |
| // layout tree). |
| line_layout_item = LineLayoutItem(this); |
| } |
| |
| SECURITY_DCHECK(line_layout_item.IsLayoutInline() || |
| line_layout_item.IsEqual(this)); |
| |
| LineLayoutInline inline_flow( |
| !line_layout_item.IsEqual(this) ? line_layout_item : nullptr); |
| |
| // Get the last box we made for this layout object. |
| parent_box = inline_flow |
| ? inline_flow.LastLineBox() |
| : LineLayoutBlockFlow(line_layout_item).LastLineBox(); |
| |
| // If this box or its ancestor is constructed then it is from a previous |
| // line, and we need to make a new box for our line. If this box or its |
| // ancestor is unconstructed but it has something following it on the line, |
| // then we know we have to make a new box as well. In this situation our |
| // inline has actually been split in two on the same line (this can happen |
| // with very fancy language mixtures). |
| bool constructed_new_box = false; |
| bool allowed_to_construct_new_box = |
| !inline_flow || inline_flow.AlwaysCreateLineBoxes(); |
| bool can_use_existing_parent_box = |
| parent_box && !ParentIsConstructedOrHaveNext(parent_box); |
| if (allowed_to_construct_new_box && !can_use_existing_parent_box) { |
| // We need to make a new box for this layout object. Once |
| // made, we need to place it at the end of the current line. |
| InlineBox* new_box = CreateInlineBoxForLayoutObject( |
| LineLayoutItem(line_layout_item), line_layout_item.IsEqual(this)); |
| SECURITY_DCHECK(new_box->IsInlineFlowBox()); |
| parent_box = ToInlineFlowBox(new_box); |
| parent_box->SetFirstLineStyleBit(line_info.IsFirstLine()); |
| parent_box->SetIsHorizontal(IsHorizontalWritingMode()); |
| constructed_new_box = true; |
| } |
| |
| if (constructed_new_box || can_use_existing_parent_box) { |
| if (!result) |
| result = parent_box; |
| |
| // If we have hit the block itself, then |box| represents the root |
| // inline box for the line, and it doesn't have to be appended to any |
| // parent inline. |
| if (child_box) |
| parent_box->AddToLine(child_box); |
| |
| if (!constructed_new_box || line_layout_item.IsEqual(this)) |
| break; |
| |
| child_box = parent_box; |
| } |
| |
| line_layout_item = line_layout_item.Parent(); |
| } while (true); |
| |
| return result; |
| } |
| |
| template <typename CharacterType> |
| static inline bool EndsWithASCIISpaces(const CharacterType* characters, |
| unsigned pos, |
| unsigned end) { |
| while (IsASCIISpace(characters[pos])) { |
| pos++; |
| if (pos >= end) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool ReachedEndOfTextRun(const BidiRunList<BidiRun>& bidi_runs) { |
| BidiRun* run = bidi_runs.LogicallyLastRun(); |
| if (!run) |
| return true; |
| unsigned pos = run->Stop(); |
| LineLayoutItem r = run->line_layout_item_; |
| if (!r.IsText() || r.IsBR()) |
| return false; |
| LineLayoutText layout_text(r); |
| unsigned length = layout_text.TextLength(); |
| if (pos >= length) |
| return true; |
| |
| if (layout_text.Is8Bit()) |
| return EndsWithASCIISpaces(layout_text.Characters8(), pos, length); |
| return EndsWithASCIISpaces(layout_text.Characters16(), pos, length); |
| } |
| |
| RootInlineBox* LayoutBlockFlow::ConstructLine(BidiRunList<BidiRun>& bidi_runs, |
| const LineInfo& line_info) { |
| DCHECK(bidi_runs.FirstRun()); |
| |
| InlineFlowBox* parent_box = nullptr; |
| int run_count = bidi_runs.RunCount() - line_info.RunsFromLeadingWhitespace(); |
| for (BidiRun* r = bidi_runs.FirstRun(); r; r = r->Next()) { |
| // Create a box for our object. |
| bool is_only_run = (run_count == 1); |
| if (run_count == 2 && !r->line_layout_item_.IsListMarker()) { |
| is_only_run = |
| (!StyleRef().IsLeftToRightDirection() ? bidi_runs.LastRun() |
| : bidi_runs.FirstRun()) |
| ->line_layout_item_.IsListMarker(); |
| } |
| |
| if (line_info.IsEmpty()) |
| continue; |
| |
| InlineBox* box; |
| if (r->line_layout_item_.IsText()) |
| box = CreateInlineBoxForText(*r, is_only_run); |
| else |
| box = CreateInlineBoxForLayoutObject(r->line_layout_item_, false, |
| is_only_run); |
| r->box_ = box; |
| |
| DCHECK(box); |
| if (!box) |
| continue; |
| |
| // If we have no parent box yet, or if the run is not simply a sibling, |
| // then we need to construct inline boxes as necessary to properly enclose |
| // the run's inline box. Segments can only be siblings at the root level, as |
| // they are positioned separately. |
| if (!parent_box || |
| (parent_box->GetLineLayoutItem() != r->line_layout_item_.Parent())) { |
| // Create new inline boxes all the way back to the appropriate insertion |
| // point. |
| parent_box = |
| CreateLineBoxes(r->line_layout_item_.Parent(), line_info, box); |
| } else { |
| // Append the inline box to this line. |
| parent_box->AddToLine(box); |
| } |
| |
| box->SetBidiLevel(r->Level()); |
| |
| if (box->IsInlineTextBox()) { |
| if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) |
| cache->InlineTextBoxesUpdated(r->line_layout_item_); |
| } |
| } |
| |
| // We should have a root inline box. It should be unconstructed and |
| // be the last continuation of our line list. |
| DCHECK(LastLineBox()); |
| DCHECK(!LastLineBox()->IsConstructed()); |
| |
| // Set bits on our inline flow boxes that indicate which sides should |
| // paint borders/margins/padding. This knowledge will ultimately be used when |
| // we determine the horizontal positions and widths of all the inline boxes on |
| // the line. |
| bool is_logically_last_run_wrapped = |
| bidi_runs.LogicallyLastRun()->line_layout_item_ && |
| bidi_runs.LogicallyLastRun()->line_layout_item_.IsText() |
| ? !ReachedEndOfTextRun(bidi_runs) |
| : true; |
| LastLineBox()->DetermineSpacingForFlowBoxes( |
| line_info.IsLastLine(), is_logically_last_run_wrapped, |
| bidi_runs.LogicallyLastRun()->line_layout_item_); |
| |
| // Now mark the line boxes as being constructed. |
| LastLineBox()->SetConstructed(); |
| |
| // Return the last line. |
| return LastRootBox(); |
| } |
| |
| ETextAlign LayoutBlockFlow::TextAlignmentForLine( |
| bool ends_with_soft_break) const { |
| return StyleRef().GetTextAlign(!ends_with_soft_break); |
| } |
| |
| static bool TextAlignmentNeedsTrailingSpace(ETextAlign text_align, |
| const ComputedStyle& style) { |
| switch (text_align) { |
| case ETextAlign::kLeft: |
| case ETextAlign::kWebkitLeft: |
| return false; |
| case ETextAlign::kRight: |
| case ETextAlign::kWebkitRight: |
| case ETextAlign::kCenter: |
| case ETextAlign::kWebkitCenter: |
| case ETextAlign::kJustify: |
| return style.CollapseWhiteSpace(); |
| case ETextAlign::kStart: |
| return style.CollapseWhiteSpace() && !style.IsLeftToRightDirection(); |
| case ETextAlign::kEnd: |
| return style.CollapseWhiteSpace() && style.IsLeftToRightDirection(); |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| static void UpdateLogicalWidthForLeftAlignedBlock( |
| bool is_left_to_right_direction, |
| BidiRun* trailing_space_run, |
| LayoutUnit& logical_left, |
| LayoutUnit total_logical_width, |
| LayoutUnit available_logical_width) { |
| // The direction of the block should determine what happens with wide lines. |
| // In particular with RTL blocks, wide lines should still spill out to the |
| // left. |
| if (is_left_to_right_direction) { |
| if (total_logical_width > available_logical_width && trailing_space_run) |
| trailing_space_run->box_->SetLogicalWidth(std::max( |
| LayoutUnit(), trailing_space_run->box_->LogicalWidth() - |
| total_logical_width + available_logical_width)); |
| return; |
| } |
| |
| if (trailing_space_run && |
| trailing_space_run->line_layout_item_.StyleRef().CollapseWhiteSpace()) |
| trailing_space_run->box_->SetLogicalWidth(LayoutUnit()); |
| else if (total_logical_width > available_logical_width) |
| logical_left -= (total_logical_width - available_logical_width); |
| } |
| |
| static void UpdateLogicalWidthForRightAlignedBlock( |
| bool is_left_to_right_direction, |
| BidiRun* trailing_space_run, |
| LayoutUnit& logical_left, |
| LayoutUnit& total_logical_width, |
| LayoutUnit available_logical_width) { |
| // Wide lines spill out of the block based off direction. |
| // So even if text-align is right, if direction is LTR, wide lines should |
| // overflow out of the right side of the block. |
| if (is_left_to_right_direction) { |
| if (trailing_space_run && |
| trailing_space_run->line_layout_item_.StyleRef().CollapseWhiteSpace()) { |
| total_logical_width -= trailing_space_run->box_->LogicalWidth(); |
| trailing_space_run->box_->SetLogicalWidth(LayoutUnit()); |
| } |
| if (total_logical_width < available_logical_width) |
| logical_left += available_logical_width - total_logical_width; |
| return; |
| } |
| |
| if (total_logical_width > available_logical_width && trailing_space_run) { |
| trailing_space_run->box_->SetLogicalWidth(std::max( |
| LayoutUnit(), trailing_space_run->box_->LogicalWidth() - |
| total_logical_width + available_logical_width)); |
| total_logical_width -= trailing_space_run->box_->LogicalWidth(); |
| } else { |
| logical_left += available_logical_width - total_logical_width; |
| } |
| } |
| |
| static void UpdateLogicalWidthForCenterAlignedBlock( |
| bool is_left_to_right_direction, |
| BidiRun* trailing_space_run, |
| LayoutUnit& logical_left, |
| LayoutUnit& total_logical_width, |
| LayoutUnit available_logical_width) { |
| LayoutUnit trailing_space_width; |
| if (trailing_space_run && |
| trailing_space_run->line_layout_item_.StyleRef().CollapseWhiteSpace()) { |
| total_logical_width -= trailing_space_run->box_->LogicalWidth(); |
| trailing_space_width = |
| std::min(trailing_space_run->box_->LogicalWidth(), |
| (available_logical_width - total_logical_width + 1) / 2); |
| trailing_space_run->box_->SetLogicalWidth( |
| std::max(LayoutUnit(), trailing_space_width)); |
| } |
| if (is_left_to_right_direction) |
| logical_left += std::max( |
| (available_logical_width - total_logical_width) / 2, LayoutUnit()); |
| else |
| logical_left += total_logical_width > available_logical_width |
| ? (available_logical_width - total_logical_width) |
| : (available_logical_width - total_logical_width) / 2 - |
| trailing_space_width; |
| } |
| |
| void LayoutBlockFlow::SetMarginsForRubyRun(BidiRun* run, |
| LayoutRubyRun* layout_ruby_run, |
| LayoutObject* previous_object, |
| const LineInfo& line_info) { |
| int start_overhang; |
| int end_overhang; |
| LayoutObject* next_object = nullptr; |
| for (BidiRun* run_with_next_object = run->Next(); run_with_next_object; |
| run_with_next_object = run_with_next_object->Next()) { |
| if (!run_with_next_object->line_layout_item_.IsOutOfFlowPositioned() && |
| !run_with_next_object->box_->IsLineBreak()) { |
| next_object = run_with_next_object->line_layout_item_.GetLayoutObject(); |
| break; |
| } |
| } |
| layout_ruby_run->GetOverhang( |
| line_info.IsFirstLine(), |
| layout_ruby_run->StyleRef().IsLeftToRightDirection() ? previous_object |
| : next_object, |
| layout_ruby_run->StyleRef().IsLeftToRightDirection() ? next_object |
| : previous_object, |
| start_overhang, end_overhang); |
| SetMarginStartForChild(*layout_ruby_run, LayoutUnit(-start_overhang)); |
| SetMarginEndForChild(*layout_ruby_run, LayoutUnit(-end_overhang)); |
| } |
| |
| static inline size_t FindWordMeasurement( |
| LineLayoutText layout_text, |
| int offset, |
| const WordMeasurements& word_measurements, |
| size_t last_index) { |
| // In LTR, lastIndex should match since the order of BidiRun (visual) and |
| // WordMeasurement (logical) are the same. |
| size_t size = word_measurements.size(); |
| if (last_index < size) { |
| const WordMeasurement& word_measurement = word_measurements[last_index]; |
| if (word_measurement.layout_text == layout_text && |
| word_measurement.start_offset == offset) |
| return last_index; |
| } |
| |
| // In RTL, scan the whole array because they are not the same. |
| for (size_t i = 0; i < size; ++i) { |
| const WordMeasurement& word_measurement = word_measurements[i]; |
| if (word_measurement.layout_text != layout_text) |
| continue; |
| if (word_measurement.start_offset == offset) |
| return i; |
| if (word_measurement.start_offset > offset) |
| break; |
| } |
| |
| // In RTL with space collpasing or in LTR/RTL mixed lines, there can be no |
| // matches because spaces are handled differently in BidiRun and |
| // WordMeasurement. This can cause slight performance hit and slight |
| // differences in glyph positions since we re-measure the whole run. |
| return size; |
| } |
| |
| static inline void SetLogicalWidthForTextRun( |
| RootInlineBox* line_box, |
| BidiRun* run, |
| LineLayoutText layout_text, |
| LayoutUnit x_pos, |
| const LineInfo& line_info, |
| GlyphOverflowAndFallbackFontsMap& text_box_data_map, |
| VerticalPositionCache& vertical_position_cache, |
| const WordMeasurements& word_measurements, |
| size_t& word_measurements_index) { |
| HashSet<const SimpleFontData*> fallback_fonts; |
| GlyphOverflow glyph_overflow; |
| |
| const Font& font = layout_text.Style(line_info.IsFirstLine())->GetFont(); |
| |
| LayoutUnit hyphen_width; |
| if (ToInlineTextBox(run->box_)->HasHyphen()) |
| hyphen_width = LayoutUnit(layout_text.HyphenWidth(font, run->Direction())); |
| |
| float measured_width = 0; |
| FloatRect glyph_bounds; |
| |
| bool kerning_is_enabled = |
| font.GetFontDescription().GetTypesettingFeatures() & kKerning; |
| |
| #if defined(OS_MACOSX) |
| // FIXME: Having any font feature settings enabled can lead to selection gaps |
| // on Chromium-mac. https://bugs.webkit.org/show_bug.cgi?id=113418 |
| bool can_use_cached_word_measurements = |
| font.CanShapeWordByWord() && |
| !font.GetFontDescription().FeatureSettings() && layout_text.Is8Bit(); |
| #else |
| bool can_use_cached_word_measurements = |
| font.CanShapeWordByWord() && layout_text.Is8Bit(); |
| #endif |
| |
| if (can_use_cached_word_measurements) { |
| int last_end_offset = run->start_; |
| size_t i = FindWordMeasurement(layout_text, last_end_offset, |
| word_measurements, word_measurements_index); |
| for (size_t size = word_measurements.size(); |
| i < size && last_end_offset < run->stop_; ++i) { |
| const WordMeasurement& word_measurement = word_measurements[i]; |
| if (word_measurement.start_offset == word_measurement.end_offset) |
| continue; |
| if (word_measurement.layout_text != layout_text || |
| word_measurement.start_offset != last_end_offset || |
| word_measurement.end_offset > run->stop_) |
| break; |
| |
| last_end_offset = word_measurement.end_offset; |
| if (kerning_is_enabled && last_end_offset == run->stop_) { |
| int word_length = last_end_offset - word_measurement.start_offset; |
| measured_width += |
| layout_text.Width(word_measurement.start_offset, word_length, x_pos, |
| run->Direction(), line_info.IsFirstLine()); |
| if (i > 0 && word_length == 1 && |
| layout_text.CharacterAt(word_measurement.start_offset) == ' ') |
| measured_width += layout_text.StyleRef().WordSpacing(); |
| } else { |
| FloatRect word_glyph_bounds = word_measurement.glyph_bounds; |
| word_glyph_bounds.Move(measured_width, 0); |
| glyph_bounds.Unite(word_glyph_bounds); |
| measured_width += word_measurement.width; |
| } |
| if (!word_measurement.fallback_fonts.IsEmpty()) { |
| HashSet<const SimpleFontData*>::const_iterator end = |
| word_measurement.fallback_fonts.end(); |
| for (HashSet<const SimpleFontData*>::const_iterator it = |
| word_measurement.fallback_fonts.begin(); |
| it != end; ++it) |
| fallback_fonts.insert(*it); |
| } |
| } |
| word_measurements_index = i; |
| if (last_end_offset != run->stop_) { |
| // If we don't have enough cached data, we'll measure the run again. |
| can_use_cached_word_measurements = false; |
| fallback_fonts.clear(); |
| } |
| } |
| |
| // Don't put this into 'else' part of the above 'if' because |
| // canUseCachedWordMeasurements may be modified in the 'if' block. |
| if (!can_use_cached_word_measurements) |
| measured_width = layout_text.Width( |
| run->start_, run->stop_ - run->start_, x_pos, run->Direction(), |
| line_info.IsFirstLine(), &fallback_fonts, &glyph_bounds); |
| |
| // Negative word-spacing and/or letter-spacing may cause some glyphs to |
| // overflow the left boundary and result negative measured width. Reset |
| // measured width to 0. |
| if (measured_width < 0) { |
| measured_width = 0; |
| } |
| |
| glyph_overflow.SetFromBounds(glyph_bounds, font, measured_width); |
| |
| run->box_->SetLogicalWidth(LayoutUnit(measured_width) + hyphen_width); |
| if (!fallback_fonts.IsEmpty()) { |
| DCHECK(run->box_->IsText()); |
| GlyphOverflowAndFallbackFontsMap::ValueType* it = |
| text_box_data_map |
| .insert(ToInlineTextBox(run->box_), |
| std::make_pair(Vector<const SimpleFontData*>(), |
| GlyphOverflow())) |
| .stored_value; |
| DCHECK(it->value.first.IsEmpty()); |
| CopyToVector(fallback_fonts, it->value.first); |
| run->box_->Parent()->ClearDescendantsHaveSameLineHeightAndBaseline(); |
| } |
| if (!glyph_overflow.IsApproximatelyZero()) { |
| DCHECK(run->box_->IsText()); |
| GlyphOverflowAndFallbackFontsMap::ValueType* it = |
| text_box_data_map |
| .insert(ToInlineTextBox(run->box_), |
| std::make_pair(Vector<const SimpleFontData*>(), |
| GlyphOverflow())) |
| .stored_value; |
| it->value.second = glyph_overflow; |
| run->box_->ClearKnownToHaveNoOverflow(); |
| } |
| } |
| |
| void LayoutBlockFlow::UpdateLogicalWidthForAlignment( |
| const ETextAlign& text_align, |
| const RootInlineBox* root_inline_box, |
| BidiRun* trailing_space_run, |
| LayoutUnit& logical_left, |
| LayoutUnit& total_logical_width, |
| LayoutUnit& available_logical_width, |
| unsigned expansion_opportunity_count) { |
| TextDirection direction; |
| if (root_inline_box && |
| root_inline_box->GetLineLayoutItem().StyleRef().GetUnicodeBidi() == |
| UnicodeBidi::kPlaintext) |
| direction = root_inline_box->Direction(); |
| else |
| direction = StyleRef().Direction(); |
| |
| // Armed with the total width of the line (without justification), |
| // we now examine our text-align property in order to determine where to |
| // position the objects horizontally. The total width of the line can be |
| // increased if we end up justifying text. |
| switch (text_align) { |
| case ETextAlign::kLeft: |
| case ETextAlign::kWebkitLeft: |
| UpdateLogicalWidthForLeftAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, logical_left, |
| total_logical_width, available_logical_width); |
| break; |
| case ETextAlign::kRight: |
| case ETextAlign::kWebkitRight: |
| UpdateLogicalWidthForRightAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, logical_left, |
| total_logical_width, available_logical_width); |
| break; |
| case ETextAlign::kCenter: |
| case ETextAlign::kWebkitCenter: |
| UpdateLogicalWidthForCenterAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, logical_left, |
| total_logical_width, available_logical_width); |
| break; |
| case ETextAlign::kJustify: |
| AdjustInlineDirectionLineBounds(expansion_opportunity_count, logical_left, |
| available_logical_width); |
| if (expansion_opportunity_count) { |
| if (trailing_space_run) { |
| total_logical_width -= trailing_space_run->box_->LogicalWidth(); |
| trailing_space_run->box_->SetLogicalWidth(LayoutUnit()); |
| } |
| break; |
| } |
| FALLTHROUGH; |
| case ETextAlign::kStart: |
| if (direction == TextDirection::kLtr) { |
| UpdateLogicalWidthForLeftAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, |
| logical_left, total_logical_width, available_logical_width); |
| } else { |
| UpdateLogicalWidthForRightAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, |
| logical_left, total_logical_width, available_logical_width); |
| } |
| break; |
| case ETextAlign::kEnd: |
| if (direction == TextDirection::kLtr) { |
| UpdateLogicalWidthForRightAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, |
| logical_left, total_logical_width, available_logical_width); |
| } else { |
| UpdateLogicalWidthForLeftAlignedBlock( |
| StyleRef().IsLeftToRightDirection(), trailing_space_run, |
| logical_left, total_logical_width, available_logical_width); |
| } |
| break; |
| } |
| } |
| |
| bool LayoutBlockFlow::CanContainFirstFormattedLine() const { |
| // The 'text-indent' only affects a line if it is the first formatted |
| // line of an element. For example, the first line of an anonymous block |
| // box is only affected if it is the first child of its parent element. |
| // https://drafts.csswg.org/css-text-3/#text-indent-property |
| return !(IsAnonymousBlock() && PreviousSibling()); |
| } |
| |
| static void UpdateLogicalInlinePositions(LayoutBlockFlow* block, |
| LayoutUnit& line_logical_left, |
| LayoutUnit& line_logical_right, |
| LayoutUnit& available_logical_width, |
| bool first_line, |
| IndentTextOrNot indent_text, |
| LayoutUnit box_logical_height) { |
| LayoutUnit line_logical_height = |
| block->MinLineHeightForReplacedObject(first_line, box_logical_height); |
| line_logical_left = block->LogicalLeftOffsetForLine( |
| block->LogicalHeight(), indent_text, line_logical_height); |
| line_logical_right = block->LogicalRightOffsetForLine( |
| block->LogicalHeight(), indent_text, line_logical_height); |
| available_logical_width = line_logical_right - line_logical_left; |
| } |
| |
| void LayoutBlockFlow::ComputeInlineDirectionPositionsForLine( |
| RootInlineBox* line_box, |
| const LineInfo& line_info, |
| BidiRun* first_run, |
| BidiRun* trailing_space_run, |
| bool reached_end, |
| GlyphOverflowAndFallbackFontsMap& text_box_data_map, |
| VerticalPositionCache& vertical_position_cache, |
| const WordMeasurements& word_measurements) { |
| bool is_first_line = |
| line_info.IsFirstLine() && CanContainFirstFormattedLine(); |
| bool is_after_hard_line_break = |
| line_box->PrevRootBox() && line_box->PrevRootBox()->EndsWithBreak(); |
| IndentTextOrNot indent_text = |
| RequiresIndent(is_first_line, is_after_hard_line_break, StyleRef()); |
| LayoutUnit line_logical_left; |
| LayoutUnit line_logical_right; |
| LayoutUnit available_logical_width; |
| UpdateLogicalInlinePositions(this, line_logical_left, line_logical_right, |
| available_logical_width, is_first_line, |
| indent_text, LayoutUnit()); |
| bool needs_word_spacing; |
| |
| if (first_run && first_run->line_layout_item_.IsAtomicInlineLevel()) { |
| LineLayoutBox layout_box(first_run->line_layout_item_); |
| UpdateLogicalInlinePositions(this, line_logical_left, line_logical_right, |
| available_logical_width, is_first_line, |
| indent_text, layout_box.LogicalHeight()); |
| } |
| |
| ComputeInlineDirectionPositionsForSegment( |
| line_box, line_info, line_logical_left, available_logical_width, |
| first_run, trailing_space_run, text_box_data_map, vertical_position_cache, |
| word_measurements); |
| // The widths of all runs are now known. We can now place every inline box |
| // (and compute accurate widths for the inline flow boxes). |
| needs_word_spacing = line_box->IsLeftToRightDirection() ? false : true; |
| line_box->PlaceBoxesInInlineDirection(line_logical_left, needs_word_spacing); |
| } |
| |
| BidiRun* LayoutBlockFlow::ComputeInlineDirectionPositionsForSegment( |
| RootInlineBox* line_box, |
| const LineInfo& line_info, |
| LayoutUnit& logical_left, |
| LayoutUnit& available_logical_width, |
| BidiRun* first_run, |
| BidiRun* trailing_space_run, |
| GlyphOverflowAndFallbackFontsMap& text_box_data_map, |
| VerticalPositionCache& vertical_position_cache, |
| const WordMeasurements& word_measurements) { |
| bool needs_word_spacing = true; |
| LayoutUnit total_logical_width = line_box->GetFlowSpacingLogicalWidth(); |
| bool is_after_expansion = true; |
| ExpansionOpportunities expansions; |
| LayoutObject* previous_object = nullptr; |
| ETextAlign text_align = line_info.GetTextAlign(); |
| TextJustify text_justify = StyleRef().GetTextJustify(); |
| |
| BidiRun* r = first_run; |
| size_t word_measurements_index = 0; |
| for (; r; r = r->Next()) { |
| if (!r->box_ || r->line_layout_item_.IsOutOfFlowPositioned() || |
| r->box_->IsLineBreak()) { |
| continue; // Positioned objects are only participating to figure out |
| // their correct static x position. They have no effect on the |
| // width. Similarly, line break boxes have no effect on the |
| // width. |
| } |
| if (r->line_layout_item_.IsText()) { |
| LineLayoutText rt(r->line_layout_item_); |
| if (text_align == ETextAlign::kJustify && r != trailing_space_run && |
| text_justify != TextJustify::kNone) { |
| if (!is_after_expansion) |
| ToInlineTextBox(r->box_)->SetCanHaveLeadingExpansion(true); |
| expansions.AddRunWithExpansions(*r, is_after_expansion, text_justify); |
| } |
| |
| if (rt.TextLength()) { |
| if (!r->start_ && needs_word_spacing && |
| IsSpaceOrNewline(rt.CharacterAt(r->start_))) |
| total_logical_width += rt.Style(line_info.IsFirstLine()) |
| ->GetFont() |
| .GetFontDescription() |
| .WordSpacing(); |
| needs_word_spacing = !IsSpaceOrNewline(rt.CharacterAt(r->stop_ - 1)); |
| } |
| |
| SetLogicalWidthForTextRun(line_box, r, rt, total_logical_width, line_info, |
| text_box_data_map, vertical_position_cache, |
| word_measurements, word_measurements_index); |
| } else { |
| is_after_expansion = false; |
| if (!r->line_layout_item_.IsLayoutInline()) { |
| LayoutBox* layout_box = |
| ToLayoutBox(r->line_layout_item_.GetLayoutObject()); |
| if (layout_box->IsRubyRun()) |
| SetMarginsForRubyRun(r, ToLayoutRubyRun(layout_box), previous_object, |
| line_info); |
| r->box_->SetLogicalWidth(LogicalWidthForChild(*layout_box)); |
| total_logical_width += |
| MarginStartForChild(*layout_box) + MarginEndForChild(*layout_box); |
| needs_word_spacing = true; |
| } |
| } |
| |
| total_logical_width += r->box_->LogicalWidth(); |
| previous_object = r->line_layout_item_.GetLayoutObject(); |
| } |
| |
| if (is_after_expansion) |
| expansions.RemoveTrailingExpansion(); |
| |
| UpdateLogicalWidthForAlignment(text_align, line_box, trailing_space_run, |
| logical_left, total_logical_width, |
| available_logical_width, expansions.Count()); |
| |
| expansions.ComputeExpansionsForJustifiedText(first_run, trailing_space_run, |
| total_logical_width, |
| available_logical_width); |
| |
| return r; |
| } |
| |
| void LayoutBlockFlow::ComputeBlockDirectionPositionsForLine( |
| RootInlineBox* line_box, |
| BidiRun* first_run, |
| GlyphOverflowAndFallbackFontsMap& text_box_data_map, |
| VerticalPositionCache& vertical_position_cache) { |
| SetLogicalHeight(line_box->AlignBoxesInBlockDirection( |
| LogicalHeight(), text_box_data_map, vertical_position_cache)); |
| |
| // Now make sure we place replaced layout objects correctly. |
| for (BidiRun* r = first_run; r; r = r->Next()) { |
| DCHECK(r->box_); |
| if (!r->box_) |
| continue; // Skip runs with no line boxes. |
| |
| // Align positioned boxes with the top of the line box. This is |
| // a reasonable approximation of an appropriate y position. |
| if (r->line_layout_item_.IsOutOfFlowPositioned()) |
| r->box_->SetLogicalTop(LogicalHeight()); |
| |
| // Position is used to properly position both replaced elements and |
| // to update the static normal flow x/y of positioned elements. |
| if (r->line_layout_item_.IsText()) |
| ToLayoutText(r->line_layout_item_.GetLayoutObject()) |
| ->PositionLineBox(r->box_); |
| else if (r->line_layout_item_.IsBox()) |
| ToLayoutBox(r->line_layout_item_.GetLayoutObject()) |
| ->PositionLineBox(r->box_); |
| } |
| } |
| |
| void LayoutBlockFlow::AppendFloatingObjectToLastLine( |
| FloatingObject& floating_object) { |
| DCHECK(!floating_object.OriginatingLine()); |
| floating_object.SetOriginatingLine(LastRootBox()); |
| LastRootBox()->AppendFloat(floating_object.GetLayoutObject()); |
| } |
| |
| // This function constructs line boxes for all of the text runs in the resolver |
| // and computes their position. |
| RootInlineBox* LayoutBlockFlow::CreateLineBoxesFromBidiRuns( |
| unsigned bidi_level, |
| BidiRunList<BidiRun>& bidi_runs, |
| const InlineIterator& end, |
| LineInfo& line_info, |
| VerticalPositionCache& vertical_position_cache, |
| BidiRun* trailing_space_run, |
| const WordMeasurements& word_measurements) { |
| if (!bidi_runs.RunCount()) |
| return nullptr; |
| |
| // FIXME: Why is this only done when we had runs? |
| line_info.SetLastLine(!end.GetLineLayoutItem()); |
| |
| RootInlineBox* line_box = ConstructLine(bidi_runs, line_info); |
| if (!line_box) |
| return nullptr; |
| |
| line_box->SetBidiLevel(bidi_level); |
| line_box->SetEndsWithBreak(line_info.PreviousLineBrokeCleanly()); |
| |
| bool is_svg_root_inline_box = line_box->IsSVGRootInlineBox(); |
| |
| GlyphOverflowAndFallbackFontsMap text_box_data_map; |
| |
| // Now we position all of our text runs horizontally. |
| if (!is_svg_root_inline_box) |
| ComputeInlineDirectionPositionsForLine( |
| line_box, line_info, bidi_runs.FirstRun(), trailing_space_run, |
| end.AtEnd(), text_box_data_map, vertical_position_cache, |
| word_measurements); |
| |
| // Now position our text runs vertically. |
| ComputeBlockDirectionPositionsForLine(line_box, bidi_runs.FirstRun(), |
| text_box_data_map, |
| vertical_position_cache); |
| |
| // SVG text layout code computes vertical & horizontal positions on its own. |
| // Note that we still need to execute computeVerticalPositionsForLine() as |
| // it calls InlineTextBox::positionLineBox(), which tracks whether the box |
| // contains reversed text or not. If we wouldn't do that editing and thus |
| // text selection in RTL boxes would not work as expected. |
| if (is_svg_root_inline_box) { |
| DCHECK(IsSVGText()); |
| ToSVGRootInlineBox(line_box)->ComputePerCharacterLayoutInformation(); |
| } |
| |
| // Compute our overflow now. |
| line_box->ComputeOverflow(line_box->LineTop(), line_box->LineBottom(), |
| text_box_data_map); |
| |
| return line_box; |
| } |
| |
| static void DeleteLineRange(LineLayoutState& layout_state, |
| RootInlineBox* start_line, |
| RootInlineBox* stop_line = nullptr) { |
| RootInlineBox* box_to_delete = start_line; |
| while (box_to_delete && box_to_delete != stop_line) { |
| // Note: deleteLineRange(firstRootBox()) is not identical to |
| // deleteLineBoxTree(). deleteLineBoxTree uses nextLineBox() instead of |
| // nextRootBox() when traversing. |
| RootInlineBox* next = box_to_delete->NextRootBox(); |
| box_to_delete->DeleteLine(); |
| box_to_delete = next; |
| } |
| } |
| |
| void LayoutBlockFlow::LayoutRunsAndFloats(LineLayoutState& layout_state) { |
| // We want to skip ahead to the first dirty line |
| InlineBidiResolver resolver; |
| RootInlineBox* start_line = DetermineStartPosition(layout_state, resolver); |
| |
| if (ContainsFloats()) |
| layout_state.SetLastFloat(floating_objects_->Set().back().get()); |
| |
| // We also find the first clean line and extract these lines. We will add |
| // them back if we determine that we're able to synchronize after handling all |
| // our dirty lines. |
| InlineIterator clean_line_start; |
| BidiStatus clean_line_bidi_status; |
| if (!layout_state.IsFullLayout() && start_line) |
| DetermineEndPosition(layout_state, start_line, clean_line_start, |
| clean_line_bidi_status); |
| |
| if (start_line) |
| DeleteLineRange(layout_state, start_line); |
| |
| LayoutRunsAndFloatsInRange(layout_state, resolver, clean_line_start, |
| clean_line_bidi_status); |
| LinkToEndLineIfNeeded(layout_state); |
| MarkDirtyFloatsForPaintInvalidation(layout_state.Floats()); |
| } |
| |
| // Before restarting the layout loop with a new logicalHeight, remove all floats |
| // that were added and reset the resolver. |
| inline const InlineIterator& LayoutBlockFlow::RestartLayoutRunsAndFloatsInRange( |
| LayoutUnit old_logical_height, |
| LayoutUnit new_logical_height, |
| FloatingObject* last_float_from_previous_line, |
| InlineBidiResolver& resolver, |
| const InlineIterator& old_end) { |
| RemoveFloatingObjectsBelow(last_float_from_previous_line, old_logical_height); |
| SetLogicalHeight(new_logical_height); |
| resolver.SetPositionIgnoringNestedIsolates(old_end); |
| return old_end; |
| } |
| |
| void LayoutBlockFlow::AppendFloatsToLastLine( |
| LineLayoutState& layout_state, |
| const InlineIterator& clean_line_start, |
| const InlineBidiResolver& resolver, |
| const BidiStatus& clean_line_bidi_status) { |
| const FloatingObjectSet& floating_object_set = floating_objects_->Set(); |
| FloatingObjectSetIterator it = floating_object_set.begin(); |
| FloatingObjectSetIterator end = floating_object_set.end(); |
| if (layout_state.LastFloat()) { |
| FloatingObjectSetIterator last_float_iterator = |
| floating_object_set.find(layout_state.LastFloat()); |
| DCHECK(last_float_iterator != end); |
| ++last_float_iterator; |
| it = last_float_iterator; |
| } |
| for (; it != end; ++it) { |
| FloatingObject& floating_object = *it->get(); |
| // If we've reached the start of clean lines any remaining floating children |
| // belong to them. |
| if (clean_line_start.GetLineLayoutItem().IsEqual( |
| floating_object.GetLayoutObject()) && |
| layout_state.EndLine()) { |
| layout_state.SetEndLineMatched(layout_state.EndLineMatched() || |
| MatchedEndLine(layout_state, resolver, |
| clean_line_start, |
| clean_line_bidi_status)); |
| if (layout_state.EndLineMatched()) { |
| layout_state.SetLastFloat(&floating_object); |
| return; |
| } |
| } |
| AppendFloatingObjectToLastLine(floating_object); |
| DCHECK_EQ(floating_object.GetLayoutObject(), |
| layout_state.Floats()[layout_state.FloatIndex()].object); |
| // If a float's geometry has changed, give up on syncing with clean lines. |
| if (layout_state.Floats()[layout_state.FloatIndex()].rect != |
| floating_object.FrameRect()) { |
| // Delete all the remaining lines. |
| DeleteLineRange(layout_state, layout_state.EndLine()); |
| layout_state.SetEndLine(nullptr); |
| } |
| layout_state.SetFloatIndex(layout_state.FloatIndex() + 1); |
| } |
| layout_state.SetLastFloat(!floating_object_set.IsEmpty() |
| ? floating_object_set.back().get() |
| : nullptr); |
| } |
| |
| void LayoutBlockFlow::LayoutRunsAndFloatsInRange( |
| LineLayoutState& layout_state, |
| InlineBidiResolver& resolver, |
| const InlineIterator& clean_line_start, |
| const BidiStatus& clean_line_bidi_status) { |
| const ComputedStyle& style_to_use = StyleRef(); |
| bool paginated = |
| View()->GetLayoutState() && View()->GetLayoutState()->IsPaginated(); |
| bool recalculate_struts = layout_state.NeedsPaginationStrutRecalculation(); |
| LineMidpointState& line_midpoint_state = resolver.GetMidpointState(); |
| InlineIterator end_of_line = resolver.GetPosition(); |
| LayoutTextInfo layout_text_info; |
| VerticalPositionCache vertical_position_cache; |
| |
| // Pagination may require us to delete and re-create a line due to floats. |
| // When this happens, |
| // we need to store the pagination strut in the meantime. |
| LayoutUnit pagination_strut_from_deleted_line; |
| |
| LineBreaker line_breaker(LineLayoutBlockFlow(this)); |
| |
| |
| while (!end_of_line.AtEnd()) { |
| // The runs from the previous line should have been cleaned up. |
| DCHECK(!resolver.Runs().RunCount()); |
| |
| // FIXME: Is this check necessary before the first iteration or can it be |
| // moved to the end? |
| if (layout_state.EndLine()) { |
| layout_state.SetEndLineMatched(layout_state.EndLineMatched() || |
| MatchedEndLine(layout_state, resolver, |
| clean_line_start, |
| clean_line_bidi_status)); |
| if (layout_state.EndLineMatched()) { |
| resolver.SetPosition( |
| InlineIterator(resolver.GetPosition().Root(), nullptr, 0), 0); |
| break; |
| } |
| } |
| |
| line_midpoint_state.Reset(); |
| |
| layout_state.GetLineInfo().SetEmpty(true); |
| layout_state.GetLineInfo().ResetRunsFromLeadingWhitespace(); |
| |
| const InlineIterator previous_endof_line = end_of_line; |
| bool is_new_uba_paragraph = |
| layout_state.GetLineInfo().PreviousLineBrokeCleanly(); |
| FloatingObject* last_float_from_previous_line = |
| (ContainsFloats()) ? floating_objects_->Set().back().get() : nullptr; |
| |
| WordMeasurements word_measurements; |
| end_of_line = |
| line_breaker.NextLineBreak(resolver, layout_state.GetLineInfo(), |
| layout_text_info, word_measurements); |
| layout_text_info.line_break_iterator_.ResetPriorContext(); |
| if (resolver.GetPosition().AtEnd()) { |
| // FIXME: We shouldn't be creating any runs in nextLineBreak to begin |
| // with! Once BidiRunList is separated from BidiResolver this will not be |
| // needed. |
| resolver.Runs().DeleteRuns(); |
| resolver.MarkCurrentRunEmpty(); // FIXME: This can probably be replaced |
| // by an ASSERT (or just removed). |
| resolver.SetPosition( |
| InlineIterator(resolver.GetPosition().Root(), nullptr, 0), 0); |
| break; |
| } |
| |
| DCHECK(end_of_line != resolver.GetPosition()); |
| RootInlineBox* line_box = nullptr; |
| |
| // This is a short-cut for empty lines. |
| if (layout_state.GetLineInfo().IsEmpty()) { |
| DCHECK(!pagination_strut_from_deleted_line); |
| if (LastRootBox()) |
| LastRootBox()->SetLineBreakInfo(end_of_line.GetLineLayoutItem(), |
| end_of_line.Offset(), |
| resolver.Status()); |
| resolver.Runs().DeleteRuns(); |
| } else { |
| VisualDirectionOverride override = |
| (style_to_use.RtlOrdering() == EOrder::kVisual |
| ? (style_to_use.Direction() == TextDirection::kLtr |
| ? kVisualLeftToRightOverride |
| : kVisualRightToLeftOverride) |
| : kNoVisualOverride); |
| if (is_new_uba_paragraph && |
| style_to_use.GetUnicodeBidi() == UnicodeBidi::kPlaintext && |
| !resolver.Context()->Parent()) { |
| TextDirection direction = DeterminePlaintextDirectionality( |
| resolver.GetPosition().Root(), |
| resolver.GetPosition().GetLineLayoutItem(), |
| resolver.GetPosition().Offset()); |
| resolver.SetStatus( |
| BidiStatus(direction, IsOverride(style_to_use.GetUnicodeBidi()))); |
| } |
| |
| ETextAlign text_align = TextAlignmentForLine( |
| !end_of_line.AtEnd() && |
| !layout_state.GetLineInfo().PreviousLineBrokeCleanly()); |
| layout_state.GetLineInfo().SetTextAlign(text_align); |
| resolver.SetNeedsTrailingSpace( |
| TextAlignmentNeedsTrailingSpace(text_align, style_to_use)); |
| |
| // FIXME: This ownership is reversed. We should own the BidiRunList and |
| // pass it to createBidiRunsForLine. |
| BidiRunList<BidiRun>& bidi_runs = resolver.Runs(); |
| ConstructBidiRunsForLine( |
| resolver, bidi_runs, end_of_line, override, |
| layout_state.GetLineInfo().PreviousLineBrokeCleanly(), |
| is_new_uba_paragraph); |
| DCHECK(resolver.GetPosition() == end_of_line); |
| |
| BidiRun* trailing_space_run = resolver.TrailingSpaceRun(); |
| |
| if (bidi_runs.RunCount() && line_breaker.LineWasHyphenated()) |
| bidi_runs.LogicallyLastRun()->has_hyphen_ = true; |
| |
| // Now that the runs have been ordered, we create the line boxes. |
| // At the same time we figure out where border/padding/margin should be |
| // applied for |
| // inline flow boxes. |
| |
| LayoutUnit old_logical_height = LogicalHeight(); |
| line_box = CreateLineBoxesFromBidiRuns( |
| resolver.Status().context->Level(), bidi_runs, end_of_line, |
| layout_state.GetLineInfo(), vertical_position_cache, |
| trailing_space_run, word_measurements); |
| |
| bidi_runs.DeleteRuns(); |
| resolver.MarkCurrentRunEmpty(); // FIXME: This can probably be replaced |
| // by an ASSERT (or just removed). |
| |
| // If we decided to re-create the line due to pagination, we better have a |
| // new line now. |
| DCHECK(line_box || !pagination_strut_from_deleted_line); |
| |
| if (line_box) { |
| line_box->SetLineBreakInfo(end_of_line.GetLineLayoutItem(), |
| end_of_line.Offset(), resolver.Status()); |
| if (recalculate_struts) { |
| if (pagination_strut_from_deleted_line) { |
| // This is a line that got re-created because it got pushed to the |
| // next fragmentainer, and there were floats in the vicinity that |
| // affected the available width. |
| // Restore the pagination info for this line. |
| line_box->SetIsFirstAfterPageBreak(true); |
| line_box->SetPaginationStrut(pagination_strut_from_deleted_line); |
| pagination_strut_from_deleted_line = LayoutUnit(); |
| } else { |
| LayoutUnit adjustment; |
| AdjustLinePositionForPagination(*line_box, adjustment); |
| if (adjustment) { |
| LayoutUnit old_line_width = AvailableLogicalWidthForLine( |
| old_logical_height, layout_state.GetLineInfo().IsFirstLine() |
| ? kIndentText |
| : kDoNotIndentText); |
| line_box->MoveInBlockDirection(adjustment); |
| if (AvailableLogicalWidthForLine( |
| old_logical_height + adjustment, |
| layout_state.GetLineInfo().IsFirstLine() |
| ? kIndentText |
| : kDoNotIndentText) != old_line_width) { |
| // We have to delete this line, remove all floats that got |
| // added, and let line layout re-run. We had just calculated the |
| // pagination strut for this line, and we need to stow it away, |
| // so that we can re-apply it when the new line has been |
| // created. |
| pagination_strut_from_deleted_line = |
| line_box->PaginationStrut(); |
| DCHECK(pagination_strut_from_deleted_line); |
| // We're also going to assume that we're right after a page |
| // break when re-creating this line, so it better be so. |
| DCHECK(line_box->IsFirstAfterPageBreak()); |
| line_box->DeleteLine(); |
| end_of_line = RestartLayoutRunsAndFloatsInRange( |
| old_logical_height, old_logical_height + adjustment, |
| last_float_from_previous_line, resolver, |
| previous_endof_line); |
| } else { |
| SetLogicalHeight(line_box->LineBottomWithLeading()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (!pagination_strut_from_deleted_line) { |
| for (const auto& positioned_object : line_breaker.PositionedObjects()) { |
| if (positioned_object.StyleRef().IsOriginalDisplayInlineType()) { |
| // Auto-positioned "inline" out-of-flow objects have already been |
| // positioned, but if we're paginated, or just ceased to be so, we |
| // need to update their position now, since the line they "belong" to |
| // may have been pushed by a pagination strut, or pulled back because |
| // a pagination strut was removed. |
| if (recalculate_struts && line_box) |
| positioned_object.Layer()->SetStaticBlockPosition( |
| line_box->LineTopWithLeading()); |
| continue; |
| } |
| SetStaticPositions(LineLayoutBlockFlow(this), positioned_object, |
| kDoNotIndentText); |
| } |
| |
| if (!layout_state.GetLineInfo().IsEmpty()) |
| layout_state.GetLineInfo().SetFirstLine(false); |
| ClearFloats(line_breaker.Clear()); |
| |
| if (floating_objects_ && LastRootBox()) { |
| InlineBidiResolver end_of_line_resolver; |
| end_of_line_resolver.SetPosition(end_of_line, |
| NumberOfIsolateAncestors(end_of_line)); |
| end_of_line_resolver.SetStatus(resolver.Status()); |
| AppendFloatsToLastLine(layout_state, clean_line_start, |
| end_of_line_resolver, clean_line_bidi_status); |
| } |
| } |
| |
| line_midpoint_state.Reset(); |
| resolver.SetPosition(end_of_line, NumberOfIsolateAncestors(end_of_line)); |
| } |
| |
| // The resolver runs should have been cleared, otherwise they're leaking. |
| DCHECK(!resolver.Runs().RunCount()); |
| |
| // In case we already adjusted the line positions during this layout to avoid |
| // widows then we need to ignore the possibility of having a new widows |
| // situation. Otherwise, we risk leaving empty containers which is against the |
| // block fragmentation principles. |
| if (paginated && StyleRef().Widows() > 1 && !DidBreakAtLineToAvoidWidow()) { |
| // Check the line boxes to make sure we didn't create unacceptable widows. |
| // However, we'll prioritize orphans - so nothing we do here should create |
| // a new orphan. |
| |
| RootInlineBox* line_box = LastRootBox(); |
| |
| // Count from the end of the block backwards, to see how many hanging |
| // lines we have. |
| RootInlineBox* first_line_in_block = FirstRootBox(); |
| int num_lines_hanging = 1; |
| while (line_box && line_box != first_line_in_block && |
| !line_box->IsFirstAfterPageBreak()) { |
| ++num_lines_hanging; |
| line_box = line_box->PrevRootBox(); |
| } |
| |
| // If there were no breaks in the block, we didn't create any widows. |
| if (!line_box || !line_box->IsFirstAfterPageBreak() || |
| line_box == first_line_in_block) |
| return; |
| |
| if (num_lines_hanging < StyleRef().Widows()) { |
| // We have detected a widow. Now we need to work out how many |
| // lines there are on the previous page, and how many we need |
| // to steal. |
| int num_lines_needed = StyleRef().Widows() - num_lines_hanging; |
| RootInlineBox* current_first_line_of_new_page = line_box; |
| |
| // Count the number of lines in the previous page. |
| line_box = line_box->PrevRootBox(); |
| int num_lines_in_previous_page = 1; |
| while (line_box && line_box != first_line_in_block && |
| !line_box->IsFirstAfterPageBreak()) { |
| ++num_lines_in_previous_page; |
| line_box = line_box->PrevRootBox(); |
| } |
| |
| // If there was an explicit value for orphans, respect that. If not, we |
| // still shouldn't create a situation where we make an orphan bigger than |
| // the initial value. This means that setting widows implies we also care |
| // about orphans, but given the specification says the initial orphan |
| // value is non-zero, this is ok. The author is always free to set orphans |
| // explicitly as well. |
| int orphans = StyleRef().Orphans(); |
| int num_lines_available = num_lines_in_previous_page - orphans; |
| if (num_lines_available <= 0) |
| return; |
| |
| int num_lines_to_take = std::min(num_lines_available, num_lines_needed); |
| // Wind back from our first widowed line. |
| line_box = current_first_line_of_new_page; |
| for (int i = 0; i < num_lines_to_take; ++i) |
| line_box = line_box->PrevRootBox(); |
| |
| // We now want to break at this line. Remember for next layout and trigger |
| // relayout. |
| SetBreakAtLineToAvoidWidow(LineCount(line_box)); |
| MarkLinesDirtyInBlockRange(LastRootBox()->LineBottomWithLeading(), |
| line_box->LineBottomWithLeading(), line_box); |
| } |
| } |
| |
| ClearDidBreakAtLineToAvoidWidow(); |
| } |
| |
| void LayoutBlockFlow::LinkToEndLineIfNeeded(LineLayoutState& layout_state) { |
| if (layout_state.EndLine()) { |
| if (layout_state.EndLineMatched()) { |
| bool recalculate_struts = |
| layout_state.NeedsPaginationStrutRecalculation(); |
| // Attach all the remaining lines, and then adjust their y-positions as |
| // needed. |
| LayoutUnit delta = LogicalHeight() - layout_state.EndLineLogicalTop(); |
| for (RootInlineBox* line = layout_state.EndLine(); line; |
| line = line->NextRootBox()) { |
| line->AttachLine(); |
| if (recalculate_struts) { |
| delta -= line->PaginationStrut(); |
| AdjustLinePositionForPagination(*line, delta); |
| } |
| if (delta) |
| line->MoveInBlockDirection(delta); |
| if (Vector<LayoutBox*>* clean_line_floats = line->FloatsPtr()) { |
| for (auto* box : *clean_line_floats) { |
| FloatingObject* floating_object = InsertFloatingObject(*box); |
| DCHECK(!floating_object->OriginatingLine()); |
| floating_object->SetOriginatingLine(line); |
| LayoutUnit logical_top = |
| LogicalTopForChild(*box) - MarginBeforeForChild(*box) + delta; |
| PlaceNewFloats(logical_top); |
| } |
| } |
| } |
| SetLogicalHeight(LastRootBox()->LineBottomWithLeading()); |
| } else { |
| // Delete all the remaining lines. |
| DeleteLineRange(layout_state, layout_state.EndLine()); |
| } |
| } |
| |
| // In case we have a float on the last line, it might not be positioned up to |
| // now. This has to be done before adding in the bottom border/padding, or the |
| // float will |
| // include the padding incorrectly. -dwh |
| if (PlaceNewFloats(LogicalHeight()) && LastRootBox()) |
| AppendFloatsToLastLine(layout_state, InlineIterator(), InlineBidiResolver(), |
| BidiStatus()); |
| } |
| |
| void LayoutBlockFlow::MarkDirtyFloatsForPaintInvalidation( |
| Vector<FloatWithRect>& floats) { |
| size_t float_count = floats.size(); |
| // Floats that did not have layout did not paint invalidations when we laid |
| // them out. They would have painted by now if they had moved, but if they |
| // stayed at (0, 0), they still need to be painted. |
| for (size_t i = 0; i < float_count; ++i) { |
| LayoutBox* f = floats[i].object; |
| if (!floats[i].ever_had_layout) { |
| if (!f->Location().X() && !f->Location().Y()) |
| f->SetShouldDoFullPaintInvalidation(); |
| } |
| InsertFloatingObject(*f); |
| } |
| PlaceNewFloats(LogicalHeight()); |
| } |
| |
| // InlineMinMaxIterator is a class that will iterate over all layout objects |
| // that contribute to inline min/max width calculations. Note the following |
| // about the way it walks: |
| // (1) Positioned content is skipped (since it does not contribute to min/max |
| // width of a block) |
| // (2) We do not drill into the children of floats or replaced elements, since |
| // you can't break in the middle of such an element. |
| // (3) Inline flows (e.g., <a>, <span>, <i>) are walked twice, since each side |
| // can have distinct borders/margin/padding that contribute to the min/max |
| // width. |
| struct InlineMinMaxIterator { |
| LayoutObject* parent; |
| LayoutObject* current; |
| bool end_of_inline; |
| |
| InlineMinMaxIterator(LayoutObject* p, bool end = false) |
| : parent(p), current(p), end_of_inline(end) {} |
| |
| LayoutObject* Next(); |
| }; |
| |
| LayoutObject* InlineMinMaxIterator::Next() { |
| LayoutObject* result = nullptr; |
| bool old_end_of_inline = end_of_inline; |
| end_of_inline = false; |
| while (current || current == parent) { |
| if (!old_end_of_inline && |
| (current == parent || |
| (!current->IsFloating() && !current->IsAtomicInlineLevel() && |
| !current->IsOutOfFlowPositioned()))) |
| result = current->SlowFirstChild(); |
| |
| if (!result) { |
| // We hit the end of our inline. (It was empty, e.g., <span></span>.) |
| if (!old_end_of_inline && current->IsLayoutInline()) { |
| result = current; |
| end_of_inline = true; |
| break; |
| } |
| |
| while (current && current != parent) { |
| result = current->NextSibling(); |
| if (result) |
| break; |
| current = current->Parent(); |
| if (current && current != parent && current->IsLayoutInline()) { |
| result = current; |
| end_of_inline = true; |
| break; |
| } |
| } |
| } |
| |
| if (!result) |
| break; |
| |
| if (!result->IsOutOfFlowPositioned() && |
| (result->IsText() || result->IsFloating() || |
| result->IsAtomicInlineLevel() || result->IsLayoutInline())) |
| break; |
| |
| current = result; |
| result = nullptr; |
| } |
| |
| // Update our position. |
| current = result; |
| return current; |
| } |
| |
| static LayoutUnit GetBPMWidth(LayoutUnit child_value, const Length& css_unit) { |
| if (css_unit.IsFixed()) |
| return LayoutUnit(css_unit.Value()); |
| if (css_unit.IsAuto()) |
| return LayoutUnit(); |
| return child_value; |
| } |
| |
| static LayoutUnit GetBorderPaddingMargin(const LayoutBoxModelObject& child, |
| bool end_of_inline) { |
| const ComputedStyle& child_style = child.StyleRef(); |
| if (end_of_inline) { |
| return GetBPMWidth(child.MarginEnd(), child_style.MarginEnd()) + |
| GetBPMWidth(child.PaddingEnd(), child_style.PaddingEnd()) + |
| child.BorderEnd(); |
| } |
| return GetBPMWidth(child.MarginStart(), child_style.MarginStart()) + |
| GetBPMWidth(child.PaddingStart(), child_style.PaddingStart()) + |
| child.BorderStart(); |
| } |
| |
| static inline void StripTrailingSpace(LayoutUnit& inline_max, |
| LayoutUnit& inline_min, |
| LayoutObject* trailing_space_child) { |
| if (trailing_space_child && trailing_space_child->IsText()) { |
| // Collapse away the trailing space at the end of a block by finding |
| // the first white-space character and subtracting its width. Subsequent |
| // white-space characters have been collapsed into the first one (which |
| // can be either a space or a tab character). |
| LayoutText* text = ToLayoutText(trailing_space_child); |
| UChar trailing_whitespace_char = ' '; |
| for (unsigned i = text->TextLength(); i > 0; i--) { |
| UChar c = text->CharacterAt(i - 1); |
| if (!Character::TreatAsSpace(c)) |
| break; |
| trailing_whitespace_char = c; |
| } |
| |
| // FIXME: This ignores first-line. |
| const Font& font = text->StyleRef().GetFont(); |
| TextRun run = |
| ConstructTextRun(font, &trailing_whitespace_char, 1, text->StyleRef(), |
| text->StyleRef().Direction()); |
| float space_width = font.Width(run); |
| inline_max -= LayoutUnit::FromFloatCeil( |
| space_width + font.GetFontDescription().WordSpacing()); |
| if (inline_min > inline_max) |
| inline_min = inline_max; |
| } |
| } |
| |
| // When converting between floating point and LayoutUnits we risk losing |
| // precision with each conversion. When this occurs while accumulating our |
| // preferred widths, we can wind up with a line width that's larger than our |
| // maxPreferredWidth due to pure float accumulation. |
| static inline LayoutUnit AdjustFloatForSubPixelLayout(float value) { |
| return LayoutUnit::FromFloatCeil(value); |
| } |
| |
| static inline void AdjustMinMaxForInlineFlow(LayoutObject* child, |
| bool end_of_inline, |
| LayoutUnit& child_min, |
| LayoutUnit& child_max) { |
| // Add in padding/border/margin from the appropriate side of |
| // the element. |
| LayoutUnit bpm = |
| GetBorderPaddingMargin(ToLayoutInline(*child), end_of_inline); |
| child_min += bpm; |
| child_max += bpm; |
| } |
| |
| static inline void AdjustMarginForInlineReplaced(LayoutObject* child, |
| LayoutUnit& child_min, |
| LayoutUnit& child_max) { |
| // Inline replaced elts add in their margins to their min/max values. |
| const ComputedStyle& child_style = child->StyleRef(); |
| Length start_margin = child_style.MarginStart(); |
| Length end_margin = child_style.MarginEnd(); |
| LayoutUnit margins; |
| if (start_margin.IsFixed()) |
| margins += AdjustFloatForSubPixelLayout(start_margin.Value()); |
| if (end_margin.IsFixed()) |
| margins += AdjustFloatForSubPixelLayout(end_margin.Value()); |
| child_min += margins; |
| child_max += margins; |
| } |
| |
| // FIXME: This function should be broken into something less monolithic. |
| // FIXME: The main loop here is very similar to LineBreaker::nextSegmentBreak. |
| // They can probably reuse code. |
| DISABLE_CFI_PERF |
| void LayoutBlockFlow::ComputeInlinePreferredLogicalWidths( |
| LayoutUnit& min_logical_width, |
| LayoutUnit& max_logical_width) { |
| LayoutUnit inline_max; |
| LayoutUnit inline_min; |
| |
| const ComputedStyle& style_to_use = StyleRef(); |
| LayoutBlock* containing_block = ContainingBlock(); |
| LayoutUnit cw = |
| containing_block ? containing_block->ContentLogicalWidth() : LayoutUnit(); |
| |
| // If we are at the start of a line, we want to ignore all white-space. |
| // Also strip spaces if we previously had text that ended in a trailing space. |
| bool strip_front_spaces = true; |
| LayoutObject* trailing_space_child = nullptr; |
| |
| // Firefox and Opera will allow a table cell to grow to fit an image inside it |
| // under very specific cirucumstances (in order to match common WinIE |
| // layouts). Not supporting the quirk has caused us to mis-layout some real |
| // sites. (See Bugzilla 10517.) |
| bool allow_images_to_break = !GetDocument().InQuirksMode() || |
| !IsTableCell() || |
| !style_to_use.LogicalWidth().IsIntrinsicOrAuto(); |
| |
| bool auto_wrap, old_auto_wrap; |
| auto_wrap = old_auto_wrap = style_to_use.AutoWrap(); |
| |
| InlineMinMaxIterator child_iterator(this); |
| |
| // Only gets added to the max preffered width once. |
| bool added_text_indent = false; |
| // Signals the text indent was more negative than the min preferred width |
| bool has_remaining_negative_text_indent = false; |
| |
| LayoutUnit text_indent = MinimumValueForLength(style_to_use.TextIndent(), cw); |
| LayoutObject* prev_float = nullptr; |
| bool is_prev_child_inline_flow = false; |
| bool should_break_line_after_text = false; |
| while (LayoutObject* child = child_iterator.Next()) { |
| auto_wrap = child->IsAtomicInlineLevel() |
| ? child->Parent()->StyleRef().AutoWrap() |
| : child->StyleRef().AutoWrap(); |
| |
| if (!child->IsBR()) { |
| // Step One: determine whether or not we need to go ahead and |
| // terminate our current line. Each discrete chunk can become |
| // the new min-width, if it is the widest chunk seen so far, and |
| // it can also become the max-width. |
| // |
| // Children fall into three categories: |
| // (1) An inline flow object. These objects always have a min/max of 0, |
| // and are included in the iteration solely so that their margins can |
| // be added in. |
| // |
| // (2) An inline non-text non-flow object, e.g., an inline replaced |
| // element. These objects can always be on a line by themselves, so in |
| // this situation we need to go ahead and break the current line, and |
| // then add in our own margins and min/max width on its own line, and |
| // then terminate the line. |
| // |
| // (3) A text object. Text runs can have breakable characters at the |
| // start, the middle or the end. They may also lose whitespace off the |
| // front if we're already ignoring whitespace. In order to compute |
| // accurate min-width information, we need three pieces of |
| // information. |
| // (a) the min-width of the first non-breakable run. Should be 0 if |
| // the text string starts with whitespace. |
| // (b) the min-width of the last non-breakable run. Should be 0 if the |
| // text string ends with whitespace. |
| // (c) the min/max width of the string (trimmed for whitespace). |
| // |
| // If the text string starts with whitespace, then we need to go ahead and |
| // terminate our current line (unless we're already in a whitespace |
| // stripping mode. |
| // |
| // If the text string has a breakable character in the middle, but didn't |
| // start with whitespace, then we add the width of the first non-breakable |
| // run and then end the current line. We then need to use the intermediate |
| // min/max width values (if any of them are larger than our current |
| // min/max). We then look at the width of the last non-breakable run and |
| // use that to start a new line (unless we end in whitespace). |
| LayoutUnit child_min; |
| LayoutUnit child_max; |
| |
| if (!child->IsText()) { |
| if (child->IsBox() && |
| ToLayoutBox(child)->NeedsPreferredWidthsRecalculation()) { |
| // We don't really know whether the containing block of this child |
| // did change or is going to change size. However, this is our only |
| // opportunity to make sure that it gets its min/max widths |
| // calculated. |
| child->SetPreferredLogicalWidthsDirty(); |
| } |
| |
| // Case (1) and (2). Inline replaced and inline flow elements. |
| if (child->IsLayoutInline()) { |
| AdjustMinMaxForInlineFlow(child, child_iterator.end_of_inline, |
| child_min, child_max); |
| inline_min += child_min; |
| inline_max += child_max; |
| child->ClearPreferredLogicalWidthsDirty(); |
| } else { |
| AdjustMarginForInlineReplaced(child, child_min, child_max); |
| } |
| } |
| |
| if (!child->IsLayoutInline() && !child->IsText()) { |
| // Case (2). Inline replaced elements and floats. |
| // Go ahead and terminate the current line as far as |
| // minwidth is concerned. |
| LayoutUnit child_min_preferred_logical_width, |
| child_max_preferred_logical_width; |
| ComputeChildPreferredLogicalWidths(*child, |
| child_min_preferred_logical_width, |
| child_max_preferred_logical_width); |
| child_min += child_min_preferred_logical_width; |
| child_max += child_max_preferred_logical_width; |
| |
| bool clear_previous_float; |
| if (child->IsFloating()) { |
| const ComputedStyle& child_style = child->StyleRef(); |
| clear_previous_float = |
| (prev_float && |
| ((prev_float->StyleRef().Floating() == EFloat::kLeft && |
| (child_style.Clear() == EClear::kBoth || |
| child_style.Clear() == EClear::kLeft)) || |
| (prev_float->StyleRef().Floating() == EFloat::kRight && |
| (child_style.Clear() == EClear::kBoth || |
| child_style.Clear() == EClear::kRight)))); |
| prev_float = child; |
| } else { |
| clear_previous_float = false; |
| } |
| |
| bool can_break_replaced_element = |
| !child->IsImage() || allow_images_to_break; |
| if ((can_break_replaced_element && (auto_wrap || old_auto_wrap) && |
| (!is_prev_child_inline_flow || should_break_line_after_text)) || |
| clear_previous_float) { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| inline_min = LayoutUnit(); |
| } |
| |
| // If we're supposed to clear the previous float, then terminate |
| // maxwidth as well. |
| if (clear_previous_float) { |
| max_logical_width = std::max(max_logical_width, inline_max); |
| inline_max = LayoutUnit(); |
| } |
| |
| // Add in text-indent. This is added in only once. |
| if (!added_text_indent && !child->IsFloating()) { |
| child_min += text_indent; |
| child_max += text_indent; |
| |
| if (child_min < LayoutUnit()) |
| text_indent = child_min; |
| else |
| added_text_indent = true; |
| } |
| |
| // Add our width to the max. |
| inline_max += std::max(LayoutUnit(), child_max); |
| |
| if (!auto_wrap || !can_break_replaced_element || |
| (is_prev_child_inline_flow && !should_break_line_after_text)) { |
| if (child->IsFloating()) |
| min_logical_width = std::max(min_logical_width, child_min); |
| else |
| inline_min += child_min; |
| } else { |
| // Now check our line. |
| min_logical_width = std::max(min_logical_width, child_min); |
| |
| // Now start a new line. |
| inline_min = LayoutUnit(); |
| } |
| |
| if (auto_wrap && can_break_replaced_element && |
| is_prev_child_inline_flow) { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| inline_min = LayoutUnit(); |
| } |
| |
| // We are no longer stripping whitespace at the start of |
| // a line. |
| if (!child->IsFloating()) { |
| strip_front_spaces = false; |
| trailing_space_child = nullptr; |
| } |
| } else if (child->IsText()) { |
| // Case (3). Text. |
| LayoutText* t = ToLayoutText(child); |
| |
| if (t->IsWordBreak()) { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| inline_min = LayoutUnit(); |
| continue; |
| } |
| |
| // Determine if we have a breakable character. Pass in |
| // whether or not we should ignore any spaces at the front |
| // of the string. If those are going to be stripped out, |
| // then they shouldn't be considered in the breakable char |
| // check. |
| bool has_breakable_char, has_break; |
| LayoutUnit first_line_min_width, last_line_min_width; |
| bool has_breakable_start, has_breakable_end; |
| LayoutUnit first_line_max_width, last_line_max_width; |
| t->TrimmedPrefWidths( |
| inline_max, first_line_min_width, has_breakable_start, |
| last_line_min_width, has_breakable_end, has_breakable_char, |
| has_break, first_line_max_width, last_line_max_width, child_min, |
| child_max, strip_front_spaces, style_to_use.Direction()); |
| |
| // This text object will not be laid out, but it may still provide a |
| // breaking opportunity. |
| if (!has_break && !child_max) { |
| if (auto_wrap && (has_breakable_start || has_breakable_end)) { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| inline_min = LayoutUnit(); |
| } |
| continue; |
| } |
| |
| if (strip_front_spaces) |
| trailing_space_child = child; |
| else |
| trailing_space_child = nullptr; |
| |
| // Add in text-indent. This is added in only once. |
| LayoutUnit ti; |
| if (!added_text_indent || has_remaining_negative_text_indent) { |
| ti = text_indent; |
| child_min += ti; |
| first_line_min_width += ti; |
| |
| // It the text indent negative and larger than the child minimum, we |
| // re-use the remainder in future minimum calculations, but using the |
| // negative value again on the maximum will lead to under-counting the |
| // max pref width. |
| if (!added_text_indent) { |
| child_max += ti; |
| first_line_max_width += ti; |
| added_text_indent = true; |
| } |
| |
| if (child_min < LayoutUnit()) { |
| text_indent = child_min; |
| has_remaining_negative_text_indent = true; |
| } |
| } |
| |
| // If we have no breakable characters at all, |
| // then this is the easy case. We add ourselves to the current |
| // min and max and continue. |
| if (!has_breakable_char) { |
| inline_min += child_min; |
| } else { |
| if (has_breakable_start) { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| } else { |
| inline_min += first_line_min_width; |
| min_logical_width = std::max(min_logical_width, inline_min); |
| child_min -= ti; |
| } |
| |
| inline_min = child_min; |
| |
| if (has_breakable_end) { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| inline_min = LayoutUnit(); |
| should_break_line_after_text = false; |
| } else { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| inline_min = last_line_min_width; |
| should_break_line_after_text = true; |
| } |
| } |
| |
| if (has_break) { |
| inline_max += first_line_max_width; |
| max_logical_width = std::max(max_logical_width, inline_max); |
| max_logical_width = std::max(max_logical_width, child_max); |
| inline_max = last_line_max_width; |
| added_text_indent = true; |
| } else { |
| inline_max += std::max(LayoutUnit(), child_max); |
| } |
| } |
| |
| // Ignore spaces after a list marker. |
| if (child->IsListMarkerIncludingNG()) |
| strip_front_spaces = true; |
| } else { |
| min_logical_width = std::max(min_logical_width, inline_min); |
| max_logical_width = std::max(max_logical_width, inline_max); |
| inline_min = inline_max = LayoutUnit(); |
| strip_front_spaces = true; |
| trailing_space_child = nullptr; |
| added_text_indent = true; |
| } |
| |
| if (!child->IsText() && child->IsLayoutInline()) |
| is_prev_child_inline_flow = true; |
| else |
| is_prev_child_inline_flow = false; |
| |
| old_auto_wrap = auto_wrap; |
| } |
| |
| if (style_to_use.CollapseWhiteSpace()) |
| StripTrailingSpace(inline_max, inline_min, trailing_space_child); |
| |
| min_logical_width = std::max(min_logical_width, inline_min); |
| max_logical_width = std::max(max_logical_width, inline_max); |
| } |
| |
| static bool IsInlineWithOutlineAndContinuation(const LayoutObject& o) { |
| return o.IsLayoutInline() && o.StyleRef().HasOutline() && |
| !o.IsElementContinuation() && ToLayoutInline(o).Continuation(); |
| } |
| |
| bool LayoutBlockFlow::ShouldTruncateOverflowingText() const { |
| const LayoutObject* object_to_check = this; |
| if (IsAnonymousBlock()) { |
| const LayoutObject* parent = Parent(); |
| if (!parent || !parent->BehavesLikeBlockContainer()) |
| return false; |
| object_to_check = parent; |
| } |
| return object_to_check->HasOverflowClip() && |
| object_to_check->StyleRef().TextOverflow() != ETextOverflow::kClip; |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBlockFlow::LayoutInlineChildren(bool relayout_children, |
| LayoutUnit after_edge) { |
| // Figure out if we should clear out our line boxes. |
| // FIXME: Handle resize eventually! |
| bool is_full_layout = |
| !FirstLineBox() || SelfNeedsLayout() || relayout_children; |
| LineLayoutState layout_state(is_full_layout); |
| |
| if (is_full_layout) { |
| // Ensure the old line boxes will be erased. |
| if (FirstLineBox()) |
| SetShouldDoFullPaintInvalidation(); |
| LineBoxes()->DeleteLineBoxes(); |
| } else if (const LayoutState* box_state = View()->GetLayoutState()) { |
| // We'll attempt to keep the line boxes that we have, but we may need to |
| // add, change or remove pagination struts in front of them. |
| if (box_state->IsPaginated() || box_state->PaginationStateChanged()) |
| layout_state.SetNeedsPaginationStrutRecalculation(); |
| } |
| |
| if (FirstChild()) { |
| for (InlineWalker walker(LineLayoutBlockFlow(this)); !walker.AtEnd(); |
| walker.Advance()) { |
| LayoutObject* o = walker.Current().GetLayoutObject(); |
| |
| // Layout may change LayoutInline's LinesBoundingBox() which affects |
| // MaskClip. |
| if (o->IsLayoutInline() && o->HasMask()) |
| o->SetNeedsPaintPropertyUpdate(); |
| |
| if (!layout_state.HasInlineChild() && o->IsInline()) |
| layout_state.SetHasInlineChild(true); |
| |
| if (o->IsAtomicInlineLevel() || o->IsFloating() || |
| o->IsOutOfFlowPositioned()) { |
| LayoutBox* box = ToLayoutBox(o); |
| box->SetShouldCheckForPaintInvalidation(); |
| |
| UpdateBlockChildDirtyBitsBeforeLayout(relayout_children, *box); |
| |
| if (o->IsOutOfFlowPositioned()) { |
| o->ContainingBlock()->InsertPositionedObject(box); |
| } else if (o->IsFloating()) { |
| layout_state.Floats().push_back(FloatWithRect(box)); |
| if (box->NeedsLayout()) { |
| // Be sure to at least mark the first line affected by the float as |
| // dirty, so that the float gets relaid out. Otherwise we'll miss |
| // it. After float layout, if it turns out that it changed size, |
| // any lines after this line will be deleted and relaid out. |
| DirtyLinesFromChangedChild(box, kMarkOnlyThis); |
| } |
| } else if (is_full_layout || o->NeedsLayout()) { |
| // Atomic inline. |
| box->DirtyLineBoxes(is_full_layout); |
| o->LayoutIfNeeded(); |
| } |
| } else if (o->IsText() || |
| (o->IsLayoutInline() && !walker.AtEndOfInline())) { |
| if (!o->IsText()) |
| ToLayoutInline(o)->UpdateAlwaysCreateLineBoxes( |
| layout_state.IsFullLayout()); |
| if (layout_state.IsFullLayout() || o->SelfNeedsLayout()) |
| DirtyLineBoxesForObject(o, layout_state.IsFullLayout()); |
| o->ClearNeedsLayout(); |
| } |
| |
| if (IsInlineWithOutlineAndContinuation(*o)) |
| SetContainsInlineWithOutlineAndContinuation(true); |
| } |
| |
| LayoutRunsAndFloats(layout_state); |
| } |
| |
| // Expand the last line to accommodate Ruby and emphasis marks. |
| int last_line_annotations_adjustment = 0; |
| if (LastRootBox()) { |
| LayoutUnit lowest_allowed_position = |
| std::max(LastRootBox()->LineBottom(), LogicalHeight() + PaddingAfter()); |
| if (!StyleRef().IsFlippedLinesWritingMode()) |
| last_line_annotations_adjustment = |
| LastRootBox() |
| ->ComputeUnderAnnotationAdjustment(lowest_allowed_position) |
| .ToInt(); |
| else |
| last_line_annotations_adjustment = |
| LastRootBox() |
| ->ComputeOverAnnotationAdjustment(lowest_allowed_position) |
| .ToInt(); |
| } |
| |
| // Now add in the bottom border/padding. |
| SetLogicalHeight(LogicalHeight() + last_line_annotations_adjustment + |
| after_edge); |
| |
| if (!FirstLineBox() && HasLineIfEmpty()) |
| SetLogicalHeight( |
| LogicalHeight() + |
| LineHeight(true, |
| IsHorizontalWritingMode() ? kHorizontalLine : kVerticalLine, |
| kPositionOfInteriorLineBoxes)); |
| |
| if (ShouldTruncateOverflowingText()) |
| CheckLinesForTextOverflow(); |
| |
| // Ensure the new line boxes will be painted. |
| if (is_full_layout && FirstLineBox()) |
| SetShouldDoFullPaintInvalidation(); |
| } |
| |
| RootInlineBox* LayoutBlockFlow::DetermineStartPosition( |
| LineLayoutState& layout_state, |
| InlineBidiResolver& resolver) { |
| RootInlineBox* curr = nullptr; |
| RootInlineBox* last = nullptr; |
| RootInlineBox* first_line_box_with_break_and_clearance = nullptr; |
| |
| // FIXME: This entire float-checking block needs to be broken into a new |
| // function. |
| if (!layout_state.IsFullLayout()) { |
| // Paginate all of the clean lines. |
| bool recalculate_struts = layout_state.NeedsPaginationStrutRecalculation(); |
| LayoutUnit pagination_delta; |
| for (curr = FirstRootBox(); curr && !curr->IsDirty(); |
| curr = curr->NextRootBox()) { |
| if (recalculate_struts) { |
| pagination_delta -= curr->PaginationStrut(); |
| AdjustLinePositionForPagination(*curr, pagination_delta); |
| if (pagination_delta) { |
| if (ContainsFloats() || !layout_state.Floats().IsEmpty()) { |
| // FIXME: Do better eventually. For now if we ever shift because of |
| // pagination and floats are present just go to a full layout. |
| layout_state.MarkForFullLayout(); |
| break; |
| } |
| curr->MoveInBlockDirection(pagination_delta); |
| } |
| } |
| |
| // If the linebox breaks cleanly and with clearance then dirty from at |
| // least this point onwards so that we can clear the correct floats |
| // without difficulty. |
| if (!first_line_box_with_break_and_clearance && |
| LineBoxHasBRWithClearance(curr)) |
| first_line_box_with_break_and_clearance = curr; |
| |
| if (layout_state.IsFullLayout()) |
| break; |
| } |
| } |
| |
| if (layout_state.IsFullLayout()) { |
| // If we encountered a new float and have inline children, mark ourself to |
| // force us to issue paint invalidations. |
| if (layout_state.HasInlineChild() && !SelfNeedsLayout()) { |
| SetNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::kFloatDescendantChanged, kMarkOnlyThis); |
| SetShouldDoFullPaintInvalidation(); |
| } |
| |
| DeleteLineBoxTree(); |
| curr = nullptr; |
| DCHECK(!FirstLineBox()); |
| DCHECK(!LastLineBox()); |
| } else { |
| if (first_line_box_with_break_and_clearance) |
| curr = first_line_box_with_break_and_clearance; |
| if (curr) { |
| // We have a dirty line. |
| if (RootInlineBox* prev_root_box = curr->PrevRootBox()) { |
| // We have a previous line. |
| if (!prev_root_box->EndsWithBreak() || !prev_root_box->LineBreakObj() || |
| (prev_root_box->LineBreakObj().IsText() && |
| prev_root_box->LineBreakPos() >= |
| ToLayoutText(prev_root_box->LineBreakObj().GetLayoutObject()) |
| ->TextLength())) { |
| // The previous line didn't break cleanly or broke at a newline |
| // that has been deleted, so treat it as dirty too. |
| curr = prev_root_box; |
| } |
| } |
| } else { |
| // No dirty lines were found. |
| // If the last line didn't break cleanly, treat it as dirty. |
| if (LastRootBox() && !LastRootBox()->EndsWithBreak()) |
| curr = LastRootBox(); |
| } |
| |
| // If we have no dirty lines, then last is just the last root box. |
| last = curr ? curr->PrevRootBox() : LastRootBox(); |
| } |
| |
| unsigned num_clean_floats = 0; |
| if (!layout_state.Floats().IsEmpty()) { |
| // Restore floats from clean lines. |
| RootInlineBox* line = FirstRootBox(); |
| while (line != curr) { |
| if (Vector<LayoutBox*>* clean_line_floats = line->FloatsPtr()) { |
| for (auto* box : *clean_line_floats) { |
| FloatingObject* floating_object = InsertFloatingObject(*box); |
| DCHECK(!floating_object->OriginatingLine()); |
| floating_object->SetOriginatingLine(line); |
| LayoutUnit logical_top = |
| LogicalTopForChild(*box) - MarginBeforeForChild(*box); |
| PlaceNewFloats(logical_top); |
| DCHECK_EQ(layout_state.Floats()[num_clean_floats].object, box); |
| num_clean_floats++; |
| } |
| } |
| line = line->NextRootBox(); |
| } |
| } |
| layout_state.SetFloatIndex(num_clean_floats); |
| |
| layout_state.GetLineInfo().SetFirstLine(!last); |
| layout_state.GetLineInfo().SetPreviousLineBrokeCleanly(!last || |
| last->EndsWithBreak()); |
| |
| if (last) { |
| SetLogicalHeight(last->LineBottomWithLeading()); |
| InlineIterator iter = InlineIterator(LineLayoutBlockFlow(this), |
| LineLayoutItem(last->LineBreakObj()), |
| last->LineBreakPos()); |
| resolver.SetPosition(iter, NumberOfIsolateAncestors(iter)); |
| resolver.SetStatus(last->LineBreakBidiStatus()); |
| } else { |
| TextDirection direction = StyleRef().Direction(); |
| if (StyleRef().GetUnicodeBidi() == UnicodeBidi::kPlaintext) |
| direction = DeterminePlaintextDirectionality(LineLayoutItem(this)); |
| resolver.SetStatus( |
| BidiStatus(direction, IsOverride(StyleRef().GetUnicodeBidi()))); |
| InlineIterator iter = InlineIterator( |
| LineLayoutBlockFlow(this), |
| BidiFirstSkippingEmptyInlines(LineLayoutBlockFlow(this), |
| resolver.Runs(), &resolver), |
| 0); |
| resolver.SetPosition(iter, NumberOfIsolateAncestors(iter)); |
| } |
| return curr; |
| } |
| |
| bool LayoutBlockFlow::LineBoxHasBRWithClearance(RootInlineBox* curr) { |
| // If the linebox breaks cleanly and with clearance then dirty from at least |
| // this point onwards so that we can clear the correct floats without |
| // difficulty. |
| if (!curr->EndsWithBreak()) |
| return false; |
| InlineBox* last_box = StyleRef().IsLeftToRightDirection() |
| ? curr->LastLeafChild() |
| : curr->FirstLeafChild(); |
| return last_box && last_box->GetLineLayoutItem().IsBR() && |
| last_box->GetLineLayoutItem().StyleRef().Clear() != EClear::kNone; |
| } |
| |
| void LayoutBlockFlow::DetermineEndPosition(LineLayoutState& layout_state, |
| RootInlineBox* start_line, |
| InlineIterator& clean_line_start, |
| BidiStatus& clean_line_bidi_status) { |
| DCHECK(!layout_state.EndLine()); |
| RootInlineBox* last = nullptr; |
| for (RootInlineBox* curr = start_line->NextRootBox(); curr; |
| curr = curr->NextRootBox()) { |
| if (!curr->IsDirty() && LineBoxHasBRWithClearance(curr)) |
| return; |
| |
| if (curr->IsDirty()) |
| last = nullptr; |
| else if (!last) |
| last = curr; |
| } |
| |
| if (!last) |
| return; |
| |
| // At this point, |last| is the first line in a run of clean lines that ends |
| // with the last line in the block. |
| |
| RootInlineBox* prev = last->PrevRootBox(); |
| clean_line_start = |
| InlineIterator(LineLayoutItem(this), LineLayoutItem(prev->LineBreakObj()), |
| prev->LineBreakPos()); |
| clean_line_bidi_status = prev->LineBreakBidiStatus(); |
| layout_state.SetEndLineLogicalTop(prev->LineBottomWithLeading()); |
| |
| for (RootInlineBox* line = last; line; line = line->NextRootBox()) |
| line->ExtractLine(); // Disconnect all line boxes from their layout objects |
| // while preserving their connections to one another. |
| |
| layout_state.SetEndLine(last); |
| } |
| |
| bool LayoutBlockFlow::CheckPaginationAndFloatsAtEndLine( |
| LineLayoutState& layout_state) { |
| if (!floating_objects_ || !layout_state.EndLine()) |
| return true; |
| |
| LayoutUnit line_delta = LogicalHeight() - layout_state.EndLineLogicalTop(); |
| |
| if (layout_state.NeedsPaginationStrutRecalculation()) { |
| // Check all lines from here to the end, and see if the hypothetical new |
| // position for the lines will result |
| // in a different available line width. |
| for (RootInlineBox* line_box = layout_state.EndLine(); line_box; |
| line_box = line_box->NextRootBox()) { |
| // This isn't the real move we're going to do, so don't update the line |
| // box's pagination strut yet. |
| LayoutUnit old_pagination_strut = line_box->PaginationStrut(); |
| line_delta -= old_pagination_strut; |
| AdjustLinePositionForPagination(*line_box, line_delta); |
| line_box->SetPaginationStrut(old_pagination_strut); |
| } |
| } |
| if (!line_delta) |
| return true; |
| |
| // See if any floats end in the range along which we want to shift the lines |
| // vertically. |
| LayoutUnit logical_top = |
| std::min(LogicalHeight(), layout_state.EndLineLogicalTop()); |
| |
| RootInlineBox* last_line = layout_state.EndLine(); |
| while (RootInlineBox* next_line = last_line->NextRootBox()) |
| last_line = next_line; |
| |
| LayoutUnit logical_bottom = |
| last_line->LineBottomWithLeading() + AbsoluteValue(line_delta); |
| |
| const FloatingObjectSet& floating_object_set = floating_objects_->Set(); |
| FloatingObjectSetIterator end = floating_object_set.end(); |
| for (FloatingObjectSetIterator it = floating_object_set.begin(); it != end; |
| ++it) { |
| const FloatingObject& floating_object = *it->get(); |
| if (LogicalBottomForFloat(floating_object) >= logical_top && |
| LogicalBottomForFloat(floating_object) < logical_bottom) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool LayoutBlockFlow::MatchedEndLine(LineLayoutState& layout_state, |
| const InlineBidiResolver& resolver, |
| const InlineIterator& end_line_start, |
| const BidiStatus& end_line_status) { |
| if (resolver.GetPosition() == end_line_start) { |
| if (resolver.Status() != end_line_status) |
| return false; |
| |
| return CheckPaginationAndFloatsAtEndLine(layout_state); |
| } |
| |
| // The first clean line doesn't match, but we can check a handful of following |
| // lines to try to match back up. |
| static int num_lines = 8; // The # of lines we're willing to match against. |
| RootInlineBox* original_end_line = layout_state.EndLine(); |
| RootInlineBox* line = original_end_line; |
| for (int i = 0; i < num_lines && line; i++, line = line->NextRootBox()) { |
| if (line->LineBreakObj() == resolver.GetPosition().GetLineLayoutItem() && |
| line->LineBreakPos() == resolver.GetPosition().Offset()) { |
| // We have a match. |
| if (line->LineBreakBidiStatus() != resolver.Status()) |
| return false; // ...but the bidi state doesn't match. |
| |
| bool matched = false; |
| RootInlineBox* result = line->NextRootBox(); |
| layout_state.SetEndLine(result); |
| if (result) { |
| layout_state.SetEndLineLogicalTop(line->LineBottomWithLeading()); |
| matched = CheckPaginationAndFloatsAtEndLine(layout_state); |
| } |
| |
| // Now delete the lines that we failed to sync. |
| DeleteLineRange(layout_state, original_end_line, result); |
| return matched; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool LayoutBlockFlow::GeneratesLineBoxesForInlineChild(LayoutObject* inline_obj) |
| |
| { |
| DCHECK_EQ(inline_obj->Parent(), this); |
| |
| InlineIterator it(LineLayoutBlockFlow(this), LineLayoutItem(inline_obj), 0); |
| // FIXME: We should pass correct value for WhitespacePosition. |
| while (!it.AtEnd() && !RequiresLineBox(it)) |
| it.Increment(); |
| |
| return !it.AtEnd(); |
| } |
| |
| void LayoutBlockFlow::AddVisualOverflowFromInlineChildren() { |
| LayoutUnit end_padding = HasOverflowClip() ? PaddingEnd() : LayoutUnit(); |
| // FIXME: Need to find another way to do this, since scrollbars could show |
| // when we don't want them to. |
| if (HasOverflowClip() && !end_padding && GetNode() && |
| IsRootEditableElement(*GetNode()) && StyleRef().IsLeftToRightDirection()) |
| end_padding = LayoutUnit(1); |
| for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox()) { |
| LayoutRect visual_overflow = |
| curr->VisualOverflowRect(curr->LineTop(), curr->LineBottom()); |
| AddContentsVisualOverflow(visual_overflow); |
| } |
| |
| if (!ContainsInlineWithOutlineAndContinuation()) |
| return; |
| |
| // Add outline rects of continuations of descendant inlines into visual |
| // overflow of this block. |
| LayoutRect outline_bounds_of_all_continuations; |
| for (InlineWalker walker(LineLayoutBlockFlow(this)); !walker.AtEnd(); |
| walker.Advance()) { |
| const LayoutObject& o = *walker.Current().GetLayoutObject(); |
| if (!IsInlineWithOutlineAndContinuation(o)) |
| continue; |
| |
| Vector<LayoutRect> outline_rects; |
| ToLayoutInline(o).AddOutlineRectsForContinuations( |
| outline_rects, LayoutPoint(), |
| o.OutlineRectsShouldIncludeBlockVisualOverflow()); |
| if (!outline_rects.IsEmpty()) { |
| LayoutRect outline_bounds = UnionRectEvenIfEmpty(outline_rects); |
| outline_bounds.Inflate(LayoutUnit(o.StyleRef().OutlineOutsetExtent())); |
| outline_bounds_of_all_continuations.Unite(outline_bounds); |
| } |
| } |
| AddContentsVisualOverflow(outline_bounds_of_all_continuations); |
| } |
| |
| void LayoutBlockFlow::AddLayoutOverflowFromInlineChildren() { |
| LayoutUnit end_padding = HasOverflowClip() ? PaddingEnd() : LayoutUnit(); |
| // FIXME: Need to find another way to do this, since scrollbars could show |
| // when we don't want them to. |
| if (HasOverflowClip() && !end_padding && GetNode() && |
| IsRootEditableElement(*GetNode()) && StyleRef().IsLeftToRightDirection()) |
| end_padding = LayoutUnit(1); |
| for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox()) |
| AddLayoutOverflow(curr->PaddedLayoutOverflowRect(end_padding)); |
| } |
| |
| void LayoutBlockFlow::DeleteEllipsisLineBoxes() { |
| ETextAlign text_align = StyleRef().GetTextAlign(); |
| IndentTextOrNot indent_text = kIndentText; |
| for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox()) { |
| if (curr->HasEllipsisBox()) { |
| curr->ClearTruncation(); |
| |
| // Shift the line back where it belongs if we cannot accommodate an |
| // ellipsis. |
| LayoutUnit logical_left = |
| LogicalLeftOffsetForLine(curr->LineTop(), indent_text); |
| LayoutUnit available_logical_width = |
| LogicalRightOffsetForLine(curr->LineTop(), kDoNotIndentText) - |
| logical_left; |
| LayoutUnit total_logical_width = curr->LogicalWidth(); |
| UpdateLogicalWidthForAlignment(text_align, curr, nullptr, logical_left, |
| total_logical_width, |
| available_logical_width, 0); |
| |
| curr->MoveInInlineDirection(logical_left - curr->LogicalLeft()); |
| } |
| ClearTruncationOnAtomicInlines(curr); |
| indent_text = kDoNotIndentText; |
| } |
| } |
| |
| void LayoutBlockFlow::ClearTruncationOnAtomicInlines(RootInlineBox* root) { |
| bool ltr = StyleRef().IsLeftToRightDirection(); |
| InlineBox* first_child = ltr ? root->LastChild() : root->FirstChild(); |
| for (InlineBox* box = first_child; box; |
| box = ltr ? box->PrevOnLine() : box->NextOnLine()) { |
| if (!box->GetLineLayoutItem().IsAtomicInlineLevel() || |
| !box->GetLineLayoutItem().IsLayoutBlockFlow()) { |
| continue; |
| } |
| |
| if (!box->GetLineLayoutItem().IsTruncated()) |
| return; |
| box->GetLineLayoutItem().SetIsTruncated(false); |
| } |
| } |
| |
| void LayoutBlockFlow::CheckLinesForTextOverflow() { |
| // Determine the width of the ellipsis using the current font. |
| const Font& font = StyleRef().GetFont(); |
| |
| const size_t kFullStopStringLength = 3; |
| const UChar kFullStopString[] = {kFullstopCharacter, kFullstopCharacter, |
| kFullstopCharacter}; |
| DEFINE_STATIC_LOCAL(AtomicString, fullstop_character_str, |
| (kFullStopString, kFullStopStringLength)); |
| AtomicString selected_ellipsis_str(&kHorizontalEllipsisCharacter, 1); |
| |
| const Font& first_line_font = FirstLineStyle()->GetFont(); |
| // FIXME: We should probably not hard-code the direction here. |
| // https://crbug.com/333004 |
| TextDirection ellipsis_direction = TextDirection::kLtr; |
| float first_line_ellipsis_width = 0; |
| float ellipsis_width = 0; |
| |
| // As per CSS3 http://www.w3.org/TR/2003/CR-css3-text-20030514/ sequence of |
| // three Full Stops (002E) can be used. |
| const SimpleFontData* font_data = first_line_font.PrimaryFont(); |
| DCHECK(font_data); |
| if (font_data && font_data->GlyphForCharacter(kHorizontalEllipsisCharacter)) { |
| first_line_ellipsis_width = first_line_font.Width( |
| ConstructTextRun(first_line_font, &kHorizontalEllipsisCharacter, 1, |
| *FirstLineStyle(), ellipsis_direction)); |
| } else { |
| selected_ellipsis_str = fullstop_character_str; |
| first_line_ellipsis_width = first_line_font.Width(ConstructTextRun( |
| first_line_font, kFullStopString, kFullStopStringLength, |
| *FirstLineStyle(), ellipsis_direction)); |
| } |
| ellipsis_width = (font == first_line_font) ? first_line_ellipsis_width : 0; |
| |
| if (!ellipsis_width) { |
| const SimpleFontData* font_data = font.PrimaryFont(); |
| DCHECK(font_data); |
| if (font_data && |
| font_data->GlyphForCharacter(kHorizontalEllipsisCharacter)) { |
| ellipsis_width = |
| font.Width(ConstructTextRun(font, &kHorizontalEllipsisCharacter, 1, |
| StyleRef(), ellipsis_direction)); |
| } else { |
| selected_ellipsis_str = fullstop_character_str; |
| ellipsis_width = font.Width( |
| ConstructTextRun(font, kFullStopString, kFullStopStringLength, |
| StyleRef(), ellipsis_direction)); |
| } |
| } |
| |
| // For LTR text truncation, we want to get the right edge of our padding box, |
| // and then we want to see if the right edge of a line box exceeds that. |
| // For RTL, we use the left edge of the padding box and check the left edge of |
| // the line box to see if it is less Include the scrollbar for overflow |
| // blocks, which means we want to use "contentWidth()". |
| bool ltr = StyleRef().IsLeftToRightDirection(); |
| ETextAlign text_align = StyleRef().GetTextAlign(); |
| IndentTextOrNot indent_text = kIndentText; |
| for (RootInlineBox* curr = FirstRootBox(); curr; curr = curr->NextRootBox()) { |
| LayoutUnit block_right_edge = |
| LogicalRightOffsetForLine(curr->LineTop(), indent_text); |
| LayoutUnit block_left_edge = |
| LogicalLeftOffsetForLine(curr->LineTop(), indent_text); |
| LayoutUnit line_box_edge = ltr ? curr->LogicalRightLayoutOverflow() |
| : curr->LogicalLeftLayoutOverflow(); |
| if ((ltr && line_box_edge > block_right_edge) || |
| (!ltr && line_box_edge < block_left_edge)) { |
| // This line spills out of our box in the appropriate direction. Now we |
| // need to see if the line can be truncated. In order for truncation to |
| // be possible, the line must have sufficient space to accommodate our |
| // truncation string, and no replaced elements (images, tables) can |
| // overlap the ellipsis space. |
| |
| LayoutUnit width(indent_text == kIndentText ? first_line_ellipsis_width |
| : ellipsis_width); |
| LayoutUnit block_edge = ltr ? block_right_edge : block_left_edge; |
| InlineBox* box_truncation_starts_at = nullptr; |
| if (curr->LineCanAccommodateEllipsis(ltr, block_edge, line_box_edge, |
| width)) { |
| LayoutUnit total_logical_width = curr->PlaceEllipsis( |
| selected_ellipsis_str, ltr, block_left_edge, block_right_edge, |
| width, LayoutUnit(), &box_truncation_starts_at); |
| // We are only interested in the delta from the base position. |
| LayoutUnit logical_left; |
| LayoutUnit available_logical_width = block_right_edge - block_left_edge; |
| UpdateLogicalWidthForAlignment(text_align, curr, nullptr, logical_left, |
| total_logical_width, |
| available_logical_width, 0); |
| if (ltr) |
| curr->MoveInInlineDirection(logical_left); |
| else |
| curr->MoveInInlineDirection( |
| logical_left - (available_logical_width - total_logical_width)); |
| } |
| TryPlacingEllipsisOnAtomicInlines( |
| curr, LogicalRightOffsetForContent(), LogicalLeftOffsetForContent(), |
| width, selected_ellipsis_str, box_truncation_starts_at); |
| } |
| |
| indent_text = kDoNotIndentText; |
| } |
| } |
| |
| void LayoutBlockFlow::TryPlacingEllipsisOnAtomicInlines( |
| RootInlineBox* root, |
| LayoutUnit block_right_edge, |
| LayoutUnit block_left_edge, |
| LayoutUnit ellipsis_width, |
| const AtomicString& selected_ellipsis_str, |
| InlineBox* box_truncation_starts_at) { |
| bool found_box = box_truncation_starts_at ? true : false; |
| bool ltr = StyleRef().IsLeftToRightDirection(); |
| LayoutUnit logical_left_offset = block_left_edge; |
| |
| // Each atomic inline block (e.g. a <span>) inside a blockflow is managed by |
| // an InlineBox that allows us to access the lineboxes that live inside the |
| // atomic inline block. |
| InlineBox* first_child = box_truncation_starts_at |
| ? box_truncation_starts_at |
| : (ltr ? root->FirstChild() : root->LastChild()); |
| for (InlineBox* box = first_child; box; |
| box = ltr ? box->NextOnLine() : box->PrevOnLine()) { |
| if (!box->GetLineLayoutItem().IsAtomicInlineLevel() || |
| !box->GetLineLayoutItem().IsLayoutBlockFlow()) { |
| if (box->GetLineLayoutItem().IsText()) |
| logical_left_offset += box->LogicalWidth(); |
| continue; |
| } |
| |
| if (found_box) { |
| box->GetLineLayoutItem().SetIsTruncated(true); |
| continue; |
| } |
| |
| RootInlineBox* first_root_box = |
| LineLayoutBlockFlow(box->GetLineLayoutItem()).FirstRootBox(); |
| if (!first_root_box) |
| continue; |
| |
| bool placed_ellipsis = false; |
| // Move the right edge of the block in so that we can test it against the |
| // width of the root line boxes. We don't resize or move the linebox to |
| // respect text-align because it is the final one of a sequence on the line. |
| if (ltr) { |
| for (RootInlineBox* curr = first_root_box; curr; |
| curr = curr->NextRootBox()) { |
| LayoutUnit curr_logical_left = |
| logical_left_offset + curr->LogicalLeft(); |
| LayoutUnit ellipsis_edge = |
| curr_logical_left + curr->LogicalWidth() + ellipsis_width; |
| if (ellipsis_edge <= block_right_edge) |
| continue; |
| InlineBox* truncation_box = nullptr; |
| curr->PlaceEllipsis(selected_ellipsis_str, ltr, block_left_edge, |
| block_right_edge, ellipsis_width, |
| logical_left_offset, &truncation_box); |
| placed_ellipsis = true; |
| } |
| } else { |
| LayoutUnit max_root_box_width; |
| for (RootInlineBox* curr = first_root_box; curr; |
| curr = curr->NextRootBox()) { |
| LayoutUnit ellipsis_edge = |
| box->LogicalLeft() + curr->LogicalLeft() - ellipsis_width; |
| if (ellipsis_edge >= block_left_edge) |
| continue; |
| // Root boxes can vary in width so move our offset out to allow |
| // comparison with the right hand edge of the block. |
| LayoutUnit logical_left_offset = box->LogicalLeft(); |
| max_root_box_width = |
| std::max<LayoutUnit>(curr->LogicalWidth(), max_root_box_width); |
| if (logical_left_offset < 0) |
| logical_left_offset += max_root_box_width - curr->LogicalWidth(); |
| InlineBox* truncation_box = nullptr; |
| curr->PlaceEllipsis(selected_ellipsis_str, ltr, block_left_edge, |
| block_right_edge, ellipsis_width, |
| logical_left_offset, &truncation_box); |
| placed_ellipsis = true; |
| } |
| } |
| found_box |= placed_ellipsis; |
| logical_left_offset += box->LogicalWidth(); |
| } |
| } |
| |
| void LayoutBlockFlow::MarkLinesDirtyInBlockRange(LayoutUnit logical_top, |
| LayoutUnit logical_bottom, |
| RootInlineBox* highest) { |
| if (logical_top >= logical_bottom) |
| return; |
| |
| RootInlineBox* lowest_dirty_line = LastRootBox(); |
| RootInlineBox* after_lowest = lowest_dirty_line; |
| while (lowest_dirty_line && |
| lowest_dirty_line->LineBottomWithLeading() >= logical_bottom && |
| logical_bottom < LayoutUnit::Max()) { |
| after_lowest = lowest_dirty_line; |
| lowest_dirty_line = lowest_dirty_line->PrevRootBox(); |
| } |
| |
| while (after_lowest && after_lowest != highest && |
| (after_lowest->LineBottomWithLeading() >= logical_top || |
| after_lowest->LineBottomWithLeading() < LayoutUnit())) { |
| after_lowest->MarkDirty(); |
| after_lowest = after_lowest->PrevRootBox(); |
| } |
| } |
| |
| LayoutUnit LayoutBlockFlow::StartAlignedOffsetForLine( |
| LayoutUnit position, |
| IndentTextOrNot indent_text) { |
| ETextAlign text_align = StyleRef().GetTextAlign(); |
| |
| bool apply_indent_text; |
| switch (text_align) { // FIXME: Handle TAEND here |
| case ETextAlign::kLeft: |
| case ETextAlign::kWebkitLeft: |
| apply_indent_text = StyleRef().IsLeftToRightDirection(); |
| break; |
| case ETextAlign::kRight: |
| case ETextAlign::kWebkitRight: |
| apply_indent_text = !StyleRef().IsLeftToRightDirection(); |
| break; |
| case ETextAlign::kStart: |
| apply_indent_text = true; |
| break; |
| default: |
| apply_indent_text = false; |
| } |
| |
| if (apply_indent_text) |
| return StartOffsetForLine(position, indent_text); |
| |
| // updateLogicalWidthForAlignment() handles the direction of the block so no |
| // need to consider it here |
| LayoutUnit total_logical_width; |
| LayoutUnit logical_left = |
| LogicalLeftOffsetForLine(LogicalHeight(), kDoNotIndentText); |
| LayoutUnit available_logical_width = |
| LogicalRightOffsetForLine(LogicalHeight(), kDoNotIndentText) - |
| logical_left; |
| UpdateLogicalWidthForAlignment(text_align, nullptr, nullptr, logical_left, |
| total_logical_width, available_logical_width, |
| 0); |
| |
| if (!StyleRef().IsLeftToRightDirection()) |
| return LogicalWidth() - logical_left; |
| return logical_left; |
| } |
| |
| void LayoutBlockFlow::SetShouldDoFullPaintInvalidationForFirstLine() { |
| DCHECK(ChildrenInline()); |
| if (RootInlineBox* first_root_box = FirstRootBox()) |
| first_root_box->SetShouldDoFullPaintInvalidationRecursively(); |
| else if (NGPaintFragment* paint_fragment = PaintFragment()) |
| paint_fragment->SetShouldDoFullPaintInvalidationForFirstLine(); |
| } |
| |
| bool LayoutBlockFlow::PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const { |
| // LayoutBlockFlow is in charge of paint invalidation of the first line. |
| if (FirstLineBox()) |
| return false; |
| |
| return LayoutBlock::PaintedOutputOfObjectHasNoEffectRegardlessOfSize(); |
| } |
| |
| } // namespace blink |