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_list_marker.h"
15 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.h"
16 #include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
17 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_text_fragment.h"
18 #include "third_party/blink/renderer/core/layout/ng/inline/ng_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/inline_text_box_painter.h"
23 #include "third_party/blink/renderer/core/paint/list_marker_painter.h"
24 #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h"
25 #include "third_party/blink/renderer/core/paint/ng/ng_text_painter.h"
26 #include "third_party/blink/renderer/core/paint/paint_info.h"
27 #include "third_party/blink/renderer/core/paint/selection_painting_utils.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 =
45       SelectionPaintingUtils::SelectionBackgroundColor(document, style, node);
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)58 inline const NGFragmentItem& AsDisplayItemClient(const NGInlineCursor& cursor) {
59   return *cursor.CurrentItem();
60 }
61 
AsDisplayItemClient(const NGTextPainterCursor & cursor)62 inline const NGPaintFragment& AsDisplayItemClient(
63     const NGTextPainterCursor& cursor) {
64   return cursor.PaintFragment();
65 }
66 
ComputeBoxRect(const NGInlineCursor & cursor,const PhysicalOffset & paint_offset,const PhysicalOffset & parent_offset)67 inline PhysicalRect ComputeBoxRect(const NGInlineCursor& cursor,
68                                    const PhysicalOffset& paint_offset,
69                                    const PhysicalOffset& parent_offset) {
70   PhysicalRect box_rect = cursor.CurrentItem()->RectInContainerBlock();
71   box_rect.offset.left += paint_offset.left;
72   // We round the y-axis to ensure consistent line heights.
73   box_rect.offset.top =
74       LayoutUnit((paint_offset.top + parent_offset.top).Round()) +
75       (box_rect.offset.top - parent_offset.top);
76   return box_rect;
77 }
78 
ComputeBoxRect(const NGTextPainterCursor & cursor,const PhysicalOffset & paint_offset,const PhysicalOffset & parent_offset)79 inline PhysicalRect ComputeBoxRect(const NGTextPainterCursor& cursor,
80                                    const PhysicalOffset& paint_offset,
81                                    const PhysicalOffset& parent_offset) {
82   PhysicalRect box_rect = cursor.PaintFragment().Rect();
83   // We round the y-axis to ensure consistent line heights.
84   PhysicalOffset adjusted_paint_offset(paint_offset.left,
85                                        LayoutUnit(paint_offset.top.Round()));
86   box_rect.offset += adjusted_paint_offset;
87   return box_rect;
88 }
89 
InlineCursorForBlockFlow(const NGInlineCursor & cursor,base::Optional<NGInlineCursor> * storage)90 inline const NGInlineCursor& InlineCursorForBlockFlow(
91     const NGInlineCursor& cursor,
92     base::Optional<NGInlineCursor>* storage) {
93   if (*storage)
94     return **storage;
95   *storage = cursor;
96   (*storage)->ExpandRootToContainingBlock();
97   return **storage;
98 }
99 
InlineCursorForBlockFlow(const NGTextPainterCursor & cursor,base::Optional<NGInlineCursor> * storage)100 inline const NGInlineCursor& InlineCursorForBlockFlow(
101     const NGTextPainterCursor& cursor,
102     base::Optional<NGInlineCursor>* storage) {
103   if (*storage)
104     return **storage;
105   storage->emplace(cursor.RootPaintFragment());
106   (*storage)->MoveTo(cursor.PaintFragment());
107   return **storage;
108 }
109 
110 // TODO(yosin): Remove |GetTextFragmentPaintInfo| once the transition to
111 // |NGFragmentItem| is done. http://crbug.com/982194
GetTextFragmentPaintInfo(const NGInlineCursor & cursor)112 inline NGTextFragmentPaintInfo GetTextFragmentPaintInfo(
113     const NGInlineCursor& cursor) {
114   return cursor.CurrentItem()->TextPaintInfo(cursor.Items());
115 }
116 
GetTextFragmentPaintInfo(const NGTextPainterCursor & cursor)117 inline NGTextFragmentPaintInfo GetTextFragmentPaintInfo(
118     const NGTextPainterCursor& cursor) {
119   return cursor.CurrentItem()->PaintInfo();
120 }
121 
122 // TODO(yosin): Remove |GetLineLeftAndRightForOffsets| once the transition to
123 // |NGFragmentItem| is done. http://crbug.com/982194
GetLineLeftAndRightForOffsets(const NGFragmentItem & text_item,StringView text,unsigned start_offset,unsigned end_offset)124 inline std::pair<LayoutUnit, LayoutUnit> GetLineLeftAndRightForOffsets(
125     const NGFragmentItem& text_item,
126     StringView text,
127     unsigned start_offset,
128     unsigned end_offset) {
129   return text_item.LineLeftAndRightForOffsets(text, start_offset, end_offset);
130 }
131 
GetLineLeftAndRightForOffsets(const NGPhysicalTextFragment & text_fragment,StringView text,unsigned start_offset,unsigned end_offset)132 inline std::pair<LayoutUnit, LayoutUnit> GetLineLeftAndRightForOffsets(
133     const NGPhysicalTextFragment& text_fragment,
134     StringView text,
135     unsigned start_offset,
136     unsigned end_offset) {
137   return text_fragment.LineLeftAndRightForOffsets(start_offset, end_offset);
138 }
139 
140 // TODO(yosin): Remove |ComputeLayoutSelectionStatus| once the transition to
141 // |NGFragmentItem| is done. http://crbug.com/982194
ComputeLayoutSelectionStatus(const NGInlineCursor & cursor)142 inline LayoutSelectionStatus ComputeLayoutSelectionStatus(
143     const NGInlineCursor& cursor) {
144   return cursor.Current()
145       .GetLayoutObject()
146       ->GetDocument()
147       .GetFrame()
148       ->Selection()
149       .ComputeLayoutSelectionStatus(cursor);
150 }
151 
152 // TODO(yosin): Remove |ComputeLocalRect| once the transition to
153 // |NGFragmentItem| is done. http://crbug.com/982194
ComputeLocalRect(const NGFragmentItem & text_item,StringView text,unsigned start_offset,unsigned end_offset)154 inline PhysicalRect ComputeLocalRect(const NGFragmentItem& text_item,
155                                      StringView text,
156                                      unsigned start_offset,
157                                      unsigned end_offset) {
158   return text_item.LocalRect(text, start_offset, end_offset);
159 }
160 
ComputeLocalRect(const NGPhysicalTextFragment & text_fragment,StringView text,unsigned start_offset,unsigned end_offset)161 inline PhysicalRect ComputeLocalRect(
162     const NGPhysicalTextFragment& text_fragment,
163     StringView text,
164     unsigned start_offset,
165     unsigned end_offset) {
166   return text_fragment.LocalRect(start_offset, end_offset);
167 }
168 
ComputeMarkersToPaint(Node * node,bool is_ellipsis)169 DocumentMarkerVector ComputeMarkersToPaint(Node* node, bool is_ellipsis) {
170   // TODO(yoichio): Handle first-letter
171   auto* text_node = DynamicTo<Text>(node);
172   if (!text_node)
173     return DocumentMarkerVector();
174   // We don't paint any marker on ellipsis.
175   if (is_ellipsis)
176     return DocumentMarkerVector();
177 
178   DocumentMarkerController& document_marker_controller =
179       node->GetDocument().Markers();
180   return document_marker_controller.ComputeMarkersToPaint(*text_node);
181 }
182 
GetTextContentOffset(const Text & text,unsigned offset)183 unsigned GetTextContentOffset(const Text& text, unsigned offset) {
184   // TODO(yoichio): Sanitize DocumentMarker around text length.
185   const Position position(text, std::min(offset, text.length()));
186   const NGOffsetMapping* const offset_mapping =
187       NGOffsetMapping::GetFor(position);
188   DCHECK(offset_mapping);
189   const base::Optional<unsigned>& ng_offset =
190       offset_mapping->GetTextContentOffset(position);
191   DCHECK(ng_offset.has_value());
192   return ng_offset.value();
193 }
194 
195 // ClampOffset modifies |offset| fixed in a range of |text_fragment| start/end
196 // offsets.
197 // |offset| points not each character but each span between character.
198 // With that concept, we can clear catch what is inside start / end.
199 // Suppose we have "foo_bar"('_' is a space).
200 // There are 8 offsets for that:
201 //  f o o _ b a r
202 // 0 1 2 3 4 5 6 7
203 // If "bar" is a TextFragment. That start(), end() {4, 7} correspond this
204 // offset. If a marker has StartOffset / EndOffset as {2, 6},
205 // ClampOffset returns{ 4,6 }, which represents "ba" on "foo_bar".
206 template <typename TextItem>
ClampOffset(unsigned offset,const TextItem & text_fragment)207 unsigned ClampOffset(unsigned offset, const TextItem& text_fragment) {
208   return std::min(std::max(offset, text_fragment.StartOffset()),
209                   text_fragment.EndOffset());
210 }
211 
PaintRect(GraphicsContext & context,const PhysicalRect & rect,const Color color)212 void PaintRect(GraphicsContext& context,
213                const PhysicalRect& rect,
214                const Color color) {
215   if (!color.Alpha())
216     return;
217   if (rect.size.IsEmpty())
218     return;
219   const IntRect pixel_snapped_rect = PixelSnappedIntRect(rect);
220   if (!pixel_snapped_rect.IsEmpty())
221     context.FillRect(pixel_snapped_rect, color);
222 }
223 
PaintRect(GraphicsContext & context,const PhysicalOffset & location,const PhysicalRect & rect,const Color color)224 void PaintRect(GraphicsContext& context,
225                const PhysicalOffset& location,
226                const PhysicalRect& rect,
227                const Color color) {
228   PaintRect(context, PhysicalRect(rect.offset + location, rect.size), color);
229 }
230 
231 template <typename TextItem>
MarkerRectForForeground(const TextItem & text_fragment,StringView text,unsigned start_offset,unsigned end_offset)232 PhysicalRect MarkerRectForForeground(const TextItem& text_fragment,
233                                      StringView text,
234                                      unsigned start_offset,
235                                      unsigned end_offset) {
236   LayoutUnit start_position, end_position;
237   std::tie(start_position, end_position) = GetLineLeftAndRightForOffsets(
238       text_fragment, text, start_offset, end_offset);
239 
240   const LayoutUnit height = text_fragment.Size()
241                                 .ConvertToLogical(static_cast<WritingMode>(
242                                     text_fragment.Style().GetWritingMode()))
243                                 .block_size;
244   return {start_position, LayoutUnit(), end_position - start_position, height};
245 }
246 
247 // Copied from InlineTextBoxPainter
248 template <typename TextItem>
PaintDocumentMarkers(GraphicsContext & context,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)249 void PaintDocumentMarkers(GraphicsContext& context,
250                           const TextItem& text_fragment,
251                           StringView text,
252                           const DocumentMarkerVector& markers_to_paint,
253                           const PhysicalOffset& box_origin,
254                           const ComputedStyle& style,
255                           DocumentMarkerPaintPhase marker_paint_phase,
256                           NGTextPainter* text_painter) {
257   if (markers_to_paint.IsEmpty())
258     return;
259 
260   DCHECK(text_fragment.GetNode());
261   const auto& text_node = To<Text>(*text_fragment.GetNode());
262   for (const DocumentMarker* marker : markers_to_paint) {
263     const unsigned marker_start_offset =
264         GetTextContentOffset(text_node, marker->StartOffset());
265     const unsigned marker_end_offset =
266         GetTextContentOffset(text_node, marker->EndOffset());
267     const unsigned paint_start_offset =
268         ClampOffset(marker_start_offset, text_fragment);
269     const unsigned paint_end_offset =
270         ClampOffset(marker_end_offset, text_fragment);
271     if (paint_start_offset == paint_end_offset)
272       continue;
273 
274     switch (marker->GetType()) {
275       case DocumentMarker::kSpelling:
276       case DocumentMarker::kGrammar: {
277         if (context.Printing())
278           break;
279         if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground)
280           continue;
281         DocumentMarkerPainter::PaintDocumentMarker(
282             context, box_origin, style, marker->GetType(),
283             MarkerRectForForeground(text_fragment, text, paint_start_offset,
284                                     paint_end_offset));
285       } break;
286 
287       case DocumentMarker::kTextFragment:
288       case DocumentMarker::kTextMatch: {
289         if (marker->GetType() == DocumentMarker::kTextMatch &&
290             !text_fragment.GetNode()
291                  ->GetDocument()
292                  .GetFrame()
293                  ->GetEditor()
294                  .MarkedTextMatchesAreHighlighted())
295           break;
296         const auto& text_marker = To<TextMarkerBase>(*marker);
297         if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) {
298           const Color color =
299               LayoutTheme::GetTheme().PlatformTextSearchHighlightColor(
300                   text_marker.IsActiveMatch(),
301                   text_fragment.GetNode()->GetDocument().InForcedColorsMode(),
302                   style.UsedColorScheme());
303           PaintRect(context, PhysicalOffset(box_origin),
304                     ComputeLocalRect(text_fragment, text, paint_start_offset,
305                                      paint_end_offset),
306                     color);
307           break;
308         }
309 
310         const TextPaintStyle text_style =
311             DocumentMarkerPainter::ComputeTextPaintStyleFrom(
312                 style, text_marker,
313                 text_fragment.GetNode()->GetDocument().InForcedColorsMode());
314         if (text_style.current_color == Color::kTransparent)
315           break;
316         text_painter->Paint(paint_start_offset, paint_end_offset,
317                             paint_end_offset - paint_start_offset, text_style,
318                             kInvalidDOMNodeId);
319       } break;
320 
321       case DocumentMarker::kComposition:
322       case DocumentMarker::kActiveSuggestion:
323       case DocumentMarker::kSuggestion: {
324         const auto& styleable_marker = To<StyleableMarker>(*marker);
325         if (marker_paint_phase == DocumentMarkerPaintPhase::kBackground) {
326           PaintRect(context, PhysicalOffset(box_origin),
327                     ComputeLocalRect(text_fragment, text, paint_start_offset,
328                                      paint_end_offset),
329                     styleable_marker.BackgroundColor());
330           break;
331         }
332         const SimpleFontData* font_data = style.GetFont().PrimaryFont();
333         DocumentMarkerPainter::PaintStyleableMarkerUnderline(
334             context, box_origin, styleable_marker, style,
335             FloatRect(MarkerRectForForeground(
336                 text_fragment, text, paint_start_offset, paint_end_offset)),
337             LayoutUnit(font_data->GetFontMetrics().Height()));
338       } break;
339 
340       default:
341         NOTREACHED();
342         break;
343     }
344   }
345 }
346 
347 class SelectionPaintState {
348   STACK_ALLOCATED();
349 
350  public:
SelectionPaintState(const NGInlineCursor & containing_block)351   explicit SelectionPaintState(const NGInlineCursor& containing_block)
352       : selection_status_(ComputeLayoutSelectionStatus(containing_block)),
353         containing_block_(containing_block) {}
354 
Status() const355   const LayoutSelectionStatus& Status() const { return selection_status_; }
356 
ShouldPaintSelectedTextOnly() const357   bool ShouldPaintSelectedTextOnly() const { return paint_selected_text_only_; }
358 
ShouldPaintSelectedTextSeparately() const359   bool ShouldPaintSelectedTextSeparately() const {
360     return paint_selected_text_separately_;
361   }
362 
IsSelectionRectComputed() const363   bool IsSelectionRectComputed() const { return selection_rect_.has_value(); }
364 
ComputeSelectionStyle(const Document & document,const ComputedStyle & style,Node * node,const PaintInfo & paint_info,const TextPaintStyle & text_style)365   void ComputeSelectionStyle(const Document& document,
366                              const ComputedStyle& style,
367                              Node* node,
368                              const PaintInfo& paint_info,
369                              const TextPaintStyle& text_style) {
370     selection_style_ = TextPainterBase::SelectionPaintingStyle(
371         document, style, node, /*have_selection*/ true, paint_info, text_style);
372     paint_selected_text_only_ =
373         (paint_info.phase == PaintPhase::kSelectionDragImage);
374     paint_selected_text_separately_ =
375         !paint_selected_text_only_ && text_style != selection_style_;
376   }
377 
ComputeSelectionRect(const PhysicalOffset & box_offset)378   void ComputeSelectionRect(const PhysicalOffset& box_offset) {
379     DCHECK(!selection_rect_);
380     selection_rect_ =
381         ComputeLocalSelectionRectForText(containing_block_, selection_status_);
382     selection_rect_->offset += box_offset;
383   }
384 
385   // Logic is copied from InlineTextBoxPainter::PaintSelection.
386   // |selection_start| and |selection_end| should be between
387   // [text_fragment.StartOffset(), text_fragment.EndOffset()].
PaintSelectionBackground(GraphicsContext & context,Node * node,const Document & document,const ComputedStyle & style)388   void PaintSelectionBackground(GraphicsContext& context,
389                                 Node* node,
390                                 const Document& document,
391                                 const ComputedStyle& style) {
392     const Color color = SelectionBackgroundColor(document, style, node,
393                                                  selection_style_.fill_color);
394     PaintRect(context, *selection_rect_, color);
395   }
396 
397   // Paint the selected text only.
PaintSelectedText(NGTextPainter & text_painter,unsigned length,const TextPaintStyle & text_style,DOMNodeId node_id)398   void PaintSelectedText(NGTextPainter& text_painter,
399                          unsigned length,
400                          const TextPaintStyle& text_style,
401                          DOMNodeId node_id) {
402     text_painter.PaintSelectedText(selection_status_.start,
403                                    selection_status_.end, length, text_style,
404                                    selection_style_, *selection_rect_, node_id);
405   }
406 
407   // 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)408   void PaintBeforeAndAfterSelectedText(NGTextPainter& text_painter,
409                                        unsigned start_offset,
410                                        unsigned end_offset,
411                                        unsigned length,
412                                        const TextPaintStyle& text_style,
413                                        DOMNodeId node_id) {
414     if (start_offset < selection_status_.start) {
415       text_painter.Paint(start_offset, selection_status_.start, length,
416                          text_style, node_id);
417     }
418     if (selection_status_.end < end_offset) {
419       text_painter.Paint(selection_status_.end, end_offset, length, text_style,
420                          node_id);
421     }
422   }
423 
424  private:
425   LayoutSelectionStatus selection_status_;
426   TextPaintStyle selection_style_;
427   base::Optional<PhysicalRect> selection_rect_;
428   const NGInlineCursor& containing_block_;
429   bool paint_selected_text_only_;
430   bool paint_selected_text_separately_;
431 };
432 
433 }  // namespace
434 
CurrentText() const435 StringView NGTextPainterCursor::CurrentText() const {
436   return CurrentItem()->Text();
437 }
438 
RootPaintFragment() const439 const NGPaintFragment& NGTextPainterCursor::RootPaintFragment() const {
440   if (!root_paint_fragment_)
441     root_paint_fragment_ = paint_fragment_.Root();
442   return *root_paint_fragment_;
443 }
444 
445 template <typename Cursor>
PaintSymbol(const LayoutObject * layout_object,const ComputedStyle & style,const PhysicalSize box_size,const PaintInfo & paint_info,const PhysicalOffset & paint_offset)446 void NGTextFragmentPainter<Cursor>::PaintSymbol(
447     const LayoutObject* layout_object,
448     const ComputedStyle& style,
449     const PhysicalSize box_size,
450     const PaintInfo& paint_info,
451     const PhysicalOffset& paint_offset) {
452   PhysicalRect marker_rect(
453       LayoutListMarker::RelativeSymbolMarkerRect(style, box_size.width));
454   marker_rect.Move(paint_offset);
455   IntRect rect = PixelSnappedIntRect(marker_rect);
456 
457   ListMarkerPainter::PaintSymbol(paint_info, layout_object, style, rect);
458 }
459 
460 // This is copied from InlineTextBoxPainter::PaintSelection() but lacks of
461 // ltr, expanding new line wrap or so which uses InlineTextBox functions.
462 template <typename Cursor>
Paint(const PaintInfo & paint_info,const PhysicalOffset & paint_offset)463 void NGTextFragmentPainter<Cursor>::Paint(const PaintInfo& paint_info,
464                                           const PhysicalOffset& paint_offset) {
465   const auto& text_item = *cursor_.CurrentItem();
466   // We can skip painting if the fragment (including selection) is invisible.
467   if (!text_item.TextLength())
468     return;
469   const IntRect visual_rect = AsDisplayItemClient(cursor_).VisualRect();
470   if (visual_rect.IsEmpty())
471     return;
472 
473   if (!text_item.TextShapeResult() &&
474       // A line break's selection tint is still visible.
475       !text_item.IsLineBreak())
476     return;
477 
478   const NGTextFragmentPaintInfo& fragment_paint_info =
479       GetTextFragmentPaintInfo(cursor_);
480   const LayoutObject* layout_object = text_item.GetLayoutObject();
481   const ComputedStyle& style = text_item.Style();
482   const Document& document = layout_object->GetDocument();
483   const bool is_printing = paint_info.IsPrinting();
484 
485   // Determine whether or not we're selected.
486   base::Optional<SelectionPaintState> selection;
487   if (UNLIKELY(!is_printing && paint_info.phase != PaintPhase::kTextClip &&
488                layout_object->IsSelected())) {
489     const NGInlineCursor& root_inline_cursor =
490         InlineCursorForBlockFlow(cursor_, &inline_cursor_for_block_flow_);
491     selection.emplace(root_inline_cursor);
492     if (!selection->Status().HasValidRange())
493       selection.reset();
494   }
495   if (!selection) {
496     // When only painting the selection drag image, don't bother to paint if
497     // there is none.
498     if (paint_info.phase == PaintPhase::kSelectionDragImage)
499       return;
500 
501     // Flow controls (line break, tab, <wbr>) need only selection painting.
502     if (text_item.IsFlowControl())
503       return;
504   }
505 
506   // The text clip phase already has a DrawingRecorder. Text clips are initiated
507   // only in BoxPainterBase::PaintFillLayer, which is already within a
508   // DrawingRecorder.
509   base::Optional<DrawingRecorder> recorder;
510   if (paint_info.phase != PaintPhase::kTextClip) {
511     if (DrawingRecorder::UseCachedDrawingIfPossible(
512             paint_info.context, AsDisplayItemClient(cursor_), paint_info.phase))
513       return;
514     recorder.emplace(paint_info.context, AsDisplayItemClient(cursor_),
515                      paint_info.phase);
516   }
517 
518   PhysicalRect box_rect = ComputeBoxRect(cursor_, paint_offset, parent_offset_);
519 
520   if (UNLIKELY(text_item.IsSymbolMarker())) {
521     // The NGInlineItem of marker might be Split(). To avoid calling PaintSymbol
522     // multiple times, only call it the first time. For an outside marker, this
523     // is when StartOffset is 0. But for an inside marker, the first StartOffset
524     // can be greater due to leading bidi control characters like U+202A/U+202B,
525     // U+202D/U+202E, U+2066/U+2067 or U+2068.
526     DCHECK_LT(fragment_paint_info.from, fragment_paint_info.text.length());
527     for (unsigned i = 0; i < fragment_paint_info.from; ++i) {
528       if (!Character::IsBidiControl(fragment_paint_info.text.CodepointAt(i)))
529         return;
530     }
531     PaintSymbol(layout_object, style, box_rect.size, paint_info,
532                 box_rect.offset);
533     return;
534   }
535 
536   GraphicsContext& context = paint_info.context;
537 
538   // Determine text colors.
539 
540   Node* node = layout_object->GetNode();
541   TextPaintStyle text_style =
542       TextPainterBase::TextPaintingStyle(document, style, paint_info);
543   if (UNLIKELY(selection)) {
544     selection->ComputeSelectionStyle(document, style, node, paint_info,
545                                      text_style);
546   }
547 
548   // Set our font.
549   const Font& font = style.GetFont();
550   const SimpleFontData* font_data = font.PrimaryFont();
551   DCHECK(font_data);
552 
553   base::Optional<GraphicsContextStateSaver> state_saver;
554 
555   // 1. Paint backgrounds behind text if needed. Examples of such backgrounds
556   // include selection and composition highlights.
557   // Since NGPaintFragment::ComputeLocalSelectionRectForText() returns
558   // PhysicalRect rather than LogicalRect, we should paint selection
559   // before GraphicsContext flip.
560   // TODO(yoichio): Make NGPhysicalTextFragment::LocalRect and
561   // NGPaintFragment::ComputeLocalSelectionRectForText logical so that we can
562   // paint selection in same flipped dimension as NGTextPainter.
563   const DocumentMarkerVector& markers_to_paint =
564       ComputeMarkersToPaint(node, text_item.IsEllipsis());
565   if (paint_info.phase != PaintPhase::kSelectionDragImage &&
566       paint_info.phase != PaintPhase::kTextClip && !is_printing) {
567     PaintDocumentMarkers(context, text_item, cursor_.CurrentText(),
568                          markers_to_paint, box_rect.offset, style,
569                          DocumentMarkerPaintPhase::kBackground, nullptr);
570     if (UNLIKELY(selection)) {
571       selection->ComputeSelectionRect(box_rect.offset);
572       selection->PaintSelectionBackground(context, node, document, style);
573     }
574   }
575 
576   const WritingMode writing_mode = style.GetWritingMode();
577   const bool is_horizontal = IsHorizontalWritingMode(writing_mode);
578   if (!is_horizontal) {
579     state_saver.emplace(context);
580     // Because we rotate the GraphicsContext to match the logical direction,
581     // transpose the |box_rect| to match to it.
582     box_rect.size = PhysicalSize(box_rect.Height(), box_rect.Width());
583     context.ConcatCTM(TextPainterBase::Rotation(
584         box_rect, writing_mode != WritingMode::kSidewaysLr
585                       ? TextPainterBase::kClockwise
586                       : TextPainterBase::kCounterclockwise));
587   }
588 
589   // 2. Now paint the foreground, including text and decorations.
590   int ascent = font_data ? font_data->GetFontMetrics().Ascent() : 0;
591   PhysicalOffset text_origin(box_rect.offset.left,
592                              box_rect.offset.top + ascent);
593   NGTextPainter text_painter(context, font, fragment_paint_info, visual_rect,
594                              text_origin, box_rect, is_horizontal);
595 
596   if (style.GetTextEmphasisMark() != TextEmphasisMark::kNone) {
597     text_painter.SetEmphasisMark(style.TextEmphasisMarkString(),
598                                  style.GetTextEmphasisPosition());
599   }
600 
601   DOMNodeId node_id = kInvalidDOMNodeId;
602   if (node) {
603     if (auto* layout_text = ToLayoutTextOrNull(node->GetLayoutObject()))
604       node_id = layout_text->EnsureNodeId();
605   }
606 
607   const unsigned length = fragment_paint_info.to - fragment_paint_info.from;
608   if (!selection || !selection->ShouldPaintSelectedTextOnly()) {
609     // Paint text decorations except line-through.
610     DecorationInfo decoration_info;
611     bool has_line_through_decoration = false;
612     if (style.TextDecorationsInEffect() != TextDecoration::kNone &&
613         // Ellipsis should not have text decorations. This is not defined, but 4
614         // impls do this.
615         !text_item.IsEllipsis()) {
616       PhysicalOffset local_origin = box_rect.offset;
617       LayoutUnit width = box_rect.Width();
618       const NGPhysicalBoxFragment* decorating_box = nullptr;
619       const ComputedStyle* decorating_box_style =
620           decorating_box ? &decorating_box->Style() : nullptr;
621 
622       text_painter.ComputeDecorationInfo(
623           decoration_info, box_rect.offset, local_origin, width,
624           style.GetFontBaseline(), style, decorating_box_style);
625 
626       NGTextDecorationOffset decoration_offset(
627           *decoration_info.style, text_item.Style(), decorating_box);
628       text_painter.PaintDecorationsExceptLineThrough(
629           decoration_offset, decoration_info, paint_info,
630           style.AppliedTextDecorations(), text_style,
631           &has_line_through_decoration);
632     }
633 
634     unsigned start_offset = fragment_paint_info.from;
635     unsigned end_offset = fragment_paint_info.to;
636 
637     if (UNLIKELY(selection && selection->ShouldPaintSelectedTextSeparately())) {
638       selection->PaintBeforeAndAfterSelectedText(
639           text_painter, start_offset, end_offset, length, text_style, node_id);
640     } else {
641       text_painter.Paint(start_offset, end_offset, length, text_style, node_id);
642     }
643 
644     // Paint line-through decoration if needed.
645     if (has_line_through_decoration) {
646       text_painter.PaintDecorationsOnlyLineThrough(
647           decoration_info, paint_info, style.AppliedTextDecorations(),
648           text_style);
649     }
650   }
651 
652   if (UNLIKELY(selection && (selection->ShouldPaintSelectedTextOnly() ||
653                              selection->ShouldPaintSelectedTextSeparately()))) {
654     // Paint only the text that is selected.
655     if (!selection->IsSelectionRectComputed())
656       selection->ComputeSelectionRect(box_rect.offset);
657     selection->PaintSelectedText(text_painter, length, text_style, node_id);
658   }
659 
660   if (paint_info.phase != PaintPhase::kForeground)
661     return;
662   PaintDocumentMarkers(context, text_item, cursor_.CurrentText(),
663                        markers_to_paint, box_rect.offset, style,
664                        DocumentMarkerPaintPhase::kForeground, &text_painter);
665 }
666 
667 template class NGTextFragmentPainter<NGTextPainterCursor>;
668 template class NGTextFragmentPainter<NGInlineCursor>;
669 
670 }  // namespace blink
671