• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-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/text/rosen_render_text.h"
17 
18 #include "rosen_text/typography.h"
19 #include "rosen_text/typography_create.h"
20 #include "render_service_client/core/ui/rs_node.h"
21 #include "unicode/uchar.h"
22 
23 #include "base/i18n/localization.h"
24 #include "core/common/font_manager.h"
25 #include "core/components/font/constants_converter.h"
26 #include "core/components/font/rosen_font_collection.h"
27 #include "core/components/text_span/rosen_render_text_span.h"
28 #include "core/pipeline/base/rosen_render_context.h"
29 
30 namespace OHOS::Ace {
31 namespace {
32 
33 const std::u16string ELLIPSIS = u"\u2026";
34 constexpr Dimension ADAPT_UNIT = 1.0_fp;
35 constexpr int32_t COMPATIBLE_VERSION = 6;
36 
37 } // namespace
38 
Update(const RefPtr<Component> & component)39 void RosenRenderText::Update(const RefPtr<Component>& component)
40 {
41     RenderText::Update(component);
42     if (auto rsNode = GetRSNode()) {
43         bool shouldClipToContent = (textStyle_.GetTextOverflow() == TextOverflow::CLIP);
44         rsNode->SetClipToFrame(shouldClipToContent);
45         if (isCustomFont_) {
46             rsNode->SetIsCustomTextType(isCustomFont_);
47         }
48     }
49 }
50 
GetBaselineDistance(TextBaseline textBaseline)51 double RosenRenderText::GetBaselineDistance(TextBaseline textBaseline)
52 {
53     if (textBaseline == TextBaseline::IDEOGRAPHIC) {
54         return paragraph_->GetIdeographicBaseline() + std::max(NormalizeToPx(textStyle_.GetBaselineOffset()), 0.0);
55     }
56     return paragraph_->GetAlphabeticBaseline() + std::max(NormalizeToPx(textStyle_.GetBaselineOffset()), 0.0);
57 }
58 
Paint(RenderContext & context,const Offset & offset)59 void RosenRenderText::Paint(RenderContext& context, const Offset& offset)
60 {
61     if (!NeedPaint()) {
62         return;
63     }
64 
65     if (needMeasure_) {
66         LOGW("Text can not paint before measure.");
67         return;
68     }
69     auto canvas = static_cast<RosenRenderContext*>(&context)->GetCanvas();
70     auto rsNode = static_cast<RosenRenderContext*>(&context)->GetRSNode();
71     if (!canvas || !rsNode || !paragraph_) {
72         LOGE("Paint canvas or paragraph is null");
73         return;
74     }
75     rsNode->SetPaintOrder(true);
76 
77     RenderNode::Paint(context, offset);
78 
79     auto baselineOffset = std::min(NormalizeToPx(textStyle_.GetBaselineOffset()), 0.0);
80     auto paintOffset = offset - Offset(0.0, baselineOffset);
81     auto textRealWidth = paragraph_->GetMaxWidth();
82     auto textRealHeight = paragraph_->GetHeight();
83     SetParagraphWidth(textRealWidth);
84     SetParagraphHeight(textRealHeight);
85     float newX = paintOffset.GetX();
86     float newY = paintOffset.GetY();
87 
88     if (text_->GetDeclarationHeight().IsValid()) {
89         switch (textStyle_.GetTextVerticalAlign()) {
90             case VerticalAlign::TOP:
91                 break;
92             case VerticalAlign::BOTTOM:
93                 newY = offset.GetY() + (GetLayoutSize().Height() - textRealHeight) -
94                        std::max(NormalizeToPx(textStyle_.GetBaselineOffset()), 0.0);
95                 break;
96             case VerticalAlign::CENTER:
97                 newY = offset.GetY() - NormalizeToPx(textStyle_.GetBaselineOffset()) +
98                        (GetLayoutSize().Height() - textRealHeight) / 2.0;
99                 break;
100             default:
101                 break;
102         }
103     }
104 
105     switch (textStyle_.GetTextAlign()) {
106         case TextAlign::LEFT:
107             break;
108         case TextAlign::START:
109             if (TextDirection::RTL == defaultTextDirection_) {
110                 newX = paintOffset.GetX() + (GetLayoutSize().Width() - textRealWidth);
111             }
112             break;
113         case TextAlign::RIGHT:
114             newX = paintOffset.GetX() + (GetLayoutSize().Width() - textRealWidth);
115             break;
116         case TextAlign::END:
117             if (TextDirection::RTL != defaultTextDirection_) {
118                 newX = paintOffset.GetX() + (GetLayoutSize().Width() - textRealWidth);
119             }
120             break;
121         case TextAlign::CENTER:
122             newX = paintOffset.GetX() + (GetLayoutSize().Width() - textRealWidth) / 2.0;
123             break;
124         case TextAlign::JUSTIFY:
125             break;
126         default:
127             break;
128     }
129 
130     PaintSelection(canvas, GetGlobalOffset());
131     paragraph_->Paint(canvas, newX, newY);
132 }
133 
NeedPaint()134 bool RosenRenderText::NeedPaint()
135 {
136     // If font is custom font, paint text until font is ready.
137     auto pipelineContext = context_.Upgrade();
138     if (pipelineContext && pipelineContext->GetFontManager()) {
139         auto fontNames = pipelineContext->GetFontManager()->GetFontNames();
140         for (const auto& familyName : textStyle_.GetFontFamilies()) {
141             if (std::find(std::begin(fontNames), std::end(fontNames), familyName) != std::end(fontNames) &&
142                 !isCallbackCalled_) {
143                 return false;
144             }
145         }
146         for (const auto& child : GetChildren()) {
147             auto span = AceType::DynamicCast<RenderTextSpan>(child);
148             if (!span) {
149                 continue;
150             }
151             for (const auto& familyName : span->GetSpanStyle().GetFontFamilies()) {
152                 if (std::find(std::begin(fontNames), std::end(fontNames), familyName) != std::end(fontNames) &&
153                     !span->IsCallbackCalled()) {
154                     return false;
155                 }
156             }
157         }
158     }
159     return true;
160 }
161 
Measure()162 Size RosenRenderText::Measure()
163 {
164     if (!text_ || CheckMeasureFlag()) {
165         return GetSize();
166     }
167 
168     textStyle_.SetMaxLines(maxLines_);
169     lastLayoutMaxWidth_ = GetLayoutParam().GetMaxSize().Width();
170     lastLayoutMinWidth_ = GetLayoutParam().GetMinSize().Width();
171     lastLayoutMaxHeight_ = GetLayoutParam().GetMaxSize().Height();
172     lastLayoutMinHeight_ = GetLayoutParam().GetMinSize().Height();
173     if (!textStyle_.GetAdaptTextSize()) {
174         if (!UpdateParagraph()) {
175             LOGE("fail to initialize text paragraph");
176             return Size();
177         }
178         paragraph_->Layout(lastLayoutMaxWidth_);
179     } else {
180         if (!AdaptTextSize(lastLayoutMaxWidth_)) {
181             LOGE("fail to initialize text paragraph in adapt text size step");
182             return Size();
183         }
184     }
185     needMeasure_ = false;
186     // If you need to lay out the text according to the maximum layout width given by the parent, use it.
187     if (text_->GetMaxWidthLayout()) {
188         paragraphNewWidth_ = GetLayoutParam().GetMaxSize().Width();
189         return GetSize();
190     }
191     // The reason for the second layout is because the TextAlign property needs the width of the layout,
192     // and the width of the second layout is used as the width of the TextAlign layout.
193     if (!NearEqual(lastLayoutMinWidth_, lastLayoutMaxWidth_)) {
194         paragraphNewWidth_ = std::clamp(GetTextWidth(), lastLayoutMinWidth_, lastLayoutMaxWidth_);
195         if (!NearEqual(paragraphNewWidth_, paragraph_->GetMaxWidth())) {
196             ApplyIndents(paragraphNewWidth_);
197             paragraph_->Layout(std::ceil(paragraphNewWidth_));
198         }
199     }
200     EffectAutoMaxLines();
201     return GetSize();
202 }
203 
IsCompatibleVersion()204 bool RosenRenderText::IsCompatibleVersion()
205 {
206     auto context = context_.Upgrade();
207     if (!context) {
208         return false;
209     }
210     return context->GetMinPlatformVersion() >= COMPATIBLE_VERSION;
211 }
212 
CheckMeasureFlag()213 bool RosenRenderText::CheckMeasureFlag()
214 {
215     if (isCallbackCalled_) {
216         needMeasure_ = true;
217     }
218     for (const auto& child : GetChildren()) {
219         auto span = AceType::DynamicCast<RenderTextSpan>(child);
220         if (span && (span->IsCallbackCalled() || span->NeedLayout())) {
221             paragraph_.reset();
222             needMeasure_ = true;
223             break;
224         }
225     }
226 
227     double paragraphMaxWidth = GetLayoutParam().GetMaxSize().Width();
228     double paragraphMinWidth = GetLayoutParam().GetMinSize().Width();
229     double paragraphMaxHeight = GetLayoutParam().GetMaxSize().Height();
230     double paragraphMinHeight = GetLayoutParam().GetMinSize().Height();
231 
232     if (!needMeasure_) {
233         // Layout when constrains of height is changed.
234         if (!NearEqual(paragraphMaxHeight, lastLayoutMaxHeight_) ||
235             !NearEqual(paragraphMinHeight, lastLayoutMinHeight_)) {
236             return false;
237         }
238         bool constrainsAffect = true;
239         auto layoutWidth = GetSize().Width();
240         if (NearEqual(paragraphMaxWidth, lastLayoutMaxWidth_) && NearEqual(paragraphMinWidth, lastLayoutMinWidth_)) {
241             // Width constrains not changed.
242             constrainsAffect = false;
243         } else if (!IsCompatibleVersion() && GreatOrEqual(layoutWidth, paragraphMinWidth) &&
244                    LessOrEqual(layoutWidth, paragraphMaxWidth) && (lastLayoutMaxWidth_ - layoutWidth > 1.0)) {
245             // Constrains changed but has no effect. For example, text width is 100 when constrains [0, 200].
246             // When constrains changed to [100, 300], there's no need to do layout.
247             // An exception is that given [0, 100], resulting in layout 100. We assume the actual layout size is more
248             // than 100 due to soft-wrap.
249             if (!textStyle_.GetAdaptTextSize()) {
250                 constrainsAffect = false;
251             }
252         }
253         if (!constrainsAffect) {
254             return true;
255         }
256     }
257     return false;
258 }
259 
EffectAutoMaxLines()260 void RosenRenderText::EffectAutoMaxLines()
261 {
262     // Effect when max-lines set auto.
263     if (!paragraph_ || !text_ || !text_->GetAutoMaxLines()) {
264         return;
265     }
266 
267     auto lineCount = GetTextLines();
268     while (lineCount > 0 && GreatNotEqual(paragraph_->GetHeight(), GetLayoutParam().GetMaxSize().Height())) {
269         textStyle_.SetMaxLines(--lineCount);
270         UpdateParagraphAndLayout(lastLayoutMaxWidth_);
271     }
272 }
273 
AdaptTextSize(double paragraphMaxWidth)274 bool RosenRenderText::AdaptTextSize(double paragraphMaxWidth)
275 {
276     const auto& preferTextSizeGroups = textStyle_.GetPreferTextSizeGroups();
277     if (!preferTextSizeGroups.empty()) {
278         return AdaptPreferTextSizeGroup(paragraphMaxWidth);
279     }
280     const auto& preferFontSizes = textStyle_.GetPreferFontSizes();
281     if (!preferFontSizes.empty()) {
282         return AdaptPreferTextSize(paragraphMaxWidth);
283     }
284     return AdaptMinTextSize(paragraphMaxWidth);
285 }
286 
AdaptMinTextSize(double paragraphMaxWidth)287 bool RosenRenderText::AdaptMinTextSize(double paragraphMaxWidth)
288 {
289     double maxFontSize = NormalizeToPx(textStyle_.GetAdaptMaxFontSize());
290     double minFontSize = NormalizeToPx(textStyle_.GetAdaptMinFontSize());
291     if (LessNotEqual(maxFontSize, minFontSize) || LessOrEqual(textStyle_.GetAdaptMinFontSize().Value(), 0.0)) {
292         if (!UpdateParagraph()) {
293             LOGE("fail to initialize text paragraph when adapt min text size.");
294             return false;
295         }
296         paragraph_->Layout(lastLayoutMaxWidth_);
297         return true;
298     }
299     Dimension step = ADAPT_UNIT;
300     if (GreatNotEqual(textStyle_.GetAdaptFontSizeStep().Value(), 0.0)) {
301         step = textStyle_.GetAdaptFontSizeStep();
302     }
303     double stepSize = NormalizeToPx(step);
304     while (GreatOrEqual(maxFontSize, minFontSize)) {
305         textStyle_.SetFontSize(Dimension(maxFontSize));
306         if (!UpdateParagraphAndLayout(paragraphMaxWidth)) {
307             return false;
308         }
309         if (!DidExceedMaxLines(paragraphMaxWidth)) {
310             break;
311         }
312         maxFontSize -= stepSize;
313     }
314     return true;
315 }
316 
AdaptPreferTextSize(double paragraphMaxWidth)317 bool RosenRenderText::AdaptPreferTextSize(double paragraphMaxWidth)
318 {
319     // Use preferFontSizes to adapt lines.
320     const auto& preferFontSizes = textStyle_.GetPreferFontSizes();
321     for (const auto& fontSize : preferFontSizes) {
322         textStyle_.SetFontSize(fontSize);
323         if (!UpdateParagraphAndLayout(paragraphMaxWidth)) {
324             return false;
325         }
326         if (!DidExceedMaxLines(paragraphMaxWidth)) {
327             break;
328         }
329     }
330     return true;
331 }
332 
AdaptPreferTextSizeGroup(double paragraphMaxWidth)333 bool RosenRenderText::AdaptPreferTextSizeGroup(double paragraphMaxWidth)
334 {
335     // Use preferTextSizeGroup.
336     const auto& preferTextSizeGroups = textStyle_.GetPreferTextSizeGroups();
337     for (const auto& preferTextSizeGroup : preferTextSizeGroups) {
338         textStyle_.SetFontSize(preferTextSizeGroup.fontSize);
339         textStyle_.SetMaxLines(preferTextSizeGroup.maxLines);
340         textStyle_.SetTextOverflow(preferTextSizeGroup.textOverflow);
341         if (!UpdateParagraphAndLayout(paragraphMaxWidth)) {
342             return false;
343         }
344         if ((preferTextSizeGroup.textOverflow == TextOverflow::NONE) || (!DidExceedMaxLines(paragraphMaxWidth))) {
345             break;
346         }
347     }
348     return true;
349 }
350 
UpdateParagraphAndLayout(double paragraphMaxWidth)351 bool RosenRenderText::UpdateParagraphAndLayout(double paragraphMaxWidth)
352 {
353     if (!UpdateParagraph()) {
354         return false;
355     }
356     if (paragraph_) {
357         paragraph_->Layout(paragraphMaxWidth);
358     }
359     return true;
360 }
361 
GetTextLines()362 uint32_t RosenRenderText::GetTextLines()
363 {
364     uint32_t textLines = 0;
365     if (paragraph_ != nullptr) {
366         textLines = paragraph_->GetLineCount();
367     }
368     return textLines;
369 }
370 
GetTouchPosition(const Offset & offset)371 int32_t RosenRenderText::GetTouchPosition(const Offset& offset)
372 {
373     if (!paragraph_) {
374         return 0;
375     }
376     return static_cast<int32_t>(paragraph_->GetGlyphIndexByCoordinate(offset.GetX(), offset.GetY()).index);
377 }
378 
GetSize()379 Size RosenRenderText::GetSize()
380 {
381     double height = paragraph_ ? paragraph_->GetHeight() : 0.0;
382     double heightFinal = std::min(
383         height + std::fabs(NormalizeToPx(textStyle_.GetBaselineOffset())), GetLayoutParam().GetMaxSize().Height());
384     const auto& VerticalAlign = textStyle_.GetTextVerticalAlign();
385     if (((VerticalAlign == VerticalAlign::TOP || VerticalAlign == VerticalAlign::CENTER ||
386             VerticalAlign == VerticalAlign::BOTTOM)) &&
387         text_->GetDeclarationHeight().IsValid()) {
388         heightFinal = GetLayoutParam().GetMaxSize().Height();
389     }
390 
391     return Size(text_->GetMaxWidthLayout() ? paragraphNewWidth_ : std::ceil(paragraphNewWidth_), heightFinal);
392 }
393 
ApplyWhiteSpace()394 std::string RosenRenderText::ApplyWhiteSpace()
395 {
396     std::string data = text_->GetData();
397     switch (textStyle_.GetWhiteSpace()) {
398         case WhiteSpace::NORMAL:
399             StringUtils::ReplaceTabAndNewLine(data);
400             break;
401         case WhiteSpace::PRE:
402             break;
403         case WhiteSpace::NOWRAP:
404             textStyle_.SetMaxLines(1);
405             StringUtils::ReplaceTabAndNewLine(data);
406             break;
407         case WhiteSpace::PRE_WRAP:
408             break;
409         case WhiteSpace::PRE_LINE:
410             StringUtils::ReplaceSpace(data);
411             break;
412         default:
413             break;
414     }
415     return data;
416 }
417 
ApplyIndents(double width)418 void RosenRenderText::ApplyIndents(double width)
419 {
420     std::vector<float> indents;
421     double indent;
422     if (textStyle_.GetTextIndent().Unit() != DimensionUnit::PERCENT) {
423         indent = NormalizeToPx(textStyle_.GetTextIndent());
424     } else {
425         indent = width * textStyle_.GetTextIndent().Value();
426     }
427 
428     if (indent > 0.0) {
429         indents.push_back(indent);
430         indents.push_back(0.0);
431     } else {
432         indents.push_back(0.0);
433         indents.push_back(-indent);
434     }
435 }
436 
UpdateParagraph()437 bool RosenRenderText::UpdateParagraph()
438 {
439     if (!text_) {
440         return false;
441     }
442 
443     using namespace Constants;
444 
445     Rosen::TypographyStyle style;
446 
447     if (alignment_.has_value()) {
448         textStyle_.SetTextAlign(alignment_.value());
449     }
450     const auto& textAlign = textStyle_.GetTextAlign();
451     if (textAlign == TextAlign::START || textAlign == TextAlign::END) {
452         std::string data = text_->GetData();
453         if (!GetChildren().empty()) {
454             for (const auto& child : GetChildren()) {
455                 auto span = DynamicCast<RenderTextSpan>(child);
456                 if (span && !span->GetSpanData().empty()) {
457                     data = span->GetSpanData();
458                     break;
459                 }
460             }
461         }
462         if (!ChangeDirectionIfNeeded(data)) {
463             defaultTextDirection_ = text_->GetTextDirection();
464         }
465     }
466     std::string displayData = ApplyWhiteSpace();
467     style.textDirection = ConvertTxtTextDirection(defaultTextDirection_);
468     style.textAlign = ConvertTxtTextAlign(textAlign);
469     style.maxLines = textStyle_.GetMaxLines();
470     style.locale = Localization::GetInstance()->GetFontLocale();
471     if (textStyle_.GetTextOverflow() == TextOverflow::ELLIPSIS) {
472         if (!IsCompatibleVersion() && textStyle_.GetMaxLines() == UINT32_MAX && !text_->GetAutoMaxLines()) {
473             style.maxLines = 1;
474         }
475         style.ellipsis = ELLIPSIS;
476         auto context = GetContext().Upgrade();
477         if (context && context->UseLiteStyle()) {
478             style.maxLines = 1;
479         }
480     }
481 
482     auto fontCollection = RosenFontCollection::GetInstance().GetFontCollection();
483     if (!fontCollection) {
484         LOGW("UpdateParagraph: fontCollection is null");
485         return false;
486     }
487     auto builder = Rosen::TypographyCreate::Create(style, fontCollection);
488     std::string textValue = "";
489 
490     Rosen::TextStyle txtStyle;
491     ConvertTxtStyle(textStyle_, context_, txtStyle);
492     builder->PushStyle(txtStyle);
493     const auto& children = GetChildren();
494     if (!children.empty()) {
495         touchRegions_.clear();
496         for (const auto& child : children) {
497             auto textSpan = AceType::DynamicCast<RosenRenderTextSpan>(child);
498             if (textSpan) {
499                 textSpan->UpdateText(*builder, touchRegions_, textValue);
500             }
501         }
502         textValue_.text = textValue;
503         textForDisplay_ = textValue;
504     } else {
505         StringUtils::TransformStrCase(displayData, (int32_t)textStyle_.GetTextCase());
506         builder->AppendText(StringUtils::Str8ToStr16(displayData));
507     }
508     paragraph_ = builder->CreateTypography();
509 
510     ApplyIndents(GetLayoutParam().GetMaxSize().Width());
511     return true;
512 }
513 
GetTextWidth()514 double RosenRenderText::GetTextWidth()
515 {
516     if (!paragraph_) {
517         return 0.0;
518     }
519     if (!IsCompatibleVersion()) {
520         return paragraph_->GetMaxIntrinsicWidth();
521     }
522     if (paragraph_ != nullptr && paragraph_->GetLineCount() == 1) {
523         return std::max(paragraph_->GetActualWidth(), paragraph_->GetMaxIntrinsicWidth());
524     }
525     return paragraph_->GetActualWidth();
526 }
527 
DidExceedMaxLines(double paragraphMaxWidth)528 bool RosenRenderText::DidExceedMaxLines(double paragraphMaxWidth)
529 {
530     if (paragraph_ != nullptr) {
531         bool didExceedMaxLines = paragraph_->DidExceedMaxLines() ||
532                                  (textStyle_.GetAdaptHeight() &&
533                                      GreatNotEqual(paragraph_->GetHeight(), GetLayoutParam().GetMaxSize().Height()));
534         if (textStyle_.GetMaxLines() == 1) {
535             return didExceedMaxLines || GreatNotEqual(GetTextWidth(), paragraphMaxWidth);
536         }
537         return didExceedMaxLines;
538     }
539     return false;
540 }
541 
ChangeDirectionIfNeeded(const std::string & data)542 bool RosenRenderText::ChangeDirectionIfNeeded(const std::string& data)
543 {
544     auto declaration = text_->GetDeclaration();
545     if (!declaration) {
546         return false;
547     }
548     auto& commonAttr = static_cast<CommonAttribute&>(declaration->GetAttribute(AttributeTag::COMMON_ATTR));
549     if (!commonAttr.IsValid() || commonAttr.direction != TextDirection::AUTO) {
550         return false;
551     }
552     auto showingTextForWString = StringUtils::ToWstring(data);
553     for (const auto& charOfShowingText : showingTextForWString) {
554         if (u_charDirection(charOfShowingText) == UCharDirection::U_LEFT_TO_RIGHT) {
555             defaultTextDirection_ = TextDirection::LTR;
556             return true;
557         } else if (u_charDirection(charOfShowingText) == UCharDirection::U_RIGHT_TO_LEFT) {
558             defaultTextDirection_ = TextDirection::RTL;
559             return true;
560         } else if (!IsCompatibleVersion() &&
561                    u_charDirection(charOfShowingText) == UCharDirection::U_RIGHT_TO_LEFT_ARABIC) {
562             defaultTextDirection_ = TextDirection::RTL;
563             return true;
564         }
565     }
566     return false;
567 }
568 
MaybeRelease()569 bool RosenRenderText::MaybeRelease()
570 {
571     auto context = GetContext().Upgrade();
572     if (context && context->GetRenderFactory() && context->GetRenderFactory()->GetRenderTextFactory()->Recycle(this)) {
573         ClearRenderObject();
574         return false;
575     }
576     return true;
577 }
578 
ClearRenderObject()579 void RosenRenderText::ClearRenderObject()
580 {
581     RenderText::ClearRenderObject();
582     paragraph_ = nullptr;
583     paragraphNewWidth_ = 0.0;
584     lastLayoutMaxWidth_ = 0.0;
585     lastLayoutMinWidth_ = 0.0;
586     lastLayoutMaxHeight_ = 0.0;
587     lastLayoutMinHeight_ = 0.0;
588 }
589 
GetHandleOffset(int32_t extend)590 Offset RosenRenderText::GetHandleOffset(int32_t extend)
591 {
592     Rect result;
593     GetCaretRect(extend, result);
594     selectHeight_ = result.Bottom() - result.Top();
595     Offset handleLocalOffset = Offset((result.Left() + result.Right()) / 2.0, result.Bottom());
596     Offset handleOffset = handleLocalOffset + GetOffsetToPage();
597     return handleOffset;
598 }
599 
600 } // namespace OHOS::Ace
601