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