| /* |
| * (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 2000 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 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 "core/layout/line/InlineTextBox.h" |
| |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/api/LineLayoutBR.h" |
| #include "core/layout/api/LineLayoutBox.h" |
| #include "core/layout/api/LineLayoutRubyRun.h" |
| #include "core/layout/api/LineLayoutRubyText.h" |
| #include "core/layout/line/AbstractInlineTextBox.h" |
| #include "core/layout/line/EllipsisBox.h" |
| #include "core/paint/InlineTextBoxPainter.h" |
| #include "platform/fonts/CharacterRange.h" |
| #include "platform/fonts/FontCache.h" |
| #include "platform/wtf/Vector.h" |
| #include "platform/wtf/text/StringBuilder.h" |
| |
| #include <algorithm> |
| |
| namespace blink { |
| |
| struct SameSizeAsInlineTextBox : public InlineBox { |
| unsigned variables[1]; |
| unsigned short variables2[2]; |
| void* pointers[2]; |
| }; |
| |
| static_assert(sizeof(InlineTextBox) == sizeof(SameSizeAsInlineTextBox), |
| "InlineTextBox should stay small"); |
| |
| typedef WTF::HashMap<const InlineTextBox*, LayoutRect> InlineTextBoxOverflowMap; |
| static InlineTextBoxOverflowMap* g_text_boxes_with_overflow; |
| |
| void InlineTextBox::Destroy() { |
| AbstractInlineTextBox::WillDestroy(this); |
| |
| if (!KnownToHaveNoOverflow() && g_text_boxes_with_overflow) |
| g_text_boxes_with_overflow->erase(this); |
| InlineBox::Destroy(); |
| } |
| |
| void InlineTextBox::OffsetRun(int delta) { |
| DCHECK(!IsDirty()); |
| start_ += delta; |
| } |
| |
| void InlineTextBox::MarkDirty() { |
| len_ = 0; |
| start_ = 0; |
| InlineBox::MarkDirty(); |
| } |
| |
| LayoutRect InlineTextBox::LogicalOverflowRect() const { |
| if (KnownToHaveNoOverflow() || !g_text_boxes_with_overflow) |
| return LogicalFrameRect(); |
| |
| const auto& it = g_text_boxes_with_overflow->find(this); |
| if (it != g_text_boxes_with_overflow->end()) |
| return it->value; |
| |
| return LogicalFrameRect(); |
| } |
| |
| void InlineTextBox::SetLogicalOverflowRect(const LayoutRect& rect) { |
| DCHECK(!KnownToHaveNoOverflow()); |
| DCHECK(rect != LogicalFrameRect()); |
| if (!g_text_boxes_with_overflow) |
| g_text_boxes_with_overflow = new InlineTextBoxOverflowMap; |
| g_text_boxes_with_overflow->Set(this, rect); |
| } |
| |
| void InlineTextBox::Move(const LayoutSize& delta) { |
| InlineBox::Move(delta); |
| |
| if (!KnownToHaveNoOverflow() && g_text_boxes_with_overflow) { |
| const auto& it = g_text_boxes_with_overflow->find(this); |
| if (it != g_text_boxes_with_overflow->end()) |
| it->value.Move(IsHorizontal() ? delta : delta.TransposedSize()); |
| } |
| } |
| |
| int InlineTextBox::BaselinePosition(FontBaseline baseline_type) const { |
| if (!IsText() || !Parent()) |
| return 0; |
| if (Parent()->GetLineLayoutItem() == GetLineLayoutItem().Parent()) |
| return Parent()->BaselinePosition(baseline_type); |
| return LineLayoutBoxModel(GetLineLayoutItem().Parent()) |
| .BaselinePosition(baseline_type, IsFirstLineStyle(), |
| IsHorizontal() ? kHorizontalLine : kVerticalLine, |
| kPositionOnContainingLine); |
| } |
| |
| LayoutUnit InlineTextBox::LineHeight() const { |
| if (!IsText() || !GetLineLayoutItem().Parent()) |
| return LayoutUnit(); |
| if (GetLineLayoutItem().IsBR()) |
| return LayoutUnit( |
| LineLayoutBR(GetLineLayoutItem()).LineHeight(IsFirstLineStyle())); |
| if (Parent()->GetLineLayoutItem() == GetLineLayoutItem().Parent()) |
| return Parent()->LineHeight(); |
| return LineLayoutBoxModel(GetLineLayoutItem().Parent()) |
| .LineHeight(IsFirstLineStyle(), |
| IsHorizontal() ? kHorizontalLine : kVerticalLine, |
| kPositionOnContainingLine); |
| } |
| |
| LayoutUnit InlineTextBox::OffsetTo(LineVerticalPositionType position_type, |
| FontBaseline baseline_type) const { |
| if (IsText() && |
| (position_type == LineVerticalPositionType::TopOfEmHeight || |
| position_type == LineVerticalPositionType::BottomOfEmHeight)) { |
| const Font& font = GetLineLayoutItem().Style(IsFirstLineStyle())->GetFont(); |
| if (const SimpleFontData* font_data = font.PrimaryFont()) { |
| const FontMetrics& metrics = font_data->GetFontMetrics(); |
| if (position_type == LineVerticalPositionType::TopOfEmHeight) { |
| // Use Ascent, not FixedAscent, to match to how InlineTextBoxPainter |
| // computes the baseline position. |
| return metrics.Ascent(baseline_type) - |
| font_data->EmHeightAscent(baseline_type); |
| } |
| if (position_type == LineVerticalPositionType::BottomOfEmHeight) { |
| return metrics.Ascent(baseline_type) + |
| font_data->EmHeightDescent(baseline_type); |
| } |
| } |
| } |
| switch (position_type) { |
| case LineVerticalPositionType::TextTop: |
| case LineVerticalPositionType::TopOfEmHeight: |
| return LayoutUnit(); |
| case LineVerticalPositionType::TextBottom: |
| case LineVerticalPositionType::BottomOfEmHeight: |
| return LogicalHeight(); |
| } |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| LayoutUnit InlineTextBox::VerticalPosition( |
| LineVerticalPositionType position_type, |
| FontBaseline baseline_type) const { |
| return LogicalTop() + OffsetTo(position_type, baseline_type); |
| } |
| |
| bool InlineTextBox::IsSelected(int start_pos, int end_pos) const { |
| int s_pos = std::max(start_pos - start_, 0); |
| // The position after a hard line break is considered to be past its end. |
| // See the corresponding code in InlineTextBox::getSelectionState. |
| int e_pos = std::min(end_pos - start_, int(len_) + (IsLineBreak() ? 0 : 1)); |
| return (s_pos < e_pos); |
| } |
| |
| SelectionState InlineTextBox::GetSelectionState() const { |
| SelectionState state = GetLineLayoutItem().GetSelectionState(); |
| if (state == SelectionStart || state == SelectionEnd || |
| state == SelectionBoth) { |
| int start_pos, end_pos; |
| std::tie(start_pos, end_pos) = GetLineLayoutItem().SelectionStartEnd(); |
| // The position after a hard line break is considered to be past its end. |
| // See the corresponding code in InlineTextBox::isSelected. |
| int last_selectable = Start() + Len() - (IsLineBreak() ? 1 : 0); |
| |
| // FIXME: Remove -webkit-line-break: LineBreakAfterWhiteSpace. |
| int end_of_line_adjustment_for_css_line_break = |
| GetLineLayoutItem().Style()->GetLineBreak() == |
| LineBreak::kAfterWhiteSpace |
| ? -1 |
| : 0; |
| bool start = (state != SelectionEnd && start_pos >= start_ && |
| start_pos <= start_ + len_ + |
| end_of_line_adjustment_for_css_line_break); |
| bool end = (state != SelectionStart && end_pos > start_ && |
| end_pos <= last_selectable); |
| if (start && end) |
| state = SelectionBoth; |
| else if (start) |
| state = SelectionStart; |
| else if (end) |
| state = SelectionEnd; |
| else if ((state == SelectionEnd || start_pos < start_) && |
| (state == SelectionStart || end_pos > last_selectable)) |
| state = SelectionInside; |
| else if (state == SelectionBoth) |
| state = SelectionNone; |
| } |
| |
| // If there are ellipsis following, make sure their selection is updated. |
| if (truncation_ != kCNoTruncation && Root().GetEllipsisBox()) { |
| EllipsisBox* ellipsis = Root().GetEllipsisBox(); |
| if (state != SelectionNone) { |
| int start, end; |
| SelectionStartEnd(start, end); |
| // The ellipsis should be considered to be selected if the end of the |
| // selection is past the beginning of the truncation and the beginning of |
| // the selection is before or at the beginning of the truncation. |
| ellipsis->SetSelectionState(end >= truncation_ && start <= truncation_ |
| ? SelectionInside |
| : SelectionNone); |
| } else { |
| ellipsis->SetSelectionState(SelectionNone); |
| } |
| } |
| |
| return state; |
| } |
| |
| bool InlineTextBox::HasWrappedSelectionNewline() const { |
| DCHECK(!GetLineLayoutItem().NeedsLayout()); |
| |
| SelectionState state = GetSelectionState(); |
| if (state != SelectionStart && state != SelectionInside) |
| return false; |
| |
| // Checking last leaf child can be slow, so we make sure to do this |
| // only after checking selection state. |
| if (Root().LastLeafChild() != this) |
| return false; |
| |
| // It's possible to have mixed LTR/RTL on a single line, and we only |
| // want to paint a newline when we're the last leaf child and we make |
| // sure there isn't a differently-directioned box following us. |
| bool is_ltr = IsLeftToRightDirection(); |
| if ((!is_ltr && Root().FirstSelectedBox() != this) || |
| (is_ltr && Root().LastSelectedBox() != this)) |
| return false; |
| |
| // If we're the last inline text box in containing block, our containing block |
| // is inline, and the selection continues into that block, then rely on the |
| // next inline text box (if any) to paint a wrapped new line as needed. |
| if (NextTextBox()) |
| return true; |
| auto root_block = Root().Block(); |
| if (root_block.IsInline() && root_block.GetSelectionState() != SelectionEnd && |
| root_block.GetSelectionState() != SelectionBoth && |
| root_block.InlineBoxWrapper() && |
| ((is_ltr && root_block.InlineBoxWrapper()->NextOnLine()) || |
| (!is_ltr && root_block.InlineBoxWrapper()->PrevOnLine()))) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| float InlineTextBox::NewlineSpaceWidth() const { |
| const ComputedStyle& style_to_use = |
| GetLineLayoutItem().StyleRef(IsFirstLineStyle()); |
| return style_to_use.GetFont().SpaceWidth(); |
| } |
| |
| LayoutRect InlineTextBox::LocalSelectionRect(int start_pos, int end_pos) const { |
| int s_pos = std::max(start_pos - start_, 0); |
| int e_pos = std::min(end_pos - start_, (int)len_); |
| |
| if (s_pos > e_pos) |
| return LayoutRect(); |
| |
| FontCachePurgePreventer font_cache_purge_preventer; |
| |
| LayoutUnit sel_top = Root().SelectionTop(); |
| LayoutUnit sel_height = Root().SelectionHeight(); |
| const ComputedStyle& style_to_use = |
| GetLineLayoutItem().StyleRef(IsFirstLineStyle()); |
| const Font& font = style_to_use.GetFont(); |
| |
| StringBuilder characters_with_hyphen; |
| bool respect_hyphen = e_pos == len_ && HasHyphen(); |
| TextRun text_run = ConstructTextRun( |
| style_to_use, respect_hyphen ? &characters_with_hyphen : 0); |
| |
| LayoutPoint starting_point = LayoutPoint(LogicalLeft(), sel_top); |
| LayoutRect r; |
| if (s_pos || e_pos != static_cast<int>(len_)) { |
| r = LayoutRect(EnclosingIntRect( |
| font.SelectionRectForText(text_run, FloatPoint(starting_point), |
| sel_height.ToInt(), s_pos, e_pos))); |
| } else { |
| // Avoid computing the font width when the entire line box is selected as an |
| // optimization. |
| r = LayoutRect(EnclosingIntRect( |
| LayoutRect(starting_point, LayoutSize(logical_width_, sel_height)))); |
| } |
| |
| LayoutUnit logical_width = r.Width(); |
| if (r.X() > LogicalRight()) |
| logical_width = LayoutUnit(); |
| else if (r.MaxX() > LogicalRight()) |
| logical_width = LogicalRight() - r.X(); |
| |
| LayoutPoint top_point; |
| LayoutUnit width; |
| LayoutUnit height; |
| if (IsHorizontal()) { |
| top_point = LayoutPoint(r.X(), sel_top); |
| width = logical_width; |
| height = sel_height; |
| if (HasWrappedSelectionNewline()) { |
| if (!IsLeftToRightDirection()) |
| top_point.SetX(LayoutUnit(top_point.X() - NewlineSpaceWidth())); |
| width += NewlineSpaceWidth(); |
| } |
| } else { |
| top_point = LayoutPoint(sel_top, r.X()); |
| width = sel_height; |
| height = logical_width; |
| // TODO(wkorman): RTL text embedded in top-to-bottom text can create |
| // bottom-to-top situations. Add tests and ensure we handle correctly. |
| if (HasWrappedSelectionNewline()) |
| height += NewlineSpaceWidth(); |
| } |
| |
| return LayoutRect(top_point, LayoutSize(width, height)); |
| } |
| |
| void InlineTextBox::DeleteLine() { |
| GetLineLayoutItem().RemoveTextBox(this); |
| Destroy(); |
| } |
| |
| void InlineTextBox::ExtractLine() { |
| if (Extracted()) |
| return; |
| |
| GetLineLayoutItem().ExtractTextBox(this); |
| } |
| |
| void InlineTextBox::AttachLine() { |
| if (!Extracted()) |
| return; |
| |
| GetLineLayoutItem().AttachTextBox(this); |
| } |
| |
| void InlineTextBox::SetTruncation(unsigned truncation) { |
| if (truncation == truncation_) |
| return; |
| |
| truncation_ = truncation; |
| } |
| |
| void InlineTextBox::ClearTruncation() { |
| SetTruncation(kCNoTruncation); |
| } |
| |
| LayoutUnit InlineTextBox::PlaceEllipsisBox(bool flow_is_ltr, |
| LayoutUnit visible_left_edge, |
| LayoutUnit visible_right_edge, |
| LayoutUnit ellipsis_width, |
| LayoutUnit& truncated_width, |
| bool& found_box, |
| LayoutUnit logical_left_offset) { |
| if (found_box) { |
| SetTruncation(kCFullTruncation); |
| return LayoutUnit(-1); |
| } |
| |
| // Criteria for full truncation: |
| // LTR: the left edge of the ellipsis is to the left of our text run. |
| // RTL: the right edge of the ellipsis is to the right of our text run. |
| LayoutUnit adjusted_logical_left = logical_left_offset + LogicalLeft(); |
| |
| // For LTR this is the left edge of the box, for RTL, the right edge in parent |
| // coordinates. |
| LayoutUnit ellipsis_x = flow_is_ltr ? visible_right_edge - ellipsis_width |
| : visible_left_edge + ellipsis_width; |
| |
| bool ltr_full_truncation = flow_is_ltr && ellipsis_x <= adjusted_logical_left; |
| bool rtl_full_truncation = |
| !flow_is_ltr && ellipsis_x > adjusted_logical_left + LogicalWidth(); |
| if (ltr_full_truncation || rtl_full_truncation) { |
| // Too far. Just set full truncation, but return -1 and let the ellipsis |
| // just be placed at the edge of the box. |
| SetTruncation(kCFullTruncation); |
| found_box = true; |
| return LayoutUnit(-1); |
| } |
| |
| bool ltr_ellipsis_within_box = |
| flow_is_ltr && ellipsis_x < adjusted_logical_left + LogicalWidth(); |
| bool rtl_ellipsis_within_box = |
| !flow_is_ltr && ellipsis_x > adjusted_logical_left; |
| if (ltr_ellipsis_within_box || rtl_ellipsis_within_box) { |
| found_box = true; |
| |
| // The inline box may have different directionality than it's parent. Since |
| // truncation behavior depends both on both the parent and the inline |
| // block's directionality, we must keep track of these separately. |
| bool ltr = IsLeftToRightDirection(); |
| if (ltr != flow_is_ltr) { |
| // Width in pixels of the visible portion of the box, excluding the |
| // ellipsis. |
| LayoutUnit visible_box_width = |
| visible_right_edge - visible_left_edge - ellipsis_width; |
| ellipsis_x = flow_is_ltr ? adjusted_logical_left + visible_box_width |
| : LogicalRight() - visible_box_width; |
| } |
| |
| // OffsetForPosition() expects the position relative to the root box. |
| if (ltr == flow_is_ltr && !flow_is_ltr && logical_left_offset < 0) |
| ellipsis_x -= logical_left_offset; |
| |
| // We measure the text using the second half of the previous character and |
| // the first half of the current one when the text is rtl. This gives a |
| // more accurate position in rtl text. |
| // TODO(crbug.com/722043: This doesn't always give the best results. |
| int offset = OffsetForPosition(ellipsis_x, !ltr); |
| // Full truncation is only necessary when we're flowing left-to-right. |
| if (flow_is_ltr && offset == 0 && ltr == flow_is_ltr) { |
| // No characters should be laid out. Set ourselves to full truncation and |
| // place the ellipsis at the min of our start and the ellipsis edge. |
| SetTruncation(kCFullTruncation); |
| truncated_width += ellipsis_width; |
| return std::min(ellipsis_x, LogicalLeft()); |
| } |
| |
| // When the text's direction doesn't match the flow's direction we can |
| // choose an offset that starts outside the visible box: compensate for that |
| // if necessary. |
| if (flow_is_ltr != ltr && LogicalLeft() < 0 && offset >= start_ && |
| PositionForOffset(offset) < LogicalLeft().Abs()) |
| offset++; |
| |
| // Set the truncation index on the text run. |
| SetTruncation(offset); |
| |
| // If we got here that means that we were only partially truncated and we |
| // need to return the pixel offset at which to place the ellipsis. Where the |
| // text and its flow have opposite directions then our offset into the text |
| // is at the start of the part that will be visible. |
| LayoutUnit width_of_visible_text(GetLineLayoutItem().Width( |
| ltr == flow_is_ltr ? start_ : start_ + offset, |
| ltr == flow_is_ltr ? offset : len_ - offset, TextPos(), |
| flow_is_ltr ? TextDirection::kLtr : TextDirection::kRtl, |
| IsFirstLineStyle())); |
| |
| // The ellipsis needs to be placed just after the last visible character. |
| // Where "after" is defined by the flow directionality, not the inline |
| // box directionality. |
| // e.g. In the case of an LTR inline box truncated in an RTL flow then we |
| // can have a situation such as |Hello| -> |...He| |
| truncated_width += width_of_visible_text + ellipsis_width; |
| if (flow_is_ltr) |
| return LogicalLeft() + width_of_visible_text; |
| LayoutUnit result = LogicalRight() - width_of_visible_text - ellipsis_width; |
| return result; |
| } |
| truncated_width += LogicalWidth(); |
| return LayoutUnit(-1); |
| } |
| |
| bool InlineTextBox::IsLineBreak() const { |
| return GetLineLayoutItem().IsBR() || |
| (GetLineLayoutItem().Style()->PreserveNewline() && Len() == 1 && |
| (*GetLineLayoutItem().GetText().Impl())[Start()] == '\n'); |
| } |
| |
| bool InlineTextBox::NodeAtPoint(HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| LayoutUnit /* lineTop */, |
| LayoutUnit /*lineBottom*/) { |
| if (IsLineBreak() || truncation_ == kCFullTruncation) |
| return false; |
| |
| LayoutPoint box_origin = PhysicalLocation(); |
| box_origin.MoveBy(accumulated_offset); |
| LayoutRect rect(box_origin, Size()); |
| if (VisibleToHitTestRequest(result.GetHitTestRequest()) && |
| location_in_container.Intersects(rect)) { |
| GetLineLayoutItem().UpdateHitTestResult( |
| result, FlipForWritingMode(location_in_container.Point() - |
| ToLayoutSize(accumulated_offset))); |
| if (result.AddNodeToListBasedTestResult(GetLineLayoutItem().GetNode(), |
| location_in_container, |
| rect) == kStopHitTesting) |
| return true; |
| } |
| return false; |
| } |
| |
| bool InlineTextBox::GetEmphasisMarkPosition( |
| const ComputedStyle& style, |
| TextEmphasisPosition& emphasis_position) const { |
| // This function returns true if there are text emphasis marks and they are |
| // suppressed by ruby text. |
| if (style.GetTextEmphasisMark() == kTextEmphasisMarkNone) |
| return false; |
| |
| emphasis_position = style.GetTextEmphasisPosition(); |
| // Ruby text is always over, so it cannot suppress emphasis marks under. |
| if (emphasis_position == kTextEmphasisPositionUnder) |
| return true; |
| |
| LineLayoutBox containing_block = GetLineLayoutItem().ContainingBlock(); |
| // This text is not inside a ruby base, so it does not have ruby text over it. |
| if (!containing_block.IsRubyBase()) |
| return true; |
| |
| // Cannot get the ruby text. |
| if (!containing_block.Parent().IsRubyRun()) |
| return true; |
| |
| LineLayoutRubyText ruby_text = |
| LineLayoutRubyRun(containing_block.Parent()).RubyText(); |
| |
| // The emphasis marks over are suppressed only if there is a ruby text box and |
| // it not empty. |
| return !ruby_text || !ruby_text.FirstLineBox(); |
| } |
| |
| void InlineTextBox::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset, |
| LayoutUnit /*lineTop*/, |
| LayoutUnit /*lineBottom*/) const { |
| InlineTextBoxPainter(*this).Paint(paint_info, paint_offset); |
| } |
| |
| void InlineTextBox::SelectionStartEnd(int& s_pos, int& e_pos) const { |
| int start_pos, end_pos; |
| if (GetLineLayoutItem().GetSelectionState() == SelectionInside) { |
| start_pos = 0; |
| end_pos = GetLineLayoutItem().TextLength(); |
| } else { |
| std::tie(start_pos, end_pos) = GetLineLayoutItem().SelectionStartEnd(); |
| if (GetLineLayoutItem().GetSelectionState() == SelectionStart) |
| end_pos = GetLineLayoutItem().TextLength(); |
| else if (GetLineLayoutItem().GetSelectionState() == SelectionEnd) |
| start_pos = 0; |
| } |
| |
| s_pos = std::max(start_pos - start_, 0); |
| e_pos = std::min(end_pos - start_, (int)len_); |
| } |
| |
| void InlineTextBox::PaintDocumentMarker(GraphicsContext& pt, |
| const LayoutPoint& box_origin, |
| const DocumentMarker& marker, |
| const ComputedStyle& style, |
| const Font& font, |
| bool grammar) const { |
| InlineTextBoxPainter(*this).PaintDocumentMarker(pt, box_origin, marker, style, |
| font, grammar); |
| } |
| |
| void InlineTextBox::PaintTextMatchMarkerForeground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& box_origin, |
| const DocumentMarker& marker, |
| const ComputedStyle& style, |
| const Font& font) const { |
| InlineTextBoxPainter(*this).PaintTextMatchMarkerForeground( |
| paint_info, box_origin, marker, style, font); |
| } |
| |
| void InlineTextBox::PaintTextMatchMarkerBackground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& box_origin, |
| const DocumentMarker& marker, |
| const ComputedStyle& style, |
| const Font& font) const { |
| InlineTextBoxPainter(*this).PaintTextMatchMarkerBackground( |
| paint_info, box_origin, marker, style, font); |
| } |
| |
| int InlineTextBox::CaretMinOffset() const { |
| return start_; |
| } |
| |
| int InlineTextBox::CaretMaxOffset() const { |
| return start_ + len_; |
| } |
| |
| LayoutUnit InlineTextBox::TextPos() const { |
| // When computing the width of a text run, LayoutBlock:: |
| // computeInlineDirectionPositionsForLine() doesn't include the actual offset |
| // from the containing block edge in its measurement. textPos() should be |
| // consistent so the text are laid out in the same width. |
| if (LogicalLeft() == 0) |
| return LayoutUnit(); |
| return LogicalLeft() - Root().LogicalLeft(); |
| } |
| |
| int InlineTextBox::OffsetForPosition(LayoutUnit line_offset, |
| bool include_partial_glyphs) const { |
| if (IsLineBreak()) |
| return 0; |
| |
| if (line_offset - LogicalLeft() > LogicalWidth()) |
| return IsLeftToRightDirection() ? Len() : 0; |
| if (line_offset - LogicalLeft() < 0) |
| return IsLeftToRightDirection() ? 0 : Len(); |
| |
| LineLayoutText text = GetLineLayoutItem(); |
| const ComputedStyle& style = text.StyleRef(IsFirstLineStyle()); |
| const Font& font = style.GetFont(); |
| return font.OffsetForPosition(ConstructTextRun(style), |
| (line_offset - LogicalLeft()).ToFloat(), |
| include_partial_glyphs); |
| } |
| |
| LayoutUnit InlineTextBox::PositionForOffset(int offset) const { |
| DCHECK_GE(offset, start_); |
| DCHECK_LE(offset, start_ + len_); |
| |
| if (IsLineBreak()) |
| return LogicalLeft(); |
| |
| LineLayoutText text = GetLineLayoutItem(); |
| const ComputedStyle& style_to_use = text.StyleRef(IsFirstLineStyle()); |
| const Font& font = style_to_use.GetFont(); |
| int from = !IsLeftToRightDirection() ? offset - start_ : 0; |
| int to = !IsLeftToRightDirection() ? len_ : offset - start_; |
| // FIXME: Do we need to add rightBearing here? |
| return LayoutUnit( |
| font.SelectionRectForText(ConstructTextRun(style_to_use), |
| IntPoint(LogicalLeft().ToInt(), 0), 0, from, to) |
| .MaxX()); |
| } |
| |
| bool InlineTextBox::ContainsCaretOffset(int offset) const { |
| // Offsets before the box are never "in". |
| if (offset < start_) |
| return false; |
| |
| int past_end = start_ + len_; |
| |
| // Offsets inside the box (not at either edge) are always "in". |
| if (offset < past_end) |
| return true; |
| |
| // Offsets outside the box are always "out". |
| if (offset > past_end) |
| return false; |
| |
| // Offsets at the end are "out" for line breaks (they are on the next line). |
| if (IsLineBreak()) |
| return false; |
| |
| // Offsets at the end are "in" for normal boxes (but the caller has to check |
| // affinity). |
| return true; |
| } |
| |
| void InlineTextBox::CharacterWidths(Vector<float>& widths) const { |
| if (!len_) |
| return; |
| |
| FontCachePurgePreventer font_cache_purge_preventer; |
| DCHECK(GetLineLayoutItem().GetText()); |
| |
| const ComputedStyle& style_to_use = |
| GetLineLayoutItem().StyleRef(IsFirstLineStyle()); |
| const Font& font = style_to_use.GetFont(); |
| |
| TextRun text_run = ConstructTextRun(style_to_use); |
| Vector<CharacterRange> ranges = font.IndividualCharacterRanges(text_run); |
| DCHECK_EQ(ranges.size(), len_); |
| |
| widths.resize(ranges.size()); |
| for (unsigned i = 0; i < ranges.size(); i++) |
| widths[i] = ranges[i].Width(); |
| } |
| |
| TextRun InlineTextBox::ConstructTextRun( |
| const ComputedStyle& style, |
| StringBuilder* characters_with_hyphen) const { |
| DCHECK(GetLineLayoutItem().GetText()); |
| |
| String string = GetLineLayoutItem().GetText(); |
| unsigned start_pos = Start(); |
| unsigned length = Len(); |
| // Ensure |this| is in sync with the corresponding LayoutText. Checking here |
| // has less binary size/perf impact than in StringView(). |
| CHECK_LE(start_pos, string.length()); |
| CHECK_LE(length, string.length() - start_pos); |
| return ConstructTextRun(style, StringView(string, start_pos, length), |
| GetLineLayoutItem().TextLength() - start_pos, |
| characters_with_hyphen); |
| } |
| |
| TextRun InlineTextBox::ConstructTextRun( |
| const ComputedStyle& style, |
| StringView string, |
| int maximum_length, |
| StringBuilder* characters_with_hyphen) const { |
| if (characters_with_hyphen) { |
| const AtomicString& hyphen_string = style.HyphenString(); |
| characters_with_hyphen->ReserveCapacity(string.length() + |
| hyphen_string.length()); |
| characters_with_hyphen->Append(string); |
| characters_with_hyphen->Append(hyphen_string); |
| string = characters_with_hyphen->ToString(); |
| maximum_length = string.length(); |
| } |
| |
| DCHECK_GE(maximum_length, static_cast<int>(string.length())); |
| |
| TextRun run(string, TextPos().ToFloat(), Expansion(), GetExpansionBehavior(), |
| Direction(), |
| DirOverride() || style.RtlOrdering() == EOrder::kVisual); |
| run.SetTabSize(!style.CollapseWhiteSpace(), style.GetTabSize()); |
| run.SetTextJustify(style.GetTextJustify()); |
| |
| // Propagate the maximum length of the characters buffer to the TextRun, even |
| // when we're only processing a substring. |
| run.SetCharactersLength(maximum_length); |
| DCHECK_GE(run.CharactersLength(), run.length()); |
| return run; |
| } |
| |
| TextRun InlineTextBox::ConstructTextRunForInspector( |
| const ComputedStyle& style) const { |
| return InlineTextBox::ConstructTextRun(style); |
| } |
| |
| const char* InlineTextBox::BoxName() const { |
| return "InlineTextBox"; |
| } |
| |
| String InlineTextBox::DebugName() const { |
| return String(BoxName()) + " '" + GetText() + "'"; |
| } |
| |
| String InlineTextBox::GetText() const { |
| return GetLineLayoutItem().GetText().Substring(Start(), Len()); |
| } |
| |
| #ifndef NDEBUG |
| |
| void InlineTextBox::ShowBox(int printed_characters) const { |
| String value = GetText(); |
| value.Replace('\\', "\\\\"); |
| value.Replace('\n', "\\n"); |
| printed_characters += fprintf(stderr, "%s %p", BoxName(), this); |
| for (; printed_characters < kShowTreeCharacterOffset; printed_characters++) |
| fputc(' ', stderr); |
| const LineLayoutText obj = GetLineLayoutItem(); |
| printed_characters = |
| fprintf(stderr, "\t%s %p", obj.GetName(), obj.DebugPointer()); |
| const int kLayoutObjectCharacterOffset = 75; |
| for (; printed_characters < kLayoutObjectCharacterOffset; |
| printed_characters++) |
| fputc(' ', stderr); |
| fprintf(stderr, "(%d,%d) \"%s\"\n", Start(), Start() + Len(), |
| value.Utf8().data()); |
| } |
| |
| #endif |
| |
| } // namespace blink |