| /* |
| * Copyright (C) 2007 Rob Buis <buis@kde.org> |
| * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) Research In Motion Limited 2010. 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/svg/line/svg_inline_text_box.h" |
| |
| #include "third_party/blink/renderer/core/layout/api/line_layout_svg_inline_text.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/pointer_events_hit_rules.h" |
| #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h" |
| #include "third_party/blink/renderer/core/paint/svg_inline_text_box_painter.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| |
| namespace blink { |
| |
| struct ExpectedSVGInlineTextBoxSize : public InlineTextBox { |
| LayoutUnit float1; |
| uint32_t bitfields : 1; |
| Vector<SVGTextFragment> vector; |
| }; |
| |
| static_assert(sizeof(SVGInlineTextBox) == sizeof(ExpectedSVGInlineTextBoxSize), |
| "SVGInlineTextBox has an unexpected size"); |
| |
| SVGInlineTextBox::SVGInlineTextBox(LineLayoutItem item, |
| int start, |
| unsigned short length) |
| : InlineTextBox(item, start, length), starts_new_text_chunk_(false) {} |
| |
| void SVGInlineTextBox::DirtyLineBoxes() { |
| InlineTextBox::DirtyLineBoxes(); |
| |
| // Clear the now stale text fragments. |
| ClearTextFragments(); |
| |
| // And clear any following text fragments as the text on which they depend may |
| // now no longer exist, or glyph positions may be wrong. |
| InlineTextBox* next_box = NextForSameLayoutObject(); |
| if (next_box) |
| next_box->DirtyLineBoxes(); |
| } |
| |
| int SVGInlineTextBox::OffsetForPosition(LayoutUnit, |
| IncludePartialGlyphsOption, |
| BreakGlyphsOption) const { |
| // SVG doesn't use the standard offset <-> position selection system, as it's |
| // not suitable for SVGs complex needs. Vertical text selection, inline boxes |
| // spanning multiple lines (contrary to HTML, etc.) |
| NOTREACHED(); |
| return 0; |
| } |
| |
| int SVGInlineTextBox::OffsetForPositionInFragment( |
| const SVGTextFragment& fragment, |
| float position) const { |
| LineLayoutSVGInlineText line_layout_item = |
| LineLayoutSVGInlineText(GetLineLayoutItem()); |
| |
| // Adjust position for the scaled font size. |
| DCHECK(line_layout_item.ScalingFactor()); |
| position *= line_layout_item.ScalingFactor(); |
| |
| // If this fragment is subjected to 'textLength' glyph adjustments, then |
| // apply the inverse to the position within the fragment. |
| if (fragment.AffectedByTextLength()) |
| position /= fragment.length_adjust_scale; |
| |
| TextRun text_run = ConstructTextRun(line_layout_item.StyleRef(), fragment); |
| return fragment.character_offset - Start() + |
| line_layout_item.ScaledFont().OffsetForPosition( |
| text_run, position, IncludePartialGlyphs, BreakGlyphs); |
| } |
| |
| LayoutUnit SVGInlineTextBox::PositionForOffset(int) const { |
| // SVG doesn't use the offset <-> position selection system. |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| FloatRect SVGInlineTextBox::SelectionRectForTextFragment( |
| const SVGTextFragment& fragment, |
| int start_position, |
| int end_position, |
| const ComputedStyle& style) const { |
| DCHECK_LT(start_position, end_position); |
| |
| LineLayoutSVGInlineText line_layout_item = |
| LineLayoutSVGInlineText(GetLineLayoutItem()); |
| |
| float scaling_factor = line_layout_item.ScalingFactor(); |
| DCHECK(scaling_factor); |
| |
| const Font& scaled_font = line_layout_item.ScaledFont(); |
| const SimpleFontData* font_data = scaled_font.PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return FloatRect(); |
| |
| const FontMetrics& scaled_font_metrics = font_data->GetFontMetrics(); |
| FloatPoint text_origin(fragment.x, fragment.y); |
| if (scaling_factor != 1) |
| text_origin.Scale(scaling_factor, scaling_factor); |
| |
| text_origin.Move(0, -scaled_font_metrics.FloatAscent()); |
| |
| FloatRect selection_rect = scaled_font.SelectionRectForText( |
| ConstructTextRun(style, fragment), text_origin, |
| fragment.height * scaling_factor, start_position, end_position); |
| if (scaling_factor == 1) |
| return selection_rect; |
| |
| selection_rect.Scale(1 / scaling_factor); |
| return selection_rect; |
| } |
| |
| LayoutRect SVGInlineTextBox::LocalSelectionRect( |
| int start_position, |
| int end_position, |
| bool consider_current_selection) const { |
| int box_start = Start(); |
| start_position = std::max(start_position - box_start, 0); |
| end_position = std::min(end_position - box_start, static_cast<int>(Len())); |
| if (start_position >= end_position) |
| return LayoutRect(); |
| |
| const ComputedStyle& style = GetLineLayoutItem().StyleRef(); |
| |
| FloatRect selection_rect; |
| int fragment_start_position = 0; |
| int fragment_end_position = 0; |
| |
| unsigned text_fragments_size = text_fragments_.size(); |
| for (unsigned i = 0; i < text_fragments_size; ++i) { |
| const SVGTextFragment& fragment = text_fragments_.at(i); |
| |
| fragment_start_position = start_position; |
| fragment_end_position = end_position; |
| if (!MapStartEndPositionsIntoFragmentCoordinates( |
| fragment, fragment_start_position, fragment_end_position)) |
| continue; |
| |
| FloatRect fragment_rect = SelectionRectForTextFragment( |
| fragment, fragment_start_position, fragment_end_position, style); |
| if (fragment.IsTransformed()) |
| fragment_rect = fragment.BuildFragmentTransform().MapRect(fragment_rect); |
| |
| selection_rect.Unite(fragment_rect); |
| } |
| |
| return LayoutRect(EnclosingIntRect(selection_rect)); |
| } |
| |
| void SVGInlineTextBox::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset, |
| LayoutUnit, |
| LayoutUnit) const { |
| SVGInlineTextBoxPainter(*this).Paint(paint_info, paint_offset); |
| } |
| |
| TextRun SVGInlineTextBox::ConstructTextRun( |
| const ComputedStyle& style, |
| const SVGTextFragment& fragment) const { |
| LineLayoutText text = GetLineLayoutItem(); |
| CHECK(!text.NeedsLayout()); |
| |
| TextRun run( |
| // characters, will be set below if non-zero. |
| static_cast<const LChar*>(nullptr), |
| 0, // length, will be set below if non-zero. |
| 0, // xPos, only relevant with allowTabs=true |
| 0, // padding, only relevant for justified text, not relevant for SVG |
| TextRun::kAllowTrailingExpansion, Direction(), |
| DirOverride() || |
| style.RtlOrdering() == EOrder::kVisual /* directionalOverride */); |
| |
| if (fragment.length) { |
| if (text.Is8Bit()) |
| run.SetText(text.Characters8() + fragment.character_offset, |
| fragment.length); |
| else |
| run.SetText(text.Characters16() + fragment.character_offset, |
| fragment.length); |
| } |
| |
| // We handle letter & word spacing ourselves. |
| run.DisableSpacing(); |
| |
| // Propagate the maximum length of the characters buffer to the TextRun, even |
| // when we're only processing a substring. |
| run.SetCharactersLength(text.TextLength() - fragment.character_offset); |
| DCHECK_GE(run.CharactersLength(), run.length()); |
| return run; |
| } |
| |
| bool SVGInlineTextBox::MapStartEndPositionsIntoFragmentCoordinates( |
| const SVGTextFragment& fragment, |
| int& start_position, |
| int& end_position) const { |
| int fragment_offset_in_box = |
| static_cast<int>(fragment.character_offset) - Start(); |
| |
| // Compute positions relative to the fragment. |
| start_position -= fragment_offset_in_box; |
| end_position -= fragment_offset_in_box; |
| |
| // Intersect with the fragment range. |
| start_position = std::max(start_position, 0); |
| end_position = std::min(end_position, static_cast<int>(fragment.length)); |
| |
| return start_position < end_position; |
| } |
| |
| void SVGInlineTextBox::PaintDocumentMarker(GraphicsContext&, |
| const LayoutPoint&, |
| const DocumentMarker&, |
| const ComputedStyle&, |
| const Font&, |
| bool) const { |
| // SVG does not have support for generic document markers (e.g., |
| // spellchecking, etc). |
| } |
| |
| void SVGInlineTextBox::PaintTextMatchMarkerForeground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& point, |
| const TextMatchMarker& marker, |
| const ComputedStyle& style, |
| const Font& font) const { |
| SVGInlineTextBoxPainter(*this).PaintTextMatchMarkerForeground( |
| paint_info, point, marker, style, font); |
| } |
| |
| void SVGInlineTextBox::PaintTextMatchMarkerBackground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& point, |
| const TextMatchMarker& marker, |
| const ComputedStyle& style, |
| const Font& font) const { |
| SVGInlineTextBoxPainter(*this).PaintTextMatchMarkerBackground( |
| paint_info, point, marker, style, font); |
| } |
| |
| FloatRect SVGInlineTextBox::CalculateBoundaries() const { |
| LineLayoutSVGInlineText line_layout_item = |
| LineLayoutSVGInlineText(GetLineLayoutItem()); |
| const SimpleFontData* font_data = line_layout_item.ScaledFont().PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return FloatRect(); |
| |
| float scaling_factor = line_layout_item.ScalingFactor(); |
| DCHECK(scaling_factor); |
| float baseline = font_data->GetFontMetrics().FloatAscent() / scaling_factor; |
| |
| FloatRect text_bounding_rect; |
| for (const SVGTextFragment& fragment : text_fragments_) |
| text_bounding_rect.Unite(fragment.OverflowBoundingBox(baseline)); |
| |
| return text_bounding_rect; |
| } |
| |
| bool SVGInlineTextBox::HitTestFragments( |
| const HitTestLocation& location_in_container) const { |
| auto line_layout_item = LineLayoutSVGInlineText(GetLineLayoutItem()); |
| const SimpleFontData* font_data = line_layout_item.ScaledFont().PrimaryFont(); |
| DCHECK(font_data); |
| if (!font_data) |
| return false; |
| |
| DCHECK(line_layout_item.ScalingFactor()); |
| float baseline = font_data->GetFontMetrics().FloatAscent() / |
| line_layout_item.ScalingFactor(); |
| for (const SVGTextFragment& fragment : text_fragments_) { |
| FloatQuad fragment_quad = fragment.BoundingQuad(baseline); |
| if (location_in_container.Intersects(fragment_quad)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool SVGInlineTextBox::NodeAtPoint(HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| LayoutUnit, |
| LayoutUnit) { |
| // FIXME: integrate with InlineTextBox::nodeAtPoint better. |
| DCHECK(!IsLineBreak()); |
| |
| auto line_layout_item = LineLayoutSVGInlineText(GetLineLayoutItem()); |
| const ComputedStyle& style = line_layout_item.StyleRef(); |
| PointerEventsHitRules hit_rules(PointerEventsHitRules::SVG_TEXT_HITTESTING, |
| result.GetHitTestRequest(), |
| style.PointerEvents()); |
| if (hit_rules.require_visible && style.Visibility() != EVisibility::kVisible) |
| return false; |
| if (hit_rules.can_hit_bounding_box || |
| (hit_rules.can_hit_stroke && |
| (style.SvgStyle().HasStroke() || !hit_rules.require_stroke)) || |
| (hit_rules.can_hit_fill && |
| (style.SvgStyle().HasFill() || !hit_rules.require_fill))) { |
| LayoutRect rect(Location(), Size()); |
| rect.MoveBy(accumulated_offset); |
| if (location_in_container.Intersects(rect)) { |
| if (HitTestFragments(location_in_container)) { |
| line_layout_item.UpdateHitTestResult( |
| result, |
| location_in_container.Point() - ToLayoutSize(accumulated_offset)); |
| if (result.AddNodeToListBasedTestResult(line_layout_item.GetNode(), |
| location_in_container, |
| rect) == kStopHitTesting) |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| } // namespace blink |