• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-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/text_layout_algorithm.h"
17 
18 #include "base/geometry/dimension.h"
19 #include "base/log/ace_trace.h"
20 #include "core/components/common/properties/alignment.h"
21 #include "core/components/text/text_theme.h"
22 #include "core/components/hyperlink/hyperlink_theme.h"
23 #include "core/components_ng/pattern/text/text_pattern.h"
24 #include "base/utils/utf_helper.h"
25 
26 namespace OHOS::Ace::NG {
27 namespace {
28 constexpr int32_t HUNDRED = 100;
29 constexpr int32_t TWENTY = 20;
30 const std::string CUSTOM_SYMBOL_SUFFIX = "_CustomSymbol";
31 const std::string DEFAULT_SYMBOL_FONTFAMILY = "HM Symbol";
32 
GetAdaptedMaxLines(const TextStyle & textStyle,const LayoutConstraintF & contentConstraint)33 uint32_t GetAdaptedMaxLines(const TextStyle& textStyle, const LayoutConstraintF& contentConstraint)
34 {
35     double minTextSizeHeight = textStyle.GetAdaptMinFontSize().ConvertToPxDistribute(
36         textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale());
37     if (LessOrEqual(minTextSizeHeight, 0.0)) {
38         minTextSizeHeight = textStyle.GetFontSize().ConvertToPxDistribute(
39             textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale());
40     }
41     double lineHeight = minTextSizeHeight;
42     if (textStyle.HasHeightOverride()) {
43         lineHeight = textStyle.GetLineHeight().Unit() == DimensionUnit::PERCENT
44                             ? textStyle.GetLineHeight().ConvertToPxWithSize(contentConstraint.maxSize.Height())
45                             : textStyle.GetLineHeight().ConvertToPxDistribute(
46                                 textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale());
47     }
48     // plus extraLine to ensure maxlines -1 is the next maxline to try for layout
49     uint32_t maxLines = contentConstraint.maxSize.Height() / (lineHeight) + 1;
50     return std::max(maxLines, static_cast<uint32_t>(0));
51 }
52 }; // namespace
53 
TextLayoutAlgorithm(std::list<RefPtr<SpanItem>> spans,RefPtr<ParagraphManager> pManager,bool isSpanStringMode,bool isMarquee)54 TextLayoutAlgorithm::TextLayoutAlgorithm(
55     std::list<RefPtr<SpanItem>> spans, RefPtr<ParagraphManager> pManager, bool isSpanStringMode, bool isMarquee)
56 {
57     paragraphManager_ = pManager;
58     isSpanStringMode_ = isSpanStringMode;
59 
60     if (!isSpanStringMode) {
61         if (!spans.empty()) {
62             spans_.emplace_back(std::move(spans));
63         }
64         return;
65     }
66     if (spans.empty()) {
67         return;
68     }
69     if (isMarquee) {
70         for (const auto& span : spans) {
71             span->SetNeedRemoveNewLine(false);
72         }
73         spans_.emplace_back(std::move(spans));
74         return;
75     }
76     ConstructParagraphSpanGroup(spans);
77     if (!spans.empty()) {
78         auto maxlines = spans.front()->textLineStyle->GetMaxLines().value_or(UINT32_MAX);
79         spanStringHasMaxLines_ |= maxlines != UINT32_MAX;
80         spans_.emplace_back(std::move(spans));
81     }
82 }
83 
84 TextLayoutAlgorithm::TextLayoutAlgorithm() = default;
85 
ConstructParagraphSpanGroup(std::list<RefPtr<SpanItem>> & spans)86 void TextLayoutAlgorithm::ConstructParagraphSpanGroup(std::list<RefPtr<SpanItem>>& spans)
87 {
88     // split spans into groups by mew paragraph style
89     auto it = spans.begin();
90     ParagraphStyle pStyle;
91     GetSpanParagraphStyle(nullptr, (*it), pStyle);
92     while (it != spans.end()) {
93         auto spanItem = *it;
94         if (!spanItem) {
95             ++it;
96             continue;
97         }
98         spanItem->SetNeedRemoveNewLine(false);
99         if (spanItem->content.back() == u'\n') {
100             if (std::next(it) == spans.end()) {
101                 break;
102             }
103             auto next = *(std::next(it));
104             ParagraphStyle nextSpanParagraphStyle;
105             if (next) {
106                 GetSpanParagraphStyle(nullptr, next, nextSpanParagraphStyle);
107             } else {
108                 break;
109             }
110             if (pStyle != nextSpanParagraphStyle ||
111                 (pStyle.leadingMargin.has_value() && pStyle.leadingMargin->pixmap) || Positive(pStyle.indent.Value()) ||
112                 pStyle.maxLines != UINT32_MAX) {
113                 std::list<RefPtr<SpanItem>> newGroup;
114                 spanItem->SetNeedRemoveNewLine(true);
115                 newGroup.splice(newGroup.begin(), spans, spans.begin(), std::next(it));
116                 spanStringHasMaxLines_ |= pStyle.maxLines != UINT32_MAX;
117                 spans_.emplace_back(std::move(newGroup));
118                 it = spans.begin();
119                 pStyle = nextSpanParagraphStyle;
120                 continue;
121             }
122         }
123         ++it;
124     }
125 }
126 
OnReset()127 void TextLayoutAlgorithm::OnReset() {}
128 
MeasureContent(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)129 std::optional<SizeF> TextLayoutAlgorithm::MeasureContent(
130     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
131 {
132     auto host = layoutWrapper->GetHostNode();
133     CHECK_NULL_RETURN(host, std::nullopt);
134     auto pattern = host->GetPattern<TextPattern>();
135     CHECK_NULL_RETURN(pattern, std::nullopt);
136     auto textLayoutProperty = DynamicCast<TextLayoutProperty>(layoutWrapper->GetLayoutProperty());
137     CHECK_NULL_RETURN(textLayoutProperty, std::nullopt);
138     CheckNeedReCreateParagraph(textLayoutProperty, pattern);
139     TextStyle textStyle;
140     bool needRemain = false;
141     ConstructTextStyles(contentConstraint, layoutWrapper, textStyle, needRemain);
142     ACE_SCOPED_TRACE(
143         "TextLayoutAlgorithm::MeasureContent[id:%d][needReCreateParagraph:%d]", host->GetId(), needReCreateParagraph_);
144     if (textStyle.GetTextOverflow() == TextOverflow::MARQUEE) { // create a paragraph with all text in 1 line
145         isMarquee_ = true;
146         auto result = BuildTextRaceParagraph(textStyle, textLayoutProperty, contentConstraint, layoutWrapper);
147         ResetNeedReCreateParagraph(textLayoutProperty, needRemain);
148         return result;
149     }
150     if (isSpanStringMode_ && spanStringHasMaxLines_) {
151         textStyle.SetMaxLines(UINT32_MAX);
152     }
153     if (isSpanStringMode_ && host->LessThanAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN)) {
154         textStyle_ = textStyle;
155         BuildParagraph(textStyle, textLayoutProperty, contentConstraint, layoutWrapper);
156     } else {
157         if (!AddPropertiesAndAnimations(textStyle, textLayoutProperty, contentConstraint, layoutWrapper)) {
158             return std::nullopt;
159         }
160     }
161     ResetNeedReCreateParagraph(textLayoutProperty, needRemain);
162     textStyle_ = textStyle;
163     baselineOffset_ = textStyle.GetBaselineOffset().ConvertToPxDistribute(
164         textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale());
165     if (NearZero(contentConstraint.maxSize.Height()) || NearZero(contentConstraint.maxSize.Width())) {
166         return SizeF {};
167     }
168     CHECK_NULL_RETURN(paragraphManager_, std::nullopt);
169     auto height = paragraphManager_->GetHeight();
170     auto maxWidth = paragraphManager_->GetMaxWidth();
171     auto longestLine = paragraphManager_->GetLongestLine();
172     auto heightFinal = static_cast<float>(height + std::fabs(baselineOffset_));
173     if (contentConstraint.selfIdealSize.Height().has_value()) {
174         heightFinal = std::min(heightFinal, contentConstraint.selfIdealSize.Height().value());
175     } else {
176         heightFinal = std::min(heightFinal, contentConstraint.maxSize.Height());
177     }
178     if (host->GetTag() == V2::TEXT_ETS_TAG && textLayoutProperty->GetContent().value_or(u"").empty() &&
179         NonPositive(longestLine)) {
180         ACE_SCOPED_TRACE("TextHeightFinal [%f], TextContentWidth [%f], FontSize [%lf]", heightFinal, maxWidth,
181             textStyle.GetFontSize().ConvertToPxDistribute(
182                 textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale()));
183         return SizeF {};
184     }
185     return SizeF(maxWidth, heightFinal);
186 }
187 
AddPropertiesAndAnimations(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & textLayoutProperty,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)188 bool TextLayoutAlgorithm::AddPropertiesAndAnimations(TextStyle& textStyle,
189     const RefPtr<TextLayoutProperty>& textLayoutProperty, const LayoutConstraintF& contentConstraint,
190     LayoutWrapper* layoutWrapper)
191 {
192     bool result = false;
193     switch (textLayoutProperty->GetHeightAdaptivePolicyValue(TextHeightAdaptivePolicy::MAX_LINES_FIRST)) {
194         case TextHeightAdaptivePolicy::MAX_LINES_FIRST:
195             result = BuildParagraph(textStyle, textLayoutProperty, contentConstraint, layoutWrapper);
196             break;
197         case TextHeightAdaptivePolicy::MIN_FONT_SIZE_FIRST:
198             result = BuildParagraphAdaptUseMinFontSize(textStyle, textLayoutProperty, contentConstraint, layoutWrapper);
199             break;
200         case TextHeightAdaptivePolicy::LAYOUT_CONSTRAINT_FIRST:
201             result =
202                 BuildParagraphAdaptUseLayoutConstraint(textStyle, textLayoutProperty, contentConstraint, layoutWrapper);
203             break;
204         default:
205             break;
206     }
207     return result;
208 }
209 
CheckNeedReCreateParagraph(const RefPtr<TextLayoutProperty> & textLayoutProperty,const RefPtr<TextPattern> & textPattern)210 void TextLayoutAlgorithm::CheckNeedReCreateParagraph(
211     const RefPtr<TextLayoutProperty>& textLayoutProperty, const RefPtr<TextPattern>& textPattern)
212 {
213     CHECK_NULL_VOID(textLayoutProperty);
214     CHECK_NULL_VOID(textPattern);
215     auto useExternalParagraph = textPattern->GetExternalParagraph() && !textPattern->NeedShowAIDetect();
216     needReCreateParagraph_ =
217         textLayoutProperty->GetNeedReCreateParagraphValue(false) || !spans_.empty() || useExternalParagraph ||
218         textPattern->IsDragging() || textLayoutProperty->GetAdaptMaxFontSize().has_value() ||
219         textLayoutProperty->GetAdaptMinFontSize().has_value() ||
220         textLayoutProperty->GetHeightAdaptivePolicyValue(TextHeightAdaptivePolicy::MAX_LINES_FIRST) !=
221             TextHeightAdaptivePolicy::MAX_LINES_FIRST ||
222         textLayoutProperty->GetEllipsisModeValue(EllipsisMode::TAIL) == EllipsisMode::MIDDLE;
223 }
224 
ResetNeedReCreateParagraph(const RefPtr<TextLayoutProperty> & textLayoutProperty,bool needRemain)225 void TextLayoutAlgorithm::ResetNeedReCreateParagraph(
226     const RefPtr<TextLayoutProperty>& textLayoutProperty, bool needRemain)
227 {
228     CHECK_NULL_VOID(!needRemain);
229     CHECK_NULL_VOID(textLayoutProperty);
230     textLayoutProperty->ResetNeedReCreateParagraph();
231 }
232 
UpdateParagraphForAISpan(const TextStyle & textStyle,LayoutWrapper * layoutWrapper,const RefPtr<Paragraph> & paragraph)233 void TextLayoutAlgorithm::UpdateParagraphForAISpan(
234     const TextStyle& textStyle, LayoutWrapper* layoutWrapper, const RefPtr<Paragraph>& paragraph)
235 {
236     CHECK_NULL_VOID(layoutWrapper);
237     auto layoutProperty = layoutWrapper->GetLayoutProperty();
238     CHECK_NULL_VOID(layoutProperty);
239     auto frameNode = layoutWrapper->GetHostNode();
240     CHECK_NULL_VOID(frameNode);
241     auto pattern = frameNode->GetPattern<TextPattern>();
242     CHECK_NULL_VOID(pattern);
243     auto wTextForAI = pattern->GetTextForAI();
244     int32_t wTextForAILength = static_cast<int32_t>(wTextForAI.length());
245     int32_t preEnd = 0;
246     DragSpanPosition dragSpanPosition;
247     dragSpanPosition.dragStart = pattern->GetRecoverStart();
248     dragSpanPosition.dragEnd = pattern->GetRecoverEnd();
249     bool isDragging = pattern->IsDragging();
250     TextStyle aiSpanStyle = textStyle;
251     pattern->ModifyAISpanStyle(aiSpanStyle);
252     for (auto kv : pattern->GetAISpanMap()) {
253         if (preEnd >= wTextForAILength) {
254             break;
255         }
256         auto aiSpan = kv.second;
257         if (aiSpan.start < preEnd) {
258             TAG_LOGI(AceLogTag::ACE_TEXT, "Error prediction");
259             continue;
260         }
261         if (preEnd < aiSpan.start) {
262             dragSpanPosition.spanStart = preEnd;
263             dragSpanPosition.spanEnd = aiSpan.start;
264             GrayDisplayAISpan(dragSpanPosition, wTextForAI, textStyle, isDragging, paragraph);
265         }
266         preEnd = aiSpan.end;
267         dragSpanPosition.spanStart = aiSpan.start;
268         dragSpanPosition.spanEnd = aiSpan.end;
269         GrayDisplayAISpan(dragSpanPosition, wTextForAI, aiSpanStyle, isDragging, paragraph);
270     }
271     if (preEnd < wTextForAILength) {
272         dragSpanPosition.spanStart = preEnd;
273         dragSpanPosition.spanEnd = wTextForAILength;
274         GrayDisplayAISpan(dragSpanPosition, wTextForAI, textStyle, isDragging, paragraph);
275     }
276 }
277 
GrayDisplayAISpan(const DragSpanPosition & dragSpanPosition,const std::u16string wTextForAI,const TextStyle & textStyle,bool isDragging,const RefPtr<Paragraph> & paragraph)278 void TextLayoutAlgorithm::GrayDisplayAISpan(const DragSpanPosition& dragSpanPosition, const std::u16string wTextForAI,
279     const TextStyle& textStyle, bool isDragging, const RefPtr<Paragraph>& paragraph)
280 {
281     int32_t dragStart = dragSpanPosition.dragStart;
282     int32_t dragEnd = dragSpanPosition.dragEnd;
283     int32_t spanStart = dragSpanPosition.spanStart;
284     int32_t spanEnd = dragSpanPosition.spanEnd;
285     std::vector<std::u16string> contents = {};
286     std::u16string firstParagraph = u"";
287     std::u16string secondParagraph = u"";
288     std::u16string thirdParagraph = u"";
289     if (dragStart > spanEnd || dragEnd < spanStart || !isDragging) {
290         firstParagraph = StringOutBoundProtection(spanStart, spanEnd - spanStart, wTextForAI);
291     } else if (spanStart <= dragStart && spanEnd >= dragStart && spanEnd <= dragEnd) {
292         firstParagraph = StringOutBoundProtection(spanStart, dragStart - spanStart, wTextForAI);
293         secondParagraph = StringOutBoundProtection(dragStart, spanEnd - dragStart, wTextForAI);
294     } else if (spanStart >= dragStart && spanEnd <= dragEnd) {
295         secondParagraph = StringOutBoundProtection(spanStart, spanEnd - spanStart, wTextForAI);
296     } else if (spanStart <= dragStart && spanEnd >= dragEnd) {
297         firstParagraph = StringOutBoundProtection(spanStart, dragStart - spanStart, wTextForAI);
298         secondParagraph = StringOutBoundProtection(dragStart, dragEnd - dragStart, wTextForAI);
299         thirdParagraph = StringOutBoundProtection(dragEnd, spanEnd - dragEnd, wTextForAI);
300     } else {
301         secondParagraph = StringOutBoundProtection(spanStart, dragEnd - spanStart, wTextForAI);
302         thirdParagraph = StringOutBoundProtection(dragEnd, spanEnd - dragEnd, wTextForAI);
303     }
304     contents = { firstParagraph, secondParagraph, thirdParagraph };
305     CreateParagraphDrag(textStyle, contents, paragraph);
306 }
307 
StringOutBoundProtection(int32_t position,int32_t length,std::u16string wTextForAI)308 std::u16string TextLayoutAlgorithm::StringOutBoundProtection(int32_t position, int32_t length,
309     std::u16string wTextForAI)
310 {
311     int32_t wTextForAILength = static_cast<int32_t>(wTextForAI.length());
312     if (position >= 0 && position < wTextForAILength && length >= 0 && length <= wTextForAILength - position) {
313         return wTextForAI.substr(position, length);
314     }
315     return u"";
316 }
317 
CreateParagraph(const TextStyle & textStyle,std::u16string content,LayoutWrapper * layoutWrapper,double maxWidth)318 bool TextLayoutAlgorithm::CreateParagraph(
319     const TextStyle& textStyle, std::u16string content, LayoutWrapper* layoutWrapper, double maxWidth)
320 {
321     if (!paragraphManager_) {
322         paragraphManager_ = AceType::MakeRefPtr<ParagraphManager>();
323     }
324     paragraphManager_->Reset();
325     auto frameNode = layoutWrapper->GetHostNode();
326     CHECK_NULL_RETURN(frameNode, false);
327     auto pattern = frameNode->GetPattern<TextPattern>();
328     CHECK_NULL_RETURN(pattern, false);
329     pattern->LogForFormRender("CreateParagraph");
330     pattern->ClearCustomSpanPlaceholderInfo();
331     if (pattern->IsSensitiveEnalbe()) {
332         UpdateSensitiveContent(content);
333     }
334     auto useExternalParagraph = pattern->GetExternalParagraph() && !pattern->NeedShowAIDetect();
335     auto externalParagraphStyle = pattern->GetExternalParagraphStyle();
336     auto paraStyle = GetParagraphStyle(textStyle, content, layoutWrapper);
337     if (pattern->GetExternalParagraph()) {
338         if (!useExternalParagraph && externalParagraphStyle) {
339             paraStyle = externalParagraphStyle.value();
340         }
341     }
342     if (frameNode->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_ELEVEN) || isSpanStringMode_) {
343         paraStyle.fontSize = textStyle.GetFontSize().ConvertToPxDistribute(
344             textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale());
345     }
346     paraStyle.leadingMarginAlign = Alignment::CENTER;
347     // SymbolGlyph
348     if (frameNode->GetTag() == V2::SYMBOL_ETS_TAG) {
349         return UpdateSymbolTextStyle(textStyle, paraStyle, layoutWrapper, frameNode);
350     }
351     if (spans_.empty() || useExternalParagraph) {
352         // only use for text.
353         return UpdateSingleParagraph(layoutWrapper, paraStyle, textStyle, content, maxWidth);
354     } else {
355         return UpdateParagraphBySpan(layoutWrapper, paraStyle, maxWidth, textStyle);
356     }
357 }
358 
UpdateSymbolTextStyle(const TextStyle & textStyle,const ParagraphStyle & paraStyle,LayoutWrapper * layoutWrapper,RefPtr<FrameNode> & frameNode)359 bool TextLayoutAlgorithm::UpdateSymbolTextStyle(const TextStyle& textStyle, const ParagraphStyle& paraStyle,
360     LayoutWrapper* layoutWrapper, RefPtr<FrameNode>& frameNode)
361 {
362     auto &&paragraph = Paragraph::Create(paraStyle, FontCollection::Current());
363     CHECK_NULL_RETURN(paragraph, false);
364     auto layoutProperty = DynamicCast<TextLayoutProperty>(layoutWrapper->GetLayoutProperty());
365     CHECK_NULL_RETURN(layoutProperty, false);
366     auto symbolSourceInfo = layoutProperty->GetSymbolSourceInfo();
367     CHECK_NULL_RETURN(symbolSourceInfo, false);
368     TextStyle symbolTextStyle = textStyle;
369     symbolTextStyle.isSymbolGlyph_ = true;
370     symbolTextStyle.SetRenderStrategy(
371         symbolTextStyle.GetRenderStrategy() < 0 ? 0 : symbolTextStyle.GetRenderStrategy());
372     symbolTextStyle.SetEffectStrategy(
373         symbolTextStyle.GetEffectStrategy() < 0 ? 0 : symbolTextStyle.GetEffectStrategy());
374     auto symbolType = textStyle.GetSymbolType();
375     symbolTextStyle.SetSymbolType(symbolType);
376     std::vector<std::string> fontFamilies;
377     if (symbolType == SymbolType::CUSTOM) {
378         auto symbolFontFamily = textStyle.GetFontFamilies();
379         for (auto& name : symbolFontFamily) {
380             if (name.find(CUSTOM_SYMBOL_SUFFIX) != std::string::npos) {
381                 fontFamilies.push_back(name);
382                 break;
383             }
384         }
385         if (fontFamilies.empty()) {
386             return false;
387         }
388         symbolTextStyle.SetFontFamilies(fontFamilies);
389     } else {
390         fontFamilies.push_back(DEFAULT_SYMBOL_FONTFAMILY);
391         symbolTextStyle.SetFontFamilies(fontFamilies);
392     }
393     paragraph->PushStyle(symbolTextStyle);
394     if (symbolTextStyle.GetSymbolEffectOptions().has_value()) {
395         auto symbolEffectOptions = layoutProperty->GetSymbolEffectOptionsValue(SymbolEffectOptions());
396         symbolEffectOptions.Reset();
397         layoutProperty->UpdateSymbolEffectOptions(symbolEffectOptions);
398     }
399     paragraph->AddSymbol(symbolSourceInfo->GetUnicode());
400     paragraph->PopStyle();
401     paragraph->Build();
402     paragraph->SetParagraphSymbolAnimation(frameNode);
403     paragraphManager_->AddParagraph({ .paragraph = paragraph, .paragraphStyle = paraStyle, .start = 0, .end = 2 });
404     return true;
405 }
406 
CreateParagraphDrag(const TextStyle & textStyle,const std::vector<std::u16string> & contents,const RefPtr<Paragraph> & paragraph)407 void TextLayoutAlgorithm::CreateParagraphDrag(
408     const TextStyle& textStyle, const std::vector<std::u16string>& contents, const RefPtr<Paragraph>& paragraph)
409 {
410     TextStyle dragTextStyle = textStyle;
411     Color color = textStyle.GetTextColor().ChangeAlpha(DRAGGED_TEXT_TRANSPARENCY);
412     dragTextStyle.SetTextColor(color);
413     Color textDecorationColor = textStyle.GetTextDecorationColor().ChangeAlpha(DRAGGED_TEXT_TRANSPARENCY);
414     dragTextStyle.SetTextDecorationColor(textDecorationColor);
415     std::vector<TextStyle> textStyles { textStyle, dragTextStyle, textStyle };
416 
417     CHECK_NULL_VOID(paragraph);
418     for (size_t i = 0; i < contents.size(); i++) {
419         std::u16string splitStr = contents[i];
420         if (splitStr.empty()) {
421             continue;
422         }
423         auto& style = textStyles[i];
424         paragraph->PushStyle(style);
425         StringUtils::TransformStrCase(splitStr, static_cast<int32_t>(style.GetTextCase()));
426         UtfUtils::HandleInvalidUTF16(reinterpret_cast<uint16_t*>(splitStr.data()), splitStr.length(), 0);
427         paragraph->AddText(splitStr);
428         paragraph->PopStyle();
429     }
430 }
431 
CreateParagraphAndLayout(const TextStyle & textStyle,const std::u16string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper,bool needLayout)432 bool TextLayoutAlgorithm::CreateParagraphAndLayout(const TextStyle& textStyle, const std::u16string& content,
433     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper, bool needLayout)
434 {
435     auto maxSize = MultipleParagraphLayoutAlgorithm::GetMaxMeasureSize(contentConstraint);
436     ACE_TEXT_SCOPED_TRACE("CreateParagraphAndLayout[maxSize:%s][Len:%d][needReCreateParagraph:%d]",
437         maxSize.ToString().c_str(), static_cast<int32_t>(content.length()), needReCreateParagraph_);
438     if (needReCreateParagraph_ && !CreateParagraph(textStyle, content, layoutWrapper, maxSize.Width())) {
439         return false;
440     }
441     CHECK_NULL_RETURN(paragraphManager_, false);
442     auto paragraphInfo = paragraphManager_->GetParagraphs();
443     for (auto pIter = paragraphInfo.begin(); pIter != paragraphInfo.end(); pIter++) {
444         auto paragraph = pIter->paragraph;
445         CHECK_NULL_RETURN(paragraph, false);
446         paragraph->Layout(maxSize.Width());
447     }
448     return true;
449 }
450 
GetContentOffset(LayoutWrapper * layoutWrapper)451 OffsetF TextLayoutAlgorithm::GetContentOffset(LayoutWrapper* layoutWrapper)
452 {
453     return SetContentOffset(layoutWrapper);
454 }
455 
AdaptMinTextSize(TextStyle & textStyle,const std::u16string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)456 bool TextLayoutAlgorithm::AdaptMinTextSize(TextStyle& textStyle, const std::u16string& content,
457     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
458 {
459     ACE_TEXT_SCOPED_TRACE("TextLayoutAlgorithm::AdaptMinTextSize[Length:%d]", static_cast<int32_t>(content.length()));
460     // IsNeedAdaptFontSize
461     double maxFontSize = 0.0;
462     double minFontSize = 0.0;
463     auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
464     CHECK_NULL_RETURN(pipeline, false);
465     GetAdaptMaxMinFontSize(textStyle, maxFontSize, minFontSize, contentConstraint);
466     if (!IsNeedAdaptFontSize(maxFontSize, minFontSize)) {
467         if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
468             TAG_LOGW(AceLogTag::ACE_TEXT, "create paragraph fail, contentConstraint:%{public}s",
469                 contentConstraint.ToString().c_str());
470             return false;
471         }
472         return true;
473     }
474     // Get suitableSize and set
475     auto ret = GetSuitableSize(textStyle, content, contentConstraint, layoutWrapper);
476     if (!ret.first) {
477         textStyle.SetFontSize(Dimension(minFontSize));
478         return CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper);
479     } else if (ret.first && NearEqual(textStyle.GetFontSize().Value(), ret.second)) {
480         return true; // The font is already set, no need to call CreateParagraphAndLayout again.
481     } else {
482         textStyle.SetFontSize(Dimension(ret.second));
483         return CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper);
484     }
485 }
486 
487 /**
488  * brief: Find the optimal font size within the range [minFontSize, maxFontSize].
489  * return: std::pair<bool, double>
490  *         - first: A boolean indicating whether a suitable size was found (true if found, false otherwise).
491  *         - second: The optimal font size if found, valid only when first is true.
492  */
GetSuitableSize(TextStyle & textStyle,const std::u16string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)493 std::pair<bool, double> TextLayoutAlgorithm::GetSuitableSize(TextStyle& textStyle, const std::u16string& content,
494     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
495 {
496     double maxFontSize = 0.0;
497     double minFontSize = 0.0;
498     GetAdaptMaxMinFontSize(textStyle, maxFontSize, minFontSize, contentConstraint);
499     auto step = Dimension(1.0, DimensionUnit::PX);
500 
501     if (GreatNotEqual(textStyle.GetAdaptFontSizeStep().Value(), 0.0)) {
502         step = textStyle.GetAdaptFontSizeStep();
503     }
504     double stepSize = step.ConvertToPxDistribute(textStyle.GetMinFontScale(),
505         textStyle.GetMaxFontScale(), textStyle.IsAllowScale());
506     if (NearEqual(stepSize, 0.0)) {
507         return {false, 0.0};
508     }
509     int32_t stepCount = (maxFontSize - minFontSize) / stepSize;
510 
511     // Compare time complexity: stepCount/2 < log(stepCount)+1, exp2 is fast.
512     if (step.GetAdaptDimensionUnit(step) != DimensionUnit::PX && exp2(stepCount / 2 - 1) < stepCount) {
513         return GetSuitableSizeLD(textStyle, content, contentConstraint, layoutWrapper, stepSize);
514     } else {
515         return GetSuitableSizeBS(textStyle, content, contentConstraint, layoutWrapper, stepSize);
516     }
517 }
518 
GetSuitableSizeLD(TextStyle & textStyle,const std::u16string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper,double stepSize)519 std::pair<bool, double> TextLayoutAlgorithm::GetSuitableSizeLD(TextStyle& textStyle, const std::u16string& content,
520     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper, double stepSize)
521 {
522     double maxFontSize = 0.0;
523     double minFontSize = 0.0;
524     GetAdaptMaxMinFontSize(textStyle, maxFontSize, minFontSize, contentConstraint);
525     auto maxSize = MultipleParagraphLayoutAlgorithm::GetMaxMeasureSize(contentConstraint);
526 
527     if (NearEqual(stepSize, 0.0)) {
528         return {false, 0.0};
529     }
530     double suitableSize = maxFontSize;
531     uint32_t suitCount = 0;
532     while (GreatOrEqual(suitableSize, minFontSize)) {
533         textStyle.SetFontSize(Dimension(suitableSize));
534         if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
535             return {false, 0.0};
536         }
537         if (!DidExceedMaxLines(maxSize)) {
538             return {true, suitableSize};
539         }
540         if (suitCount % HUNDRED == 0) {
541             auto host = layoutWrapper->GetHostNode();
542             CHECK_NULL_RETURN(host, {});
543             TAG_LOGW(AceLogTag::ACE_TEXT,
544                 "suit layout:%{public}d, [id:%{public}d, suitSize:%{public}f, minFontSize:%{public}f, "
545                 "stepSize:%{public}f]",
546                 suitCount, host->GetId(), suitableSize, minFontSize, stepSize);
547         }
548         suitCount++;
549         suitableSize -= stepSize;
550     }
551     return {false, 0.0};
552 }
553 
GetSuitableSizeBS(TextStyle & textStyle,const std::u16string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper,double stepSize)554 std::pair<bool, double> TextLayoutAlgorithm::GetSuitableSizeBS(TextStyle& textStyle, const std::u16string& content,
555     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper, double stepSize)
556 {
557     double maxFontSize = 0.0;
558     double minFontSize = 0.0;
559     GetAdaptMaxMinFontSize(textStyle, maxFontSize, minFontSize, contentConstraint);
560     auto maxSize = MultipleParagraphLayoutAlgorithm::GetMaxMeasureSize(contentConstraint);
561 
562     // Boundary check: for efficiency and to ensure the optimal size is within [minFontSize, maxFontSize].
563     textStyle.SetFontSize(Dimension(maxFontSize));
564     if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
565         TAG_LOGW(AceLogTag::ACE_TEXT, "GetSuitableSizeBS create paragraph fail");
566         return {false, 0.0};
567     }
568     if (!DidExceedMaxLines(maxSize)) {
569         return {true, maxFontSize};
570     }
571 
572     if (NearEqual(stepSize, 0.0)) {
573         return {false, 0.0};
574     }
575     int32_t stepCount = (maxFontSize - minFontSize) / stepSize;
576 
577     // Binary search: to find the optimal size within [minFontSize, maxFontSize].
578     int32_t leftBound = 0;
579     int32_t rightBound = stepCount;
580     int32_t mid = (leftBound + rightBound) / 2;
581     uint32_t suitCount = 0;
582     while (leftBound < rightBound) {
583         double suitSz = minFontSize + mid * stepSize;
584         textStyle.SetFontSize(Dimension(suitSz));
585         if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
586             return {false, 0.0};
587         }
588         if (!DidExceedMaxLines(maxSize)) {
589             leftBound = mid;
590         } else {
591             rightBound = mid - 1;
592         }
593         if (suitCount % TWENTY == 0) {
594             auto host = layoutWrapper->GetHostNode();
595             CHECK_NULL_RETURN(host, {});
596             TAG_LOGI(AceLogTag::ACE_TEXT,
597                 "suit layout:%{public}d, [id:%{public}d, suitSz:%{public}f, stepCount:%{public}d, stepSize:%{public}f]",
598                 suitCount, host->GetId(), suitSz, stepCount, stepSize);
599         }
600         suitCount++;
601         mid = (leftBound + rightBound + 1) / 2;
602     }
603     return {true, minFontSize + leftBound * stepSize};
604 }
605 
GetBaselineOffset() const606 float TextLayoutAlgorithm::GetBaselineOffset() const
607 {
608     return baselineOffset_;
609 }
610 
UpdateSingleParagraph(LayoutWrapper * layoutWrapper,ParagraphStyle paraStyle,const TextStyle & textStyle,const std::u16string & content,double maxWidth)611 bool TextLayoutAlgorithm::UpdateSingleParagraph(LayoutWrapper* layoutWrapper, ParagraphStyle paraStyle,
612     const TextStyle& textStyle, const std::u16string& content, double maxWidth)
613 {
614     auto host = layoutWrapper->GetHostNode();
615     CHECK_NULL_RETURN(host, false);
616     ACE_TEXT_SCOPED_TRACE("TextLayoutAlgorithm::UpdateSingleParagraph[id:%d][length:%d][w:%f]", host->GetId(),
617         static_cast<int32_t>(content.length()), maxWidth);
618     auto pattern = host->GetPattern<TextPattern>();
619     CHECK_NULL_RETURN(pattern, false);
620     auto externalParagraph = pattern->GetExternalParagraph();
621     RefPtr<Paragraph> paragraph;
622     if (externalParagraph) {
623         paragraph = Paragraph::Create(externalParagraph.value());
624     } else {
625         paragraph = Paragraph::Create(paraStyle, FontCollection::Current());
626     }
627     CHECK_NULL_RETURN(paragraph, false);
628     auto textStyleTmp = textStyle;
629     textStyleTmp.ResetTextBaseline();
630     paragraph->PushStyle(textStyleTmp);
631     if (pattern->NeedShowAIDetect()) {
632         UpdateParagraphForAISpan(textStyle, layoutWrapper, paragraph);
633     } else {
634         if (pattern->IsDragging()) {
635             auto dragContents = pattern->GetDragContents();
636             CreateParagraphDrag(textStyle, dragContents, paragraph);
637         } else {
638             auto value = content;
639             StringUtils::TransformStrCase(value, static_cast<int32_t>(textStyle.GetTextCase()));
640             UtfUtils::HandleInvalidUTF16(reinterpret_cast<uint16_t*>(value.data()), value.length(), 0);
641             paragraph->AddText(value);
642         }
643     }
644     paragraph->Build();
645     ApplyIndent(paraStyle, paragraph, maxWidth, textStyle);
646     paragraphManager_->AddParagraph({ .paragraph = paragraph,
647         .paragraphStyle = paraStyle,
648         .start = 0,
649         .end = content.length() });
650     return true;
651 }
652 
BuildParagraph(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)653 bool TextLayoutAlgorithm::BuildParagraph(TextStyle& textStyle, const RefPtr<TextLayoutProperty>& layoutProperty,
654     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
655 {
656     if (!textStyle.GetAdaptTextSize() ||
657         (!spans_.empty() && Container::LessThanAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN))) {
658         if (!CreateParagraphAndLayout(textStyle, layoutProperty->GetContent().value_or(u""), contentConstraint,
659             layoutWrapper)) {
660             TAG_LOGW(AceLogTag::ACE_TEXT, "BuildParagraph fail, contentConstraint:%{public}s",
661                 contentConstraint.ToString().c_str());
662             return false;
663         }
664     } else {
665         if (!AdaptMinTextSize(textStyle, layoutProperty->GetContent().value_or(u""), contentConstraint,
666             layoutWrapper)) {
667             return false;
668         }
669     }
670     return ParagraphReLayout(contentConstraint);
671 }
672 
BuildParagraphAdaptUseMinFontSize(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)673 bool TextLayoutAlgorithm::BuildParagraphAdaptUseMinFontSize(TextStyle& textStyle,
674     const RefPtr<TextLayoutProperty>& layoutProperty, const LayoutConstraintF& contentConstraint,
675     LayoutWrapper* layoutWrapper)
676 {
677     if (!AdaptMaxTextSize(textStyle, layoutProperty->GetContent().value_or(u""), contentConstraint, layoutWrapper)) {
678         return false;
679     }
680     return ParagraphReLayout(contentConstraint);
681 }
682 
BuildParagraphAdaptUseLayoutConstraint(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)683 bool TextLayoutAlgorithm::BuildParagraphAdaptUseLayoutConstraint(TextStyle& textStyle,
684     const RefPtr<TextLayoutProperty>& layoutProperty, const LayoutConstraintF& contentConstraint,
685     LayoutWrapper* layoutWrapper)
686 {
687     // Create the paragraph and obtain the height.
688     if (!BuildParagraph(textStyle, layoutProperty, contentConstraint, layoutWrapper)) {
689         return false;
690     }
691 
692     CHECK_NULL_RETURN(paragraphManager_, false);
693     if (textStyle.GetMaxLines() == UINT32_MAX) {
694         uint32_t maxLines = GetAdaptedMaxLines(textStyle, contentConstraint);
695         textStyle.SetMaxLines(maxLines);
696     }
697     auto lineCount = static_cast<uint32_t>(paragraphManager_->GetLineCount());
698     lineCount = std::max(std::min(textStyle.GetMaxLines(), lineCount), static_cast<uint32_t>(0));
699     textStyle.SetMaxLines(lineCount);
700     textStyle.DisableAdaptTextSize();
701 
702     auto height = static_cast<float>(paragraphManager_->GetHeight());
703     uint32_t adaptCount = 0;
704     while (GreatNotEqual(height, contentConstraint.maxSize.Height())) {
705         auto maxLines = textStyle.GetMaxLines();
706         if (maxLines == 0) {
707             break;
708         } else {
709             maxLines = textStyle.GetMaxLines() - 1;
710             textStyle.SetMaxLines(maxLines);
711         }
712         if (!BuildParagraph(textStyle, layoutProperty, contentConstraint, layoutWrapper)) {
713             return false;
714         }
715         if (adaptCount % HUNDRED == 0) {
716             auto host = layoutWrapper->GetHostNode();
717             CHECK_NULL_RETURN(host, {});
718             TAG_LOGW(AceLogTag::ACE_TEXT,
719                 "AdaptLayout:%{public}d, [id:%{public}d, height:%{public}f, constraint:%{public}s, "
720                 "maxlines:%{public}d]",
721                 adaptCount, host->GetId(), height, contentConstraint.ToString().c_str(), maxLines);
722         }
723         adaptCount++;
724         height = static_cast<float>(paragraphManager_->GetHeight());
725     }
726     return true;
727 }
728 
BuildTextRaceParagraph(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)729 std::optional<SizeF> TextLayoutAlgorithm::BuildTextRaceParagraph(TextStyle& textStyle,
730     const RefPtr<TextLayoutProperty>& layoutProperty, const LayoutConstraintF& contentConstraint,
731     LayoutWrapper* layoutWrapper)
732 {
733     // create a paragraph with all text in 1 line
734     textStyle.SetTextOverflow(TextOverflow::CLIP);
735     textStyle.SetMaxLines(1);
736     textStyle.SetTextIndent(Dimension(0.0f));
737     std::u16string content = layoutProperty->GetContent().value_or(u"");
738     std::replace(content.begin(), content.end(), u'\n', u' ');
739     if (!textStyle.GetAdaptTextSize()) {
740         if (!CreateParagraph(textStyle, content, layoutWrapper)) {
741             return std::nullopt;
742         }
743     } else {
744         if (!AdaptMinTextSize(textStyle, content, contentConstraint, layoutWrapper)) {
745             return std::nullopt;
746         }
747     }
748 
749     textStyle_ = textStyle;
750     auto paragraph = GetSingleParagraph();
751     // layout the paragraph to the width of text
752     paragraph->Layout(std::numeric_limits<float>::max());
753     float paragraphWidth = paragraph->GetLongestLineWithIndent();
754     if (contentConstraint.selfIdealSize.Width().has_value()) {
755         paragraphWidth = std::max(contentConstraint.selfIdealSize.Width().value(), paragraphWidth);
756     } else {
757         paragraphWidth = std::max(contentConstraint.minSize.Width(), paragraphWidth);
758     }
759     paragraphWidth = std::ceil(paragraphWidth);
760     paragraph->Layout(paragraphWidth);
761 
762     auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck();
763     // calculate the content size
764     auto height = static_cast<float>(paragraph->GetHeight());
765     baselineOffset_ = static_cast<float>(
766         layoutProperty->GetBaselineOffsetValue(Dimension())
767             .ConvertToPxDistribute(textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale()));
768     float heightFinal =
769         std::min(static_cast<float>(height + std::fabs(baselineOffset_)), contentConstraint.maxSize.Height());
770 
771     float widthFinal = paragraphWidth;
772     if (contentConstraint.selfIdealSize.Width().has_value()) {
773         if (contentConstraint.selfIdealSize.Width().value() < paragraphWidth) {
774             widthFinal = contentConstraint.selfIdealSize.Width().value();
775         }
776     } else if (contentConstraint.maxSize.Width() < paragraphWidth) {
777         widthFinal = contentConstraint.maxSize.Width();
778     }
779     return SizeF(widthFinal, heightFinal);
780 }
781 
AdaptMaxTextSize(TextStyle & textStyle,const std::u16string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)782 bool TextLayoutAlgorithm::AdaptMaxTextSize(TextStyle& textStyle, const std::u16string& content,
783     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
784 {
785     constexpr Dimension ADAPT_UNIT = 1.0_fp;
786     auto textLayoutProperty = DynamicCast<TextLayoutProperty>(layoutWrapper->GetLayoutProperty());
787     CHECK_NULL_RETURN(textLayoutProperty, false);
788     auto step = textLayoutProperty->GetAdaptFontSizeStepValue(ADAPT_UNIT);
789     return AdaptMaxFontSize(textStyle, content, step, contentConstraint, layoutWrapper);
790 }
791 
UpdateSensitiveContent(std::u16string & content)792 void TextLayoutAlgorithm::UpdateSensitiveContent(std::u16string& content)
793 {
794     std::replace_if(
795         content.begin(), content.end(),
796         [](char16_t ch) {
797             return ch != u'\n';
798         }, u'-');
799 }
800 
GetTextStyle() const801 std::optional<TextStyle> TextLayoutAlgorithm::GetTextStyle() const
802 {
803     return textStyle_;
804 }
805 
GetLineCount() const806 size_t TextLayoutAlgorithm::GetLineCount() const
807 {
808     size_t count = 0;
809     CHECK_NULL_RETURN(paragraphManager_, 0);
810     auto paragraphInfo = paragraphManager_->GetParagraphs();
811     for (auto pIter = paragraphInfo.begin(); pIter != paragraphInfo.end(); pIter++) {
812         auto paragraph = pIter->paragraph;
813         CHECK_NULL_RETURN(paragraph, 0);
814         count += paragraph->GetLineCount();
815     }
816     return count;
817 }
818 
DidExceedMaxLines(const SizeF & maxSize)819 bool TextLayoutAlgorithm::DidExceedMaxLines(const SizeF& maxSize)
820 {
821     CHECK_NULL_RETURN(paragraphManager_, false);
822     bool didExceedMaxLines = paragraphManager_->DidExceedMaxLines();
823     didExceedMaxLines = didExceedMaxLines || GreatNotEqual(paragraphManager_->GetHeight(), maxSize.Height());
824     didExceedMaxLines =
825         didExceedMaxLines || GreatNotEqual(paragraphManager_->GetLongestLineWithIndent(), maxSize.Width());
826     return didExceedMaxLines;
827 }
828 
IsAdaptExceedLimit(const SizeF & maxSize)829 bool TextLayoutAlgorithm::IsAdaptExceedLimit(const SizeF& maxSize)
830 {
831     CHECK_NULL_RETURN(paragraphManager_, false);
832     return (paragraphManager_->GetLineCount() > 1) || paragraphManager_->DidExceedMaxLines() ||
833            GreatNotEqual(paragraphManager_->GetLongestLineWithIndent(), maxSize.Width());
834 }
835 } // namespace OHOS::Ace::NG
836