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