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