1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components_ng/pattern/text/text_base.h"
17 #include "core/components_ng/property/measure_utils.h"
18 #include "core/text/text_emoji_processor.h"
19 #include <cstdint>
20
21 namespace OHOS::Ace::NG {
22 namespace {
23 const Dimension SELECTED_BLANK_LINE_WIDTH = 2.0_vp;
24 constexpr size_t UTF16_SURROGATE_PAIR_LENGTH = 2;
25 constexpr size_t UTF16_SINGLE_CHAR_LENGTH = 1;
26 }; // namespace
27
SetSelectionNode(const SelectedByMouseInfo & info)28 void TextBase::SetSelectionNode(const SelectedByMouseInfo& info)
29 {
30 auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
31 CHECK_NULL_VOID(pipeline);
32 auto selectOverlayManager = pipeline->GetSelectOverlayManager();
33 selectOverlayManager->SetSelectedNodeByMouse(info);
34 }
35
GetGraphemeClusterLength(const std::u16string & text,int32_t extend,bool checkPrev)36 int32_t TextBase::GetGraphemeClusterLength(
37 const std::u16string& text, int32_t extend, bool checkPrev)
38 {
39 char16_t aroundChar = 0;
40 if (checkPrev) {
41 if (static_cast<size_t>(extend) <= text.length()) {
42 aroundChar = text[std::max(0, extend - 1)];
43 }
44 } else {
45 if (static_cast<size_t>(extend) <= (text.length())) {
46 aroundChar = text[std::min(text.length() ? static_cast<int32_t>(text.length()) - 1 : 0, extend)];
47 }
48 }
49 return StringUtils::NotInUtf16Bmp(aroundChar) ? 2 : 1;
50 }
51
CalculateSelectedRect(std::vector<RectF> & selectedRect,float longestLine,TextDirection direction)52 void TextBase::CalculateSelectedRect(std::vector<RectF>& selectedRect, float longestLine, TextDirection direction)
53 {
54 if (selectedRect.size() <= 1 || direction == TextDirection::RTL) {
55 return;
56 }
57 std::map<float, RectF> lineGroup;
58 for (const auto& localRect : selectedRect) {
59 if (NearZero(localRect.Width()) && NearZero(localRect.Height())) {
60 continue;
61 }
62 auto it = lineGroup.find(localRect.GetY());
63 if (it == lineGroup.end()) {
64 lineGroup.emplace(localRect.GetY(), localRect);
65 } else {
66 auto lineRect = it->second;
67 it->second = lineRect.CombineRectT(localRect);
68 }
69 }
70 selectedRect.clear();
71 auto firstRect = lineGroup.begin()->second;
72 float lastLineBottom = firstRect.Top();
73 auto end = *(lineGroup.rbegin());
74 for (const auto& line : lineGroup) {
75 auto width = (line == end) ? line.second.Width() : longestLine - line.second.Left();
76 auto rect = RectF(line.second.Left(), lastLineBottom, width, line.second.Bottom() - lastLineBottom);
77 selectedRect.emplace_back(rect);
78 lastLineBottom = line.second.Bottom();
79 }
80 }
81
GetSelectedBlankLineWidth()82 float TextBase::GetSelectedBlankLineWidth()
83 {
84 auto pipeline = PipelineContext::GetCurrentContext();
85 CHECK_NULL_RETURN(pipeline, static_cast<float>(SELECTED_BLANK_LINE_WIDTH.ConvertToPx()));
86 auto blankWidth = pipeline->NormalizeToPx(SELECTED_BLANK_LINE_WIDTH);
87 return static_cast<float>(blankWidth);
88 }
89
CalculateSelectedRectEx(std::vector<RectF> & selectedRects,float lastLineBottom,const std::optional<TextDirection> & direction)90 void TextBase::CalculateSelectedRectEx(std::vector<RectF>& selectedRects, float lastLineBottom,
91 const std::optional<TextDirection>& direction)
92 {
93 if (selectedRects.empty()) {
94 return;
95 }
96 std::map<float, std::pair<RectF, std::vector<RectF>>> lineGroup;
97 SelectedRectsToLineGroup(selectedRects, lineGroup);
98 selectedRects.clear();
99 if (lineGroup.empty()) {
100 return;
101 }
102 lastLineBottom = LessNotEqual(lastLineBottom, 0.0f) ? lineGroup.begin()->second.first.Top() : lastLineBottom;
103 for (const auto& line : lineGroup) {
104 const auto& lineRect = line.second.first;
105 const auto& lineRects = line.second.second;
106 for (const auto& lineItem : lineRects) {
107 RectF rect = RectF(lineItem.Left(), lastLineBottom, lineItem.Width(), lineRect.Bottom() - lastLineBottom);
108 selectedRects.emplace_back(rect);
109 }
110 lastLineBottom = line.second.first.Bottom();
111 }
112 }
113
UpdateSelectedBlankLineRect(RectF & rect,float blankWidth,TextAlign textAlign,float longestLine)114 bool TextBase::UpdateSelectedBlankLineRect(RectF& rect, float blankWidth, TextAlign textAlign, float longestLine)
115 {
116 CHECK_EQUAL_RETURN(NearZero(rect.Width()), false, false);
117 switch (textAlign) {
118 case TextAlign::JUSTIFY:
119 case TextAlign::START: {
120 if (GreatNotEqual(longestLine, 0.0f) && GreatNotEqual(rect.Left() + blankWidth, longestLine)) {
121 rect.SetLeft(longestLine - blankWidth);
122 }
123 break;
124 }
125 case TextAlign::CENTER:
126 rect.SetLeft(rect.Left() - (blankWidth / 2.0f));
127 break;
128 case TextAlign::END: {
129 auto left = rect.Left() - blankWidth;
130 if (GreatOrEqual(left, 0.0f)) {
131 rect.SetLeft(left);
132 }
133 break;
134 }
135 default:
136 return false;
137 }
138 rect.SetWidth(blankWidth);
139 return true;
140 }
141
SelectedRectsToLineGroup(const std::vector<RectF> & selectedRect,std::map<float,std::pair<RectF,std::vector<RectF>>> & lineGroup)142 void TextBase::SelectedRectsToLineGroup(const std::vector<RectF>& selectedRect,
143 std::map<float, std::pair<RectF, std::vector<RectF>>>& lineGroup)
144 {
145 for (const auto& localRect : selectedRect) {
146 if (NearZero(localRect.Width()) && NearZero(localRect.Height())) {
147 continue;
148 }
149 auto it = lineGroup.find(localRect.GetY());
150 if (it == lineGroup.end()) {
151 std::vector<RectF> rects = { localRect };
152 lineGroup.emplace(localRect.GetY(), std::make_pair(localRect, rects));
153 continue;
154 }
155 auto lineRect = it->second.first;
156 it->second.first = lineRect.CombineRectT(localRect);
157 if (it->second.second.empty()) {
158 it->second.second.emplace_back(localRect);
159 continue;
160 }
161 auto backRect = it->second.second.back();
162 if (NearEqual(backRect.Left(), localRect.Right()) || NearEqual(backRect.Right(), localRect.Left())) {
163 it->second.second.back() = backRect.CombineRectT(localRect);
164 } else {
165 it->second.second.emplace_back(localRect);
166 }
167 }
168 }
169
CheckTextAlignByDirection(TextAlign textAlign,TextDirection direction)170 TextAlign TextBase::CheckTextAlignByDirection(TextAlign textAlign, TextDirection direction)
171 {
172 if (direction == TextDirection::RTL) {
173 if (textAlign == TextAlign::START) {
174 return TextAlign::END;
175 } else if (textAlign == TextAlign::END) {
176 return TextAlign::START;
177 }
178 }
179 return textAlign;
180 }
181
RevertLocalPointWithTransform(const RefPtr<FrameNode> & targetNode,OffsetF & point)182 void TextBase::RevertLocalPointWithTransform(const RefPtr<FrameNode>& targetNode, OffsetF& point)
183 {
184 auto pattern = targetNode->GetPattern<Pattern>();
185 CHECK_NULL_VOID(pattern);
186 auto parent = pattern->GetHost();
187 CHECK_NULL_VOID(parent);
188 std::stack<RefPtr<FrameNode>> nodeStack;
189 while (parent) {
190 nodeStack.push(parent);
191 parent = parent->GetAncestorNodeOfFrame(true);
192 }
193 CHECK_NULL_VOID(!nodeStack.empty());
194 PointF localPoint(point.GetX(), point.GetY());
195 while (!nodeStack.empty()) {
196 parent = nodeStack.top();
197 CHECK_NULL_VOID(parent);
198 nodeStack.pop();
199 auto renderContext = parent->GetRenderContext();
200 CHECK_NULL_VOID(renderContext);
201 renderContext->GetPointWithRevert(localPoint);
202 auto rectOffset = renderContext->GetPaintRectWithoutTransform().GetOffset();
203 localPoint = localPoint - rectOffset;
204 }
205 point.SetX(localPoint.GetX());
206 point.SetY(localPoint.GetY());
207 }
208
HasRenderTransform(const RefPtr<FrameNode> & targetNode)209 bool TextBase::HasRenderTransform(const RefPtr<FrameNode>& targetNode)
210 {
211 auto pattern = targetNode->GetPattern<Pattern>();
212 CHECK_NULL_RETURN(pattern, false);
213 auto host = pattern->GetHost();
214 CHECK_NULL_RETURN(host, false);
215 auto hasTransform = false;
216 while (host) {
217 auto renderContext = host->GetRenderContext();
218 CHECK_NULL_RETURN(renderContext, false);
219 if (host->GetTag() == V2::WINDOW_SCENE_ETS_TAG) {
220 break;
221 }
222 if (!hasTransform) {
223 auto noTransformRect = renderContext->GetPaintRectWithoutTransform();
224 auto transformRect = renderContext->GetPaintRectWithTransform();
225 hasTransform = noTransformRect != transformRect;
226 } else {
227 break;
228 }
229 host = host->GetAncestorNodeOfFrame(true);
230 }
231 return hasTransform;
232 }
233
ConvertStr8toStr16(const std::string & value)234 std::u16string TextBase::ConvertStr8toStr16(const std::string& value)
235 {
236 auto content = value;
237 std::u16string result = StringUtils::Str8ToStr16(content);
238 if (result.length() == 0 && value.length() != 0) {
239 content = TextEmojiProcessor::ConvertU8stringUnpairedSurrogates(value);
240 result = StringUtils::Str8ToStr16(content);
241 }
242 return result;
243 }
244
TruncateText(const std::u16string & text,const size_t & length) const245 std::u16string TextBase::TruncateText(const std::u16string& text, const size_t& length) const
246 {
247 const size_t maxLength = length;
248 size_t charCount = 0;
249 size_t byteIndex = 0;
250
251 while (byteIndex < text.size() && charCount < maxLength) {
252 charCount++;
253 byteIndex += (text[byteIndex] >= 0xD800 && text[byteIndex] <= 0xDBFF) ?
254 UTF16_SURROGATE_PAIR_LENGTH : UTF16_SINGLE_CHAR_LENGTH;
255 }
256
257 if (charCount >= maxLength) {
258 return text.substr(0, byteIndex);
259 }
260 return text;
261 }
262
CountUtf16Chars(const std::u16string & s)263 size_t TextBase::CountUtf16Chars(const std::u16string& s)
264 {
265 size_t charCount = 0;
266 size_t i = 0;
267 while (i < s.size()) {
268 charCount++;
269 i += (s[i] >= 0xD800 && s[i] <= 0xDBFF) ? UTF16_SURROGATE_PAIR_LENGTH : UTF16_SINGLE_CHAR_LENGTH;
270 }
271 return charCount;
272 }
273
GetLayoutCalPolicy(LayoutWrapper * layoutWrapper,bool isHorizontal)274 LayoutCalPolicy TextBase::GetLayoutCalPolicy(LayoutWrapper* layoutWrapper, bool isHorizontal)
275 {
276 CHECK_NULL_RETURN(layoutWrapper, LayoutCalPolicy::NO_MATCH);
277 auto layoutProperty = layoutWrapper->GetLayoutProperty();
278 CHECK_NULL_RETURN(layoutProperty, LayoutCalPolicy::NO_MATCH);
279 auto layoutPolicyProperty = layoutProperty->GetLayoutPolicyProperty();
280 CHECK_NULL_RETURN(layoutPolicyProperty, LayoutCalPolicy::NO_MATCH);
281 if (isHorizontal) {
282 CHECK_NULL_RETURN(layoutPolicyProperty->widthLayoutPolicy_, LayoutCalPolicy::NO_MATCH);
283 return layoutPolicyProperty->widthLayoutPolicy_.value();
284 }
285 CHECK_NULL_RETURN(layoutPolicyProperty->heightLayoutPolicy_, LayoutCalPolicy::NO_MATCH);
286 return layoutPolicyProperty->heightLayoutPolicy_.value();
287 }
288
GetConstraintMaxLength(LayoutWrapper * layoutWrapper,const LayoutConstraintF & constraint,bool isHorizontal)289 float TextBase::GetConstraintMaxLength(
290 LayoutWrapper* layoutWrapper, const LayoutConstraintF& constraint, bool isHorizontal)
291 {
292 auto layoutCalPolicy = GetLayoutCalPolicy(layoutWrapper, isHorizontal);
293 if (layoutCalPolicy == LayoutCalPolicy::MATCH_PARENT) {
294 return isHorizontal ? constraint.parentIdealSize.Width().value_or(constraint.maxSize.Width())
295 : constraint.parentIdealSize.Height().value_or(constraint.maxSize.Height());
296 }
297 return isHorizontal ? constraint.maxSize.Width() : constraint.maxSize.Height();
298 }
299
GetCalcLayoutConstraintLength(LayoutWrapper * layoutWrapper,bool isMax,bool isWidth)300 std::optional<float> TextBase::GetCalcLayoutConstraintLength(LayoutWrapper* layoutWrapper, bool isMax, bool isWidth)
301 {
302 CHECK_NULL_RETURN(layoutWrapper, std::nullopt);
303 auto layoutProperty = layoutWrapper->GetLayoutProperty();
304 CHECK_NULL_RETURN(layoutProperty, std::nullopt);
305 const auto& layoutCalcConstraint = layoutProperty->GetCalcLayoutConstraint();
306 CHECK_NULL_RETURN(layoutCalcConstraint, std::nullopt);
307 auto layoutConstraint = layoutProperty->GetLayoutConstraint();
308 CHECK_NULL_RETURN(layoutConstraint, std::nullopt);
309 auto calcLayoutConstraintMaxMinSize = isMax ? layoutCalcConstraint->maxSize : layoutCalcConstraint->minSize;
310 CHECK_NULL_RETURN(calcLayoutConstraintMaxMinSize, std::nullopt);
311 auto optionalCalcLength =
312 isWidth ? calcLayoutConstraintMaxMinSize->Width() : calcLayoutConstraintMaxMinSize->Height();
313 auto percentLength =
314 isWidth ? layoutConstraint->percentReference.Width() : layoutConstraint->percentReference.Height();
315 CHECK_NULL_RETURN(optionalCalcLength, std::nullopt);
316 return ConvertToPx(optionalCalcLength, ScaleProperty::CreateScaleProperty(), percentLength);
317 }
318
DoGestureSelection(const TouchEventInfo & info)319 void TextGestureSelector::DoGestureSelection(const TouchEventInfo& info)
320 {
321 if (!isStarted_ || info.GetChangedTouches().empty()) {
322 return;
323 }
324 auto locationInfo = info.GetChangedTouches().front();
325 auto touchType = locationInfo.GetTouchType();
326 switch (touchType) {
327 case TouchType::UP:
328 EndGestureSelection(locationInfo);
329 break;
330 case TouchType::MOVE:
331 DoTextSelectionTouchMove(info);
332 break;
333 case TouchType::CANCEL:
334 CancelGestureSelection();
335 break;
336 default:
337 break;
338 }
339 }
340
DoTextSelectionTouchMove(const TouchEventInfo & info)341 void TextGestureSelector::DoTextSelectionTouchMove(const TouchEventInfo& info)
342 {
343 auto locationInfo = info.GetChangedTouches().front();
344 auto localOffset = locationInfo.GetLocalLocation();
345 if (!isSelecting_) {
346 if (LessOrEqual((localOffset - startOffset_).GetDistance(), minMoveDistance_.ConvertToPx())) {
347 return;
348 }
349 isSelecting_ = true;
350 selectingFingerId_ = locationInfo.GetFingerId();
351 }
352 auto index = GetTouchIndex({ localOffset.GetX(), localOffset.GetY() });
353 auto start = std::min(index, start_);
354 auto end = std::max(index, end_);
355 OnTextGestureSelectionUpdate(start, end, info);
356 }
357
DetectTextDiff(const std::string & latestContent)358 std::pair<std::string, std::string> TextBase::DetectTextDiff(const std::string& latestContent)
359 {
360 const std::string& beforeText = textCache_;
361 std::string addedText;
362 std::string removedText;
363 size_t prefixLen = 0;
364 size_t minLength = std::min(beforeText.length(), latestContent.length());
365 while (prefixLen < minLength && beforeText[prefixLen] == latestContent[prefixLen]) {
366 prefixLen++;
367 }
368 while (prefixLen > 0 && ((beforeText[prefixLen] & 0xC0) == 0x80)) {
369 prefixLen--;
370 }
371 size_t suffixLen = 0;
372 size_t remainBefore = beforeText.length() - prefixLen;
373 size_t remainAfter = latestContent.length() - prefixLen;
374 size_t minSuffix = std::min(remainBefore, remainAfter);
375 while (suffixLen < minSuffix &&
376 beforeText[beforeText.length() - 1 - suffixLen] == latestContent[latestContent.length() - 1 - suffixLen]) {
377 suffixLen++;
378 }
379 while (suffixLen > 0 && ((beforeText[beforeText.size() - suffixLen] & 0xC0) == 0x80)) {
380 suffixLen--;
381 }
382 size_t removeStart = prefixLen;
383 size_t removeEnd = beforeText.length() - suffixLen;
384 if (removeEnd > removeStart) {
385 removedText = beforeText.substr(removeStart, removeEnd - removeStart);
386 }
387 size_t addStart = prefixLen;
388 size_t addEnd = latestContent.length() - suffixLen;
389 if (addEnd > addStart) {
390 addedText = latestContent.substr(addStart, addEnd - addStart);
391 }
392 return {std::move(addedText), std::move(removedText)};
393 }
394 }