| /* |
| * Copyright (C) 2003, 2006, 2008 Apple 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 "third_party/blink/renderer/core/layout/line/root_inline_box.h" |
| |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_item.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/line/ellipsis_box.h" |
| #include "third_party/blink/renderer/core/layout/line/glyph_overflow.h" |
| #include "third_party/blink/renderer/core/layout/line/inline_text_box.h" |
| #include "third_party/blink/renderer/core/layout/vertical_position_cache.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| #include "third_party/blink/renderer/core/paint/root_inline_box_painter.h" |
| #include "third_party/blink/renderer/platform/text/bidi_resolver.h" |
| #include "third_party/blink/renderer/platform/wtf/text/unicode.h" |
| |
| namespace blink { |
| |
| struct SameSizeAsRootInlineBox : public InlineFlowBox { |
| unsigned unsigned_variable; |
| void* pointers[3]; |
| LayoutUnit layout_variables[6]; |
| }; |
| |
| static_assert(sizeof(RootInlineBox) == sizeof(SameSizeAsRootInlineBox), |
| "RootInlineBox should stay small"); |
| |
| typedef WTF::HashMap<const RootInlineBox*, EllipsisBox*> EllipsisBoxMap; |
| static EllipsisBoxMap* g_ellipsis_box_map = nullptr; |
| |
| RootInlineBox::RootInlineBox(LineLayoutItem block) |
| : InlineFlowBox(block), line_break_pos_(0), line_break_obj_(nullptr) { |
| SetIsHorizontal(block.IsHorizontalWritingMode()); |
| } |
| |
| void RootInlineBox::Destroy() { |
| DetachEllipsisBox(); |
| InlineFlowBox::Destroy(); |
| } |
| |
| void RootInlineBox::DetachEllipsisBox() { |
| if (HasEllipsisBox()) { |
| EllipsisBox* box = g_ellipsis_box_map->Take(this); |
| box->SetParent(nullptr); |
| box->Destroy(); |
| SetHasEllipsisBox(false); |
| } |
| } |
| |
| LineBoxList* RootInlineBox::LineBoxes() const { |
| return Block().LineBoxes(); |
| } |
| |
| void RootInlineBox::ClearTruncation() { |
| if (HasEllipsisBox()) { |
| DetachEllipsisBox(); |
| InlineFlowBox::ClearTruncation(); |
| } |
| } |
| |
| LayoutUnit RootInlineBox::BaselinePosition(FontBaseline baseline_type) const { |
| return BoxModelObject().BaselinePosition( |
| baseline_type, IsFirstLineStyle(), |
| IsHorizontal() ? kHorizontalLine : kVerticalLine, |
| kPositionOfInteriorLineBoxes); |
| } |
| |
| LayoutUnit RootInlineBox::LineHeight() const { |
| return BoxModelObject().LineHeight( |
| IsFirstLineStyle(), IsHorizontal() ? kHorizontalLine : kVerticalLine, |
| kPositionOfInteriorLineBoxes); |
| } |
| |
| bool RootInlineBox::LineCanAccommodateEllipsis(bool ltr, |
| LayoutUnit block_edge, |
| LayoutUnit line_box_edge, |
| LayoutUnit ellipsis_width) { |
| // First sanity-check the unoverflowed width of the whole line to see if there |
| // is sufficient room. |
| LayoutUnit delta = |
| ltr ? line_box_edge - block_edge : block_edge - line_box_edge; |
| if (LogicalWidth() - delta < ellipsis_width) |
| return false; |
| |
| // Next iterate over all the line boxes on the line. If we find a replaced |
| // element that intersects then we refuse to accommodate the ellipsis. |
| // Otherwise we're ok. |
| return InlineFlowBox::CanAccommodateEllipsis(ltr, block_edge, ellipsis_width); |
| } |
| |
| LayoutUnit RootInlineBox::PlaceEllipsis(const AtomicString& ellipsis_str, |
| bool ltr, |
| LayoutUnit block_left_edge, |
| LayoutUnit block_right_edge, |
| LayoutUnit ellipsis_width, |
| LayoutUnit logical_left_offset, |
| InlineBox** found_box, |
| ForceEllipsisOnLine force_ellipsis) { |
| // Create an ellipsis box if we don't already have one. If we already have one |
| // we're just here to blank out (truncate) the text boxes. |
| if (!*found_box) { |
| EllipsisBox* ellipsis_box = new EllipsisBox( |
| GetLineLayoutItem(), ellipsis_str, this, ellipsis_width, |
| LogicalHeight(), Location(), !PrevRootBox(), IsHorizontal()); |
| |
| if (!g_ellipsis_box_map) |
| g_ellipsis_box_map = new EllipsisBoxMap(); |
| g_ellipsis_box_map->insert(this, ellipsis_box); |
| SetHasEllipsisBox(true); |
| } |
| |
| // FIXME: Do we need an RTL version of this? |
| LayoutUnit adjusted_logical_left = logical_left_offset + LogicalLeft(); |
| if (force_ellipsis == ForceEllipsis && ltr && |
| (adjusted_logical_left + LogicalWidth() + ellipsis_width) <= |
| block_right_edge) { |
| if (HasEllipsisBox()) |
| GetEllipsisBox()->SetLogicalLeft(LogicalLeft() + LogicalWidth()); |
| return LogicalWidth() + ellipsis_width; |
| } |
| |
| // Now attempt to find the nearest glyph horizontally and place just to the |
| // right (or left in RTL) of that glyph. Mark all of the objects that |
| // intersect the ellipsis box as not painting (as being truncated). |
| LayoutUnit truncated_width; |
| LayoutUnit position = |
| PlaceEllipsisBox(ltr, block_left_edge, block_right_edge, ellipsis_width, |
| truncated_width, found_box, logical_left_offset); |
| if (HasEllipsisBox()) |
| GetEllipsisBox()->SetLogicalLeft(position); |
| return truncated_width; |
| } |
| |
| LayoutUnit RootInlineBox::PlaceEllipsisBox(bool ltr, |
| LayoutUnit block_left_edge, |
| LayoutUnit block_right_edge, |
| LayoutUnit ellipsis_width, |
| LayoutUnit& truncated_width, |
| InlineBox** found_box, |
| LayoutUnit logical_left_offset) { |
| LayoutUnit result = InlineFlowBox::PlaceEllipsisBox( |
| ltr, block_left_edge, block_right_edge, ellipsis_width, truncated_width, |
| found_box, logical_left_offset); |
| if (result == -1) { |
| result = ltr ? std::max<LayoutUnit>( |
| LayoutUnit(), |
| block_right_edge - ellipsis_width - logical_left_offset) |
| : block_left_edge - logical_left_offset; |
| truncated_width = block_right_edge - block_left_edge - logical_left_offset; |
| } |
| return result; |
| } |
| |
| void RootInlineBox::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset, |
| LayoutUnit line_top, |
| LayoutUnit line_bottom) const { |
| RootInlineBoxPainter(*this).Paint(paint_info, paint_offset, line_top, |
| line_bottom); |
| } |
| |
| bool RootInlineBox::NodeAtPoint(HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| LayoutUnit line_top, |
| LayoutUnit line_bottom) { |
| if (HasEllipsisBox() && VisibleToHitTestRequest(result.GetHitTestRequest())) { |
| if (GetEllipsisBox()->NodeAtPoint(result, location_in_container, |
| accumulated_offset, line_top, |
| line_bottom)) { |
| GetLineLayoutItem().UpdateHitTestResult( |
| result, |
| location_in_container.Point() - ToLayoutSize(accumulated_offset)); |
| return true; |
| } |
| } |
| return InlineFlowBox::NodeAtPoint(result, location_in_container, |
| accumulated_offset, line_top, line_bottom); |
| } |
| |
| void RootInlineBox::Move(const LayoutSize& delta) { |
| InlineFlowBox::Move(delta); |
| LayoutUnit block_direction_delta = |
| IsHorizontal() ? delta.Height() : delta.Width(); |
| line_top_ += block_direction_delta; |
| line_bottom_ += block_direction_delta; |
| line_top_with_leading_ += block_direction_delta; |
| line_bottom_with_leading_ += block_direction_delta; |
| selection_bottom_ += block_direction_delta; |
| if (HasEllipsisBox()) |
| GetEllipsisBox()->Move(delta); |
| } |
| |
| void RootInlineBox::ChildRemoved(InlineBox* box) { |
| if (box->GetLineLayoutItem() == line_break_obj_) |
| SetLineBreakInfo(nullptr, 0, BidiStatus()); |
| |
| for (RootInlineBox* prev = PrevRootBox(); |
| prev && prev->LineBreakObj() == box->GetLineLayoutItem(); |
| prev = prev->PrevRootBox()) { |
| prev->SetLineBreakInfo(nullptr, 0, BidiStatus()); |
| prev->MarkDirty(); |
| } |
| } |
| |
| static inline void ApplyLineHeightStep(uint8_t line_height_step, |
| LayoutUnit& max_ascent, |
| LayoutUnit& max_descent) { |
| // Round up to the multiple of units, by adding spaces to over/under equally. |
| // https://drafts.csswg.org/css-rhythm/#line-height-step |
| int remainder = (max_ascent + max_descent).ToInt() % line_height_step; |
| if (!remainder) |
| return; |
| DCHECK_GT(remainder, 0); |
| int space = line_height_step - remainder; |
| max_descent += space / 2; |
| max_ascent += space - space / 2; |
| } |
| |
| LayoutUnit RootInlineBox::AlignBoxesInBlockDirection( |
| LayoutUnit height_of_block, |
| GlyphOverflowAndFallbackFontsMap& text_box_data_map, |
| VerticalPositionCache& vertical_position_cache) { |
| // SVG will handle vertical alignment on its own. |
| if (IsSVGRootInlineBox()) |
| return LayoutUnit(); |
| |
| LayoutUnit max_position_top; |
| LayoutUnit max_position_bottom; |
| LayoutUnit max_ascent; |
| LayoutUnit max_descent; |
| bool set_max_ascent = false; |
| bool set_max_descent = false; |
| |
| // Figure out if we're in no-quirks mode. |
| bool no_quirks_mode = GetLineLayoutItem().GetDocument().InNoQuirksMode(); |
| |
| baseline_type_ = DominantBaseline(); |
| |
| ComputeLogicalBoxHeights(this, max_position_top, max_position_bottom, |
| max_ascent, max_descent, set_max_ascent, |
| set_max_descent, no_quirks_mode, text_box_data_map, |
| BaselineType(), vertical_position_cache); |
| |
| if (max_ascent + max_descent < |
| std::max(max_position_top, max_position_bottom)) |
| AdjustMaxAscentAndDescent(max_ascent, max_descent, max_position_top.ToInt(), |
| max_position_bottom.ToInt()); |
| |
| if (uint8_t line_height_step = |
| GetLineLayoutItem().StyleRef().LineHeightStep()) |
| ApplyLineHeightStep(line_height_step, max_ascent, max_descent); |
| |
| LayoutUnit max_height = LayoutUnit(max_ascent + max_descent); |
| LayoutUnit line_top = height_of_block; |
| LayoutUnit line_bottom = height_of_block; |
| LayoutUnit line_top_including_margins = height_of_block; |
| LayoutUnit line_bottom_including_margins = height_of_block; |
| LayoutUnit selection_bottom = height_of_block; |
| bool set_line_top = false; |
| bool has_annotations_before = false; |
| bool has_annotations_after = false; |
| PlaceBoxesInBlockDirection( |
| height_of_block, max_height, max_ascent, no_quirks_mode, line_top, |
| line_bottom, selection_bottom, set_line_top, line_top_including_margins, |
| line_bottom_including_margins, has_annotations_before, |
| has_annotations_after, BaselineType()); |
| has_annotations_before_ = has_annotations_before; |
| has_annotations_after_ = has_annotations_after; |
| |
| max_height = max_height.ClampNegativeToZero(); |
| |
| SetLineTopBottomPositions(line_top, line_bottom, height_of_block, |
| height_of_block + max_height, selection_bottom); |
| |
| LayoutUnit annotations_adjustment = BeforeAnnotationsAdjustment(); |
| if (annotations_adjustment) { |
| // FIXME: Need to handle pagination here. We might have to move to the next |
| // page/column as a result of the ruby expansion. |
| MoveInBlockDirection(annotations_adjustment); |
| height_of_block += annotations_adjustment; |
| } |
| |
| return height_of_block + max_height; |
| } |
| |
| LayoutUnit RootInlineBox::BeforeAnnotationsAdjustment() const { |
| LayoutUnit result; |
| |
| if (!GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode()) { |
| // Annotations under the previous line may push us down. |
| if (PrevRootBox() && PrevRootBox()->HasAnnotationsAfter()) |
| result = PrevRootBox()->ComputeUnderAnnotationAdjustment(LineTop()); |
| |
| if (!HasAnnotationsBefore()) |
| return result; |
| |
| // Annotations over this line may push us further down. |
| LayoutUnit highest_allowed_position = |
| PrevRootBox() |
| ? std::min(PrevRootBox()->LineBottom(), LineTop()) + result |
| : static_cast<LayoutUnit>(Block().BorderBefore()); |
| result = ComputeOverAnnotationAdjustment(highest_allowed_position); |
| } else { |
| // Annotations under this line may push us up. |
| if (HasAnnotationsBefore()) |
| result = ComputeUnderAnnotationAdjustment( |
| PrevRootBox() ? PrevRootBox()->LineBottom() |
| : static_cast<LayoutUnit>(Block().BorderBefore())); |
| |
| if (!PrevRootBox() || !PrevRootBox()->HasAnnotationsAfter()) |
| return result; |
| |
| // We have to compute the expansion for annotations over the previous line |
| // to see how much we should move. |
| LayoutUnit lowest_allowed_position = |
| std::max(PrevRootBox()->LineBottom(), LineTop()) - result; |
| result = |
| PrevRootBox()->ComputeOverAnnotationAdjustment(lowest_allowed_position); |
| } |
| |
| return result; |
| } |
| |
| bool RootInlineBox::IsSelected() const { |
| // Walk over all of the selected boxes. |
| for (InlineBox* box = FirstLeafChild(); box; box = box->NextLeafChild()) { |
| if (box->IsSelected()) |
| return true; |
| } |
| return false; |
| } |
| |
| InlineBox* RootInlineBox::FirstSelectedBox() const { |
| for (InlineBox* box = FirstLeafChild(); box; box = box->NextLeafChild()) { |
| if (box->IsSelected()) |
| return box; |
| } |
| |
| return nullptr; |
| } |
| |
| InlineBox* RootInlineBox::LastSelectedBox() const { |
| for (InlineBox* box = LastLeafChild(); box; box = box->PrevLeafChild()) { |
| if (box->IsSelected()) |
| return box; |
| } |
| |
| return nullptr; |
| } |
| |
| LayoutUnit RootInlineBox::SelectionTop() const { |
| LayoutUnit selection_top = line_top_; |
| if (has_annotations_before_) |
| selection_top -= !GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode() |
| ? ComputeOverAnnotationAdjustment(line_top_) |
| : ComputeUnderAnnotationAdjustment(line_top_); |
| |
| if (GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode() || |
| !PrevRootBox()) |
| return selection_top; |
| |
| return std::min(selection_top, PrevRootBox()->SelectionBottom()); |
| } |
| |
| LayoutUnit RootInlineBox::SelectionBottom() const { |
| LayoutUnit selection_bottom = |
| GetLineLayoutItem().GetDocument().InNoQuirksMode() ? selection_bottom_ |
| : line_bottom_; |
| |
| if (has_annotations_after_) |
| selection_bottom += |
| !GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode() |
| ? ComputeUnderAnnotationAdjustment(line_bottom_) |
| : ComputeOverAnnotationAdjustment(line_bottom_); |
| |
| if (!GetLineLayoutItem().StyleRef().IsFlippedLinesWritingMode() || |
| !NextRootBox()) |
| return selection_bottom; |
| |
| return std::max(selection_bottom, NextRootBox()->SelectionTop()); |
| } |
| |
| LayoutUnit RootInlineBox::BlockDirectionPointInLine() const { |
| return !Block().StyleRef().IsFlippedBlocksWritingMode() |
| ? std::max(LineTop(), SelectionTop()) |
| : std::min(LineBottom(), SelectionBottom()); |
| } |
| |
| LineLayoutBlockFlow RootInlineBox::Block() const { |
| return LineLayoutBlockFlow(GetLineLayoutItem()); |
| } |
| |
| static bool IsEditableLeaf(InlineBox* leaf) { |
| return leaf && leaf->GetLineLayoutItem().GetNode() && |
| HasEditableStyle(*leaf->GetLineLayoutItem().GetNode()); |
| } |
| |
| InlineBox* RootInlineBox::ClosestLeafChildForPoint( |
| const LayoutPoint& point_in_contents, |
| bool only_editable_leaves) const { |
| return ClosestLeafChildForLogicalLeftPosition( |
| Block().IsHorizontalWritingMode() ? point_in_contents.X() |
| : point_in_contents.Y(), |
| only_editable_leaves); |
| } |
| |
| InlineBox* RootInlineBox::ClosestLeafChildForLogicalLeftPosition( |
| LayoutUnit left_position, |
| bool only_editable_leaves) const { |
| InlineBox* first_leaf = FirstLeafChild(); |
| InlineBox* last_leaf = LastLeafChild(); |
| |
| if (first_leaf != last_leaf) { |
| if (first_leaf->IsLineBreak()) |
| first_leaf = first_leaf->NextLeafChildIgnoringLineBreak(); |
| else if (last_leaf->IsLineBreak()) |
| last_leaf = last_leaf->PrevLeafChildIgnoringLineBreak(); |
| } |
| |
| if (first_leaf == last_leaf && |
| (!only_editable_leaves || IsEditableLeaf(first_leaf))) |
| return first_leaf; |
| |
| // Avoid returning a list marker when possible. |
| if (left_position <= first_leaf->LogicalLeft() && |
| !first_leaf->GetLineLayoutItem().IsListMarker() && |
| (!only_editable_leaves || IsEditableLeaf(first_leaf))) { |
| // The leftPosition coordinate is less or equal to left edge of the |
| // firstLeaf. Return it. |
| return first_leaf; |
| } |
| |
| if (left_position >= last_leaf->LogicalRight() && |
| !last_leaf->GetLineLayoutItem().IsListMarker() && |
| (!only_editable_leaves || IsEditableLeaf(last_leaf))) { |
| // The leftPosition coordinate is greater or equal to right edge of the |
| // lastLeaf. Return it. |
| return last_leaf; |
| } |
| |
| InlineBox* closest_leaf = nullptr; |
| for (InlineBox* leaf = first_leaf; leaf; |
| leaf = leaf->NextLeafChildIgnoringLineBreak()) { |
| if (!leaf->GetLineLayoutItem().IsListMarker() && |
| (!only_editable_leaves || IsEditableLeaf(leaf))) { |
| closest_leaf = leaf; |
| if (left_position < leaf->LogicalRight()) { |
| // The x coordinate is less than the right edge of the box. |
| // Return it. |
| return leaf; |
| } |
| } |
| } |
| |
| return closest_leaf ? closest_leaf : last_leaf; |
| } |
| |
| BidiStatus RootInlineBox::LineBreakBidiStatus() const { |
| return BidiStatus( |
| static_cast<WTF::Unicode::CharDirection>(line_break_bidi_status_eor_), |
| static_cast<WTF::Unicode::CharDirection>( |
| line_break_bidi_status_last_strong_), |
| static_cast<WTF::Unicode::CharDirection>(line_break_bidi_status_last_), |
| line_break_context_); |
| } |
| |
| void RootInlineBox::SetLineBreakInfo(LineLayoutItem obj, |
| unsigned break_pos, |
| const BidiStatus& status) { |
| // When setting lineBreakObj, the LayoutObject must not be a LayoutInline |
| // with no line boxes, otherwise all sorts of invariants are broken later. |
| // This has security implications because if the LayoutObject does not point |
| // to at least one line box, then that LayoutInline can be deleted later |
| // without resetting the lineBreakObj, leading to use-after-free. |
| SECURITY_DCHECK(!obj || obj.IsText() || |
| !(obj.IsLayoutInline() && obj.IsBox() && |
| !LineLayoutBox(obj).InlineBoxWrapper())); |
| |
| line_break_obj_ = obj; |
| line_break_pos_ = break_pos; |
| line_break_bidi_status_eor_ = status.eor; |
| line_break_bidi_status_last_strong_ = status.last_strong; |
| line_break_bidi_status_last_ = status.last; |
| line_break_context_ = status.context; |
| } |
| |
| EllipsisBox* RootInlineBox::GetEllipsisBox() const { |
| if (!HasEllipsisBox()) |
| return nullptr; |
| return g_ellipsis_box_map->at(this); |
| } |
| |
| void RootInlineBox::RemoveLineBoxFromLayoutObject() { |
| Block().LineBoxes()->RemoveLineBox(this); |
| } |
| |
| void RootInlineBox::ExtractLineBoxFromLayoutObject() { |
| Block().LineBoxes()->ExtractLineBox(this); |
| } |
| |
| void RootInlineBox::AttachLineBoxToLayoutObject() { |
| Block().LineBoxes()->AttachLineBox(this); |
| } |
| |
| LayoutRect RootInlineBox::PaddedLayoutOverflowRect( |
| LayoutUnit end_padding) const { |
| LayoutRect line_layout_overflow = LayoutOverflowRect(LineTop(), LineBottom()); |
| if (!end_padding) |
| return line_layout_overflow; |
| |
| if (IsHorizontal()) { |
| if (IsLeftToRightDirection()) |
| line_layout_overflow.ShiftMaxXEdgeTo(std::max<LayoutUnit>( |
| line_layout_overflow.MaxX(), LogicalRight() + end_padding)); |
| else |
| line_layout_overflow.ShiftXEdgeTo(std::min<LayoutUnit>( |
| line_layout_overflow.X(), LogicalLeft() - end_padding)); |
| } else { |
| if (IsLeftToRightDirection()) |
| line_layout_overflow.ShiftMaxYEdgeTo(std::max<LayoutUnit>( |
| line_layout_overflow.MaxY(), LogicalRight() + end_padding)); |
| else |
| line_layout_overflow.ShiftYEdgeTo(std::min<LayoutUnit>( |
| line_layout_overflow.Y(), LogicalLeft() - end_padding)); |
| } |
| |
| return line_layout_overflow; |
| } |
| |
| static void SetAscentAndDescent(LayoutUnit& ascent, |
| LayoutUnit& descent, |
| LayoutUnit new_ascent, |
| LayoutUnit new_descent, |
| bool& ascent_descent_set) { |
| if (!ascent_descent_set) { |
| ascent_descent_set = true; |
| ascent = new_ascent; |
| descent = new_descent; |
| } else { |
| ascent = std::max(ascent, new_ascent); |
| descent = std::max(descent, new_descent); |
| } |
| } |
| |
| void RootInlineBox::AscentAndDescentForBox( |
| InlineBox* box, |
| GlyphOverflowAndFallbackFontsMap& text_box_data_map, |
| LayoutUnit& ascent, |
| LayoutUnit& descent, |
| bool& affects_ascent, |
| bool& affects_descent) const { |
| bool ascent_descent_set = false; |
| |
| if (box->GetLineLayoutItem().IsAtomicInlineLevel()) { |
| ascent = box->BaselinePosition(BaselineType()); |
| descent = box->LineHeight() - ascent; |
| |
| // Replaced elements always affect both the ascent and descent. |
| affects_ascent = true; |
| affects_descent = true; |
| return; |
| } |
| |
| Vector<const SimpleFontData*>* used_fonts = nullptr; |
| if (box->IsText()) { |
| GlyphOverflowAndFallbackFontsMap::iterator it = |
| text_box_data_map.find(ToInlineTextBox(box)); |
| used_fonts = it == text_box_data_map.end() ? nullptr : &it->value.first; |
| } |
| |
| bool include_leading = IncludeLeadingForBox(box); |
| bool set_used_font_with_leading = false; |
| |
| if (used_fonts && !used_fonts->IsEmpty() && |
| (box->GetLineLayoutItem() |
| .Style(IsFirstLineStyle()) |
| ->LineHeight() |
| .IsNegative() && |
| include_leading)) { |
| const SimpleFontData* primary_font = box->GetLineLayoutItem() |
| .Style(IsFirstLineStyle()) |
| ->GetFont() |
| .PrimaryFont(); |
| if (primary_font) |
| used_fonts->push_back(primary_font); |
| for (size_t i = 0; i < used_fonts->size(); ++i) { |
| const FontMetrics& font_metrics = used_fonts->at(i)->GetFontMetrics(); |
| LayoutUnit used_font_ascent(font_metrics.Ascent(BaselineType())); |
| LayoutUnit used_font_descent(font_metrics.Descent(BaselineType())); |
| LayoutUnit half_leading( |
| (font_metrics.LineSpacing() - font_metrics.Height()) / 2); |
| LayoutUnit used_font_ascent_and_leading = used_font_ascent + half_leading; |
| LayoutUnit used_font_descent_and_leading = |
| font_metrics.LineSpacing() - used_font_ascent_and_leading; |
| if (include_leading) { |
| SetAscentAndDescent(ascent, descent, used_font_ascent_and_leading, |
| used_font_descent_and_leading, ascent_descent_set); |
| set_used_font_with_leading = true; |
| } |
| if (!affects_ascent) |
| affects_ascent = used_font_ascent - box->LogicalTop() > 0; |
| if (!affects_descent) |
| affects_descent = used_font_descent + box->LogicalTop() > 0; |
| } |
| } |
| |
| // If leading is included for the box, then we compute that box. |
| if (include_leading && !set_used_font_with_leading) { |
| LayoutUnit ascent_with_leading = box->BaselinePosition(BaselineType()); |
| LayoutUnit descent_with_leading = box->LineHeight() - ascent_with_leading; |
| SetAscentAndDescent(ascent, descent, ascent_with_leading, |
| descent_with_leading, ascent_descent_set); |
| |
| // Examine the font box for inline flows and text boxes to see if any part |
| // of it is above the baseline. If the top of our font box relative to the |
| // root box baseline is above the root box baseline, then we are |
| // contributing to the maxAscent value. Descent is similar. If any part of |
| // our font box is below the root box's baseline, then we contribute to the |
| // maxDescent value. |
| affects_ascent = ascent_with_leading - box->LogicalTop() > 0; |
| affects_descent = descent_with_leading + box->LogicalTop() > 0; |
| } |
| } |
| |
| LayoutUnit RootInlineBox::VerticalPositionForBox( |
| InlineBox* box, |
| VerticalPositionCache& vertical_position_cache) { |
| if (box->GetLineLayoutItem().IsText()) |
| return box->Parent()->LogicalTop(); |
| |
| LineLayoutBoxModel box_model = box->BoxModelObject(); |
| DCHECK(box_model.IsInline()); |
| if (!box_model.IsInline()) |
| return LayoutUnit(); |
| |
| // This method determines the vertical position for inline elements. |
| bool first_line = IsFirstLineStyle(); |
| if (first_line && |
| !box_model.GetDocument().GetStyleEngine().UsesFirstLineRules()) |
| first_line = false; |
| |
| // Check the cache. |
| bool is_layout_inline = box_model.IsLayoutInline(); |
| if (is_layout_inline && !first_line) { |
| LayoutUnit vertical_position = |
| LayoutUnit(vertical_position_cache.Get(box_model, BaselineType())); |
| if (vertical_position != kPositionUndefined) |
| return vertical_position; |
| } |
| |
| LayoutUnit vertical_position; |
| EVerticalAlign vertical_align = box_model.StyleRef().VerticalAlign(); |
| if (vertical_align == EVerticalAlign::kTop || |
| vertical_align == EVerticalAlign::kBottom) |
| return LayoutUnit(); |
| |
| LineLayoutItem parent = box_model.Parent(); |
| if (parent.IsLayoutInline() && |
| parent.StyleRef().VerticalAlign() != EVerticalAlign::kTop && |
| parent.StyleRef().VerticalAlign() != EVerticalAlign::kBottom) |
| vertical_position = box->Parent()->LogicalTop(); |
| |
| if (vertical_align != EVerticalAlign::kBaseline) { |
| const Font& font = parent.Style(first_line)->GetFont(); |
| const SimpleFontData* font_data = font.PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return LayoutUnit(); |
| |
| const FontMetrics& font_metrics = font_data->GetFontMetrics(); |
| int font_size = font.GetFontDescription().ComputedPixelSize(); |
| |
| LineDirectionMode line_direction = |
| parent.IsHorizontalWritingMode() ? kHorizontalLine : kVerticalLine; |
| |
| if (vertical_align == EVerticalAlign::kSub) { |
| vertical_position += font_size / 5 + 1; |
| } else if (vertical_align == EVerticalAlign::kSuper) { |
| vertical_position -= font_size / 3 + 1; |
| } else if (vertical_align == EVerticalAlign::kTextTop) { |
| vertical_position += box_model.BaselinePosition( |
| BaselineType(), first_line, line_direction) - |
| font_metrics.Ascent(BaselineType()); |
| } else if (vertical_align == EVerticalAlign::kMiddle) { |
| vertical_position = vertical_position - |
| LayoutUnit(font_metrics.XHeight() / 2) - |
| box_model.LineHeight(first_line, line_direction) / 2 + |
| box_model.BaselinePosition(BaselineType(), first_line, |
| line_direction); |
| } else if (vertical_align == EVerticalAlign::kTextBottom) { |
| vertical_position += font_metrics.Descent(BaselineType()); |
| // lineHeight - baselinePosition is always 0 for replaced elements (except |
| // inline blocks), so don't bother wasting time in that case. |
| if (!box_model.IsAtomicInlineLevel() || |
| box_model.IsInlineBlockOrInlineTable()) |
| vertical_position -= (box_model.LineHeight(first_line, line_direction) - |
| box_model.BaselinePosition( |
| BaselineType(), first_line, line_direction)); |
| } else if (vertical_align == EVerticalAlign::kBaselineMiddle) { |
| vertical_position += |
| -box_model.LineHeight(first_line, line_direction) / 2 + |
| box_model.BaselinePosition(BaselineType(), first_line, |
| line_direction); |
| } else if (vertical_align == EVerticalAlign::kLength) { |
| LayoutUnit line_height; |
| // Per http://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align: |
| // 'Percentages: refer to the 'line-height' of the element itself'. |
| if (box_model.StyleRef().GetVerticalAlignLength().IsPercentOrCalc()) |
| line_height = LayoutUnit(box_model.StyleRef().ComputedLineHeight()); |
| else |
| line_height = box_model.LineHeight(first_line, line_direction); |
| vertical_position -= ValueForLength( |
| box_model.StyleRef().GetVerticalAlignLength(), line_height); |
| } |
| } |
| |
| // Store the cached value. |
| if (is_layout_inline && !first_line) |
| vertical_position_cache.Set(box_model, BaselineType(), |
| vertical_position.ToInt()); |
| |
| return vertical_position; |
| } |
| |
| bool RootInlineBox::IncludeLeadingForBox(InlineBox* box) const { |
| return !(box->GetLineLayoutItem().IsAtomicInlineLevel() || |
| (box->GetLineLayoutItem().IsText() && !box->IsText())); |
| } |
| |
| const InlineBox* RootInlineBox::GetLogicalStartNonPseudoBox() const { |
| Vector<InlineBox*> leaf_boxes_in_logical_order; |
| CollectLeafBoxesInLogicalOrder(leaf_boxes_in_logical_order); |
| for (size_t i = 0; i < leaf_boxes_in_logical_order.size(); ++i) { |
| if (leaf_boxes_in_logical_order[i]->GetLineLayoutItem().NonPseudoNode()) |
| return leaf_boxes_in_logical_order[i]; |
| } |
| return nullptr; |
| } |
| |
| const InlineBox* RootInlineBox::GetLogicalEndNonPseudoBox() const { |
| Vector<InlineBox*> leaf_boxes_in_logical_order; |
| CollectLeafBoxesInLogicalOrder(leaf_boxes_in_logical_order); |
| for (size_t i = leaf_boxes_in_logical_order.size(); i > 0; --i) { |
| if (leaf_boxes_in_logical_order[i - 1] |
| ->GetLineLayoutItem() |
| .NonPseudoNode()) { |
| return leaf_boxes_in_logical_order[i - 1]; |
| } |
| } |
| return nullptr; |
| } |
| |
| const char* RootInlineBox::BoxName() const { |
| return "RootInlineBox"; |
| } |
| |
| } // namespace blink |