1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components/text_overlay/text_overlay_manager.h"
17 
18 #include <cmath>
19 #include <string>
20 
21 #include "core/components/font/constants_converter.h"
22 #include "core/components/stack/stack_element.h"
23 #include "core/components/text_overlay/text_overlay_component.h"
24 #ifndef USE_GRAPHIC_TEXT_GINE
25 #include "txt/paragraph_txt.h"
26 #else
27 #include "rosen_text/typography.h"
28 #include "unicode/uchar.h"
29 #endif
30 #ifndef USE_ROSEN_DRAWING
31 #include "include/core/SkCanvas.h"
32 #else
33 #include "core/components_ng/render/drawing.h"
34 #endif
35 
36 namespace OHOS::Ace {
37 
38 namespace {
39 constexpr char16_t NEWLINE_CODE = u'\n';
40 constexpr double FIFTY_PERCENT = 0.5;
41 constexpr int32_t SHOW_HANDLE_DURATION = 250;
42 } // namespace
43 
44 TextOverlayBase::~TextOverlayBase() = default;
45 
MakeEmptyOffset() const46 Offset TextOverlayBase::MakeEmptyOffset() const
47 {
48     if (realTextDirection_ == TextDirection::RTL) {
49         return Offset(textOverlayPaintRect_.Width(), 0.0);
50     }
51 
52     switch (textAlign_) {
53         case TextAlign::LEFT: {
54             return Offset::Zero();
55         }
56         case TextAlign::RIGHT: {
57             return Offset(textOverlayPaintRect_.Width(), 0.0);
58         }
59         case TextAlign::JUSTIFY:
60         case TextAlign::CENTER: {
61             return Offset(textOverlayPaintRect_.Width() / 2.0, 0.0);
62         }
63         case TextAlign::END: {
64             switch (defaultTextDirection_) {
65                 case TextDirection::RTL: {
66                     return Offset::Zero();
67                 }
68                 case TextDirection::LTR:
69                 default: {
70                     return Offset(textOverlayPaintRect_.Width(), 0.0);
71                 }
72             }
73         }
74         case TextAlign::START:
75         default: {
76             // Default to start.
77             switch (defaultTextDirection_) {
78                 case TextDirection::RTL: {
79                     return Offset(textOverlayPaintRect_.Width(), 0.0);
80                 }
81                 case TextDirection::LTR:
82                 default: {
83                     return Offset::Zero();
84                 }
85             }
86         }
87     }
88 }
89 
GetBoundaryOfParagraph(bool isLeftBoundary) const90 double TextOverlayBase::GetBoundaryOfParagraph(bool isLeftBoundary) const
91 {
92     if (!paragraph_ || textValue_.text.empty()) {
93         return 0.0;
94     }
95 #ifndef USE_GRAPHIC_TEXT_GINE
96     auto boxes = paragraph_->GetRectsForRange(0, textValue_.GetWideText().length(),
97         txt::Paragraph::RectHeightStyle::kMax, txt::Paragraph::RectWidthStyle::kTight);
98 #else
99     auto boxes = paragraph_->GetTextRectsByBoundary(0, textValue_.GetWideText().length(),
100         Rosen::TextRectHeightStyle::COVER_TOP_AND_BOTTOM, Rosen::TextRectWidthStyle::TIGHT);
101 #endif
102     if (boxes.empty()) {
103         return 0.0;
104     }
105 #ifndef USE_GRAPHIC_TEXT_GINE
106     double leftBoundaryOfParagraph = boxes.front().rect.fLeft;
107     double rightBoundaryOfParagraph = boxes.front().rect.fLeft;
108     double bottomBoundaryOfParagraph = boxes.front().rect.fBottom;
109 #else
110     double leftBoundaryOfParagraph = boxes.front().rect.GetLeft();
111     double rightBoundaryOfParagraph = boxes.front().rect.GetLeft();
112     double bottomBoundaryOfParagraph = boxes.front().rect.GetBottom();
113 #endif
114     for (const auto& box : boxes) {
115 #ifndef USE_GRAPHIC_TEXT_GINE
116         if (cursorPositionType_ == CursorPositionType::END && !NearEqual(box.rect.fBottom, bottomBoundaryOfParagraph)) {
117             bottomBoundaryOfParagraph = box.rect.fBottom;
118             leftBoundaryOfParagraph = box.rect.fLeft;
119             rightBoundaryOfParagraph = box.rect.fRight;
120 #else
121         if (cursorPositionType_ == CursorPositionType::END &&
122             !NearEqual(box.rect.GetBottom(), bottomBoundaryOfParagraph)) {
123             bottomBoundaryOfParagraph = box.rect.GetBottom();
124             leftBoundaryOfParagraph = box.rect.GetLeft();
125             rightBoundaryOfParagraph = box.rect.GetRight();
126 #endif
127             continue;
128         }
129 #ifndef USE_GRAPHIC_TEXT_GINE
130         leftBoundaryOfParagraph = std::min(static_cast<double>(box.rect.fLeft), leftBoundaryOfParagraph);
131         rightBoundaryOfParagraph = std::max(static_cast<double>(box.rect.fRight), rightBoundaryOfParagraph);
132 #else
133         leftBoundaryOfParagraph = std::min(static_cast<double>(box.rect.GetLeft()), leftBoundaryOfParagraph);
134         rightBoundaryOfParagraph = std::max(static_cast<double>(box.rect.GetRight()), rightBoundaryOfParagraph);
135 #endif
136     }
137     return isLeftBoundary ? leftBoundaryOfParagraph : rightBoundaryOfParagraph;
138 }
139 
140 bool TextOverlayBase::ComputeOffsetForCaretUpstream(int32_t extent, CaretMetrics& result) const
141 {
142     auto text = StringUtils::Str8ToStr16(textForDisplay_);
143     if (!paragraph_ || text.empty()) {
144         return false;
145     }
146 
147     char16_t prevChar = 0;
148     if (static_cast<size_t>(extent) <= text.length()) {
149         prevChar = text[std::max(0, extent - 1)];
150     }
151 
152     result.Reset();
153     int32_t graphemeClusterLength = StringUtils::NotInUtf16Bmp(prevChar) ? 2 : 1;
154     int32_t prev = extent - graphemeClusterLength;
155 #ifndef USE_GRAPHIC_TEXT_GINE
156     auto boxes = paragraph_->GetRectsForRange(
157         prev, extent, txt::Paragraph::RectHeightStyle::kMax, txt::Paragraph::RectWidthStyle::kTight);
158 #else
159     auto boxes = paragraph_->GetTextRectsByBoundary(
160         prev, extent, Rosen::TextRectHeightStyle::COVER_TOP_AND_BOTTOM, Rosen::TextRectWidthStyle::TIGHT);
161 #endif
162     while (boxes.empty() && !textValue_.text.empty()) {
163         graphemeClusterLength *= 2;
164         prev = extent - graphemeClusterLength;
165         if (prev < 0) {
166 #ifndef USE_GRAPHIC_TEXT_GINE
167             boxes = paragraph_->GetRectsForRange(
168                 0, extent, txt::Paragraph::RectHeightStyle::kMax, txt::Paragraph::RectWidthStyle::kTight);
169 #else
170             boxes = paragraph_->GetTextRectsByBoundary(
171                 0, extent, Rosen::TextRectHeightStyle::COVER_TOP_AND_BOTTOM, Rosen::TextRectWidthStyle::TIGHT);
172 #endif
173             break;
174         }
175 #ifndef USE_GRAPHIC_TEXT_GINE
176         boxes = paragraph_->GetRectsForRange(
177             prev, extent, txt::Paragraph::RectHeightStyle::kMax, txt::Paragraph::RectWidthStyle::kTight);
178 #else
179         boxes = paragraph_->GetTextRectsByBoundary(
180             prev, extent, Rosen::TextRectHeightStyle::COVER_TOP_AND_BOTTOM, Rosen::TextRectWidthStyle::TIGHT);
181 #endif
182     }
183     if (boxes.empty()) {
184         return false;
185     }
186 
187     const auto& textBox = *boxes.begin();
188 
189     if (prevChar == NEWLINE_CODE) {
190         // Return the start of next line.
191         auto emptyOffset = MakeEmptyOffset();
192         result.offset.SetX(emptyOffset.GetX());
193 #ifndef USE_GRAPHIC_TEXT_GINE
194         result.offset.SetY(textBox.rect.fBottom);
195 #else
196         result.offset.SetY(textBox.rect.GetBottom());
197 #endif
198         result.height = caretProto_.Height();
199         return true;
200     }
201 
202 #ifndef USE_GRAPHIC_TEXT_GINE
203     bool isLtr = textBox.direction == txt::TextDirection::ltr;
204 #else
205     bool isLtr = textBox.direction == Rosen::TextDirection::LTR;
206 #endif
207     // Caret is within width of the upstream glyphs.
208 #ifndef USE_GRAPHIC_TEXT_GINE
209     double caretEnd = isLtr ? textBox.rect.fRight : textBox.rect.fLeft;
210 #else
211     double caretEnd = isLtr ? textBox.rect.GetRight() : textBox.rect.GetLeft();
212 #endif
213     if (cursorPositionType_ == CursorPositionType::END) {
214         caretEnd = GetBoundaryOfParagraph(realTextDirection_ != TextDirection::LTR);
215     }
216     double dx = isLtr ? caretEnd : caretEnd - caretProto_.Width();
217     double offsetX = std::min(dx, paragraph_->GetMaxWidth());
218     result.offset.SetX(offsetX);
219 #ifndef USE_GRAPHIC_TEXT_GINE
220     result.offset.SetY(textBox.rect.fTop);
221     result.height = textBox.rect.fBottom - textBox.rect.fTop;
222 #else
223     result.offset.SetY(textBox.rect.GetTop());
224     result.height = textBox.rect.GetBottom() - textBox.rect.GetTop();
225 #endif
226 
227     return true;
228 }
229 
230 bool TextOverlayBase::ComputeOffsetForCaretDownstream(int32_t extent, CaretMetrics& result) const
231 {
232     if (!paragraph_ || static_cast<size_t>(extent) >= textValue_.GetWideText().length()) {
233         return false;
234     }
235 
236     result.Reset();
237     const int32_t graphemeClusterLength = 1;
238     const int32_t next = extent + graphemeClusterLength;
239 #ifndef USE_GRAPHIC_TEXT_GINE
240     auto boxes = paragraph_->GetRectsForRange(
241         extent, next, txt::Paragraph::RectHeightStyle::kMax, txt::Paragraph::RectWidthStyle::kTight);
242 #else
243     auto boxes = paragraph_->GetTextRectsByBoundary(
244         extent, next, Rosen::TextRectHeightStyle::COVER_TOP_AND_BOTTOM, Rosen::TextRectWidthStyle::TIGHT);
245 #endif
246     if (boxes.empty()) {
247         return false;
248     }
249 
250     const auto& textBox = *boxes.begin();
251 #ifndef USE_GRAPHIC_TEXT_GINE
252     bool isLtr = textBox.direction == txt::TextDirection::ltr;
253 #else
254     bool isLtr = textBox.direction == Rosen::TextDirection::LTR;
255 #endif
256     // Caret is within width of the downstream glyphs.
257 #ifndef USE_GRAPHIC_TEXT_GINE
258     double caretStart = isLtr ? textBox.rect.fLeft : textBox.rect.fRight;
259 #else
260     double caretStart = isLtr ? textBox.rect.GetLeft() : textBox.rect.GetRight();
261 #endif
262     if (cursorPositionType_ == CursorPositionType::END) {
263         caretStart = GetBoundaryOfParagraph(realTextDirection_ != TextDirection::LTR);
264     }
265     double dx = isLtr ? caretStart : caretStart - caretProto_.Width();
266     double offsetX = std::min(dx, paragraph_->GetMaxWidth());
267     result.offset.SetX(offsetX);
268 #ifndef USE_GRAPHIC_TEXT_GINE
269     result.offset.SetY(textBox.rect.fTop);
270     result.height = textBox.rect.fBottom - textBox.rect.fTop;
271 #else
272     result.offset.SetY(textBox.rect.GetTop());
273     result.height = textBox.rect.GetBottom() - textBox.rect.GetTop();
274 #endif
275 
276     return true;
277 }
278 
279 bool TextOverlayBase::ComputeOffsetForCaretCloserToClick(int32_t extent, CaretMetrics& result) const
280 {
281     CaretMetrics upStreamMetrics;
282     bool upStreamSuccess = ComputeOffsetForCaretUpstream(extent, upStreamMetrics);
283     CaretMetrics downStreamMetrics;
284     bool downStreamSuccess = ComputeOffsetForCaretDownstream(extent, downStreamMetrics);
285     bool nearToUpStream = LessOrEqual(std::abs(upStreamMetrics.offset.GetX() - clickOffset_.GetX()),
286         std::abs(downStreamMetrics.offset.GetX() - clickOffset_.GetX()));
287     result = nearToUpStream ? upStreamMetrics : downStreamMetrics;
288     return upStreamSuccess || downStreamSuccess;
289 }
290 
291 DirectionStatus TextOverlayBase::GetDirectionStatusOfPosition(int32_t position) const
292 {
293     const char mark = ' ';
294     std::string tempBefore = textValue_.GetSelectedText(TextSelection(0, position));
295     StringUtils::DeleteAllMark(tempBefore, mark);
296     const auto& textBeforeCursor = StringUtils::ToWstring(tempBefore);
297 
298     std::string tempAfter = textValue_.GetSelectedText(TextSelection(position, textValue_.GetWideText().length()));
299     StringUtils::DeleteAllMark(tempAfter, mark);
300     const auto& textAfterCursor = StringUtils::ToWstring(tempAfter);
301 
302     bool isBeforeCharRtl = false;
303     if (!textBeforeCursor.empty()) {
304         const auto& charBefore = textBeforeCursor.back();
305         isBeforeCharRtl = (u_charDirection(charBefore) == UCharDirection::U_RIGHT_TO_LEFT ||
306                            u_charDirection(charBefore) == UCharDirection::U_RIGHT_TO_LEFT_ARABIC);
307     }
308 
309     bool isAfterCharRtl = false;
310     if (!textAfterCursor.empty()) {
311         const auto& charAfter = textAfterCursor.front();
312         isAfterCharRtl = (u_charDirection(charAfter) == UCharDirection::U_RIGHT_TO_LEFT ||
313                           u_charDirection(charAfter) == UCharDirection::U_RIGHT_TO_LEFT_ARABIC);
314     }
315     return static_cast<DirectionStatus>(
316         (static_cast<uint8_t>(isBeforeCharRtl) << 1) | static_cast<uint8_t>(isAfterCharRtl));
317 }
318 
319 bool TextOverlayBase::GetCaretRect(int32_t extent, Rect& caretRect, double caretHeightOffset) const
320 {
321     CaretMetrics metrics;
322     bool computeSuccess = false;
323     DirectionStatus directionStatus = GetDirectionStatusOfPosition(extent);
324     if (extent != 0 && extent != static_cast<int32_t>(textValue_.GetWideText().length()) &&
325         (directionStatus == DirectionStatus::LEFT_RIGHT || directionStatus == DirectionStatus::RIGHT_LEFT) &&
326         cursorPositionType_ != CursorPositionType::NONE &&
327         LessOrEqual(clickOffset_.GetX(), textOverlayPaintRect_.Width())) {
328         computeSuccess = ComputeOffsetForCaretCloserToClick(cursorPositionForShow_, metrics);
329     } else {
330         if (textAffinity_ == TextAffinity::DOWNSTREAM) {
331             computeSuccess =
332                 ComputeOffsetForCaretDownstream(extent, metrics) || ComputeOffsetForCaretUpstream(extent, metrics);
333         } else {
334             computeSuccess =
335                 ComputeOffsetForCaretUpstream(extent, metrics) || ComputeOffsetForCaretDownstream(extent, metrics);
336         }
337     }
338     if (computeSuccess && !textValue_.text.empty()) {
339         if (metrics.height <= 0 || std::isnan(metrics.height)) {
340             // The reason may be text lines is exceed the paragraph maxline.
341             return false;
342         }
343         caretRect.SetRect(metrics.offset.GetX(), metrics.offset.GetY() + caretHeightOffset, cursorWidth_,
344             metrics.height - caretHeightOffset * 2.0);
345     } else {
346         // Use proto caret.
347         caretRect = caretProto_ + MakeEmptyOffset();
348     }
349 
350     return true;
351 }
352 
353 int32_t TextOverlayBase::GetCursorPositionForClick(const Offset& offset, const Offset& globalOffset)
354 {
355     if (!paragraph_) {
356         return 0;
357     }
358     cursorPositionType_ = CursorPositionType::NORMAL;
359     clickOffset_ = offset - globalOffset - textOffsetForShowCaret_;
360     // Solve can't select right boundary of RTL language.
361     double rightBoundary = GetBoundaryOfParagraph(false);
362     if (GreatOrEqual(clickOffset_.GetX(), rightBoundary)) {
363         int32_t rightBoundaryPosition = static_cast<int32_t>(
364             paragraph_->GetGlyphIndexByCoordinate(rightBoundary - cursorWidth_, clickOffset_.GetY()).index);
365 
366         return realTextDirection_ == TextDirection::RTL ? 0 : rightBoundaryPosition;
367     }
368 
369 #ifndef USE_GRAPHIC_TEXT_GINE
370     return static_cast<int32_t>(
371         paragraph_->GetGlyphPositionAtCoordinate(clickOffset_.GetX(), clickOffset_.GetY()).position);
372 #else
373     return static_cast<int32_t>(paragraph_->GetGlyphIndexByCoordinate(clickOffset_.GetX(), clickOffset_.GetY()).index);
374 #endif
375 }
376 
377 int32_t TextOverlayBase::GetGraphemeClusterLength(int32_t extend, bool isPrefix) const
378 {
379     auto text = textForDisplay_;
380     char16_t aroundChar = 0;
381     if (isPrefix) {
382         if (static_cast<size_t>(extend) <= text.length()) {
383             aroundChar = text[std::max(0, extend - 1)];
384         }
385     } else {
386         if (static_cast<size_t>(extend) < (text.length())) {
387             aroundChar = text[std::min(text.length() ? static_cast<int32_t>(text.length()) - 1 : 0, extend)];
388         }
389     }
390     return StringUtils::NotInUtf16Bmp(aroundChar) ? 2 : 1;
391 }
392 
393 void TextOverlayBase::InitAnimation(const WeakPtr<PipelineContext>& pipelineContext)
394 {
395     auto context = pipelineContext.Upgrade();
396     if (!context) {
397         LOGE("Context is null.");
398         return;
399     }
400 
401     if (!textOverlay_) {
402         LOGE("InitAnimation error, textOverlay is nullptr");
403         return;
404     }
405 
406     // Get the handleDiameter in theme, textoverlay is not nullptr
407     double initHandleDiameter = textOverlay_->GetHandleDiameter().Value();
408     double initHandleDiameterInner = textOverlay_->GetHandleDiameterInner().Value();
409 
410     // Add the animation for handleDiameter
411     auto diameterAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(
412         initHandleDiameter * FIFTY_PERCENT, initHandleDiameter, Curves::ELASTICS);
413     diameterAnimation->AddListener([weak = AceType::WeakClaim(this)](double value) {
414         auto renderNode = weak.Upgrade();
415         if (renderNode && renderNode->updateHandleDiameter_) {
416             renderNode->updateHandleDiameter_(value);
417         }
418     });
419 
420     // Add the animation for handleDiameterinner
421     auto diameterInnerAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(
422         initHandleDiameterInner * FIFTY_PERCENT, initHandleDiameterInner, Curves::ELASTICS);
423     diameterInnerAnimation->AddListener([weak = AceType::WeakClaim(this)](double value) {
424         auto renderNode = weak.Upgrade();
425         if (renderNode && renderNode->updateHandleDiameterInner_) {
426             renderNode->updateHandleDiameterInner_(value);
427         }
428     });
429 
430     // Add the animation
431     animator_ = CREATE_ANIMATOR(context);
432     animator_->AddInterpolator(diameterAnimation);
433     animator_->AddInterpolator(diameterInnerAnimation);
434     animator_->SetDuration(SHOW_HANDLE_DURATION);
435     animator_->Play();
436 }
437 
438 #ifndef USE_ROSEN_DRAWING
439 void TextOverlayBase::PaintSelection(SkCanvas* canvas, const Offset& globalOffset)
440 #else
441 void TextOverlayBase::PaintSelection(RSCanvas* canvas, const Offset& globalOffset)
442 #endif
443 {
444     selectedRect_.clear();
445     if (!IsSelectiveDevice()) {
446         return;
447     }
448     using namespace Constants;
449 
450     if (!paragraph_ || (canvas == nullptr)) {
451         return;
452     }
453     const auto& selection = textValue_.selection;
454     if (textValue_.text.empty() || selection.GetStart() == selection.GetEnd()) {
455         return;
456     }
457 #ifndef USE_GRAPHIC_TEXT_GINE
458     const auto& boxes = paragraph_->GetRectsForRange(selection.GetStart(), selection.GetEnd(),
459         txt::Paragraph::RectHeightStyle::kMax, txt::Paragraph::RectWidthStyle::kTight);
460 #else
461     const auto& boxes = paragraph_->GetTextRectsByBoundary(selection.GetStart(), selection.GetEnd(),
462         Rosen::TextRectHeightStyle::COVER_TOP_AND_BOTTOM, Rosen::TextRectWidthStyle::TIGHT);
463 #endif
464     if (boxes.empty()) {
465         return;
466     }
467 #ifndef USE_ROSEN_DRAWING
468     canvas->save();
469     SkPaint paint;
470     paint.setColor(selectedColor_.GetValue());
471     Offset effectiveOffset = textOffsetForShowCaret_;
472     for (const auto& box : boxes) {
473         auto selectionRect = ConvertSkRect(box.rect) + effectiveOffset;
474         selectedRect_.emplace_back(selectionRect + globalOffset);
475 #ifndef USE_GRAPHIC_TEXT_GINE
476         if (box.direction == txt::TextDirection::ltr) {
477 #else
478         if (box.direction == Rosen::TextDirection::LTR) {
479 #endif
480             canvas->drawRect(SkRect::MakeLTRB(selectionRect.Left(), selectionRect.Top(), selectionRect.Right(),
481                                  selectionRect.Bottom()),
482                 paint);
483         } else {
484             canvas->drawRect(SkRect::MakeLTRB(selectionRect.Right(), selectionRect.Top(), selectionRect.Left(),
485                                  selectionRect.Bottom()),
486                 paint);
487         }
488     }
489     canvas->restore();
490 #else
491     canvas->Save();
492     RSPen pen;
493     pen.SetColor(selectedColor_.GetValue());
494     Offset effectiveOffset = textOffsetForShowCaret_;
495     canvas->AttachPen(pen);
496     for (const auto& box : boxes) {
497         auto selectionRect = ConvertSkRect(box.rect) + effectiveOffset;
498         selectedRect_.emplace_back(selectionRect + globalOffset);
499 #ifndef USE_GRAPHIC_TEXT_GINE
500         if (box.direction == txt::TextDirection::ltr) {
501 #else
502         if (box.direction == Rosen::TextDirection::LTR) {
503 #endif
504             canvas->DrawRect(
505                 RSRect(selectionRect.Left(), selectionRect.Top(), selectionRect.Right(), selectionRect.Bottom()));
506         } else {
507             canvas->DrawRect(
508                 RSRect(selectionRect.Right(), selectionRect.Top(), selectionRect.Left(), selectionRect.Bottom()));
509         }
510     }
511     canvas->DetachPen();
512     canvas->Restore();
513 #endif
514 }
515 
516 void TextOverlayBase::InitSelection(const Offset& pos, const Offset& globalOffset)
517 {
518     int32_t extend = GetCursorPositionForClick(pos, globalOffset);
519     int32_t extendEnd = extend + GetGraphemeClusterLength(extend, false);
520     textValue_.UpdateSelection(extend, extendEnd);
521 }
522 
523 void TextOverlayBase::UpdateStartSelection(int32_t end, const Offset& pos, const Offset& globalOffset)
524 {
525     int32_t extend = GetCursorPositionForClick(pos, globalOffset);
526     textValue_.UpdateSelection(extend, end);
527 }
528 
529 void TextOverlayBase::UpdateEndSelection(int32_t start, const Offset& pos, const Offset& globalOffset)
530 {
531     int32_t extend = GetCursorPositionForClick(pos, globalOffset);
532     textValue_.UpdateSelection(start, extend);
533 }
534 
535 void TextOverlayBase::ChangeSelection(int32_t start, int32_t end)
536 {
537     textValue_.UpdateSelection(start, end);
538 }
539 
540 RefPtr<TextOverlayManager> TextOverlayBase::GetTextOverlayManager(const WeakPtr<PipelineContext>& pipelineContext)
541 {
542     auto context = pipelineContext.Upgrade();
543     if (!context) {
544         return nullptr;
545     }
546 
547     return context->GetTextOverlayManager();
548 }
549 
550 TextOverlayManager::TextOverlayManager(const WeakPtr<PipelineContext>& context)
551 {
552     context_ = context;
553 }
554 
555 TextOverlayManager::~TextOverlayManager() = default;
556 
557 const RefPtr<RenderNode> TextOverlayManager::GetTargetNode() const
558 {
559     auto textOverlayBase = textOverlayBase_.Upgrade();
560     if (!textOverlayBase) {
561         return nullptr;
562     }
563 
564     auto targetNode = AceType::DynamicCast<RenderNode>(textOverlayBase);
565     if (!targetNode) {
566         return nullptr;
567     }
568     return targetNode;
569 }
570 
571 void TextOverlayManager::PopTextOverlay()
572 {
573     coordinateOffset_ = Offset();
574     const auto& stackElement = stackElement_.Upgrade();
575     if (stackElement) {
576         stackElement->PopTextOverlay();
577     }
578 }
579 
580 void TextOverlayManager::PushTextOverlayToStack(
581     const RefPtr<TextOverlayComponent>& textOverlay, const WeakPtr<PipelineContext>& pipelineContext)
582 {
583     if (!textOverlay) {
584         LOGE("TextOverlay is null");
585         return;
586     }
587 
588     auto context = pipelineContext.Upgrade();
589     if (!context) {
590         LOGE("Context is null");
591         return;
592     }
593 
594     auto lastStack = context->GetLastStack();
595     if (!lastStack) {
596         LOGE("LastStack is null");
597         return;
598     }
599 
600     lastStack->PushComponent(textOverlay, false);
601     stackElement_ = WeakClaim(RawPtr(lastStack));
602 }
603 
604 void TextOverlayManager::HandleCtrlC() const
605 {
606     auto context = context_.Upgrade();
607     if (!context) {
608         LOGE("get context fail");
609         return;
610     }
611     auto clipboard = ClipboardProxy::GetInstance()->GetClipboard(context->GetTaskExecutor());
612     if (!clipboard) {
613         LOGE("get clipboard fail");
614         return;
615     }
616     auto textOverlayBase = textOverlayBase_.Upgrade();
617     if (!textOverlayBase) {
618         LOGE("get textOverlayBase fail");
619         return;
620     }
621     clipboard->SetData(textOverlayBase->GetSelectedContent());
622 }
623 
624 bool TextOverlayBase::IsSelectedText(const Offset& pos, const Offset& globalOffset)
625 {
626     int32_t tempText = GetCursorPositionForClick(pos, globalOffset);
627     return (tempText >= textValue_.selection.GetStart() && tempText <= textValue_.selection.GetEnd());
628 }
629 
630 } // namespace OHOS::Ace
631