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