1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.h"
6
7 #include "third_party/blink/renderer/core/editing/editor.h"
8 #include "third_party/blink/renderer/core/editing/frame_selection.h"
9 #include "third_party/blink/renderer/core/editing/markers/composition_marker.h"
10 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
11 #include "third_party/blink/renderer/core/editing/markers/text_match_marker.h"
12 #include "third_party/blink/renderer/core/frame/local_frame.h"
13 #include "third_party/blink/renderer/core/layout/geometry/logical_rect.h"
14 #include "third_party/blink/renderer/core/layout/layout_ruby_run.h"
15 #include "third_party/blink/renderer/core/layout/list_marker.h"
16 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
17 #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
18 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
19 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
20 #include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h"
21 #include "third_party/blink/renderer/core/paint/document_marker_painter.h"
22 #include "third_party/blink/renderer/core/paint/highlight_painting_utils.h"
23 #include "third_party/blink/renderer/core/paint/inline_text_box_painter.h"
24 #include "third_party/blink/renderer/core/paint/list_marker_painter.h"
25 #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
26 #include "third_party/blink/renderer/core/paint/ng/ng_text_painter.h"
27 #include "third_party/blink/renderer/core/paint/paint_info.h"
28 #include "third_party/blink/renderer/core/paint/text_painter_base.h"
29 #include "third_party/blink/renderer/core/style/applied_text_decoration.h"
30 #include "third_party/blink/renderer/core/style/computed_style.h"
31 #include "third_party/blink/renderer/platform/fonts/character_range.h"
32 #include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
33 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
34 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
35
36 namespace blink {
37
38 namespace {
39
SelectionBackgroundColor(const Document & document,const ComputedStyle & style,Node * node,Color text_color)40 Color SelectionBackgroundColor(const Document& document,
41 const ComputedStyle& style,
42 Node* node,
43 Color text_color) {
44 const Color color = HighlightPaintingUtils::HighlightBackgroundColor(
45 document, style, node, kPseudoIdSelection);
46 if (!color.Alpha())
47 return Color();
48
49 // If the text color ends up being the same as the selection background,
50 // invert the selection background.
51 if (text_color == color)
52 return Color(0xff - color.Red(), 0xff - color.Green(), 0xff - color.Blue());
53 return color;
54 }
55
56 // TODO(yosin): Remove |AsDisplayItemClient| once the transition to
57 // |NGFragmentItem| is done. http://crbug.com/982194
AsDisplayItemClient(const NGInlineCursor & cursor,bool for_selection)58 inline const DisplayItemClient& AsDisplayItemClient(
59 const NGInlineCursor& cursor,
60 bool for_selection) {
61 if (UNLIKELY(for_selection)) {
62 if (const auto* selection_client =
63 cursor.Current().GetSelectionDisplayItemClient())
64 return *selection_client;
65 }
66 return *cursor.Current().GetDisplayItemClient();
67 }
68
AsDisplayItemClient(const NGTextPainterCursor & cursor,bool for_selection)69 inline const DisplayItemClient& AsDisplayItemClient(
70 const NGTextPainterCursor& cursor,
71 bool for_selection) {
72 return cursor.PaintFragment();
73 }
74
ComputeBoxRect(const NGInlineCursor & cursor,const PhysicalOffset & paint_offset,const PhysicalOffset & parent_offset)75 inline PhysicalRect ComputeBoxRect(const NGInlineCursor& cursor,
76 const PhysicalOffset& paint_offset,
77 const PhysicalOffset& parent_offset) {
78 PhysicalRect box_rect = cursor.CurrentItem()->RectInContainerBlock();
79 box_rect.offset.left += paint_offset.left;
80 // We round the y-axis to ensure consistent line heights.
81 box_rect.offset.top =
82 LayoutUnit((paint_offset.top + parent_offset.top).Round()) +
83 (box_rect.offset.top - parent_offset.top);
84 return box_rect;
85 }
86
ComputeBoxRect(const NGTextPainterCursor & cursor,const PhysicalOffset & paint_offset,const PhysicalOffset & parent_offset)87 inline PhysicalRect ComputeBoxRect(const NGTextPainterCursor& cursor,
88 const PhysicalOffset& paint_offset,
89 const PhysicalOffset& parent_offset) {
90 PhysicalRect box_rect = cursor.PaintFragment().Rect();
91 // We round the y-axis to ensure consistent line heights.
92 PhysicalOffset adjusted_paint_offset(paint_offset.left,
93 LayoutUnit(paint_offset.top.Round()));
94 box_rect.offset += adjusted_paint_offset;
95 return box_rect;
96 }
97
InlineCursorForBlockFlow(const NGInlineCursor & cursor,base::Optional<NGInlineCursor> * storage)98 inline const NGInlineCursor& InlineCursorForBlockFlow(
99 const NGInlineCursor& cursor,
100 base::Optional<NGInlineCursor>* storage) {
101 if (*storage)
102 return **storage;
103 *storage = cursor;
104 (*storage)->ExpandRootToContainingBlock();
105 return **storage;
106 }
107
InlineCursorForBlockFlow(const NGTextPainterCursor & cursor,base::Optional<NGInlineCursor> * storage)108 inline const NGInlineCursor& InlineCursorForBlockFlow(
109 const NGTextPainterCursor& cursor,
110 base::Optional<NGInlineCursor>* storage) {
111 if (*storage)
112 return **storage;
113 storage->emplace(cursor.RootPaintFragment());
114 (*storage)->MoveTo(cursor.PaintFragment());
115 return **storage;
116 }
117
118 // TODO(yosin): Remove |GetTextFragmentPaintInfo| once the transition to
119 // |NGFragmentItem| is done. http://crbug.com/982194
GetTextFragmentPaintInfo(const NGInlineCursor & cursor)120 inline NGTextFragmentPaintInfo GetTextFragmentPaintInfo(
121 const NGInlineCursor& cursor) {
122 return cursor.CurrentItem()->TextPaintInfo(cursor.Items());
123 }
124
GetTextFragmentPaintInfo(const NGTextPainterCursor & cursor)125 inline NGTextFragmentPaintInfo GetTextFragmentPaintInfo(
126 const NGTextPainterCursor& cursor) {
127 return cursor.CurrentItem()->PaintInfo();
128 }
129
130 // TODO(yosin): Remove |GetLineLeftAndRightForOffsets| once the transition to
131 // |NGFragmentItem| is done. http://crbug.com/982194
GetLineLeftAndRightForOffsets(const NGFragmentItem & text_item,StringView text,unsigned start_offset,unsigned end_offset)132 inline std::pair<LayoutUnit, LayoutUnit> GetLineLeftAndRightForOffsets(
133 const NGFragmentItem& text_item,
134 StringView text,
135 unsigned start_offset,
136 unsigned end_offset) {
137 return text_item.LineLeftAndRightForOffsets(text, start_offset, end_offset);
138 }
139
GetLineLeftAndRightForOffsets(const NGPhysicalTextFragment & text_fragment,StringView text,unsigned start_offset,unsigned end_offset)140 inline std::pair<LayoutUnit, LayoutUnit> GetLineLeftAndRightForOffsets(
141 const NGPhysicalTextFragment& text_fragment,
142 StringView text,
143 unsigned start_offset,
144 unsigned end_offset) {
145 return text_fragment.LineLeftAndRightForOffsets(start_offset, end_offset);
146 }
147
148 // TODO(yosin): Remove |ComputeLayoutSelectionStatus| once the transition to
149 // |NGFragmentItem| is done. http://crbug.com/982194
ComputeLayoutSelectionStatus(const NGInlineCursor & cursor)150 inline LayoutSelectionStatus ComputeLayoutSelectionStatus(
151 const NGInlineCursor& cursor) {
152 return cursor.Current()
153 .GetLayoutObject()
154 ->GetDocument()
155 .GetFrame()
156 ->Selection()
157 .ComputeLayoutSelectionStatus(cursor);
158 }
159
160 // TODO(yosin): Remove |ComputeLocalRect| once the transition to
161 // |NGFragmentItem| is done. http://crbug.com/982194
ComputeLocalRect(const NGFragmentItem & text_item,StringView text,unsigned start_offset,unsigned end_offset)162 inline PhysicalRect ComputeLocalRect(const NGFragmentItem& text_item,
163 StringView text,
164 unsigned start_offset,
165 unsigned end_offset) {
166 return text_item.LocalRect(text, start_offset, end_offset);
167 }
168
ComputeLocalRect(const NGPhysicalTextFragment & text_fragment,StringView text,unsigned start_offset,unsigned end_offset)169 inline PhysicalRect ComputeLocalRect(
170 const NGPhysicalTextFragment& text_fragment,
171 StringView text,
172 unsigned start_offset,
173 unsigned end_offset) {
174 return text_fragment.LocalRect(start_offset, end_offset);
175 }
176
ComputeMarkersToPaint(Node * node,bool is_ellipsis)177 DocumentMarkerVector ComputeMarkersToPaint(Node* node, bool is_ellipsis) {
178 // TODO(yoichio): Handle first-letter
179 auto* text_node = DynamicTo<Text>(node);
180 if (!text_node)
181 return DocumentMarkerVector();
182 // We don't paint any marker on ellipsis.
183 if (is_ellipsis)
184 return DocumentMarkerVector();
185
186 DocumentMarkerController& document_marker_controller =
187 node->GetDocument().Markers();
188 return document_marker_controller.ComputeMarkersToPaint(*text_node);
189 }
190
GetTextContentOffset(const Text & text,unsigned offset)191 unsigned GetTextContentOffset(const Text& text, unsigned offset) {
192 // TODO(yoichio): Sanitize DocumentMarker around text length.
193 const Position position(text, std::min(offset, text.length()));
194 const NGOffsetMapping* const offset_mapping =
195 NGOffsetMapping::GetFor(position);
196 DCHECK(offset_mapping);
197 const base::Optional<unsigned>& ng_offset =
198 offset_mapping->GetTextContentOffset(position);
199 DCHECK(ng_offset.has_value());
200 return ng_offset.value();
201 }
202
203 // ClampOffset modifies |offset| fixed in a range of |text_fragment| start/end
204 // offsets.
205 // |offset| points not each character but each span between character.
206 // With that concept, we can clear catch what is inside start / end.
207 // Suppose we have "foo_bar"('_' is a space).
208 // There are 8 offsets for that:
209 // f o o _ b a r
210 // 0 1 2 3 4 5 6 7
211 // If "bar" is a TextFragment. That start(), end() {4, 7} correspond this
212 // offset. If a marker has StartOffset / EndOffset as {2, 6},
213 // ClampOffset returns{ 4,6 }, which represents "ba" on "foo_bar".
214 template <typename TextItem>
ClampOffset(unsigned offset,const TextItem & text_fragment)215 unsigned ClampOffset(unsigned offset, const TextItem& text_fragment) {
216 return std::min(std::max(offset, text_fragment.StartOffset()),
217 text_fragment.EndOffset());
218 }
219
PaintRect(GraphicsContext & context,const PhysicalRect & rect,const Color color)220 void PaintRect(GraphicsContext& context,
221 const PhysicalRect& rect,
222 const Color color) {
223 if (!color.Alpha())
224 return;
225 if (rect.size.IsEmpty())
226 return;
227 const IntRect pixel_snapped_rect = PixelSnappedIntRect(rect);
228 if (!pixel_snapped_rect.IsEmpty())
229 context.FillRect(pixel_snapped_rect, color);
230 }
231
PaintRect(GraphicsContext & context,const PhysicalOffset & location,const PhysicalRect & rect,const Color color)232 void PaintRect(GraphicsContext& context,
233 const PhysicalOffset& location,
234 const PhysicalRect& rect,
235 const Color color) {
236 PaintRect(context, PhysicalRect(rect.offset + location, rect.size), color);
237 }
238
239 template <typename TextItem>
MarkerRectForForeground(const TextItem & text_fragment,StringView text,unsigned start_offset,unsigned end_offset)240 PhysicalRect MarkerRectForForeground(const TextItem& text_fragment,
241 StringView text,
242 unsigned start_offset,
243 unsigned end_offset) {
244 LayoutUnit start_position, end_position;
245 std::tie(start_position, end_position) = GetLineLeftAndRightForOffsets(
246 text_fragment, text, start_offset, end_offset);
247
248 const LayoutUnit height = text_fragment.Size()
249 .ConvertToLogical(static_cast<WritingMode>(
250 text_fragment.Style().GetWritingMode()))
251 .block_size;
252 return {start_position, LayoutUnit(), end_position - start_position, height};
253 }
254
255 // Copied from InlineTextBoxPainter
256 template <typename TextItem>
PaintDocumentMarkers(const PaintInfo & paint_info,const TextItem & text_fragment,StringView text,const DocumentMarkerVector & markers_to_paint,const PhysicalOffset & box_origin,const ComputedStyle & style,DocumentMarkerPaintPhase marker_paint_phase,NGTextPainter * text_painter)257 void PaintDocumentMarkers(const PaintInfo& paint_info,
258 const TextItem& text_fragment,
259 StringView text,
260 const DocumentMarkerVector& markers_to_paint,
261 const PhysicalOffset& box_origin,
262 const ComputedStyle& style,
263 DocumentMarkerPaintPhase marker_paint_phase,
264 NGTextPainter* text_painter) {
265 if (markers_to_paint.IsEmpty())
266 return;
267
268 DCHECK(text_fragment.GetNode());
269 const auto& text_node = To<Text>(*text_fragment.GetNode());
270 for (const DocumentMarker* marker : markers_to_paint) {
271 const unsigned marker_start_offset =
272 GetTextContentOffset(text_node, marker->StartOffset());
273 const unsigned marker_end_offset =
274 GetTextContentOffset(text_node, marker->EndOffset());
275 const unsigned paint_start_offset =
276 ClampOffset(marker_start_offset, text_fragment);
277 const unsigned paint_end_offset =
278 ClampOffset(marker_end_offset, text_fragment);
279 if (paint_start_offset == paint_end_offset)
280 continue;
281
282 switch (marker->GetType()) {
283 case DocumentMarker::kSpelling:
284 case DocumentMarker::kGrammar: {
285 if (paint_info.context.Printing())
286 break;
287 if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground)
288 continue;
289 DocumentMarkerPainter::PaintDocumentMarker(
290 paint_info, box_origin, style, marker->GetType(),
291 MarkerRectForForeground(text_fragment, text, paint_start_offset,
292 paint_end_offset));
293 } break;
294
295 case DocumentMarker::kTextFragment:
296 case DocumentMarker::kTextMatch: {
297 Node* node = text_fragment.GetNode();
298 const Document& document = node->GetDocument();
299 if (marker->GetType() == DocumentMarker::kTextMatch &&
300 !document.GetFrame()->GetEditor().MarkedTextMatchesAreHighlighted())
301 break;
302 const auto& text_marker = To<TextMarkerBase>(*marker);
303 if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) {
304 Color color;
305 if (marker->GetType() == DocumentMarker::kTextMatch) {
306 color = LayoutTheme::GetTheme().PlatformTextSearchHighlightColor(
307 text_marker.IsActiveMatch(), document.InForcedColorsMode(),
308 style.UsedColorScheme());
309 } else {
310 color = HighlightPaintingUtils::HighlightBackgroundColor(
311 document, style, node, kPseudoIdTargetText);
312 }
313 PaintRect(paint_info.context, PhysicalOffset(box_origin),
314 ComputeLocalRect(text_fragment, text, paint_start_offset,
315 paint_end_offset),
316 color);
317 break;
318 }
319
320 const TextPaintStyle text_style =
321 DocumentMarkerPainter::ComputeTextPaintStyleFrom(
322 document, node, style, text_marker, paint_info);
323 if (text_style.current_color == Color::kTransparent)
324 break;
325 text_painter->Paint(paint_start_offset, paint_end_offset,
326 paint_end_offset - paint_start_offset, text_style,
327 kInvalidDOMNodeId);
328 } break;
329
330 case DocumentMarker::kComposition:
331 case DocumentMarker::kActiveSuggestion:
332 case DocumentMarker::kSuggestion: {
333 const auto& styleable_marker = To<StyleableMarker>(*marker);
334 if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) {
335 PaintRect(paint_info.context, PhysicalOffset(box_origin),
336 ComputeLocalRect(text_fragment, text, paint_start_offset,
337 paint_end_offset),
338 styleable_marker.BackgroundColor());
339 break;
340 }
341 if (DocumentMarkerPainter::ShouldPaintMarkerUnderline(
342 styleable_marker)) {
343 const SimpleFontData* font_data = style.GetFont().PrimaryFont();
344 DocumentMarkerPainter::PaintStyleableMarkerUnderline(
345 paint_info.context, box_origin, styleable_marker, style,
346 FloatRect(MarkerRectForForeground(
347 text_fragment, text, paint_start_offset, paint_end_offset)),
348 LayoutUnit(font_data->GetFontMetrics().Height()),
349 text_fragment.GetNode()->GetDocument().InDarkMode());
350 }
351 } break;
352
353 default:
354 NOTREACHED();
355 break;
356 }
357 }
358 }
359
360 class SelectionPaintState {
361 STACK_ALLOCATED();
362
363 public:
SelectionPaintState(const NGInlineCursor & containing_block)364 explicit SelectionPaintState(const NGInlineCursor& containing_block)
365 : selection_status_(ComputeLayoutSelectionStatus(containing_block)),
366 containing_block_(containing_block) {}
367
Status() const368 const LayoutSelectionStatus& Status() const { return selection_status_; }
369
GetSelectionStyle() const370 const TextPaintStyle& GetSelectionStyle() const { return selection_style_; }
371
ShouldPaintSelectedTextOnly() const372 bool ShouldPaintSelectedTextOnly() const { return paint_selected_text_only_; }
373
ShouldPaintSelectedTextSeparately() const374 bool ShouldPaintSelectedTextSeparately() const {
375 return paint_selected_text_separately_;
376 }
377
IsSelectionRectComputed() const378 bool IsSelectionRectComputed() const { return selection_rect_.has_value(); }
379
ComputeSelectionStyle(const Document & document,const ComputedStyle & style,Node * node,const PaintInfo & paint_info,const TextPaintStyle & text_style)380 void ComputeSelectionStyle(const Document& document,
381 const ComputedStyle& style,
382 Node* node,
383 const PaintInfo& paint_info,
384 const TextPaintStyle& text_style) {
385 selection_style_ = TextPainterBase::SelectionPaintingStyle(
386 document, style, node, paint_info, text_style);
387 paint_selected_text_only_ =
388 (paint_info.phase == PaintPhase::kSelectionDragImage);
389 paint_selected_text_separately_ =
390 !paint_selected_text_only_ && text_style != selection_style_;
391 }
392
ComputeSelectionRect(const PhysicalOffset & box_offset)393 PhysicalRect ComputeSelectionRect(const PhysicalOffset& box_offset) {
394 DCHECK(!selection_rect_);
395 selection_rect_ =
396 ComputeLocalSelectionRectForText(containing_block_, selection_status_);
397 selection_rect_->offset += box_offset;
398 return *selection_rect_;
399 }
400
401 // Logic is copied from InlineTextBoxPainter::PaintSelection.
402 // |selection_start| and |selection_end| should be between
403 // [text_fragment.StartOffset(), text_fragment.EndOffset()].
PaintSelectionBackground(GraphicsContext & context,Node * node,const Document & document,const ComputedStyle & style)404 void PaintSelectionBackground(GraphicsContext& context,
405 Node* node,
406 const Document& document,
407 const ComputedStyle& style) {
408 const Color color = SelectionBackgroundColor(document, style, node,
409 selection_style_.fill_color);
410 PaintRect(context, *selection_rect_, color);
411 }
412
413 // Called before we paint vertical selected text under a rotation transform.
MapSelectionRectIntoRotatedSpace(const AffineTransform & rotation)414 void MapSelectionRectIntoRotatedSpace(const AffineTransform& rotation) {
415 DCHECK(selection_rect_);
416 *selection_rect_ = PhysicalRect::EnclosingRect(
417 rotation.Inverse().MapRect(FloatRect(*selection_rect_)));
418 }
419
420 // Paint the selected text only.
PaintSelectedText(NGTextPainter & text_painter,unsigned length,const TextPaintStyle & text_style,DOMNodeId node_id)421 void PaintSelectedText(NGTextPainter& text_painter,
422 unsigned length,
423 const TextPaintStyle& text_style,
424 DOMNodeId node_id) {
425 text_painter.PaintSelectedText(selection_status_.start,
426 selection_status_.end, length, text_style,
427 selection_style_, *selection_rect_, node_id);
428 }
429
430 // Paint the text except selected parts. Does nothing if all is selected.
PaintBeforeAndAfterSelectedText(NGTextPainter & text_painter,unsigned start_offset,unsigned end_offset,unsigned length,const TextPaintStyle & text_style,DOMNodeId node_id)431 void PaintBeforeAndAfterSelectedText(NGTextPainter& text_painter,
432 unsigned start_offset,
433 unsigned end_offset,
434 unsigned length,
435 const TextPaintStyle& text_style,
436 DOMNodeId node_id) {
437 if (start_offset < selection_status_.start) {
438 text_painter.Paint(start_offset, selection_status_.start, length,
439 text_style, node_id);
440 }
441 if (selection_status_.end < end_offset) {
442 text_painter.Paint(selection_status_.end, end_offset, length, text_style,
443 node_id);
444 }
445 }
446
447 private:
448 LayoutSelectionStatus selection_status_;
449 TextPaintStyle selection_style_;
450 base::Optional<PhysicalRect> selection_rect_;
451 const NGInlineCursor& containing_block_;
452 bool paint_selected_text_only_;
453 bool paint_selected_text_separately_;
454 };
455
456 // Check if text-emphasis and ruby annotation text are on different sides.
457 // See InlineTextBox::GetEmphasisMarkPosition().
458 //
459 // TODO(layout-dev): The current behavior is compatible with the legacy layout.
460 // However, the specification asks to draw emphasis marks over ruby annotation
461 // text.
462 // https://drafts.csswg.org/css-text-decor-4/#text-emphasis-position-property
ShouldPaintEmphasisMark(const ComputedStyle & style,const LayoutObject & layout_object)463 bool ShouldPaintEmphasisMark(const ComputedStyle& style,
464 const LayoutObject& layout_object) {
465 if (style.GetTextEmphasisMark() == TextEmphasisMark::kNone)
466 return false;
467 const LayoutObject* containing_block = layout_object.ContainingBlock();
468 if (!containing_block || !containing_block->IsRubyBase())
469 return true;
470 const LayoutObject* parent = containing_block->Parent();
471 if (!parent || !parent->IsRubyRun())
472 return true;
473 const LayoutRubyText* ruby_text = To<LayoutRubyRun>(parent)->RubyText();
474 if (!ruby_text)
475 return true;
476 if (!NGInlineCursor(*ruby_text))
477 return true;
478 const LineLogicalSide ruby_logical_side =
479 parent->StyleRef().GetRubyPosition() == RubyPosition::kBefore
480 ? LineLogicalSide::kOver
481 : LineLogicalSide::kUnder;
482 return ruby_logical_side != style.GetTextEmphasisLineLogicalSide();
483 }
484
485 } // namespace
486
CurrentText() const487 StringView NGTextPainterCursor::CurrentText() const {
488 return CurrentItem()->Text();
489 }
490
RootPaintFragment() const491 const NGPaintFragment& NGTextPainterCursor::RootPaintFragment() const {
492 if (!root_paint_fragment_)
493 root_paint_fragment_ = paint_fragment_.Root();
494 return *root_paint_fragment_;
495 }
496
497 template <typename Cursor>
PaintSymbol(const LayoutObject * layout_object,const ComputedStyle & style,const PhysicalSize box_size,const PaintInfo & paint_info,const PhysicalOffset & paint_offset)498 void NGTextFragmentPainter<Cursor>::PaintSymbol(
499 const LayoutObject* layout_object,
500 const ComputedStyle& style,
501 const PhysicalSize box_size,
502 const PaintInfo& paint_info,
503 const PhysicalOffset& paint_offset) {
504 PhysicalRect marker_rect(
505 ListMarker::RelativeSymbolMarkerRect(style, box_size.width));
506 marker_rect.Move(paint_offset);
507 IntRect rect = PixelSnappedIntRect(marker_rect);
508
509 ListMarkerPainter::PaintSymbol(paint_info, layout_object, style, rect);
510 }
511
512 // This is copied from InlineTextBoxPainter::PaintSelection() but lacks of
513 // ltr, expanding new line wrap or so which uses InlineTextBox functions.
514 template <typename Cursor>
Paint(const PaintInfo & paint_info,const PhysicalOffset & paint_offset)515 void NGTextFragmentPainter<Cursor>::Paint(const PaintInfo& paint_info,
516 const PhysicalOffset& paint_offset) {
517 const auto& text_item = *cursor_.CurrentItem();
518 // We can skip painting if the fragment (including selection) is invisible.
519 if (!text_item.TextLength())
520 return;
521
522 if (!text_item.TextShapeResult() &&
523 // A line break's selection tint is still visible.
524 !text_item.IsLineBreak())
525 return;
526
527 const ComputedStyle& style = text_item.Style();
528 if (style.Visibility() != EVisibility::kVisible)
529 return;
530
531 const NGTextFragmentPaintInfo& fragment_paint_info =
532 GetTextFragmentPaintInfo(cursor_);
533 const LayoutObject* layout_object = text_item.GetLayoutObject();
534 const Document& document = layout_object->GetDocument();
535 const bool is_printing = paint_info.IsPrinting();
536
537 // Determine whether or not we're selected.
538 base::Optional<SelectionPaintState> selection;
539 if (UNLIKELY(!is_printing && paint_info.phase != PaintPhase::kTextClip &&
540 layout_object->IsSelected())) {
541 const NGInlineCursor& root_inline_cursor =
542 InlineCursorForBlockFlow(cursor_, &inline_cursor_for_block_flow_);
543 selection.emplace(root_inline_cursor);
544 if (!selection->Status().HasValidRange())
545 selection.reset();
546 }
547 if (!selection) {
548 // When only painting the selection drag image, don't bother to paint if
549 // there is none.
550 if (paint_info.phase == PaintPhase::kSelectionDragImage)
551 return;
552
553 // Flow controls (line break, tab, <wbr>) need only selection painting.
554 if (text_item.IsFlowControl())
555 return;
556 }
557
558 PhysicalRect box_rect = ComputeBoxRect(cursor_, paint_offset, parent_offset_);
559 PhysicalRect ink_overflow = text_item.SelfInkOverflow();
560 ink_overflow.Move(box_rect.offset);
561 IntRect visual_rect = EnclosingIntRect(ink_overflow);
562
563 // The text clip phase already has a DrawingRecorder. Text clips are initiated
564 // only in BoxPainterBase::PaintFillLayer, which is already within a
565 // DrawingRecorder.
566 base::Optional<DrawingRecorder> recorder;
567 if (paint_info.phase != PaintPhase::kTextClip) {
568 const auto& display_item_client =
569 AsDisplayItemClient(cursor_, selection.has_value());
570 if (DrawingRecorder::UseCachedDrawingIfPossible(
571 paint_info.context, display_item_client, paint_info.phase))
572 return;
573 recorder.emplace(paint_info.context, display_item_client, paint_info.phase,
574 visual_rect);
575 }
576
577 if (UNLIKELY(text_item.IsSymbolMarker())) {
578 // The NGInlineItem of marker might be Split(). To avoid calling PaintSymbol
579 // multiple times, only call it the first time. For an outside marker, this
580 // is when StartOffset is 0. But for an inside marker, the first StartOffset
581 // can be greater due to leading bidi control characters like U+202A/U+202B,
582 // U+202D/U+202E, U+2066/U+2067 or U+2068.
583 DCHECK_LT(fragment_paint_info.from, fragment_paint_info.text.length());
584 for (unsigned i = 0; i < fragment_paint_info.from; ++i) {
585 if (!Character::IsBidiControl(fragment_paint_info.text.CodepointAt(i)))
586 return;
587 }
588 PaintSymbol(layout_object, style, box_rect.size, paint_info,
589 box_rect.offset);
590 return;
591 }
592
593 GraphicsContext& context = paint_info.context;
594
595 // Determine text colors.
596
597 Node* node = layout_object->GetNode();
598 TextPaintStyle text_style =
599 TextPainterBase::TextPaintingStyle(document, style, paint_info);
600 if (UNLIKELY(selection)) {
601 selection->ComputeSelectionStyle(document, style, node, paint_info,
602 text_style);
603 }
604
605 // Set our font.
606 const Font& font = style.GetFont();
607 const SimpleFontData* font_data = font.PrimaryFont();
608 DCHECK(font_data);
609
610 // 1. Paint backgrounds behind text if needed. Examples of such backgrounds
611 // include selection and composition highlights. They use physical coordinates
612 // so are painted before GraphicsContext rotation.
613 const DocumentMarkerVector& markers_to_paint =
614 ComputeMarkersToPaint(node, text_item.IsEllipsis());
615 if (paint_info.phase != PaintPhase::kSelectionDragImage &&
616 paint_info.phase != PaintPhase::kTextClip && !is_printing) {
617 PaintDocumentMarkers(paint_info, text_item, cursor_.CurrentText(),
618 markers_to_paint, box_rect.offset, style,
619 DocumentMarkerPaintPhase::kBackground, nullptr);
620 if (UNLIKELY(selection)) {
621 auto selection_rect = selection->ComputeSelectionRect(box_rect.offset);
622 selection->PaintSelectionBackground(context, node, document, style);
623 if (recorder)
624 recorder->UniteVisualRect(EnclosingIntRect(selection_rect));
625 }
626 }
627
628 base::Optional<GraphicsContextStateSaver> state_saver;
629 base::Optional<AffineTransform> rotation;
630 const WritingMode writing_mode = style.GetWritingMode();
631 const bool is_horizontal = IsHorizontalWritingMode(writing_mode);
632 if (!is_horizontal) {
633 state_saver.emplace(context);
634 // Because we rotate the GraphicsContext to match the logical direction,
635 // transpose the |box_rect| to match to it.
636 box_rect.size = PhysicalSize(box_rect.Height(), box_rect.Width());
637 rotation.emplace(TextPainterBase::Rotation(
638 box_rect, writing_mode != WritingMode::kSidewaysLr
639 ? TextPainterBase::kClockwise
640 : TextPainterBase::kCounterclockwise));
641 context.ConcatCTM(*rotation);
642 }
643
644 // 2. Now paint the foreground, including text and decorations.
645 int ascent = font_data ? font_data->GetFontMetrics().Ascent() : 0;
646 PhysicalOffset text_origin(box_rect.offset.left,
647 box_rect.offset.top + ascent);
648 NGTextPainter text_painter(context, font, fragment_paint_info, visual_rect,
649 text_origin, box_rect, is_horizontal);
650
651 if (ShouldPaintEmphasisMark(style, *layout_object)) {
652 text_painter.SetEmphasisMark(style.TextEmphasisMarkString(),
653 style.GetTextEmphasisPosition());
654 }
655
656 DOMNodeId node_id = kInvalidDOMNodeId;
657 if (node) {
658 if (auto* layout_text = DynamicTo<LayoutText>(node->GetLayoutObject()))
659 node_id = layout_text->EnsureNodeId();
660 }
661
662 const unsigned length = fragment_paint_info.to - fragment_paint_info.from;
663 if (!selection || !selection->ShouldPaintSelectedTextOnly()) {
664 // Paint text decorations except line-through.
665 base::Optional<TextDecorationInfo> decoration_info;
666 bool has_line_through_decoration = false;
667 if (style.TextDecorationsInEffect() != TextDecoration::kNone &&
668 // Ellipsis should not have text decorations. This is not defined, but 4
669 // impls do this.
670 !text_item.IsEllipsis()) {
671 PhysicalOffset local_origin = box_rect.offset;
672 LayoutUnit width = box_rect.Width();
673 base::Optional<AppliedTextDecoration> selection_text_decoration =
674 UNLIKELY(selection)
675 ? base::Optional<AppliedTextDecoration>(
676 selection->GetSelectionStyle().selection_text_decoration)
677 : base::nullopt;
678
679 decoration_info.emplace(box_rect.offset, local_origin, width,
680 style.GetFontBaseline(), style,
681 selection_text_decoration, nullptr);
682 NGTextDecorationOffset decoration_offset(decoration_info->Style(),
683 text_item.Style(), nullptr);
684 text_painter.PaintDecorationsExceptLineThrough(
685 decoration_offset, decoration_info.value(), paint_info,
686 style.AppliedTextDecorations(), text_style,
687 &has_line_through_decoration);
688 }
689
690 unsigned start_offset = fragment_paint_info.from;
691 unsigned end_offset = fragment_paint_info.to;
692
693 if (UNLIKELY(selection && selection->ShouldPaintSelectedTextSeparately())) {
694 selection->PaintBeforeAndAfterSelectedText(
695 text_painter, start_offset, end_offset, length, text_style, node_id);
696 } else {
697 text_painter.Paint(start_offset, end_offset, length, text_style, node_id);
698 }
699
700 // Paint line-through decoration if needed.
701 if (has_line_through_decoration) {
702 text_painter.PaintDecorationsOnlyLineThrough(
703 decoration_info.value(), paint_info, style.AppliedTextDecorations(),
704 text_style);
705 }
706 }
707
708 if (UNLIKELY(selection && (selection->ShouldPaintSelectedTextOnly() ||
709 selection->ShouldPaintSelectedTextSeparately()))) {
710 // Paint only the text that is selected.
711 if (!selection->IsSelectionRectComputed())
712 selection->ComputeSelectionRect(box_rect.offset);
713 if (rotation)
714 selection->MapSelectionRectIntoRotatedSpace(*rotation);
715 selection->PaintSelectedText(text_painter, length, text_style, node_id);
716 }
717
718 if (paint_info.phase != PaintPhase::kForeground)
719 return;
720 PaintDocumentMarkers(paint_info, text_item, cursor_.CurrentText(),
721 markers_to_paint, box_rect.offset, style,
722 DocumentMarkerPaintPhase::kForeground, &text_painter);
723 }
724
725 template class NGTextFragmentPainter<NGTextPainterCursor>;
726 template class NGTextFragmentPainter<NGInlineCursor>;
727
728 } // namespace blink
729