• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-2023 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 #include "core/components_ng/pattern/text_field/text_select_controller.h"
16 
17 #include "base/geometry/ng/rect_t.h"
18 #include "base/geometry/offset.h"
19 #include "base/log/log_wrapper.h"
20 #include "base/utils/utf_helper.h"
21 #include "base/utils/utils.h"
22 #include "core/common/ai/data_detector_mgr.h"
23 #include "core/components_ng/pattern/text_field/text_field_pattern.h"
24 #include "core/components_ng/pattern/text_field/text_input_ai_checker.h"
25 
26 namespace OHOS::Ace::NG {
27 namespace {
28 const std::string NEWLINE = "\n";
29 const std::u16string WIDE_NEWLINE = UtfUtils::Str8DebugToStr16(NEWLINE);
30 } // namespace
UpdateHandleIndex(int32_t firstHandleIndex,int32_t secondHandleIndex)31 void TextSelectController::UpdateHandleIndex(int32_t firstHandleIndex, int32_t secondHandleIndex)
32 {
33     firstHandleInfo_.index = firstHandleIndex;
34     secondHandleInfo_.index = secondHandleIndex;
35     caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
36     CalculateHandleOffset();
37     UpdateCaretOffset();
38 }
39 
UpdateCaretIndex(int32_t index)40 void TextSelectController::UpdateCaretIndex(int32_t index)
41 {
42     auto newIndex = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetTextUtf16Value().length()));
43     caretInfo_.index = newIndex;
44     TAG_LOGD(AceLogTag::ACE_TEXT_FIELD, "newIndex change to %{public}d", newIndex);
45     firstHandleInfo_.index = newIndex;
46     secondHandleInfo_.index = newIndex;
47 }
48 
CalculateEmptyValueCaretRect(float width)49 RectF TextSelectController::CalculateEmptyValueCaretRect(float width)
50 {
51     RectF rect;
52     auto pattern = pattern_.Upgrade();
53     CHECK_NULL_RETURN(pattern, rect);
54     auto textField = DynamicCast<TextFieldPattern>(pattern);
55     CHECK_NULL_RETURN(textField, rect);
56     auto layoutProperty = textField->GetLayoutProperty<TextFieldLayoutProperty>();
57     CHECK_NULL_RETURN(layoutProperty, rect);
58     rect.SetOffset(contentRect_.GetOffset());
59     rect.SetHeight(textField->PreferredLineHeight());
60     rect.SetWidth(GreatNotEqual(width, 0.0f) ? width : caretInfo_.rect.Width());
61     auto textAlign = layoutProperty->GetTextAlignValue(TextAlign::START);
62     auto direction = layoutProperty->GetNonAutoLayoutDirection();
63     textField->CheckTextAlignByDirection(textAlign, direction);
64 
65     switch (textAlign) {
66         case TextAlign::START:
67             rect.SetLeft(contentRect_.GetX());
68             break;
69         case TextAlign::CENTER:
70             if (layoutProperty->GetPlaceholderValue(u"").empty() || !paragraph_) {
71                 rect.SetLeft(static_cast<float>(contentRect_.GetX()) + contentRect_.Width() / 2.0f);
72             } else {
73                 CaretMetricsF caretMetrics;
74                 CalcCaretMetricsByPosition(0, caretMetrics, TextAffinity::DOWNSTREAM);
75                 rect.SetLeft(caretMetrics.offset.GetX());
76             }
77             break;
78         case TextAlign::END:
79             rect.SetLeft(static_cast<float>(contentRect_.GetX()) + contentRect_.Width() -
80                          static_cast<float>(rect.Width()));
81             break;
82         default:
83             break;
84     }
85 
86     auto align = textField->IsTextArea() ? Alignment::TOP_CENTER : Alignment::CENTER;
87     if (layoutProperty->GetPositionProperty()) {
88         align = layoutProperty->GetPositionProperty()->GetAlignment().value_or(align);
89     }
90     OffsetF offset = Alignment::GetAlignPosition(contentRect_.GetSize(), rect.GetSize(), align);
91     rect.SetTop(offset.GetY() + contentRect_.GetY());
92     if (textAlign != TextAlign::END) {
93         AdjustHandleAtEdge(rect);
94     }
95     if (GreatNotEqual(rect.Height(), contentRect_.Height()) && !textField->IsTextArea()) {
96         rect.SetHeight(contentRect_.Height());
97     }
98     return rect;
99 }
100 
FitCaretMetricsToTouchPoint(CaretMetricsF & caretMetrics,const Offset & touchOffset)101 void TextSelectController::FitCaretMetricsToTouchPoint(CaretMetricsF& caretMetrics, const Offset& touchOffset)
102 {
103     auto index = ConvertTouchOffsetToPosition(touchOffset);
104     // adust Y to align in one line center.
105     AdjustCursorPosition(index, touchOffset);
106     CalcCaretMetricsByPositionNearTouchOffset(index, caretMetrics,
107         OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
108 
109     // ajust X to support display caret at anywhere
110     caretMetrics.offset.SetX(touchOffset.GetX());
111 }
112 
FitCaretMetricsToContentRect(CaretMetricsF & caretMetrics)113 void TextSelectController::FitCaretMetricsToContentRect(CaretMetricsF& caretMetrics)
114 {
115     auto pattern = pattern_.Upgrade();
116     CHECK_NULL_VOID(pattern);
117     auto textField = DynamicCast<TextFieldPattern>(pattern);
118     CHECK_NULL_VOID(textField);
119     if (GreatNotEqual(caretMetrics.height, contentRect_.Height()) && !textField->IsTextArea()) {
120         caretMetrics.offset.SetY(caretMetrics.offset.GetY() + caretMetrics.height - contentRect_.Height());
121         caretMetrics.height = contentRect_.Height();
122     }
123 }
124 
CalcCaretMetricsByPosition(int32_t extent,CaretMetricsF & caretCaretMetric,TextAffinity textAffinity)125 void TextSelectController::CalcCaretMetricsByPosition(
126     int32_t extent, CaretMetricsF& caretCaretMetric, TextAffinity textAffinity)
127 {
128     CHECK_NULL_VOID(paragraph_);
129     paragraph_->CalcCaretMetricsByPosition(extent, caretCaretMetric, textAffinity);
130     auto pattern = pattern_.Upgrade();
131     CHECK_NULL_VOID(pattern);
132     auto textField = DynamicCast<TextFieldPattern>(pattern);
133     CHECK_NULL_VOID(textField);
134     auto textRect = textField->GetTextRect();
135     caretCaretMetric.offset.AddX(textRect.GetX());
136     caretCaretMetric.offset.AddY(textRect.GetY());
137     FitCaretMetricsToContentRect(caretCaretMetric);
138 }
139 
CalcCaretMetricsByPositionNearTouchOffset(int32_t extent,CaretMetricsF & caretMetrics,const OffsetF & touchOffset)140 void TextSelectController::CalcCaretMetricsByPositionNearTouchOffset(
141     int32_t extent, CaretMetricsF& caretMetrics, const OffsetF& touchOffset)
142 {
143     CHECK_NULL_VOID(paragraph_);
144     auto pattern = pattern_.Upgrade();
145     CHECK_NULL_VOID(pattern);
146     auto textField = DynamicCast<TextFieldPattern>(pattern);
147     CHECK_NULL_VOID(textField);
148     auto textRect = textField->GetTextRect();
149     paragraph_->CalcCaretMetricsByPosition(extent, caretMetrics, touchOffset - textRect.GetOffset(), textAffinity_);
150     caretMetrics.offset.AddX(textRect.GetX());
151     caretMetrics.offset.AddY(textRect.GetY());
152     FitCaretMetricsToContentRect(caretMetrics);
153 }
154 
UpdateCaretRectByPositionNearTouchOffset(int32_t position,const Offset & touchOffset)155 void TextSelectController::UpdateCaretRectByPositionNearTouchOffset(int32_t position, const Offset& touchOffset)
156 {
157     CaretMetricsF caretMetrics;
158     CalcCaretMetricsByPositionNearTouchOffset(position, caretMetrics,
159         OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
160 
161     caretInfo_.UpdateOffset(caretMetrics.offset);
162     UpdateCaretHeight(caretMetrics.height);
163 }
164 
UpdateCaretInfoByOffset(const Offset & localOffset,bool moveContent,bool floatCaret)165 void TextSelectController::UpdateCaretInfoByOffset(const Offset& localOffset, bool moveContent, bool floatCaret)
166 {
167     auto index = ConvertTouchOffsetToPosition(localOffset);
168     AdjustCursorPosition(index, localOffset);
169     UpdateCaretIndex(index);
170     if (!contentController_->IsEmpty()) {
171         UpdateCaretRectByPositionNearTouchOffset(index, localOffset);
172         auto offset = caretInfo_.rect.GetOffset();
173         if (moveContent) {
174             MoveHandleToContentRect(caretInfo_.rect, 0.0f);
175         } else {
176             AdjustHandleAtEdge(caretInfo_.rect);
177         }
178         UpdateCaretOriginalRect(offset);
179     } else {
180         SetCaretRectAtEmptyValue();
181     }
182 
183     CHECK_NULL_VOID(floatCaret);
184     auto pattern = pattern_.Upgrade();
185     CHECK_NULL_VOID(pattern);
186     auto textField = DynamicCast<TextFieldPattern>(pattern);
187     CHECK_NULL_VOID(textField);
188     textField->AdjustFloatingCaretInfo(localOffset, caretInfo_, floatingCaretInfo_);
189 }
190 
CalcCaretOffsetByOffset(const Offset & localOffset)191 OffsetF TextSelectController::CalcCaretOffsetByOffset(const Offset& localOffset)
192 {
193     auto index = ConvertTouchOffsetToPosition(localOffset);
194     AdjustCursorPosition(index, localOffset);
195     if (!contentController_->IsEmpty()) {
196         CaretMetricsF caretMetrics;
197         CalcCaretMetricsByPositionNearTouchOffset(index, caretMetrics,
198             OffsetF(static_cast<float>(localOffset.GetX()), static_cast<float>(localOffset.GetY())));
199         return caretMetrics.offset;
200     } else {
201         return CalculateEmptyValueCaretRect().GetOffset();
202     }
203 }
204 
ConvertTouchOffsetToPosition(const Offset & localOffset,bool isSelectionPos) const205 int32_t TextSelectController::ConvertTouchOffsetToPosition(const Offset& localOffset, bool isSelectionPos) const
206 {
207     CHECK_NULL_RETURN(paragraph_, 0);
208     if (contentController_->IsEmpty()) {
209         return 0;
210     }
211     auto pattern = pattern_.Upgrade();
212     CHECK_NULL_RETURN(pattern, 0);
213     auto textField = DynamicCast<TextFieldPattern>(pattern);
214     CHECK_NULL_RETURN(textField, 0);
215     auto textRect = textField->GetTextRect();
216     auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
217     return paragraph_->GetGlyphIndexByCoordinate(offset, isSelectionPos);
218 }
219 
UpdateSelectByOffset(const Offset & localOffset)220 void TextSelectController::UpdateSelectByOffset(const Offset& localOffset)
221 {
222     CHECK_NULL_VOID(paragraph_ && !contentController_->IsEmpty());
223     auto pattern = pattern_.Upgrade();
224     CHECK_NULL_VOID(pattern);
225     auto textField = DynamicCast<TextFieldPattern>(pattern);
226     CHECK_NULL_VOID(textField);
227     auto textRect = textField->GetTextRect();
228     auto contentRect = textField->GetTextContentRect();
229     auto touchLocalOffset = localOffset;
230     if (textField->IsTextArea() && GreatNotEqual(touchLocalOffset.GetY(), textRect.Bottom())) {
231         // click at end of a paragraph.
232         touchLocalOffset.SetX(textField->IsLTRLayout() ? contentRect.Right() : textRect.Left());
233     }
234 
235     auto range = GetSelectRangeByOffset(touchLocalOffset);
236     int32_t start = range.first;
237     int32_t end = range.second;
238     UpdateHandleIndex(start, end);
239     if (IsSelected()) {
240         MoveFirstHandleToContentRect(GetFirstHandleIndex(), false);
241         MoveSecondHandleToContentRect(GetSecondHandleIndex(), false);
242     } else {
243         MoveCaretToContentRect(GetCaretIndex());
244     }
245 }
246 
UpdateSelectPragraphByOffset(const Offset & localOffset)247 void TextSelectController::UpdateSelectPragraphByOffset(const Offset& localOffset)
248 {
249     CHECK_NULL_VOID(paragraph_ && !contentController_->IsEmpty());
250 
251     auto range = GetSelectParagraphByOffset(localOffset);
252     int32_t start = range.first;
253     int32_t end = range.second;
254     UpdateHandleIndex(start, end);
255     if (IsSelected()) {
256         MoveFirstHandleToContentRect(GetFirstHandleIndex());
257         MoveSecondHandleToContentRect(GetSecondHandleIndex());
258     } else {
259         MoveCaretToContentRect(GetCaretIndex());
260     }
261 }
262 
GetSelectRangeByOffset(const Offset & localOffset)263 std::pair<int32_t, int32_t> TextSelectController::GetSelectRangeByOffset(const Offset& localOffset)
264 {
265     std::pair<int32_t, int32_t> err(-1, -1);
266     CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), err);
267     int32_t start = 0;
268     int32_t end = 0;
269     auto pos = ConvertTouchOffsetToPosition(localOffset, true);
270     // Ensure that the end is selected.
271     if (pos >= static_cast<int32_t>(paragraph_->GetParagraphText().length())) {
272         pos -= 1;
273     }
274 
275     auto pattern = pattern_.Upgrade();
276     CHECK_NULL_RETURN(pattern, err);
277     auto textField = DynamicCast<TextFieldPattern>(pattern);
278     CHECK_NULL_RETURN(textField, err);
279     bool smartSelect = false;
280     if (!textField->IsUsingMouse()) {
281         smartSelect = AdjustWordSelection(pos, start, end, localOffset);
282     }
283 
284     if (!smartSelect && !paragraph_->GetWordBoundary(pos, start, end)) {
285         start = pos;
286         end = std::min(static_cast<int32_t>(contentController_->GetTextUtf16Value().length()),
287             pos + GetGraphemeClusterLength(contentController_->GetTextUtf16Value(), pos, true));
288     }
289     if (SystemProperties::GetDebugEnabled()) {
290         TAG_LOGD(AceLogTag::ACE_TEXT,
291             "current word position = %{public}d, select position {start:%{public}d, end:%{public}d}", pos, start, end);
292     }
293     return { start, end };
294 }
295 
GetSelectParagraphByOffset(const Offset & localOffset)296 std::pair<int32_t, int32_t> TextSelectController::GetSelectParagraphByOffset(const Offset& localOffset)
297 {
298     std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
299     std::pair<int32_t, int32_t> err(-1, -1);
300     CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), err);
301     int32_t start = 0;
302     int32_t end = 0;
303     auto pos = ConvertTouchOffsetToPosition(localOffset, true);
304     // Ensure that the end is selected.
305     if (pos >= static_cast<int32_t>(paragraph_->GetParagraphText().length())) {
306         pos -= 1;
307     }
308 
309     auto pattern = pattern_.Upgrade();
310     CHECK_NULL_RETURN(pattern, err);
311     auto textField = DynamicCast<TextFieldPattern>(pattern);
312     CHECK_NULL_RETURN(textField, err);
313     if (!textField->IsUsingMouse()) {
314         AdjustWordSelection(pos, start, end, localOffset);
315     }
316 
317     GetSubParagraphByOffset(pos, start, end);
318 
319     if (SystemProperties::GetDebugEnabled()) {
320         TAG_LOGD(AceLogTag::ACE_TEXT,
321             "current word position = %{public}d, select position {start:%{public}d, end:%{public}d}", pos, start, end);
322     }
323     return { start, end };
324 }
325 
GetSubParagraphByOffset(int32_t pos,int32_t & start,int32_t & end)326 void TextSelectController::GetSubParagraphByOffset(int32_t pos, int32_t &start, int32_t &end)
327 {
328     auto data = contentController_->GetTextUtf16Value();
329     auto dataLen = static_cast<int32_t>(data.length());
330     bool leftContinue = true;
331     bool rightContinue = true;
332     int32_t offset = 0;
333     if (pos <= 0) {
334         leftContinue = false;
335         start = 0;
336     }
337     while (leftContinue || rightContinue) {
338         if (leftContinue) {
339             if (pos - offset < 0 || pos - offset >= dataLen ||
340                 data[pos - offset] == WIDE_NEWLINE[0]) {
341                 start = pos - offset + 1;
342                 leftContinue = false;
343             }
344         }
345         if (rightContinue) {
346             if (pos + offset >= dataLen || data[pos + offset] == WIDE_NEWLINE[0]) {
347                 end = pos + offset;
348                 rightContinue = false;
349             }
350         }
351         ++offset;
352     }
353 }
354 
GetGraphemeClusterLength(const std::u16string & text,int32_t extend,bool checkPrev)355 int32_t TextSelectController::GetGraphemeClusterLength(const std::u16string& text, int32_t extend, bool checkPrev)
356 {
357     char16_t aroundChar = 0;
358     if (static_cast<size_t>(extend) <= text.length()) {
359         if (checkPrev) {
360             aroundChar = text[std::max(0, extend - 1)];
361         } else {
362             aroundChar = text[std::min(static_cast<int32_t>(text.length() - 1), extend)];
363         }
364     }
365     return StringUtils::NotInUtf16Bmp(aroundChar) ? 2 : 1;
366 }
367 
CalculateHandleOffset()368 void TextSelectController::CalculateHandleOffset()
369 {
370     // calculate firstHandleOffset, secondHandleOffset and handlePaintSize
371     if (contentController_->IsEmpty()) {
372         SetCaretRectAtEmptyValue();
373         return;
374     }
375     CaretMetricsF secondHandleMetrics;
376     CalcCaretMetricsByPosition(GetSecondHandleIndex(), secondHandleMetrics, TextAffinity::UPSTREAM);
377     RectF secondHandle;
378     secondHandle.SetOffset(secondHandleMetrics.offset);
379     secondHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), secondHandleMetrics.height });
380     secondHandle.SetHeight(secondHandleMetrics.height);
381     AdjustHandleOffset(secondHandle);
382     secondHandleInfo_.rect = secondHandle;
383 
384     if (!IsSelected()) {
385         return;
386     }
387 
388     CaretMetricsF firstHandleMetrics;
389     CalcCaretMetricsByPosition(GetFirstHandleIndex(), firstHandleMetrics, TextAffinity::DOWNSTREAM);
390     OffsetF firstHandleOffset = firstHandleMetrics.offset;
391 
392     RectF firstHandle;
393     firstHandle.SetOffset(firstHandleOffset);
394     firstHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), firstHandleMetrics.height });
395     AdjustHandleOffset(firstHandle);
396     firstHandleInfo_.rect = firstHandle;
397 }
398 
ToString() const399 std::string TextSelectController::ToString() const
400 {
401     std::string result;
402     result.append("first handle offset: ");
403     result.append(std::to_string(firstHandleInfo_.index));
404     result.append(", second handle offset: ");
405     result.append(std::to_string(secondHandleInfo_.index));
406     return result;
407 }
408 
GetSelectedRects() const409 std::vector<RectF> TextSelectController::GetSelectedRects() const
410 {
411     if (!IsSelected()) {
412         return {};
413     }
414     std::vector<RectF> selectedRects;
415     CHECK_NULL_RETURN(paragraph_, selectedRects);
416     paragraph_->GetRectsForRange(GetStartIndex(), GetEndIndex(), selectedRects);
417     return selectedRects;
418 }
419 
MoveHandleToContentRect(RectF & handleRect,float boundaryAdjustment) const420 void TextSelectController::MoveHandleToContentRect(RectF& handleRect, float boundaryAdjustment) const
421 {
422     auto pattern = pattern_.Upgrade();
423     CHECK_NULL_VOID(pattern);
424     auto textField = DynamicCast<TextFieldPattern>(pattern);
425     CHECK_NULL_VOID(textField);
426     auto textRect = textField->GetTextRect();
427     if (GreatNotEqual(textRect.Height(), contentRect_.Height())) {
428         auto contentBottomBoundary = contentRect_.GetY() + contentRect_.Height();
429         if (LessNotEqual(handleRect.GetY(), contentRect_.GetY()) &&
430             LessOrEqual(handleRect.Height(), contentRect_.Height())) {
431             auto dy = contentRect_.GetY() - handleRect.GetY();
432             textRect.SetTop(textRect.GetY() + dy);
433             handleRect.SetTop(handleRect.GetY() + dy);
434         } else if (GreatNotEqual(handleRect.GetY() + handleRect.Height(), contentBottomBoundary)) {
435             auto dy = handleRect.GetY() + handleRect.Height() - contentBottomBoundary;
436             textRect.SetTop(textRect.GetY() - dy);
437             handleRect.SetTop(handleRect.GetY() - dy);
438         } else if (LessNotEqual(handleRect.GetY() + handleRect.Height(),
439             contentRect_.GetY() + contentRect_.Height()) &&
440             GreatNotEqual(handleRect.Height(), contentRect_.Height())) {
441             auto dy = contentRect_.GetY() - handleRect.GetY();
442             textRect.SetTop(textRect.GetY() + dy);
443             handleRect.SetTop(handleRect.GetY() + dy);
444         }
445     }
446 
447     if (GreatNotEqual(textRect.Width(), contentRect_.Width())) {
448         auto contentRightBoundary = contentRect_.GetX() + contentRect_.Width() - boundaryAdjustment;
449         if (LessNotEqual(handleRect.GetX(), contentRect_.GetX())) {
450             auto dx = contentRect_.GetX() - handleRect.GetX();
451             textRect.SetLeft(textRect.GetX() + dx);
452             handleRect.SetLeft(handleRect.GetX() + dx);
453         } else if (GreatNotEqual(handleRect.GetX(), contentRightBoundary)) {
454             auto dx = handleRect.GetX() - contentRightBoundary;
455             textRect.SetLeft(textRect.GetX() - dx);
456             handleRect.SetLeft(handleRect.GetX() - dx);
457         }
458     }
459     textField->SetTextRect(textRect);
460     AdjustHandleAtEdge(handleRect);
461     textField->UpdateScrollBarOffset();
462     TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "after move, handleRect.GetX():%{public}f,handleRect.GetY():%{public}f",
463         handleRect.GetX(), handleRect.GetY());
464 }
465 
AdjustHandleAtEdge(RectF & handleRect) const466 void TextSelectController::AdjustHandleAtEdge(RectF& handleRect) const
467 {
468     AdjustHandleOffset(handleRect);
469     AdjustHandleInBoundary(handleRect);
470 }
471 
AdjustHandleOffset(RectF & handleRect) const472 void TextSelectController::AdjustHandleOffset(RectF& handleRect) const
473 {
474     handleRect.SetOffset(OffsetF(handleRect.GetX() - handleRect.Width() / 2.0f, handleRect.GetY()));
475     // The handle position does not extend beyond the left edge of the text.
476     auto pattern = pattern_.Upgrade();
477     CHECK_NULL_VOID(pattern);
478     auto textField = DynamicCast<TextFieldPattern>(pattern);
479     CHECK_NULL_VOID(textField);
480     auto textRect = textField->GetTextRect();
481     if (LessNotEqual(handleRect.GetX(), textRect.GetX())) {
482         handleRect.SetOffset(OffsetF(textRect.GetX(), handleRect.GetY()));
483     }
484 }
485 
MoveFirstHandleToContentRect(int32_t index,bool moveHandle,bool moveContent)486 void TextSelectController::MoveFirstHandleToContentRect(int32_t index, bool moveHandle, bool moveContent)
487 {
488     CaretMetricsF firstHandleMetrics;
489     firstHandleInfo_.index = index;
490     CalcCaretMetricsByPosition(
491         GetFirstHandleIndex(), firstHandleMetrics, HasReverse() ? TextAffinity::UPSTREAM : TextAffinity::DOWNSTREAM);
492     OffsetF firstHandleOffset = firstHandleMetrics.offset;
493     RectF firstHandle;
494     firstHandle.SetOffset(firstHandleOffset);
495     firstHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), firstHandleMetrics.height });
496     if (moveContent) {
497         MoveHandleToContentRect(firstHandle);
498     } else {
499         AdjustHandleAtEdge(firstHandle);
500     }
501     firstHandleInfo_.rect = firstHandle;
502 
503     caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
504     UpdateCaretOffset(TextAffinity::DOWNSTREAM, moveHandle);
505     UpdateSecondHandleOffset();
506 }
507 
MoveSecondHandleToContentRect(int32_t index,bool moveHandle,bool moveContent)508 void TextSelectController::MoveSecondHandleToContentRect(int32_t index, bool moveHandle, bool moveContent)
509 {
510     CaretMetricsF secondHandleMetrics;
511     secondHandleInfo_.index = index;
512     CalcCaretMetricsByPosition(
513         GetSecondHandleIndex(), secondHandleMetrics, HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM);
514     RectF secondHandle;
515     secondHandle.SetOffset(secondHandleMetrics.offset);
516     secondHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), secondHandleMetrics.height });
517     if (moveContent) {
518         MoveHandleToContentRect(secondHandle);
519     } else {
520         AdjustHandleAtEdge(secondHandle);
521     }
522     secondHandleInfo_.rect = secondHandle;
523 
524     caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
525     UpdateCaretOffset(TextAffinity::DOWNSTREAM, moveHandle);
526     UpdateFirstHandleOffset();
527 }
528 
MoveCaretToContentRect(int32_t index,TextAffinity textAffinity,bool isEditorValueChanged,bool moveContent)529 void TextSelectController::MoveCaretToContentRect(
530     int32_t index, TextAffinity textAffinity, bool isEditorValueChanged, bool moveContent)
531 {
532     if (isEditorValueChanged) {
533         textAffinity_ = textAffinity;
534     }
535     index = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetTextUtf16Value().length()));
536     CaretMetricsF caretMetrics;
537     caretInfo_.index = index;
538     firstHandleInfo_.index = index;
539     secondHandleInfo_.index = index;
540     if (contentController_->IsEmpty()) {
541         SetCaretRectAtEmptyValue();
542         return;
543     }
544     CalcCaretMetricsByPosition(GetCaretIndex(), caretMetrics, textAffinity_);
545     OffsetF caretOffset = caretMetrics.offset;
546     RectF caretRect;
547     caretRect.SetOffset(caretOffset);
548     auto pattern = pattern_.Upgrade();
549     CHECK_NULL_VOID(pattern);
550     auto textField = DynamicCast<TextFieldPattern>(pattern);
551     CHECK_NULL_VOID(textField);
552     caretRect.SetSize({ caretInfo_.rect.Width(),
553         LessOrEqual(caretMetrics.height, 0.0) ? textField->PreferredLineHeight() : caretMetrics.height });
554 
555     // Adjusts one character width.
556     float boundaryAdjustment = 0.0f;
557     auto textRect = textField->GetTextRect();
558     if (isEditorValueChanged && GreatNotEqual(textRect.Width(), contentRect_.Width()) &&
559         GreatNotEqual(contentRect_.Width(), 0.0) &&
560         caretInfo_.index < static_cast<int32_t>(contentController_->GetTextUtf16Value().length()) && paragraph_) {
561         boundaryAdjustment = paragraph_->GetCharacterWidth(caretInfo_.index);
562         if (SystemProperties::GetDebugEnabled()) {
563             TAG_LOGD(AceLogTag::ACE_TEXT, "caretInfo_.index = %{public}d, boundaryAdjustment =%{public}f",
564                 caretInfo_.index, boundaryAdjustment);
565         }
566     }
567     if (moveContent) {
568         MoveHandleToContentRect(caretRect, boundaryAdjustment);
569     } else {
570         AdjustHandleAtEdge(caretRect);
571     }
572     caretInfo_.rect = caretRect;
573     UpdateCaretOriginalRect(caretMetrics.offset);
574 }
575 
MoveCaretAnywhere(const Offset & touchOffset)576 void TextSelectController::MoveCaretAnywhere(const Offset& touchOffset)
577 {
578     CaretMetricsF caretMetrics;
579     if (contentController_->IsEmpty()) {
580         SetCaretRectAtEmptyValue();
581         return;
582     }
583     FitCaretMetricsToTouchPoint(caretMetrics, touchOffset);
584     OffsetF caretOffset = caretMetrics.offset;
585     RectF caretRect;
586     caretRect.SetOffset(caretOffset);
587     auto pattern = pattern_.Upgrade();
588     CHECK_NULL_VOID(pattern);
589     auto textField = DynamicCast<TextFieldPattern>(pattern);
590     CHECK_NULL_VOID(textField);
591     caretRect.SetSize({ caretInfo_.rect.Width(),
592         LessOrEqual(caretMetrics.height, 0.0) ? textField->PreferredLineHeight() : caretMetrics.height });
593 
594     // Adjusts one character width.
595     float boundaryAdjustment = 0.0f;
596     MoveHandleToContentRect(caretRect, boundaryAdjustment);
597     caretInfo_.rect = caretRect;
598     UpdateCaretOriginalRect(caretMetrics.offset);
599     auto index = ConvertTouchOffsetToPosition(touchOffset);
600     AdjustCursorPosition(index, touchOffset);
601     UpdateCaretIndex(index);
602 }
603 
UpdateFirstHandleOffset()604 void TextSelectController::UpdateFirstHandleOffset()
605 {
606     CaretMetricsF caretMetrics;
607     CalcCaretMetricsByPosition(
608         GetFirstHandleIndex(), caretMetrics, HasReverse() ? TextAffinity::UPSTREAM : TextAffinity::DOWNSTREAM);
609     firstHandleInfo_.rect.SetOffset(caretMetrics.offset);
610     firstHandleInfo_.rect.SetHeight(caretMetrics.height);
611     firstHandleInfo_.rect.SetWidth(SelectHandleInfo::GetDefaultLineWidth().ConvertToPx());
612     AdjustHandleOffset(firstHandleInfo_.rect);
613     AdjustHandleOffsetWithBoundary(firstHandleInfo_.rect);
614 }
615 
UpdateSecondHandleOffset()616 void TextSelectController::UpdateSecondHandleOffset()
617 {
618     CaretMetricsF caretMetrics;
619     CalcCaretMetricsByPosition(
620         GetSecondHandleIndex(), caretMetrics, HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM);
621     secondHandleInfo_.rect.SetOffset(caretMetrics.offset);
622     secondHandleInfo_.rect.SetHeight(caretMetrics.height);
623     secondHandleInfo_.rect.SetWidth(SelectHandleInfo::GetDefaultLineWidth().ConvertToPx());
624     AdjustHandleOffset(secondHandleInfo_.rect);
625     AdjustHandleOffsetWithBoundary(secondHandleInfo_.rect);
626 }
627 
UpdateCaretOffset(TextAffinity textAffinity,bool moveHandle)628 void TextSelectController::UpdateCaretOffset(TextAffinity textAffinity, bool moveHandle)
629 {
630     textAffinity_ = textAffinity;
631     if (contentController_->IsEmpty()) {
632         SetCaretRectAtEmptyValue();
633         return;
634     }
635     CaretMetricsF caretMetrics;
636     CalcCaretMetricsByPosition(GetCaretIndex(), caretMetrics, textAffinity);
637 
638     RectF caretRect;
639     caretRect.SetOffset(caretMetrics.offset);
640     auto pattern = pattern_.Upgrade();
641     CHECK_NULL_VOID(pattern);
642     auto textField = DynamicCast<TextFieldPattern>(pattern);
643     CHECK_NULL_VOID(textField);
644     caretRect.SetSize(SizeF(caretInfo_.rect.Width(),
645         LessOrEqual(caretMetrics.height, 0.0) ? textField->PreferredLineHeight() : caretMetrics.height));
646     caretInfo_.rect = caretRect;
647     if (moveHandle) {
648         MoveHandleToContentRect(caretInfo_.rect, 0.0f);
649     } else {
650         AdjustHandleAtEdge(caretInfo_.rect);
651     }
652     UpdateCaretOriginalRect(caretMetrics.offset);
653 }
654 
UpdateCaretOffset(const OffsetF & offset)655 void TextSelectController::UpdateCaretOffset(const OffsetF& offset)
656 {
657     caretInfo_.rect.SetOffset(caretInfo_.rect.GetOffset() + offset);
658     caretInfo_.originalRect.SetOffset(caretInfo_.originalRect.GetOffset() + offset);
659     secondHandleInfo_.UpdateOffset(caretInfo_.rect.GetOffset() + offset);
660 }
661 
UpdateSecondHandleInfoByMouseOffset(const Offset & localOffset)662 void TextSelectController::UpdateSecondHandleInfoByMouseOffset(const Offset& localOffset)
663 {
664     auto index = ConvertTouchOffsetToPosition(localOffset);
665     if (GreatNotEqual(localOffset.GetX(), contentRect_.GetX() + contentRect_.Width()) && paragraph_) {
666         float boundaryAdjustment = paragraph_->GetCharacterWidth(caretInfo_.index);
667         index = ConvertTouchOffsetToPosition({localOffset.GetX() + boundaryAdjustment, localOffset.GetY()});
668     }
669     MoveSecondHandleToContentRect(index, false, false);
670     caretInfo_.index = index;
671     UpdateCaretRectByPositionNearTouchOffset(index, localOffset);
672     auto caretRect = GetCaretRect();
673     MoveHandleToContentRect(caretRect);
674     caretInfo_.rect = caretRect;
675 }
676 
MoveSecondHandleByKeyBoard(int32_t index,std::optional<TextAffinity> textAffinity)677 void TextSelectController::MoveSecondHandleByKeyBoard(int32_t index, std::optional<TextAffinity> textAffinity)
678 {
679     index = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetTextUtf16Value().length()));
680     MoveSecondHandleToContentRect(index);
681     caretInfo_.index = index;
682     auto caretTextAffinity = HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM;
683     if (textAffinity) {
684         caretTextAffinity = textAffinity.value();
685     }
686     UpdateCaretOffset(caretTextAffinity);
687     auto caretRect = GetCaretRect();
688     MoveHandleToContentRect(caretRect);
689     caretInfo_.rect = caretRect;
690 }
691 
FireSelectEvent()692 void TextSelectController::FireSelectEvent()
693 {
694     if (!onAccessibilityCallback_) {
695         return;
696     }
697     bool needReport = !GetFirstIndex().has_value() || !GetSecondIndex().has_value();
698     bool secondIndexChange = false;
699     if (GetFirstIndex().has_value()) {
700         needReport |= GetFirstIndex().value() != firstHandleInfo_.index;
701     }
702 
703     if (GetSecondIndex().has_value()) {
704         needReport |= GetSecondIndex().value() != secondHandleInfo_.index;
705         secondIndexChange = GetSecondIndex().value() != secondHandleInfo_.index;
706     }
707 
708     auto pattern = pattern_.Upgrade();
709     CHECK_NULL_VOID(pattern);
710     auto textField = DynamicCast<TextFieldPattern>(pattern);
711     CHECK_NULL_VOID(textField);
712     auto eventHub = textField->GetOrCreateEventHub<TextFieldEventHub>();
713     CHECK_NULL_VOID(eventHub);
714 
715     if (needReport && textField->IsModifyDone() && (textField->HasFocus()
716         || (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)))) {
717         UpdateFirstIndex(firstHandleInfo_.index);
718         UpdateSecondIndex(secondHandleInfo_.index);
719         onAccessibilityCallback_();
720         eventHub->FireOnSelectionChange(std::min(firstHandleInfo_.index, secondHandleInfo_.index),
721             std::max(firstHandleInfo_.index, secondHandleInfo_.index));
722         if (secondIndexChange) {
723             // when second index change, avoid caret in time
724             textField->TriggerAvoidWhenCaretGoesDown();
725         }
726     }
727 }
728 
ResetHandles()729 void TextSelectController::ResetHandles()
730 {
731     firstHandleInfo_.index = caretInfo_.index;
732     secondHandleInfo_.index = caretInfo_.index;
733     UpdateFirstHandleOffset();
734     UpdateSecondHandleOffset();
735 }
736 
NeedAIAnalysis(int32_t & index,const CaretUpdateType targetType,const Offset & touchOffset,std::chrono::duration<float,std::ratio<1,SECONDS_TO_MILLISECONDS>> timeout)737 bool TextSelectController::NeedAIAnalysis(int32_t& index, const CaretUpdateType targetType, const Offset& touchOffset,
738     std::chrono::duration<float, std::ratio<1, SECONDS_TO_MILLISECONDS>> timeout)
739 {
740     auto pattern = pattern_.Upgrade();
741     CHECK_NULL_RETURN(pattern, false);
742     auto textField = DynamicCast<TextFieldPattern>(pattern);
743     CHECK_NULL_RETURN(textField, false);
744 
745     if (!InputAIChecker::NeedAIAnalysis(contentController_->GetTextUtf16Value().empty(), targetType, timeout)) {
746         return false;
747     }
748     if (IsClickAtBoundary(index, touchOffset) && targetType == CaretUpdateType::PRESSED) {
749         TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsClickAtBoundary is boundary, return!");
750         return false;
751     }
752 
753     if (textField->IsInPasswordMode()) {
754         TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsInPasswordMode, return!");
755         return false;
756     }
757 
758     if (contentController_->IsIndexBeforeOrInEmoji(index)) {
759         TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsIndexBeforeOrInEmoji, return!");
760         return false;
761     }
762     return true;
763 }
764 
AdjustCursorPosition(int32_t & index,const OHOS::Ace::Offset & touchOffset)765 void TextSelectController::AdjustCursorPosition(int32_t& index, const OHOS::Ace::Offset& touchOffset)
766 {
767     auto timeout = GetLastClickTime() - lastAiPosTimeStamp_;
768     if (NeedAIAnalysis(index, CaretUpdateType::PRESSED, touchOffset, timeout)) {
769         // consider to limit the whole string length
770         int32_t startIndex = -1;
771         int32_t subIndex = index;
772         // the subindex match the sub content,we do choose the subtext to ai analysis to avoid the content too large
773         std::string content = contentController_->GetSelectedLimitValue(subIndex, startIndex);
774         DataDetectorMgr::GetInstance().AdjustCursorPosition(subIndex, content, lastAiPosTimeStamp_, GetLastClickTime());
775         index = startIndex + subIndex;
776         TAG_LOGI(AceLogTag::ACE_RICH_TEXT, "ai pos, startIndex:%{public}d,subIndex:%{public}d", startIndex, subIndex);
777     }
778 }
779 
AdjustWordSelection(int32_t & index,int32_t & start,int32_t & end,const OHOS::Ace::Offset & touchOffset)780 bool TextSelectController::AdjustWordSelection(
781     int32_t& index, int32_t& start, int32_t& end, const OHOS::Ace::Offset& touchOffset)
782 {
783     auto timeout = GetLastClickTime() - lastAiPosTimeStamp_;
784     if (NeedAIAnalysis(index, CaretUpdateType::DOUBLE_CLICK, touchOffset, timeout)) {
785         // consider the limint the whole string length
786         int32_t startIndex = -1;
787         int32_t subIndex = index;
788         // to avoid the content too large
789         std::string content = contentController_->GetSelectedLimitValue(subIndex, startIndex);
790         int32_t aiPosStart = -1;
791         int32_t aiPosEnd = -1;
792         DataDetectorMgr::GetInstance().AdjustWordSelection(subIndex, content, aiPosStart, aiPosEnd);
793         TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "after ai ,startIndex:%{public}d-sub:%{public}d", aiPosStart, aiPosEnd);
794         if (aiPosStart >= 0 && aiPosEnd >= 0) {
795             index = startIndex + subIndex;
796             start = startIndex + aiPosStart;
797             end = startIndex + aiPosEnd;
798             return true;
799         }
800     }
801     return false;
802 }
803 
IsClickAtBoundary(int32_t index,const OHOS::Ace::Offset & touchOffset)804 bool TextSelectController::IsClickAtBoundary(int32_t index, const OHOS::Ace::Offset& touchOffset)
805 {
806     if (InputAIChecker::IsSingleClickAtBoundary(index, contentController_->GetTextUtf16Value().length())) {
807         return true;
808     }
809 
810     auto pattern = pattern_.Upgrade();
811     CHECK_NULL_RETURN(pattern, false);
812     auto textField = DynamicCast<TextFieldPattern>(pattern);
813     CHECK_NULL_RETURN(textField, false);
814     auto textRect = textField->GetTextRect();
815 
816     CaretMetricsF caretMetrics;
817     CalcCaretMetricsByPositionNearTouchOffset(
818         index, caretMetrics, OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
819 
820     return InputAIChecker::IsMultiClickAtBoundary(caretMetrics.offset, textRect);
821 }
822 
GetLastClickTime() const823 const TimeStamp& TextSelectController::GetLastClickTime() const
824 {
825     auto pattern = pattern_.Upgrade();
826     CHECK_NULL_RETURN(pattern, lastAiPosTimeStamp_);
827     auto textField = DynamicCast<TextFieldPattern>(pattern);
828     CHECK_NULL_RETURN(textField, lastAiPosTimeStamp_);
829     return textField->GetLastClickTime();
830 }
831 
IsTouchAtLineEnd(const Offset & localOffset) const832 bool TextSelectController::IsTouchAtLineEnd(const Offset& localOffset) const
833 {
834     CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), false);
835     auto index = ConvertTouchOffsetToPosition(localOffset);
836     if (index == static_cast<int32_t>(contentController_->GetTextUtf16Value().length())) {
837         return true;
838     }
839     auto pattern = pattern_.Upgrade();
840     CHECK_NULL_RETURN(pattern, false);
841     auto textField = DynamicCast<TextFieldPattern>(pattern);
842     CHECK_NULL_RETURN(textField, false);
843     auto textRect = textField->GetTextRect();
844     auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
845     LineMetrics lineMetrics;
846     if (paragraph_->GetLineMetricsByCoordinate(offset, lineMetrics)) {
847         if (textField->IsLTRLayout()) {
848             return GreatNotEqual(offset.GetX(), lineMetrics.x + lineMetrics.width);
849         } else {
850             return LessNotEqual(offset.GetX(), lineMetrics.x);
851         }
852     }
853     return false;
854 }
855 
GetTouchLinePos(const Offset & localOffset)856 TouchPosition TextSelectController::GetTouchLinePos(const Offset& localOffset)
857 {
858     CHECK_NULL_RETURN(paragraph_, TouchPosition::MID);
859     auto pattern = pattern_.Upgrade();
860     CHECK_NULL_RETURN(pattern, TouchPosition::MID);
861     auto textField = DynamicCast<TextFieldPattern>(pattern);
862     CHECK_NULL_RETURN(textField, TouchPosition::MID);
863     if (contentController_->IsEmpty()) {
864         return textField->IsLTRLayout() ? TouchPosition::RIGHT : TouchPosition::LEFT;
865     }
866     auto index = ConvertTouchOffsetToPosition(localOffset);
867     if (index == 0) {
868         return textField->IsLTRLayout() ? TouchPosition::LEFT : TouchPosition::RIGHT;
869     }
870     if (index == static_cast<int32_t>(contentController_->GetTextUtf16Value().length())) {
871         return textField->IsLTRLayout() ? TouchPosition::RIGHT : TouchPosition::LEFT;
872     }
873     auto textRect = textField->GetTextRect();
874     auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
875     LineMetrics lineMetrics;
876     if (paragraph_->GetLineMetricsByCoordinate(offset, lineMetrics)) {
877         if (GreatNotEqual(offset.GetX(), lineMetrics.x + lineMetrics.width)) {
878             return TouchPosition::RIGHT;
879         }
880         if (LessNotEqual(offset.GetX(), lineMetrics.x)) {
881             return TouchPosition::LEFT;
882         }
883     }
884     return TouchPosition::MID;
885 }
886 
UpdateSelectWithBlank(const Offset & localOffset)887 void TextSelectController::UpdateSelectWithBlank(const Offset& localOffset)
888 {
889     auto pattern = pattern_.Upgrade();
890     CHECK_NULL_VOID(pattern);
891     auto textField = DynamicCast<TextFieldPattern>(pattern);
892     CHECK_NULL_VOID(textField);
893     auto textRect = textField->GetTextRect();
894     auto contentRect = textField->GetTextContentRect();
895     auto touchLocalOffset = localOffset;
896     if (textField->IsTextArea() && GreatNotEqual(touchLocalOffset.GetY(), textRect.Bottom())) {
897         // click at end of a paragraph.
898         touchLocalOffset.SetX(textField->IsLTRLayout() ? contentRect.Right() : textRect.Left());
899     }
900     if (IsTouchAtLineEnd(touchLocalOffset)) {
901         UpdateCaretInfoByOffset(touchLocalOffset);
902     } else {
903         UpdateSelectByOffset(localOffset);
904     }
905 }
906 
SetCaretRectAtEmptyValue()907 void TextSelectController::SetCaretRectAtEmptyValue()
908 {
909     caretInfo_.rect = CalculateEmptyValueCaretRect();
910     caretInfo_.originalRect = CalculateEmptyValueCaretRect(caretInfo_.originalRect.Width());
911 }
912 
UpdateCaretOriginalRect(const OffsetF & offset)913 void TextSelectController::UpdateCaretOriginalRect(const OffsetF& offset)
914 {
915     caretInfo_.originalRect.SetOffset(OffsetF(offset.GetX(), caretInfo_.rect.Top()));
916     caretInfo_.originalRect.SetHeight(caretInfo_.rect.Height());
917     AdjustHandleAtEdge(caretInfo_.originalRect);
918 }
919 
AdjustHandleInBoundary(RectF & handleRect) const920 void TextSelectController::AdjustHandleInBoundary(RectF& handleRect) const
921 {
922     auto pattern = pattern_.Upgrade();
923     CHECK_NULL_VOID(pattern);
924     auto textField = DynamicCast<TextFieldPattern>(pattern);
925     CHECK_NULL_VOID(textField);
926     // Adjusted handle to the content area when they are at the content area boundary.
927     if (LessNotEqual(handleRect.GetX(), contentRect_.GetX())) {
928         handleRect.SetLeft(contentRect_.GetX());
929     }
930 
931     auto textRectRightBoundary = contentRect_.GetX() + contentRect_.Width();
932     if (GreatNotEqual(handleRect.GetX() + handleRect.Width(), textRectRightBoundary) &&
933         GreatNotEqual(contentRect_.Width(), 0.0) && !textField->GetTextUtf16Value().empty()) {
934         handleRect.SetLeft(textRectRightBoundary - handleRect.Width());
935     }
936 }
937 
AdjustHandleOffsetWithBoundary(RectF & handleRect)938 void TextSelectController::AdjustHandleOffsetWithBoundary(RectF& handleRect)
939 {
940     auto pattern = pattern_.Upgrade();
941     CHECK_NULL_VOID(pattern);
942     auto textField = DynamicCast<TextFieldPattern>(pattern);
943     CHECK_NULL_VOID(textField);
944     if (textField->IsTextArea()) {
945         AdjustHandleInBoundary(handleRect);
946         return;
947     }
948     auto textRect = textField->GetTextRect();
949     auto contentRect = textField->GetContentRect();
950     // TextInput scroll to the far right.
951     if (NearEqual(textRect.Right(), contentRect.Right()) && GreatNotEqual(handleRect.Right(), contentRect.Right()) &&
952         GreatNotEqual(contentRect.Width(), 0.0) && !textField->GetTextUtf16Value().empty()) {
953         handleRect.SetLeft(contentRect.Right() - handleRect.Width());
954     }
955     // TextInput scroll to the far left.
956     if (NearEqual(textRect.Left(), contentRect.Left()) && LessNotEqual(handleRect.Left(), contentRect.Left())) {
957         handleRect.SetLeft(contentRect.Left());
958     }
959 }
960 
AdjustAllHandlesWithBoundary()961 void TextSelectController::AdjustAllHandlesWithBoundary()
962 {
963     AdjustHandleOffsetWithBoundary(firstHandleInfo_.rect);
964     AdjustHandleOffsetWithBoundary(secondHandleInfo_.rect);
965 }
966 } // namespace OHOS::Ace::NG