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 &¶graph = 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