1 /*
2  * Copyright (c) 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 
16 #include "core/components_ng/pattern/text_drag/text_drag_pattern.h"
17 
18 #include <algorithm>
19 
20 #include "base/utils/utils.h"
21 #include "core/components/text/text_theme.h"
22 #include "core/components_ng/pattern/text/text_pattern.h"
23 #include "core/components_ng/pattern/text_drag/text_drag_base.h"
24 #include "core/components_ng/render/drawing.h"
25 #include "core/components_v2/inspector/inspector_constants.h"
26 
27 namespace {
28 // uncertainty range when comparing selectedTextBox to contentRect
29 constexpr float BOX_EPSILON = 0.2f;
30 constexpr float CONSTANT_HALF = 2.0f;
31 } // namespace
32 
33 namespace OHOS::Ace::NG {
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)34 bool TextDragPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
35 {
36     return true;
37 }
38 
GetFirstBoxRect(const std::vector<RectF> & boxes,const RectF & contentRect,const float textStartY)39 const RectF GetFirstBoxRect(const std::vector<RectF>& boxes, const RectF& contentRect, const float textStartY)
40 {
41     for (const auto& box : boxes) {
42         if (box.Bottom() + textStartY > contentRect.Top() + BOX_EPSILON) {
43             return box;
44         }
45     }
46     return boxes.front();
47 } // Obtains the first line in the visible area of the text box, including the truncated part.
48 
GetLastBoxRect(const std::vector<RectF> & boxes,const RectF & contentRect,const float textStartY)49 const RectF GetLastBoxRect(const std::vector<RectF>& boxes, const RectF& contentRect, const float textStartY)
50 {
51     bool hasResult = false;
52     RectF result;
53     auto maxBottom = contentRect.GetY() + SystemProperties::GetDevicePhysicalHeight();
54     for (const auto& box : boxes) {
55         auto caculateBottom = box.Bottom() + textStartY;
56         bool isReachingBottom = (caculateBottom >= maxBottom) || (caculateBottom >= contentRect.Bottom());
57         if (isReachingBottom && !hasResult) {
58             result = box;
59             hasResult = true;
60             continue;
61         }
62         if (hasResult && box.Bottom() == result.Bottom()) {
63             result = box;
64         } else if (hasResult && box.Bottom() != result.Bottom()) {
65             return result;
66         }
67     }
68     return boxes.back();
69 } // Obtains the last line in the visible area of the text box, including the truncated part.
70 
CreateDragNode(const RefPtr<FrameNode> & hostNode)71 RefPtr<FrameNode> TextDragPattern::CreateDragNode(const RefPtr<FrameNode>& hostNode)
72 {
73     CHECK_NULL_RETURN(hostNode, nullptr);
74     auto hostPattern = hostNode->GetPattern<TextDragBase>();
75     const auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
76     auto dragNode = FrameNode::GetOrCreateFrameNode(
77         V2::TEXTDRAG_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr<TextDragPattern>(); });
78     auto dragContext = dragNode->GetRenderContext();
79     auto hostContext = hostNode->GetRenderContext();
80     if (hostContext->HasForegroundColor()) {
81         dragContext->UpdateForegroundColor(hostContext->GetForegroundColor().value());
82     }
83     if (hostContext->HasForegroundColorStrategy()) {
84         dragContext->UpdateForegroundColorStrategy(hostContext->GetForegroundColorStrategy().value());
85     }
86     auto dragPattern = dragNode->GetPattern<TextDragPattern>();
87     auto data = CalculateTextDragData(hostPattern, dragNode);
88     TAG_LOGI(AceLogTag::ACE_TEXT, "CreateDragNode SelectPositionInfo startX = %{public}f, startY = %{public}f,\
89              endX = %{public}f, endY = %{public}f", data.selectPosition_.startX_, data.selectPosition_.startY_,
90              data.selectPosition_.endX_, data.selectPosition_.endY_);
91     dragPattern->Initialize(hostPattern->GetDragParagraph(), data);
92     dragPattern->SetLastLineHeight(data.lineHeight_);
93 
94     CalcSize size(NG::CalcLength(dragPattern->GetFrameWidth()), NG::CalcLength(dragPattern->GetFrameHeight()));
95     dragNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(size);
96     return dragNode;
97 }
98 
CalculateTextDragData(RefPtr<TextDragBase> & pattern,RefPtr<FrameNode> & dragNode)99 TextDragData TextDragPattern::CalculateTextDragData(RefPtr<TextDragBase>& pattern, RefPtr<FrameNode>& dragNode)
100 {
101     auto dragContext = dragNode->GetRenderContext();
102     auto dragPattern = dragNode->GetPattern<TextDragPattern>();
103     OffsetF textStartOffset = {pattern->GetTextRect().GetX(),
104         pattern->IsTextArea() ? pattern->GetTextRect().GetY() : pattern->GetTextContentRect().GetY()};
105     auto contentRect = pattern->GetTextContentRect(true);
106     float bothOffset = TEXT_DRAG_OFFSET.ConvertToPx() * CONSTANT_HALF;
107     auto boxes = pattern->GetTextBoxes();
108     CHECK_NULL_RETURN(!boxes.empty(), {});
109     auto globalOffset = pattern->GetParentGlobalOffset();
110     RectF leftHandler = GetHandler(true, boxes, contentRect, globalOffset, textStartOffset);
111     RectF rightHandler = GetHandler(false, boxes, contentRect, globalOffset, textStartOffset);
112     AdjustHandlers(contentRect, leftHandler, rightHandler);
113     float width = rightHandler.GetX() - leftHandler.GetX();
114     float height = rightHandler.GetY() - leftHandler.GetY() + rightHandler.Height();
115     auto dragOffset = TEXT_DRAG_OFFSET.ConvertToPx();
116     float globalX = leftHandler.GetX() + globalOffset.GetX() - dragOffset;
117     float globalY = leftHandler.GetY() + globalOffset.GetY() - dragOffset;
118     auto box = boxes.front();
119     float delta = 0.0f;
120     float handlersDistance = width;
121     if (leftHandler.GetY() == rightHandler.GetY()) {
122         if (handlersDistance + bothOffset < TEXT_DRAG_MIN_WIDTH.ConvertToPx()) {
123             delta = TEXT_DRAG_MIN_WIDTH.ConvertToPx() - (handlersDistance + bothOffset);
124             width += delta;
125             globalX -= delta / CONSTANT_HALF;
126         }
127     } else {
128         globalX = contentRect.Left() + globalOffset.GetX() - dragOffset;
129         width = contentRect.Width();
130     }
131     float contentX = (leftHandler.GetY() == rightHandler.GetY() ? box.Left() : 0) - dragOffset - delta / CONSTANT_HALF;
132     dragPattern->SetContentOffset({contentX, box.Top() - dragOffset});
133     dragContext->UpdatePosition(OffsetT<Dimension>(Dimension(globalX), Dimension(globalY)));
134     auto offsetXValue = globalOffset.GetX() - globalX;
135     auto offsetYValue = globalOffset.GetY() - globalY;
136     RectF rect(textStartOffset.GetX() + offsetXValue, textStartOffset.GetY() + offsetYValue, width, height);
137     SelectPositionInfo info(leftHandler.GetX() + offsetXValue, leftHandler.GetY() + offsetYValue,
138         rightHandler.GetX() + offsetXValue, rightHandler.GetY() + offsetYValue);
139     info.InitGlobalInfo(globalX, globalY);
140     TextDragData data(rect, width + bothOffset, height + bothOffset, leftHandler.Height(), rightHandler.Height());
141     data.initSelecitonInfo(info, leftHandler.GetY() == rightHandler.GetY());
142     return data;
143 }
144 
GetHandler(const bool isLeftHandler,const std::vector<RectF> boxes,const RectF contentRect,const OffsetF globalOffset,const OffsetF textStartOffset)145 RectF TextDragPattern::GetHandler(const bool isLeftHandler, const std::vector<RectF> boxes, const RectF contentRect,
146     const OffsetF globalOffset, const OffsetF textStartOffset)
147 {
148     auto adjustedTextStartY = textStartOffset.GetY() + std::min(globalOffset.GetY(), 0.0f);
149     auto box = isLeftHandler ? GetFirstBoxRect(boxes, contentRect, adjustedTextStartY) :
150         GetLastBoxRect(boxes, contentRect, adjustedTextStartY);
151     auto handlerX = (isLeftHandler ? box.Left() : box.Right()) + textStartOffset.GetX();
152     return {handlerX, box.Top() + textStartOffset.GetY(), 0, box.Height()};
153 }
154 
AdjustHandlers(const RectF contentRect,RectF & leftHandler,RectF & rightHandler)155 void TextDragPattern::AdjustHandlers(const RectF contentRect, RectF& leftHandler, RectF& rightHandler)
156 {
157     if (leftHandler.GetY() != rightHandler.GetY()) {
158         return;
159     }
160     leftHandler.SetLeft(std::max(leftHandler.GetX(), contentRect.Left()));
161     rightHandler.SetLeft(std::min(rightHandler.GetX(), contentRect.Right()));
162 }
163 
GenerateClipPath()164 std::shared_ptr<RSPath> TextDragPattern::GenerateClipPath()
165 {
166     std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
167     auto selectPosition = GetSelectPosition();
168     float startX = selectPosition.startX_;
169     float startY = selectPosition.startY_;
170     float textStart = GetTextRect().GetX();
171     float textEnd = textStart + GetTextRect().Width();
172     float endX = selectPosition.endX_;
173     float endY = selectPosition.endY_;
174     auto lineHeight = GetLineHeight();
175     if (OneLineSelected()) {
176         path->MoveTo(startX, endY);
177         path->LineTo(endX, endY);
178         path->LineTo(endX, endY + lineHeight);
179         path->LineTo(startX, endY + lineHeight);
180         path->LineTo(startX, endY);
181     } else {
182         endX = std::min(selectPosition.endX_, textEnd);
183         path->MoveTo(startX, startY);
184         path->LineTo(textEnd, startY);
185         path->LineTo(textEnd, endY);
186         path->LineTo(endX, endY);
187         path->LineTo(endX, endY + lastLineHeight_);
188         path->LineTo(textStart, endY + lastLineHeight_);
189         path->LineTo(textStart, startY + lineHeight);
190         path->LineTo(startX, startY + lineHeight);
191         path->LineTo(startX, startY);
192     }
193     return path;
194 }
195 
GenerateBackgroundPath(float offset,float radiusRatio)196 std::shared_ptr<RSPath> TextDragPattern::GenerateBackgroundPath(float offset, float radiusRatio)
197 {
198     std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
199     std::vector<TextPoint> points;
200     GenerateBackgroundPoints(points, offset);
201     CalculateLineAndArc(points, path, radiusRatio);
202     return path;
203 }
204 
GenerateSelBackgroundPath(float offset)205 std::shared_ptr<RSPath> TextDragPattern::GenerateSelBackgroundPath(float offset)
206 {
207     std::shared_ptr<RSPath> path = std::make_shared<RSPath>();
208     std::vector<TextPoint> points;
209     GenerateBackgroundPoints(points, offset);
210     CalculateLine(points, path);
211     return path;
212 }
213 
GenerateBackgroundPoints(std::vector<TextPoint> & points,float offset,bool needAdjust)214 void TextDragPattern::GenerateBackgroundPoints(std::vector<TextPoint>& points, float offset, bool needAdjust)
215 {
216     auto radius = GetDragCornerRadius().ConvertToPx();
217     auto bothOffset = offset * 2; // 2 : double
218     auto minWidth = TEXT_DRAG_MIN_WIDTH.ConvertToPx();
219     auto selectPosition = GetSelectPosition();
220     float startX = selectPosition.startX_;
221     float startY = selectPosition.startY_;
222     float endX = selectPosition.endX_;
223     float endY = selectPosition.endY_;
224     float textStart = GetTextRect().GetX();
225     float textEnd = textStart + GetTextRect().Width();
226     auto lineHeight = GetLineHeight();
227     if (OneLineSelected()) {
228         if (needAdjust && (endX - startX) + bothOffset < minWidth) {
229             float delta = minWidth - ((endX - startX) + bothOffset);
230             startX -= delta / 2.0f; // 2 : half
231             endX += delta / 2.0f;   // 2 : half
232         }
233         points.push_back(TextPoint(startX - offset, startY - offset));
234         points.push_back(TextPoint(endX + offset, endY - offset));
235         points.push_back(TextPoint(endX + offset, endY + lineHeight + offset));
236         points.push_back(TextPoint(startX - offset, endY + lineHeight + offset));
237         points.push_back(TextPoint(startX - offset, endY - offset));
238         points.push_back(TextPoint(endX + offset, endY - offset));
239     } else {
240         points.push_back(TextPoint(startX - offset, startY - offset));
241         points.push_back(TextPoint(textEnd + offset, startY - offset));
242         if (textEnd - radius < endX + radius) {
243             points.push_back(TextPoint(textEnd + offset, endY + lastLineHeight_ + offset));
244         } else {
245             points.push_back(TextPoint(textEnd + offset, endY + offset));
246             points.push_back(TextPoint(endX + offset, endY + offset));
247             points.push_back(TextPoint(endX + offset, endY + lastLineHeight_ + offset));
248         }
249         points.push_back(TextPoint(textStart - offset, endY + lastLineHeight_ + offset));
250         if (startX - radius < textStart + radius) {
251             points[0] = TextPoint(textStart - offset, startY - offset);
252             points.push_back(TextPoint(textStart - offset, startY - offset));
253         } else {
254             points.push_back(TextPoint(textStart - offset, startY + lineHeight - offset));
255             points.push_back(TextPoint(startX - offset, startY + lineHeight - offset));
256             points.push_back(TextPoint(startX - offset, startY - offset));
257         }
258         points.push_back(TextPoint(textEnd + offset, startY - offset));
259     }
260 }
261 
CalculateLineAndArc(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path,float radiusRatio)262 void TextDragPattern::CalculateLineAndArc(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path,
263     float radiusRatio)
264 {
265     auto originRadius = GetDragCornerRadius().ConvertToPx();
266     auto radius = originRadius * radiusRatio;
267     path->MoveTo(points[0].x + radius, points[0].y);
268     auto frontPoint = points[0];
269     size_t step = 2;
270     for (size_t i = 0; i + step < points.size(); i++) {
271         auto firstPoint = points[i];
272         auto crossPoint = points[i + 1];
273         auto secondPoint = points[i + step];
274         float tempRadius = radius;
275         if (crossPoint.y == firstPoint.y) {
276             int directionX = (crossPoint.x - firstPoint.x) > 0 ? 1 : -1;
277             int directionY = (secondPoint.y - crossPoint.y) > 0 ? 1 : -1;
278             auto direction =
279                 (directionX * directionY > 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
280             bool isInwardFoldingCorner = frontPoint.x == firstPoint.x && firstPoint.y == crossPoint.y &&
281                 secondPoint.x == crossPoint.x;
282             isInwardFoldingCorner = isInwardFoldingCorner && (frontPoint.y - firstPoint.y) *
283                 (crossPoint.y - secondPoint.y) > 0;
284             if (isInwardFoldingCorner) {
285                 radius = std::min(std::abs(secondPoint.y - crossPoint.y) / CONSTANT_HALF, tempRadius);
286             }
287             path->LineTo(crossPoint.x - radius * directionX, crossPoint.y);
288             path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x, crossPoint.y + radius * directionY);
289         } else {
290             int directionX = (secondPoint.x - crossPoint.x) > 0 ? 1 : -1;
291             int directionY = (crossPoint.y - firstPoint.y) > 0 ? 1 : -1;
292             auto direction =
293                 (directionX * directionY < 0) ? RSPathDirection::CW_DIRECTION : RSPathDirection::CCW_DIRECTION;
294             bool isInwardFoldingCorner = frontPoint.y == firstPoint.y && firstPoint.x == crossPoint.x &&
295                 secondPoint.y == crossPoint.y;
296             isInwardFoldingCorner = isInwardFoldingCorner && (frontPoint.x - firstPoint.x) *
297                 (crossPoint.x - secondPoint.x) > 0;
298             if (isInwardFoldingCorner) {
299                 radius = std::min(std::abs(firstPoint.y - crossPoint.y) / CONSTANT_HALF, tempRadius);
300             }
301             path->LineTo(crossPoint.x, crossPoint.y - radius * directionY);
302             path->ArcTo(radius, radius, 0.0f, direction, crossPoint.x + radius * directionX, secondPoint.y);
303         }
304         radius = tempRadius;
305         frontPoint = firstPoint;
306     }
307     path->MoveTo(points[0].x + radius, points[0].y);
308 }
309 
CalculateLine(std::vector<TextPoint> & points,std::shared_ptr<RSPath> & path)310 void TextDragPattern::CalculateLine(std::vector<TextPoint>& points, std::shared_ptr<RSPath>& path)
311 {
312     auto radius = GetDragCornerRadius().ConvertToPx();
313     path->MoveTo(points[0].x + radius, points[0].y);
314     size_t step = 2;
315     for (size_t i = 0; i + step < points.size(); i++) {
316         auto crossPoint = points[i + 1];
317         path->LineTo(crossPoint.x, crossPoint.y);
318     }
319 }
320 
GetDragBackgroundColor()321 Color TextDragPattern::GetDragBackgroundColor()
322 {
323     auto pipeline = PipelineContext::GetCurrentContextSafely();
324     CHECK_NULL_RETURN(pipeline, Color(TEXT_DRAG_COLOR_BG));
325     auto textTheme = pipeline->GetTheme<TextTheme>();
326     CHECK_NULL_RETURN(textTheme, Color(TEXT_DRAG_COLOR_BG));
327     return textTheme->GetDragBackgroundColor();
328 }
329 } // namespace OHOS::Ace::NG
330