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