| /* |
| * Copyright 2015 Google Inc. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "cobalt/layout/inline_container_box.h" |
| |
| #include <limits> |
| |
| #include "cobalt/cssom/keyword_value.h" |
| #include "cobalt/layout/line_box.h" |
| #include "cobalt/layout/used_style.h" |
| |
| namespace cobalt { |
| namespace layout { |
| |
| InlineContainerBox::InlineContainerBox( |
| const scoped_refptr<cssom::CSSComputedStyleDeclaration>& |
| css_computed_style_declaration, |
| UsedStyleProvider* used_style_provider, |
| LayoutStatTracker* layout_stat_tracker) |
| : ContainerBox(css_computed_style_declaration, used_style_provider, |
| layout_stat_tracker), |
| should_collapse_leading_white_space_(false), |
| should_collapse_trailing_white_space_(false), |
| has_leading_white_space_(false), |
| has_trailing_white_space_(false), |
| is_collapsed_(false), |
| justifies_line_existence_(false), |
| first_box_justifying_line_existence_index_(0), |
| used_font_(used_style_provider->GetUsedFontList( |
| css_computed_style_declaration->data()->font_family(), |
| css_computed_style_declaration->data()->font_size(), |
| css_computed_style_declaration->data()->font_style(), |
| css_computed_style_declaration->data()->font_weight())) {} |
| |
| InlineContainerBox::~InlineContainerBox() {} |
| |
| Box::Level InlineContainerBox::GetLevel() const { return kInlineLevel; } |
| |
| bool InlineContainerBox::TryAddChild(const scoped_refptr<Box>& child_box) { |
| switch (child_box->GetLevel()) { |
| case kBlockLevel: |
| if (!child_box->IsAbsolutelyPositioned()) { |
| // Only inline-level boxes are allowed as in-flow children of an inline |
| // container box. |
| return false; |
| } |
| // Fall through if out-of-flow. |
| |
| case kInlineLevel: |
| // If the inline container box already contains a line break, then no |
| // additional children can be added to it. |
| if (HasTrailingLineBreak()) { |
| return false; |
| } |
| |
| PushBackDirectChild(child_box); |
| return true; |
| |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| scoped_refptr<ContainerBox> InlineContainerBox::TrySplitAtEnd() { |
| scoped_refptr<InlineContainerBox> box_after_split( |
| new InlineContainerBox(css_computed_style_declaration(), |
| used_style_provider(), layout_stat_tracker())); |
| // When an inline box is split, margins, borders, and padding have no visual |
| // effect where the split occurs. |
| // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting |
| // TODO: Implement the above comment. |
| |
| return box_after_split; |
| } |
| |
| LayoutUnit InlineContainerBox::GetInlineLevelBoxHeight() const { |
| return line_height_; |
| } |
| |
| LayoutUnit InlineContainerBox::GetInlineLevelTopMargin() const { |
| return inline_top_margin_; |
| } |
| |
| void InlineContainerBox::UpdateContentSizeAndMargins( |
| const LayoutParams& layout_params) { |
| // Lay out child boxes as one line without width constraints and white space |
| // trimming. |
| const render_tree::FontMetrics& font_metrics = used_font_->GetFontMetrics(); |
| LayoutUnit box_top_height = LayoutUnit(font_metrics.ascent()); |
| LineBox line_box(box_top_height, true, computed_style()->line_height(), |
| font_metrics, should_collapse_leading_white_space_, |
| should_collapse_trailing_white_space_, layout_params, |
| kLeftToRightBaseDirection, cssom::KeywordValue::GetLeft(), |
| computed_style()->font_size(), LayoutUnit(), LayoutUnit()); |
| |
| for (Boxes::const_iterator child_box_iterator = child_boxes().begin(); |
| child_box_iterator != child_boxes().end(); ++child_box_iterator) { |
| Box* child_box = *child_box_iterator; |
| if (child_box->IsAbsolutelyPositioned()) { |
| line_box.BeginEstimateStaticPosition(child_box); |
| } else { |
| line_box.BeginAddChildAndMaybeOverflow(child_box); |
| } |
| } |
| line_box.EndUpdates(); |
| |
| // Although the spec says: |
| // |
| // The "width" property does not apply. |
| // https://www.w3.org/TR/CSS21/visudet.html#inline-width |
| // |
| // ...it is not the entire truth. It merely means that we have to ignore |
| // the computed value of "width". Instead we use the shrink-to-fit width of |
| // a hypothetical line box that contains all children. Later on this allow |
| // to apply the following rule: |
| // |
| // When an inline box exceeds the width of a line box, it is split into |
| // several boxes. |
| // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting |
| set_width(line_box.shrink_to_fit_width()); |
| |
| base::optional<LayoutUnit> maybe_margin_left = GetUsedMarginLeftIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::optional<LayoutUnit> maybe_margin_right = GetUsedMarginRightIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| |
| // A computed value of "auto" for "margin-left" or "margin-right" becomes |
| // a used value of "0". |
| // https://www.w3.org/TR/CSS21/visudet.html#inline-width |
| set_margin_left(maybe_margin_left.value_or(LayoutUnit())); |
| set_margin_right(maybe_margin_right.value_or(LayoutUnit())); |
| |
| // The "height" property does not apply. The height of the content area should |
| // be based on the font, but this specification does not specify how. [...] |
| // However, we suggest that the height is chosen such that the content area |
| // is just high enough for [...] the maximum ascenders and descenders, of all |
| // the fonts in the element. |
| // https://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced |
| // |
| // Above definition of used height matches the height of hypothetical line box |
| // that contains all children. |
| set_height(LayoutUnit(font_metrics.em_box_height())); |
| |
| // On a non-replaced inline element, 'line-height' specifies the height that |
| // is used in the calculation of the line box height. |
| // https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height |
| line_height_ = line_box.height(); |
| |
| // Vertical margins will not have any effect on non-replaced inline elements. |
| // https://www.w3.org/TR/CSS21/box.html#margin-properties |
| set_margin_top(LayoutUnit()); |
| set_margin_bottom(LayoutUnit()); |
| inline_top_margin_ = line_box.baseline_offset_from_top() - line_box.top() - |
| border_top_width() - padding_top(); |
| |
| has_leading_white_space_ = line_box.HasLeadingWhiteSpace(); |
| has_trailing_white_space_ = line_box.HasTrailingWhiteSpace(); |
| is_collapsed_ = line_box.IsCollapsed(); |
| justifies_line_existence_ = |
| line_box.LineExists() || HasNonZeroMarginOrBorderOrPadding(); |
| first_box_justifying_line_existence_index_ = |
| line_box.GetFirstBoxJustifyingLineExistenceIndex(); |
| baseline_offset_from_margin_box_top_ = line_box.baseline_offset_from_top(); |
| } |
| |
| WrapResult InlineContainerBox::TryWrapAt( |
| WrapAtPolicy wrap_at_policy, WrapOpportunityPolicy wrap_opportunity_policy, |
| bool is_line_existence_justified, LayoutUnit available_width, |
| bool should_collapse_trailing_white_space) { |
| DCHECK(!IsAbsolutelyPositioned()); |
| DCHECK(is_line_existence_justified || justifies_line_existence_); |
| |
| switch (wrap_at_policy) { |
| case kWrapAtPolicyBefore: |
| return TryWrapAtBefore(wrap_opportunity_policy, |
| is_line_existence_justified, available_width, |
| should_collapse_trailing_white_space); |
| case kWrapAtPolicyLastOpportunityWithinWidth: |
| return TryWrapAtLastOpportunityWithinWidth( |
| wrap_opportunity_policy, is_line_existence_justified, available_width, |
| should_collapse_trailing_white_space); |
| case kWrapAtPolicyLastOpportunity: |
| return TryWrapAtLastOpportunityBeforeIndex( |
| child_boxes().size(), wrap_opportunity_policy, |
| is_line_existence_justified, available_width, |
| should_collapse_trailing_white_space); |
| case kWrapAtPolicyFirstOpportunity: |
| return TryWrapAtFirstOpportunity( |
| wrap_opportunity_policy, is_line_existence_justified, available_width, |
| should_collapse_trailing_white_space); |
| default: |
| NOTREACHED(); |
| return kWrapResultNoWrap; |
| } |
| } |
| |
| Box* InlineContainerBox::GetSplitSibling() const { return split_sibling_; } |
| |
| bool InlineContainerBox::DoesFulfillEllipsisPlacementRequirement() const { |
| for (Boxes::const_iterator child_box_iterator = child_boxes().begin(); |
| child_box_iterator != child_boxes().end(); ++child_box_iterator) { |
| Box* child_box = *child_box_iterator; |
| if (child_box->DoesFulfillEllipsisPlacementRequirement()) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void InlineContainerBox::DoPreEllipsisPlacementProcessing() { |
| for (Boxes::const_iterator child_box_iterator = child_boxes().begin(); |
| child_box_iterator != child_boxes().end(); ++child_box_iterator) { |
| (*child_box_iterator)->DoPreEllipsisPlacementProcessing(); |
| } |
| } |
| |
| void InlineContainerBox::DoPostEllipsisPlacementProcessing() { |
| for (Boxes::const_iterator child_box_iterator = child_boxes().begin(); |
| child_box_iterator != child_boxes().end(); ++child_box_iterator) { |
| (*child_box_iterator)->DoPostEllipsisPlacementProcessing(); |
| } |
| } |
| |
| bool InlineContainerBox::TrySplitAtSecondBidiLevelRun() { |
| const int kInvalidLevel = -1; |
| int last_level = kInvalidLevel; |
| |
| Boxes::const_iterator child_box_iterator = child_boxes().begin(); |
| while (child_box_iterator != child_boxes().end()) { |
| Box* child_box = *child_box_iterator; |
| int current_level = child_box->GetBidiLevel().value_or(last_level); |
| |
| // If the last level isn't equal to the current level, then check on whether |
| // or not the last level is kInvalidLevel. If it is, then no initial value |
| // has been set yet. Otherwise, the intersection of two bidi levels has been |
| // found where a split should occur. |
| if (last_level != current_level) { |
| if (last_level == kInvalidLevel) { |
| last_level = current_level; |
| } else { |
| break; |
| } |
| } |
| |
| // Try to split the child box's internals. |
| if (child_box->TrySplitAtSecondBidiLevelRun()) { |
| child_box_iterator = InsertSplitSiblingOfDirectChild(child_box_iterator); |
| break; |
| } |
| |
| ++child_box_iterator; |
| } |
| |
| // If the iterator reached the end, then no split was found. |
| if (child_box_iterator == child_boxes().end()) { |
| return false; |
| } |
| |
| SplitAtIterator(child_box_iterator); |
| return true; |
| } |
| |
| base::optional<int> InlineContainerBox::GetBidiLevel() const { |
| if (!child_boxes().empty()) { |
| return child_boxes().front()->GetBidiLevel(); |
| } |
| |
| return base::nullopt; |
| } |
| |
| void InlineContainerBox::SetShouldCollapseLeadingWhiteSpace( |
| bool should_collapse_leading_white_space) { |
| if (should_collapse_leading_white_space_ != |
| should_collapse_leading_white_space) { |
| should_collapse_leading_white_space_ = should_collapse_leading_white_space; |
| InvalidateUpdateSizeInputs(); |
| } |
| } |
| |
| void InlineContainerBox::SetShouldCollapseTrailingWhiteSpace( |
| bool should_collapse_trailing_white_space) { |
| if (should_collapse_trailing_white_space_ != |
| should_collapse_trailing_white_space) { |
| should_collapse_trailing_white_space_ = |
| should_collapse_trailing_white_space; |
| InvalidateUpdateSizeInputs(); |
| } |
| } |
| |
| bool InlineContainerBox::HasLeadingWhiteSpace() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| DCHECK_EQ(width(), width()); // Width should not be NaN. |
| |
| return has_leading_white_space_; |
| } |
| |
| bool InlineContainerBox::HasTrailingWhiteSpace() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| DCHECK_EQ(width(), width()); // Width should not be NaN. |
| |
| return has_trailing_white_space_; |
| } |
| |
| bool InlineContainerBox::IsCollapsed() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| DCHECK_EQ(width(), width()); // Width should not be NaN. |
| |
| return is_collapsed_; |
| } |
| |
| bool InlineContainerBox::JustifiesLineExistence() const { |
| return justifies_line_existence_; |
| } |
| |
| bool InlineContainerBox::HasTrailingLineBreak() const { |
| return !child_boxes().empty() && child_boxes().back()->HasTrailingLineBreak(); |
| } |
| |
| bool InlineContainerBox::AffectsBaselineInBlockFormattingContext() const { |
| NOTREACHED() << "Should only be called in a block formatting context."; |
| return true; |
| } |
| |
| LayoutUnit InlineContainerBox::GetBaselineOffsetFromTopMarginEdge() const { |
| return baseline_offset_from_margin_box_top_; |
| } |
| |
| bool InlineContainerBox::IsTransformable() const { return false; } |
| |
| #ifdef COBALT_BOX_DUMP_ENABLED |
| |
| void InlineContainerBox::DumpClassName(std::ostream* stream) const { |
| *stream << "InlineContainerBox "; |
| } |
| |
| void InlineContainerBox::DumpProperties(std::ostream* stream) const { |
| ContainerBox::DumpProperties(stream); |
| |
| *stream << std::boolalpha << "line_height=" << line_height_.toFloat() << " " |
| << "inline_top_margin=" << inline_top_margin_.toFloat() << " " |
| << "has_leading_white_space=" << has_leading_white_space_ << " " |
| << "has_trailing_white_space=" << has_trailing_white_space_ << " " |
| << "is_collapsed=" << is_collapsed_ << " " |
| << "justifies_line_existence=" << justifies_line_existence_ << " " |
| << std::noboolalpha; |
| } |
| |
| #endif // COBALT_BOX_DUMP_ENABLED |
| |
| void InlineContainerBox::DoPlaceEllipsisOrProcessPlacedEllipsis( |
| BaseDirection base_direction, LayoutUnit desired_offset, |
| bool* is_placement_requirement_met, bool* is_placed, |
| LayoutUnit* placed_offset) { |
| // If the ellipsis hasn't been previously placed, but the placement |
| // requirement is met and its desired offset comes before the content box's |
| // start edge, then place the ellipsis at its desired position. This can occur |
| // when the desired position falls between the start edge of the margin box |
| // and the start edge of its content box. |
| if (!*is_placed && *is_placement_requirement_met) { |
| LayoutUnit content_box_start_edge_offset = |
| GetContentBoxStartEdgeOffsetFromContainingBlock(base_direction); |
| if ((base_direction == kRightToLeftBaseDirection && |
| desired_offset >= content_box_start_edge_offset) || |
| (base_direction != kRightToLeftBaseDirection && |
| desired_offset <= content_box_start_edge_offset)) { |
| *is_placed = true; |
| *placed_offset = desired_offset; |
| } |
| } |
| |
| bool was_placed_before_children = *is_placed; |
| |
| // Subtract the content box offset from the desired offset. This box's |
| // children will treat its content box left edge as the base ellipsis offset |
| // position, and the content box offset will be re-added after the ellipsis |
| // is placed. This allows its children to only focus on their offset from |
| // their containing block, and not worry about nested containing blocks. |
| LayoutUnit content_box_offset = |
| GetContentBoxLeftEdgeOffsetFromContainingBlock(); |
| desired_offset -= content_box_offset; |
| |
| // Walk each child box in base direction order attempting to place the |
| // ellipsis and update the box's ellipsis state. Even after the ellipsis is |
| // placed, subsequent boxes must still be processed, as their state my change |
| // as a result of having an ellipsis preceding them on the line. |
| if (base_direction == kRightToLeftBaseDirection) { |
| for (Boxes::const_reverse_iterator child_box_iterator = |
| child_boxes().rbegin(); |
| child_box_iterator != child_boxes().rend(); ++child_box_iterator) { |
| Box* child_box = *child_box_iterator; |
| child_box->TryPlaceEllipsisOrProcessPlacedEllipsis( |
| base_direction, desired_offset, is_placement_requirement_met, |
| is_placed, placed_offset); |
| } |
| } else { |
| for (Boxes::const_iterator child_box_iterator = child_boxes().begin(); |
| child_box_iterator != child_boxes().end(); ++child_box_iterator) { |
| Box* child_box = *child_box_iterator; |
| child_box->TryPlaceEllipsisOrProcessPlacedEllipsis( |
| base_direction, desired_offset, is_placement_requirement_met, |
| is_placed, placed_offset); |
| } |
| } |
| |
| // If the ellipsis was not placed prior to processing the child boxes, then it |
| // is guaranteed that the placement location comes after the start edge of the |
| // content box. The content box's offset needs to be added back to the placed |
| // offset, so that the offset again references this box's containing block. |
| // Additionally, in the event that the ellipsis was not placed within a child |
| // box, it will now be placed. |
| if (!was_placed_before_children) { |
| // If the ellipsis hasn't been placed yet, then place the ellipsis at its |
| // desired position. This case can occur when the desired position falls |
| // between the end edge of the box's content and the end edge of the box's |
| // margin. In this case, |is_placement_requirement_met| does not need to be |
| // checked, as it is guaranteed that one of the child boxes met the |
| // requirement. |
| if (!(*is_placed)) { |
| *is_placed = true; |
| *placed_offset = desired_offset; |
| } |
| |
| *placed_offset += content_box_offset; |
| } |
| } |
| |
| WrapResult InlineContainerBox::TryWrapAtBefore( |
| WrapOpportunityPolicy wrap_opportunity_policy, bool is_line_requirement_met, |
| LayoutUnit available_width, bool should_collapse_trailing_white_space) { |
| // If there are no boxes within the inline container box, then there is no |
| // first box to try to wrap before. This box does not provide a wrappable |
| // point on its own. Additionally, if the line requirement has not been met |
| // before this box, then no wrap is available. |
| if (child_boxes().size() == 0 || !is_line_requirement_met) { |
| return kWrapResultNoWrap; |
| } else { |
| // Otherwise, attempt to wrap before the first child box. |
| return TryWrapAtIndex(0, kWrapAtPolicyBefore, wrap_opportunity_policy, |
| is_line_requirement_met, available_width, |
| should_collapse_trailing_white_space); |
| } |
| } |
| |
| WrapResult InlineContainerBox::TryWrapAtLastOpportunityWithinWidth( |
| WrapOpportunityPolicy wrap_opportunity_policy, |
| bool is_line_existence_justified, LayoutUnit available_width, |
| bool should_collapse_trailing_white_space) { |
| DCHECK_GT(GetMarginBoxWidth(), available_width); |
| |
| // Calculate the available width where the content begins. If the content |
| // does not begin within the available width, then the wrap can only occur |
| // before the inline container box. |
| available_width -= GetContentBoxLeftEdgeOffsetFromMarginBox(); |
| if (available_width < LayoutUnit()) { |
| return TryWrapAtBefore(wrap_opportunity_policy, is_line_existence_justified, |
| available_width, |
| should_collapse_trailing_white_space); |
| } |
| |
| // Determine the child box where the overflow occurs. If the overflow does not |
| // occur until after the end of the content, then the overflow index will be |
| // set to the number of child boxes. |
| size_t overflow_index = 0; |
| while (overflow_index < child_boxes().size()) { |
| Box* child_box = child_boxes()[overflow_index]; |
| // Absolutely positioned boxes are not included in width calculations. |
| if (child_box->IsAbsolutelyPositioned()) { |
| continue; |
| } |
| |
| LayoutUnit child_width = child_box->GetMarginBoxWidth(); |
| if (child_width > available_width) { |
| break; |
| } |
| available_width -= child_width; |
| ++overflow_index; |
| } |
| |
| // If the overflow occurs before the line is justified, then no wrap is |
| // available. |
| if (!is_line_existence_justified && |
| overflow_index < first_box_justifying_line_existence_index_) { |
| return kWrapResultNoWrap; |
| } |
| |
| // If the overflow occurred within the content and not after, then attempt to |
| // wrap within the box that overflowed and return the result if the wrap is |
| // successful. |
| if (overflow_index < child_boxes().size()) { |
| WrapResult wrap_result = |
| TryWrapAtIndex(overflow_index, kWrapAtPolicyLastOpportunityWithinWidth, |
| wrap_opportunity_policy, is_line_existence_justified, |
| available_width, should_collapse_trailing_white_space); |
| if (wrap_result != kWrapResultNoWrap) { |
| return wrap_result; |
| } |
| } |
| |
| // If no wrap was found within the box that overflowed, then attempt to wrap |
| // within an earlier child box. |
| return TryWrapAtLastOpportunityBeforeIndex( |
| overflow_index, wrap_opportunity_policy, is_line_existence_justified, |
| available_width, should_collapse_trailing_white_space); |
| } |
| |
| WrapResult InlineContainerBox::TryWrapAtLastOpportunityBeforeIndex( |
| size_t index, WrapOpportunityPolicy wrap_opportunity_policy, |
| bool is_line_existence_justified, LayoutUnit available_width, |
| bool should_collapse_trailing_white_space) { |
| WrapResult wrap_result = kWrapResultNoWrap; |
| |
| // If the line is already justified, then any child before the index is |
| // potentially wrappable. Otherwise, children preceding the first box that |
| // justifies the line's existence do not need to be checked, as they can |
| // never be wrappable. |
| size_t first_wrappable_index = |
| is_line_existence_justified ? 0 |
| : first_box_justifying_line_existence_index_; |
| |
| // Iterate backwards through the children attempting to wrap until a wrap is |
| // successful or the first wrappable index is reached. |
| while (wrap_result == kWrapResultNoWrap && index > first_wrappable_index) { |
| --index; |
| wrap_result = |
| TryWrapAtIndex(index, kWrapAtPolicyLastOpportunity, |
| wrap_opportunity_policy, is_line_existence_justified, |
| available_width, should_collapse_trailing_white_space); |
| } |
| |
| return wrap_result; |
| } |
| |
| WrapResult InlineContainerBox::TryWrapAtFirstOpportunity( |
| WrapOpportunityPolicy wrap_opportunity_policy, |
| bool is_line_existence_justified, LayoutUnit available_width, |
| bool should_collapse_trailing_white_space) { |
| WrapResult wrap_result = kWrapResultNoWrap; |
| |
| // If the line is already justified, then any child is potentially wrappable. |
| // Otherwise, children preceding the first box that justifies the line's |
| // existence do not need to be checked, as they can never be wrappable. |
| size_t check_index = is_line_existence_justified |
| ? 0 |
| : first_box_justifying_line_existence_index_; |
| |
| // Iterate forward through the children attempting to wrap until a wrap is |
| // successful or all of the children have been attempted. |
| for (; wrap_result == kWrapResultNoWrap && check_index < child_boxes().size(); |
| ++check_index) { |
| wrap_result = |
| TryWrapAtIndex(check_index, kWrapAtPolicyFirstOpportunity, |
| wrap_opportunity_policy, is_line_existence_justified, |
| available_width, should_collapse_trailing_white_space); |
| } |
| |
| return wrap_result; |
| } |
| |
| WrapResult InlineContainerBox::TryWrapAtIndex( |
| size_t wrap_index, WrapAtPolicy wrap_at_policy, |
| WrapOpportunityPolicy wrap_opportunity_policy, |
| bool is_line_existence_justified, LayoutUnit available_width, |
| bool should_collapse_trailing_white_space) { |
| Box* child_box = child_boxes()[wrap_index]; |
| // Absolutely positioned boxes are not wrappable. |
| if (child_box->IsAbsolutelyPositioned()) { |
| return kWrapResultNoWrap; |
| } |
| |
| // Check for whether the line is justified before this child. If it is not, |
| // then verify that the line is justified within this child. This function |
| // should not be called for unjustified indices. |
| bool is_line_existence_justified_before_index = |
| is_line_existence_justified || |
| wrap_index > first_box_justifying_line_existence_index_; |
| DCHECK(is_line_existence_justified_before_index || |
| wrap_index == first_box_justifying_line_existence_index_); |
| |
| WrapResult wrap_result = child_box->TryWrapAt( |
| wrap_at_policy, wrap_opportunity_policy, |
| is_line_existence_justified_before_index, available_width, |
| should_collapse_trailing_white_space); |
| // If the no wrap was found, then simply return out. There's nothing to do. |
| if (wrap_result == kWrapResultNoWrap) { |
| return kWrapResultNoWrap; |
| // Otherwise, if the wrap is before the first child, then the wrap is |
| // happening before the full inline container box and no split is |
| // occurring. |
| // When breaks happen before the first or the last character of a box, |
| // the break occurs immediately before/after the box (at its margin edge) |
| // rather than breaking the box between its content edge and the content. |
| // https://www.w3.org/TR/css-text-3/#line-breaking |
| } else if (wrap_result == kWrapResultWrapBefore && wrap_index == 0) { |
| return kWrapResultWrapBefore; |
| // In all other cases, the inline container box is being split as a result |
| // of the wrap. |
| } else { |
| Boxes::const_iterator wrap_iterator = |
| child_boxes().begin() + static_cast<int>(wrap_index); |
| // If the child was split during its wrap, then the split sibling that was |
| // produced by the split needs to be added to the container's children. |
| if (wrap_result == kWrapResultSplitWrap) { |
| wrap_iterator = InsertSplitSiblingOfDirectChild(wrap_iterator); |
| } |
| |
| SplitAtIterator(wrap_iterator); |
| return kWrapResultSplitWrap; |
| } |
| } |
| |
| void InlineContainerBox::SplitAtIterator( |
| Boxes::const_iterator child_split_iterator) { |
| // TODO: When an inline box is split, margins, borders, and padding |
| // have no visual effect where the split occurs. |
| // https://www.w3.org/TR/CSS21/visuren.html#inline-formatting |
| |
| // Move the children after the split into a new box. |
| scoped_refptr<InlineContainerBox> box_after_split( |
| new InlineContainerBox(css_computed_style_declaration(), |
| used_style_provider(), layout_stat_tracker())); |
| |
| // Update the split sibling links. |
| box_after_split->split_sibling_ = split_sibling_; |
| split_sibling_ = box_after_split; |
| |
| // Now move the children, starting at the iterator, from this container to the |
| // split sibling. |
| MoveDirectChildrenToSplitSibling(child_split_iterator); |
| |
| #ifdef _DEBUG |
| // Make sure that the |UpdateContentSizeAndMargins| is called afterwards. |
| |
| set_width(LayoutUnit()); |
| set_height(LayoutUnit()); |
| |
| set_margin_left(LayoutUnit()); |
| set_margin_top(LayoutUnit()); |
| set_margin_right(LayoutUnit()); |
| set_margin_bottom(LayoutUnit()); |
| #endif // _DEBUG |
| } |
| |
| } // namespace layout |
| } // namespace cobalt |