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 <limits>
19
20 #include "text_layout_adapter.h"
21
22 #include "base/geometry/dimension.h"
23 #include "base/i18n/localization.h"
24 #include "base/utils/utils.h"
25 #include "core/components/text/text_theme.h"
26 #include "core/components_ng/base/frame_node.h"
27 #include "core/components_ng/pattern/image/image_layout_property.h"
28 #include "core/components_ng/pattern/text/text_layout_property.h"
29 #include "core/components_ng/pattern/text/text_pattern.h"
30 #include "core/components_ng/render/drawing_prop_convertor.h"
31 #include "core/components_ng/render/font_collection.h"
32 #include "core/pipeline_ng/pipeline_context.h"
33 #include "core/common/font_manager.h"
34
35 namespace OHOS::Ace::NG {
36 namespace {
37 /**
38 * The baseline information needs to be calculated based on contentOffsetY.
39 */
GetContentOffsetY(LayoutWrapper * layoutWrapper)40 float GetContentOffsetY(LayoutWrapper* layoutWrapper)
41 {
42 auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
43 const auto& padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
44 auto offsetY = padding.top.value_or(0);
45 auto align = Alignment::CENTER;
46 if (layoutWrapper->GetLayoutProperty()->GetPositionProperty()) {
47 align = layoutWrapper->GetLayoutProperty()->GetPositionProperty()->GetAlignment().value_or(align);
48 }
49 const auto& content = layoutWrapper->GetGeometryNode()->GetContent();
50 if (content) {
51 offsetY += Alignment::GetAlignPosition(size, content->GetRect().GetSize(), align).GetY();
52 }
53 return offsetY;
54 }
55 } // namespace
56
57 TextLayoutAlgorithm::TextLayoutAlgorithm() = default;
58
OnReset()59 void TextLayoutAlgorithm::OnReset() {}
60
MeasureContent(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)61 std::optional<SizeF> TextLayoutAlgorithm::MeasureContent(
62 const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
63 {
64 if (!contentConstraint.maxSize.IsPositive()) {
65 return std::nullopt;
66 }
67
68 auto frameNode = layoutWrapper->GetHostNode();
69 CHECK_NULL_RETURN(frameNode, std::nullopt);
70 auto pipeline = frameNode->GetContext();
71 CHECK_NULL_RETURN(pipeline, std::nullopt);
72 auto textLayoutProperty = DynamicCast<TextLayoutProperty>(layoutWrapper->GetLayoutProperty());
73 CHECK_NULL_RETURN(textLayoutProperty, std::nullopt);
74 auto pattern = frameNode->GetPattern<TextPattern>();
75 CHECK_NULL_RETURN(pattern, std::nullopt);
76 auto contentModifier = pattern->GetContentModifier();
77
78 TextStyle textStyle = CreateTextStyleUsingTheme(
79 textLayoutProperty->GetFontStyle(), textLayoutProperty->GetTextLineStyle(), pipeline->GetTheme<TextTheme>());
80 if (contentModifier) {
81 SetPropertyToModifier(textLayoutProperty, contentModifier);
82 contentModifier->ModifyTextStyle(textStyle);
83 contentModifier->SetFontReady(false);
84 }
85 // Register callback for fonts.
86 FontRegisterCallback(frameNode, textStyle);
87
88 // Determines whether a foreground color is set or inherited.
89 UpdateTextColorIfForeground(frameNode, textStyle);
90
91 if (textStyle.GetTextOverflow() == TextOverflow::MARQUEE) {
92 return BuildTextRaceParagraph(textStyle, textLayoutProperty, contentConstraint, pipeline, layoutWrapper);
93 }
94
95 if (!AddPropertiesAndAnimations(textStyle, textLayoutProperty, contentConstraint, pipeline, layoutWrapper)) {
96 return std::nullopt;
97 }
98
99 textStyle_ = textStyle;
100
101 auto height = static_cast<float>(paragraph_->GetHeight());
102 double baselineOffset = 0.0;
103 textStyle.GetBaselineOffset().NormalizeToPx(
104 pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(), 0.0f, baselineOffset);
105
106 baselineOffset_ = static_cast<float>(baselineOffset);
107
108 auto heightFinal = static_cast<float>(height + std::fabs(baselineOffset));
109 if (contentConstraint.selfIdealSize.Height().has_value()) {
110 heightFinal = std::min(
111 static_cast<float>(height + std::fabs(baselineOffset)), contentConstraint.selfIdealSize.Height().value());
112 } else {
113 heightFinal =
114 std::min(static_cast<float>(height + std::fabs(baselineOffset)), contentConstraint.maxSize.Height());
115 }
116 return SizeF(paragraph_->GetMaxWidth(), heightFinal);
117 }
118
AddPropertiesAndAnimations(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & textLayoutProperty,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)119 bool TextLayoutAlgorithm::AddPropertiesAndAnimations(TextStyle& textStyle,
120 const RefPtr<TextLayoutProperty>& textLayoutProperty, const LayoutConstraintF& contentConstraint,
121 const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
122 {
123 bool result = false;
124 switch (textLayoutProperty->GetHeightAdaptivePolicyValue(TextHeightAdaptivePolicy::MAX_LINES_FIRST)) {
125 case TextHeightAdaptivePolicy::MAX_LINES_FIRST:
126 result = BuildParagraph(textStyle, textLayoutProperty, contentConstraint, pipeline, layoutWrapper);
127 break;
128 case TextHeightAdaptivePolicy::MIN_FONT_SIZE_FIRST:
129 result = BuildParagraphAdaptUseMinFontSize(
130 textStyle, textLayoutProperty, contentConstraint, pipeline, layoutWrapper);
131 break;
132 case TextHeightAdaptivePolicy::LAYOUT_CONSTRAINT_FIRST:
133 result = BuildParagraphAdaptUseLayoutConstraint(
134 textStyle, textLayoutProperty, contentConstraint, pipeline, layoutWrapper);
135 break;
136 default:
137 break;
138 }
139 return result;
140 }
141
FontRegisterCallback(const RefPtr<FrameNode> & frameNode,const TextStyle & textStyle)142 void TextLayoutAlgorithm::FontRegisterCallback(const RefPtr<FrameNode>& frameNode, const TextStyle& textStyle)
143 {
144 auto callback = [weakNode = WeakPtr<FrameNode>(frameNode)] {
145 auto frameNode = weakNode.Upgrade();
146 CHECK_NULL_VOID(frameNode);
147 frameNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
148 auto pattern = frameNode->GetPattern<TextPattern>();
149 CHECK_NULL_VOID(pattern);
150 auto modifier = DynamicCast<TextContentModifier>(pattern->GetContentModifier());
151 CHECK_NULL_VOID(modifier);
152 modifier->SetFontReady(true);
153 };
154 auto pipeline = frameNode->GetContext();
155 CHECK_NULL_VOID(pipeline);
156 auto fontManager = pipeline->GetFontManager();
157 if (fontManager) {
158 bool isCustomFont = false;
159 for (const auto& familyName : textStyle.GetFontFamilies()) {
160 bool customFont = fontManager->RegisterCallbackNG(frameNode, familyName, callback);
161 if (customFont) {
162 isCustomFont = true;
163 }
164 }
165 fontManager->AddVariationNodeNG(frameNode);
166 if (isCustomFont) {
167 auto pattern = frameNode->GetPattern<TextPattern>();
168 CHECK_NULL_VOID(pattern);
169 pattern->SetIsCustomFont(true);
170 auto modifier = DynamicCast<TextContentModifier>(pattern->GetContentModifier());
171 CHECK_NULL_VOID(modifier);
172 modifier->SetIsCustomFont(true);
173 }
174 }
175 }
176
Measure(LayoutWrapper * layoutWrapper)177 void TextLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
178 {
179 BoxLayoutAlgorithm::Measure(layoutWrapper);
180 auto baselineDistance = 0.0f;
181 if (paragraph_) {
182 baselineDistance = paragraph_->GetAlphabeticBaseline() + std::max(GetBaselineOffset(), 0.0f);
183 }
184 if (!NearZero(baselineDistance, 0.0f)) {
185 baselineDistance += GetContentOffsetY(layoutWrapper);
186 }
187 layoutWrapper->GetGeometryNode()->SetBaselineDistance(baselineDistance);
188 }
189
UpdateParagraph(LayoutWrapper * layoutWrapper)190 void TextLayoutAlgorithm::UpdateParagraph(LayoutWrapper* layoutWrapper)
191 {
192 int32_t spanTextLength = 0;
193 CHECK_NULL_VOID(layoutWrapper);
194 auto layoutProperty = layoutWrapper->GetLayoutProperty();
195 CHECK_NULL_VOID(layoutProperty);
196 auto frameNode = layoutWrapper->GetHostNode();
197 const auto& layoutConstrain = layoutProperty->CreateChildConstraint();
198 const auto& children = layoutWrapper->GetAllChildrenWithBuild();
199 auto iterItems = children.begin();
200 for (const auto& child : spanItemChildren_) {
201 if (!child) {
202 continue;
203 }
204 auto imageSpanItem = AceType::DynamicCast<ImageSpanItem>(child);
205 if (imageSpanItem) {
206 if (iterItems == children.end() || !(*iterItems)) {
207 continue;
208 }
209 (*iterItems)->Measure(layoutConstrain);
210 auto verticalAlign = VerticalAlign::BOTTOM;
211 auto imageLayoutProperty = DynamicCast<ImageLayoutProperty>((*iterItems)->GetLayoutProperty());
212 if (imageLayoutProperty) {
213 verticalAlign = imageLayoutProperty->GetVerticalAlign().value_or(VerticalAlign::BOTTOM);
214 }
215 auto geometryNode = (*iterItems)->GetGeometryNode();
216 if (!geometryNode) {
217 iterItems++;
218 continue;
219 }
220 auto width = geometryNode->GetMarginFrameSize().Width();
221 auto height = geometryNode->GetMarginFrameSize().Height();
222 child->placeHolderIndex = child->UpdateParagraph(frameNode, paragraph_, width, height, verticalAlign);
223 child->content = " ";
224 child->position = spanTextLength + 1;
225 spanTextLength += 1;
226 iterItems++;
227 } else {
228 child->UpdateParagraph(frameNode, paragraph_);
229 child->position = spanTextLength + StringUtils::ToWstring(child->content).length();
230 spanTextLength += StringUtils::ToWstring(child->content).length();
231 }
232 }
233 }
234
CreateParagraph(const TextStyle & textStyle,std::string content,LayoutWrapper * layoutWrapper)235 bool TextLayoutAlgorithm::CreateParagraph(const TextStyle& textStyle, std::string content, LayoutWrapper* layoutWrapper)
236 {
237 ParagraphStyle paraStyle = { .direction = GetTextDirection(content),
238 .align = textStyle.GetTextAlign(),
239 .maxLines = textStyle.GetMaxLines(),
240 .fontLocale = Localization::GetInstance()->GetFontLocale(),
241 .wordBreak = textStyle.GetWordBreak(),
242 .textOverflow = textStyle.GetTextOverflow() };
243 paragraph_ = Paragraph::Create(paraStyle, FontCollection::Current());
244 CHECK_NULL_RETURN(paragraph_, false);
245 paragraph_->PushStyle(textStyle);
246
247 if (spanItemChildren_.empty()) {
248 StringUtils::TransformStrCase(content, static_cast<int32_t>(textStyle.GetTextCase()));
249 paragraph_->AddText(StringUtils::Str8ToStr16(content));
250 } else {
251 UpdateParagraph(layoutWrapper);
252 }
253 paragraph_->Build();
254 return true;
255 }
256
CreateParagraphAndLayout(const TextStyle & textStyle,const std::string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)257 bool TextLayoutAlgorithm::CreateParagraphAndLayout(const TextStyle& textStyle, const std::string& content,
258 const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
259 {
260 if (!CreateParagraph(textStyle, content, layoutWrapper)) {
261 return false;
262 }
263 CHECK_NULL_RETURN(paragraph_, false);
264 auto maxSize = GetMaxMeasureSize(contentConstraint);
265 if (GreatNotEqual(textStyle.GetTextIndent().Value(), 0.0)) {
266 ApplyIndents(textStyle, maxSize.Width());
267 }
268 paragraph_->Layout(maxSize.Width());
269
270 bool ret = CreateImageSpanAndLayout(textStyle, content, contentConstraint, layoutWrapper);
271 return ret;
272 }
273
GetContentOffset(LayoutWrapper * layoutWrapper) const274 OffsetF TextLayoutAlgorithm::GetContentOffset(LayoutWrapper* layoutWrapper) const
275 {
276 OffsetF contentOffset(0.0, 0.0);
277 CHECK_NULL_RETURN(layoutWrapper, contentOffset);
278
279 auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
280 const auto& padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
281 MinusPaddingToSize(padding, size);
282 auto left = padding.left.value_or(0);
283 auto top = padding.top.value_or(0);
284 auto paddingOffset = OffsetF(left, top);
285 auto align = Alignment::CENTER;
286 if (layoutWrapper->GetLayoutProperty()->GetPositionProperty()) {
287 align = layoutWrapper->GetLayoutProperty()->GetPositionProperty()->GetAlignment().value_or(align);
288 }
289
290 const auto& content = layoutWrapper->GetGeometryNode()->GetContent();
291 if (content) {
292 contentOffset = Alignment::GetAlignPosition(size, content->GetRect().GetSize(), align) + paddingOffset;
293 content->SetOffset(contentOffset);
294 }
295 return contentOffset;
296 }
297
Layout(LayoutWrapper * layoutWrapper)298 void TextLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
299 {
300 CHECK_NULL_VOID(layoutWrapper);
301 auto contentOffset = GetContentOffset(layoutWrapper);
302 CHECK_NULL_VOID(paragraph_);
303 std::vector<int32_t> placeHolderIndex;
304 for (const auto& child : spanItemChildren_) {
305 if (!child) {
306 continue;
307 }
308 auto imageSpanItem = AceType::DynamicCast<ImageSpanItem>(child);
309 if (imageSpanItem) {
310 placeHolderIndex.emplace_back(child->placeHolderIndex);
311 }
312 }
313 if (spanItemChildren_.empty() || placeHolderIndex.empty()) {
314 return;
315 }
316
317 size_t index = 0;
318 size_t imageSpanIndex = 0;
319 std::vector<Rect> rectsForPlaceholders;
320 paragraph_->GetRectsForPlaceholders(rectsForPlaceholders);
321 const auto& children = layoutWrapper->GetAllChildrenWithBuild();
322 for (const auto& child : children) {
323 if (!child) {
324 ++index;
325 continue;
326 }
327 if (index >= placeHolderIndex.size()) {
328 return;
329 }
330 imageSpanIndex = placeHolderIndex.at(index);
331 if (imageSpanIndex >= rectsForPlaceholders.size() || imageSpanIndex < 0) {
332 ++index;
333 continue;
334 }
335 auto rect = rectsForPlaceholders.at(imageSpanIndex);
336 LOGI("ImageSpan Left= %{public}f, Top = %{public}f, width = %{public}f, height = %{public}f", rect.Left(),
337 rect.Top(), rect.Width(), rect.Height());
338 auto geometryNode = child->GetGeometryNode();
339 if (!geometryNode) {
340 ++index;
341 continue;
342 }
343 geometryNode->SetMarginFrameOffset(contentOffset + OffsetF(rect.Left(), rect.Top()));
344 child->Layout();
345 ++index;
346 }
347
348 #ifdef ENABLE_DRAG_FRAMEWORK
349 auto frameNode = layoutWrapper->GetHostNode();
350 CHECK_NULL_VOID(frameNode);
351 auto pipeline = frameNode->GetContext();
352 CHECK_NULL_VOID(pipeline);
353 auto pattern = frameNode->GetPattern<TextPattern>();
354 CHECK_NULL_VOID(pattern);
355 pattern->InitSpanImageLayout(placeHolderIndex, rectsForPlaceholders, contentOffset);
356 #endif
357 }
358
AdaptMinTextSize(TextStyle & textStyle,const std::string & content,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)359 bool TextLayoutAlgorithm::AdaptMinTextSize(TextStyle& textStyle, const std::string& content,
360 const LayoutConstraintF& contentConstraint, const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
361 {
362 double maxFontSize = 0.0;
363 double minFontSize = 0.0;
364 if (!textStyle.GetAdaptMaxFontSize().NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(),
365 pipeline->GetLogicScale(), contentConstraint.maxSize.Height(), maxFontSize)) {
366 return false;
367 }
368 if (!textStyle.GetAdaptMinFontSize().NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(),
369 pipeline->GetLogicScale(), contentConstraint.maxSize.Height(), minFontSize)) {
370 return false;
371 }
372 if (LessNotEqual(maxFontSize, minFontSize) || LessOrEqual(minFontSize, 0.0)) {
373 if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
374 LOGE("fail to initialize text paragraph when adapt min text size.");
375 return false;
376 }
377 return true;
378 }
379 constexpr Dimension ADAPT_UNIT = 1.0_fp;
380 Dimension step = ADAPT_UNIT;
381 if (GreatNotEqual(textStyle.GetAdaptFontSizeStep().Value(), 0.0)) {
382 step = textStyle.GetAdaptFontSizeStep();
383 }
384 double stepSize = 0.0;
385 if (!step.NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(),
386 contentConstraint.maxSize.Height(), stepSize)) {
387 return false;
388 }
389 auto maxSize = GetMaxMeasureSize(contentConstraint);
390 while (GreatOrEqual(maxFontSize, minFontSize)) {
391 textStyle.SetFontSize(Dimension(maxFontSize));
392 if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
393 return false;
394 }
395 if (!DidExceedMaxLines(maxSize)) {
396 break;
397 }
398 maxFontSize -= stepSize;
399 }
400 return true;
401 }
402
DidExceedMaxLines(const SizeF & maxSize)403 bool TextLayoutAlgorithm::DidExceedMaxLines(const SizeF& maxSize)
404 {
405 CHECK_NULL_RETURN(paragraph_, false);
406 bool didExceedMaxLines = paragraph_->DidExceedMaxLines();
407 didExceedMaxLines = didExceedMaxLines || GreatNotEqual(paragraph_->GetHeight(), maxSize.Height());
408 didExceedMaxLines = didExceedMaxLines || GreatNotEqual(paragraph_->GetLongestLine(), maxSize.Width());
409 return didExceedMaxLines;
410 }
411
GetTextDirection(const std::string & content)412 TextDirection TextLayoutAlgorithm::GetTextDirection(const std::string& content)
413 {
414 TextDirection textDirection = TextDirection::LTR;
415 auto showingTextForWString = StringUtils::ToWstring(content);
416 for (const auto& charOfShowingText : showingTextForWString) {
417 if (TextLayoutadapter::IsLeftToRight(charOfShowingText)) {
418 return TextDirection::LTR;
419 } else if (TextLayoutadapter::IsRightToLeft(charOfShowingText)) {
420 return TextDirection::RTL;
421 } else if (TextLayoutadapter::IsRightTOLeftArabic(charOfShowingText)) {
422 return TextDirection::RTL;
423 }
424 }
425 return textDirection;
426 }
427
GetTextWidth() const428 float TextLayoutAlgorithm::GetTextWidth() const
429 {
430 CHECK_NULL_RETURN(paragraph_, 0.0);
431 return paragraph_->GetTextWidth();
432 }
433
GetParagraph()434 const RefPtr<Paragraph>& TextLayoutAlgorithm::GetParagraph()
435 {
436 return paragraph_;
437 }
438
GetBaselineOffset() const439 float TextLayoutAlgorithm::GetBaselineOffset() const
440 {
441 return baselineOffset_;
442 }
443
GetMaxMeasureSize(const LayoutConstraintF & contentConstraint) const444 SizeF TextLayoutAlgorithm::GetMaxMeasureSize(const LayoutConstraintF& contentConstraint) const
445 {
446 auto maxSize = contentConstraint.selfIdealSize;
447 maxSize.UpdateIllegalSizeWithCheck(contentConstraint.maxSize);
448 return maxSize.ConvertToSizeT();
449 }
450
GetSpanItemChildren()451 std::list<RefPtr<SpanItem>>&& TextLayoutAlgorithm::GetSpanItemChildren()
452 {
453 return std::move(spanItemChildren_);
454 }
455
BuildParagraph(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)456 bool TextLayoutAlgorithm::BuildParagraph(TextStyle& textStyle, const RefPtr<TextLayoutProperty>& layoutProperty,
457 const LayoutConstraintF& contentConstraint, const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
458 {
459 if (!textStyle.GetAdaptTextSize()) {
460 if (!CreateParagraphAndLayout(
461 textStyle, layoutProperty->GetContent().value_or(""), contentConstraint, layoutWrapper)) {
462 return false;
463 }
464 } else {
465 if (!AdaptMinTextSize(
466 textStyle, layoutProperty->GetContent().value_or(""), contentConstraint, pipeline, layoutWrapper)) {
467 return false;
468 }
469 }
470
471 // Confirmed specification: The width of the text paragraph covers the width of the component, so this code is
472 // generally not allowed to be modified
473 if (!contentConstraint.selfIdealSize.Width()) {
474 float paragraphNewWidth = std::min(GetTextWidth(), paragraph_->GetMaxWidth());
475 paragraphNewWidth =
476 std::clamp(paragraphNewWidth, contentConstraint.minSize.Width(), contentConstraint.maxSize.Width());
477 if (!NearEqual(paragraphNewWidth, paragraph_->GetMaxWidth())) {
478 paragraph_->Layout(std::ceil(paragraphNewWidth));
479 }
480 }
481 return true;
482 }
483
BuildParagraphAdaptUseMinFontSize(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)484 bool TextLayoutAlgorithm::BuildParagraphAdaptUseMinFontSize(TextStyle& textStyle,
485 const RefPtr<TextLayoutProperty>& layoutProperty, const LayoutConstraintF& contentConstraint,
486 const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
487 {
488 if (!AdaptMaxTextSize(
489 textStyle, layoutProperty->GetContent().value_or(""), contentConstraint, pipeline, layoutWrapper)) {
490 return false;
491 }
492
493 // Confirmed specification: The width of the text paragraph covers the width of the component, so this code is
494 // generally not allowed to be modified
495 if (!contentConstraint.selfIdealSize.Width()) {
496 float paragraphNewWidth = std::min(GetTextWidth(), paragraph_->GetMaxWidth());
497 paragraphNewWidth =
498 std::clamp(paragraphNewWidth, contentConstraint.minSize.Width(), contentConstraint.maxSize.Width());
499 if (!NearEqual(paragraphNewWidth, paragraph_->GetMaxWidth())) {
500 paragraph_->Layout(std::ceil(paragraphNewWidth));
501 }
502 }
503
504 return true;
505 }
506
BuildParagraphAdaptUseLayoutConstraint(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)507 bool TextLayoutAlgorithm::BuildParagraphAdaptUseLayoutConstraint(TextStyle& textStyle,
508 const RefPtr<TextLayoutProperty>& layoutProperty, const LayoutConstraintF& contentConstraint,
509 const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
510 {
511 // Create the paragraph and obtain the height.
512 if (!BuildParagraph(textStyle, layoutProperty, contentConstraint, pipeline, layoutWrapper)) {
513 return false;
514 }
515 auto height = static_cast<float>(paragraph_->GetHeight());
516 double minTextSizeHeight = 0.0;
517 textStyle.GetAdaptMinFontSize().NormalizeToPx(
518 pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(), height, minTextSizeHeight);
519 if (LessOrEqual(minTextSizeHeight, 0.0)) {
520 textStyle.GetFontSize().NormalizeToPx(
521 pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(), height, minTextSizeHeight);
522 }
523 if (textStyle.GetMaxLines() == UINT32_MAX) {
524 double baselineOffset = 0.0;
525 textStyle.GetBaselineOffset().NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(),
526 pipeline->GetLogicScale(), contentConstraint.maxSize.Height(), baselineOffset);
527 double lineHeight = minTextSizeHeight;
528 if (textStyle.HasHeightOverride()) {
529 textStyle.GetLineHeight().NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(),
530 pipeline->GetLogicScale(), minTextSizeHeight, lineHeight);
531 }
532 uint32_t maxLines = (contentConstraint.maxSize.Height() - baselineOffset - minTextSizeHeight) / (lineHeight);
533 textStyle.SetMaxLines(maxLines);
534 textStyle.DisableAdaptTextSize();
535
536 if (!BuildParagraph(textStyle, layoutProperty, contentConstraint, pipeline, layoutWrapper)) {
537 return false;
538 }
539 }
540 // Reducing the value of MaxLines to make sure the parapraph could be layout in the constraint of height.
541 height = static_cast<float>(paragraph_->GetHeight());
542 while (GreatNotEqual(height, contentConstraint.maxSize.Height())) {
543 auto maxLines = textStyle.GetMaxLines();
544 if (maxLines == 0) {
545 break;
546 } else {
547 maxLines = textStyle.GetMaxLines() - 1;
548 textStyle.SetMaxLines(maxLines);
549 }
550 if (!BuildParagraph(textStyle, layoutProperty, contentConstraint, pipeline, layoutWrapper)) {
551 return false;
552 }
553 height = static_cast<float>(paragraph_->GetHeight());
554 }
555 return true;
556 }
557
BuildTextRaceParagraph(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)558 std::optional<SizeF> TextLayoutAlgorithm::BuildTextRaceParagraph(TextStyle& textStyle,
559 const RefPtr<TextLayoutProperty>& layoutProperty, const LayoutConstraintF& contentConstraint,
560 const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
561 {
562 // create a paragraph with all text in 1 line
563 textStyle.SetTextOverflow(TextOverflow::CLIP);
564 textStyle.SetMaxLines(1);
565 textStyle.SetTextAlign(TextAlign::START);
566 if (!CreateParagraph(textStyle, layoutProperty->GetContent().value_or(""), layoutWrapper)) {
567 return std::nullopt;
568 }
569 if (!paragraph_) {
570 return std::nullopt;
571 }
572
573 // layout the paragraph to the width of text
574 paragraph_->Layout(std::numeric_limits<float>::max());
575 float paragraphWidth = paragraph_->GetMaxWidth();
576 paragraph_->Layout(paragraphWidth);
577
578 textStyle_ = textStyle;
579
580 // calculate the content size
581 auto height = static_cast<float>(paragraph_->GetHeight());
582 double baselineOffset = 0.0;
583 if (layoutProperty->GetBaselineOffsetValue(Dimension())
584 .NormalizeToPx(
585 pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(), height, baselineOffset)) {
586 baselineOffset_ = static_cast<float>(baselineOffset);
587 }
588 float heightFinal =
589 std::min(static_cast<float>(height + std::fabs(baselineOffset)), contentConstraint.maxSize.Height());
590
591 float widthFinal = paragraphWidth;
592 if (contentConstraint.selfIdealSize.Width().has_value()) {
593 if (contentConstraint.selfIdealSize.Width().value() < paragraphWidth) {
594 widthFinal = contentConstraint.selfIdealSize.Width().value();
595 }
596 } else if (contentConstraint.maxSize.Width() < paragraphWidth) {
597 widthFinal = contentConstraint.maxSize.Width();
598 }
599 return SizeF(widthFinal, heightFinal);
600 }
601
SetPropertyToModifier(const RefPtr<TextLayoutProperty> & layoutProperty,RefPtr<TextContentModifier> modifier)602 void TextLayoutAlgorithm::SetPropertyToModifier(
603 const RefPtr<TextLayoutProperty>& layoutProperty, RefPtr<TextContentModifier> modifier)
604 {
605 auto fontFamily = layoutProperty->GetFontFamily();
606 if (fontFamily.has_value()) {
607 modifier->SetFontFamilies(fontFamily.value());
608 }
609 auto fontSize = layoutProperty->GetFontSize();
610 if (fontSize.has_value()) {
611 modifier->SetFontSize(fontSize.value());
612 }
613 auto fontWeight = layoutProperty->GetFontWeight();
614 if (fontWeight.has_value()) {
615 modifier->SetFontWeight(fontWeight.value());
616 }
617 auto textColor = layoutProperty->GetTextColor();
618 if (textColor.has_value()) {
619 modifier->SetTextColor(textColor.value());
620 }
621 auto textShadow = layoutProperty->GetTextShadow();
622 if (textShadow.has_value()) {
623 modifier->SetTextShadow(textShadow.value());
624 }
625 auto textDecorationColor = layoutProperty->GetTextDecorationColor();
626 if (textDecorationColor.has_value()) {
627 modifier->SetTextDecorationColor(textDecorationColor.value());
628 }
629 auto textDecoration = layoutProperty->GetTextDecoration();
630 if (textDecoration.has_value()) {
631 modifier->SetTextDecoration(textDecoration.value());
632 }
633 auto baselineOffset = layoutProperty->GetBaselineOffset();
634 if (baselineOffset.has_value()) {
635 modifier->SetBaselineOffset(baselineOffset.value());
636 }
637 }
638
AdaptMaxTextSize(TextStyle & textStyle,const std::string & content,const LayoutConstraintF & contentConstraint,const RefPtr<PipelineContext> & pipeline,LayoutWrapper * layoutWrapper)639 bool TextLayoutAlgorithm::AdaptMaxTextSize(TextStyle& textStyle, const std::string& content,
640 const LayoutConstraintF& contentConstraint, const RefPtr<PipelineContext>& pipeline, LayoutWrapper* layoutWrapper)
641 {
642 double maxFontSize = 0.0;
643 double minFontSize = 0.0;
644 if (!textStyle.GetAdaptMaxFontSize().NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(),
645 pipeline->GetLogicScale(), contentConstraint.maxSize.Height(), maxFontSize)) {
646 return false;
647 }
648 if (!textStyle.GetAdaptMinFontSize().NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(),
649 pipeline->GetLogicScale(), contentConstraint.maxSize.Height(), minFontSize)) {
650 return false;
651 }
652 if (LessNotEqual(maxFontSize, minFontSize) || LessOrEqual(minFontSize, 0.0)) {
653 // minFontSize or maxFontSize is invalid
654 if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
655 LOGE("fail to initialize text paragraph when adapt min text size.");
656 return false;
657 }
658 return true;
659 }
660 constexpr Dimension ADAPT_UNIT = 1.0_fp;
661 Dimension step = ADAPT_UNIT;
662 if (GreatNotEqual(textStyle.GetAdaptFontSizeStep().Value(), 0.0)) {
663 step = textStyle.GetAdaptFontSizeStep();
664 }
665 double stepSize = 0.0;
666 if (!step.NormalizeToPx(pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(),
667 contentConstraint.maxSize.Height(), stepSize)) {
668 return false;
669 }
670 RefPtr<Paragraph> paragraph;
671 auto maxSize = GetMaxMeasureSize(contentConstraint);
672 // Use the minFontSize to layout the paragraph. While using the minFontSize, if the paragraph could be layout in 1
673 // line, then increase the font size and try to layout using the maximum available fontsize.
674 while (LessOrEqual(minFontSize, maxFontSize)) {
675 textStyle.SetFontSize(Dimension(minFontSize));
676 if (!CreateParagraphAndLayout(textStyle, content, contentConstraint, layoutWrapper)) {
677 return false;
678 }
679 if (paragraph_->GetLineCount() > 1 || paragraph_->DidExceedMaxLines() ||
680 GreatNotEqual(paragraph_->GetLongestLine(), maxSize.Width())) {
681 if (paragraph != nullptr) {
682 paragraph_ = paragraph;
683 }
684 break;
685 }
686 minFontSize += stepSize;
687 paragraph = paragraph_;
688 }
689 return true;
690 }
691
GetTextStyle() const692 std::optional<TextStyle> TextLayoutAlgorithm::GetTextStyle() const
693 {
694 return textStyle_;
695 }
696
UpdateTextColorIfForeground(const RefPtr<FrameNode> & frameNode,TextStyle & textStyle)697 void TextLayoutAlgorithm::UpdateTextColorIfForeground(const RefPtr<FrameNode>& frameNode, TextStyle& textStyle)
698 {
699 auto renderContext = frameNode->GetRenderContext();
700 if (renderContext->HasForegroundColor()) {
701 if (renderContext->GetForegroundColorValue().GetValue() != textStyle.GetTextColor().GetValue()) {
702 textStyle.SetTextColor(Color::FOREGROUND);
703 }
704 } else if (renderContext->HasForegroundColorStrategy()) {
705 textStyle.SetTextColor(Color::FOREGROUND);
706 }
707 }
708
ApplyIndents(const TextStyle & textStyle,double width)709 void TextLayoutAlgorithm::ApplyIndents(const TextStyle& textStyle, double width)
710 {
711 CHECK_NULL_VOID(paragraph_);
712 auto pipeline = PipelineContext::GetCurrentContext();
713 CHECK_NULL_VOID(pipeline);
714 double indent = 0.0;
715 if (textStyle.GetTextIndent().Unit() != DimensionUnit::PERCENT) {
716 if (!textStyle.GetTextIndent().NormalizeToPx(
717 pipeline->GetDipScale(), pipeline->GetFontScale(), pipeline->GetLogicScale(), width, indent)) {
718 return;
719 }
720 } else {
721 indent = width * textStyle.GetTextIndent().Value();
722 }
723 std::vector<float> indents;
724 indents.emplace_back(static_cast<float>(indent));
725 indents.emplace_back(0.0);
726 paragraph_->SetIndents(indents);
727 }
728
IncludeImageSpan(LayoutWrapper * layoutWrapper)729 bool TextLayoutAlgorithm::IncludeImageSpan(LayoutWrapper* layoutWrapper)
730 {
731 CHECK_NULL_RETURN(layoutWrapper, false);
732 return (!layoutWrapper->GetAllChildrenWithBuild().empty());
733 }
734
GetSpanAndImageSpanList(std::list<RefPtr<SpanItem>> & spanList,std::map<int32_t,std::pair<Rect,RefPtr<ImageSpanItem>>> & imageSpanList)735 void TextLayoutAlgorithm::GetSpanAndImageSpanList(
736 std::list<RefPtr<SpanItem>>& spanList, std::map<int32_t, std::pair<Rect, RefPtr<ImageSpanItem>>>& imageSpanList)
737 {
738 std::vector<Rect> rectsForPlaceholders;
739 paragraph_->GetRectsForPlaceholders(rectsForPlaceholders);
740
741 for (const auto& child : spanItemChildren_) {
742 if (!child) {
743 continue;
744 }
745 auto imageSpanItem = AceType::DynamicCast<ImageSpanItem>(child);
746 if (imageSpanItem) {
747 int32_t index = child->placeHolderIndex;
748 if (index >= 0 && index < static_cast<int32_t>(rectsForPlaceholders.size())) {
749 imageSpanList.emplace(index, std::make_pair(rectsForPlaceholders.at(index), imageSpanItem));
750 }
751 } else {
752 spanList.emplace_back(child);
753 }
754 }
755 }
756
SplitSpanContentByLines(const TextStyle & textStyle,const std::list<RefPtr<SpanItem>> & spanList,std::map<int32_t,std::pair<Rect,std::list<RefPtr<SpanItem>>>> & spanContentLines)757 void TextLayoutAlgorithm::SplitSpanContentByLines(const TextStyle& textStyle,
758 const std::list<RefPtr<SpanItem>>& spanList,
759 std::map<int32_t, std::pair<Rect, std::list<RefPtr<SpanItem>>>>& spanContentLines)
760 {
761 int32_t currentLine = 0;
762 size_t currentLength = 0;
763 for (const auto& child : spanList) {
764 if (!child) {
765 continue;
766 }
767 std::string textValue = child->content;
768 std::vector<Rect> selectedRects;
769 if (!textValue.empty()) {
770 paragraph_->GetRectsForRange(currentLength, currentLength + textValue.size(), selectedRects);
771 }
772 currentLength += textValue.size();
773 Rect currentRect;
774 auto preLinetLastSpan = spanContentLines.rbegin();
775 double preLineFontSize = textStyle.GetFontSize().Value();
776 if (preLinetLastSpan != spanContentLines.rend()) {
777 currentRect = preLinetLastSpan->second.first;
778 if (preLinetLastSpan->second.second.back() && preLinetLastSpan->second.second.back()->fontStyle) {
779 preLineFontSize = preLinetLastSpan->second.second.back()->fontStyle->GetFontSize().value().Value();
780 }
781 }
782 for (const auto& rect : selectedRects) {
783 if (!NearEqual(currentRect.GetOffset().GetY(), rect.GetOffset().GetY())) {
784 currentRect = rect;
785 ++currentLine;
786 }
787
788 auto iter = spanContentLines.find(currentLine);
789 if (iter != spanContentLines.end()) {
790 auto iterSecond = std::find(iter->second.second.begin(), iter->second.second.end(), child);
791 if (iterSecond != iter->second.second.end()) {
792 continue;
793 }
794 if (NearEqual(rect.GetOffset().GetY(), currentRect.GetOffset().GetY()) && child->fontStyle &&
795 NearEqual(preLineFontSize, child->fontStyle->GetFontSize().value().Value())) {
796 continue;
797 }
798 iter->second.second.emplace_back(child);
799 } else {
800 std::list<RefPtr<SpanItem>> spanLineList;
801 spanLineList.emplace_back(child);
802 spanContentLines.emplace(currentLine, std::make_pair(currentRect, spanLineList));
803 }
804 }
805 }
806 }
807
SetImageSpanTextStyleByLines(const TextStyle & textStyle,std::map<int32_t,std::pair<Rect,RefPtr<ImageSpanItem>>> & imageSpanList,std::map<int32_t,std::pair<Rect,std::list<RefPtr<SpanItem>>>> & spanContentLines)808 void TextLayoutAlgorithm::SetImageSpanTextStyleByLines(const TextStyle& textStyle,
809 std::map<int32_t, std::pair<Rect, RefPtr<ImageSpanItem>>>& imageSpanList,
810 std::map<int32_t, std::pair<Rect, std::list<RefPtr<SpanItem>>>>& spanContentLines)
811 {
812 auto pipelineContext = PipelineContext::GetCurrentContext();
813 CHECK_NULL_VOID(pipelineContext);
814
815 auto imageSpanItem = imageSpanList.begin();
816 for (auto spanItem = spanContentLines.begin(); spanItem != spanContentLines.end(); spanItem++) {
817 for (; imageSpanItem != imageSpanList.end();) {
818 auto imageSpan = imageSpanItem->second.second;
819 if (!imageSpan) {
820 continue;
821 }
822 imageSpan->textStyle = textStyle;
823
824 auto offset = imageSpanItem->second.first.GetOffset();
825 auto imageSpanItemRect = imageSpanItem->second.first;
826 imageSpanItemRect.SetOffset(Offset(spanItem->second.first.GetOffset().GetX(), offset.GetY()));
827 bool isIntersectWith = spanItem->second.first.IsIntersectWith(imageSpanItemRect);
828 if (!isIntersectWith) {
829 break;
830 }
831 Dimension maxFontSize;
832 TextStyle spanTextStyle = textStyle;
833 for (const auto& child : spanItem->second.second) {
834 if (!child || !child->fontStyle) {
835 continue;
836 }
837 if (!child->fontStyle->GetFontSize().has_value()) {
838 continue;
839 }
840 if (LessNotEqual(maxFontSize.Value(), child->fontStyle->GetFontSize().value().Value())) {
841 maxFontSize = child->fontStyle->GetFontSize().value();
842 spanTextStyle = CreateTextStyleUsingTheme(
843 child->fontStyle, child->textLineStyle, pipelineContext->GetTheme<TextTheme>());
844 }
845 }
846 imageSpan->textStyle = spanTextStyle;
847 imageSpanItem++;
848 }
849 }
850 }
851
SetImageSpanTextStyle(const TextStyle & textStyle)852 void TextLayoutAlgorithm::SetImageSpanTextStyle(const TextStyle& textStyle)
853 {
854 CHECK_NULL_VOID(paragraph_);
855
856 std::list<RefPtr<SpanItem>> spanList;
857 std::map<int32_t, std::pair<Rect, RefPtr<ImageSpanItem>>> imageSpanList;
858 GetSpanAndImageSpanList(spanList, imageSpanList);
859
860 // split text content by lines
861 std::map<int32_t, std::pair<Rect, std::list<RefPtr<SpanItem>>>> spanContentLines;
862 SplitSpanContentByLines(textStyle, spanList, spanContentLines);
863
864 // set imagespan textstyle
865 SetImageSpanTextStyleByLines(textStyle, imageSpanList, spanContentLines);
866 }
867
CreateImageSpanAndLayout(const TextStyle & textStyle,const std::string & content,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)868 bool TextLayoutAlgorithm::CreateImageSpanAndLayout(const TextStyle& textStyle, const std::string& content,
869 const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
870 {
871 bool includeImageSpan = IncludeImageSpan(layoutWrapper);
872 if (includeImageSpan) {
873 SetImageSpanTextStyle(textStyle);
874 if (!CreateParagraph(textStyle, content, layoutWrapper)) {
875 return false;
876 }
877 CHECK_NULL_RETURN(paragraph_, false);
878 auto maxSize = GetMaxMeasureSize(contentConstraint);
879 if (GreatNotEqual(textStyle.GetTextIndent().Value(), 0.0)) {
880 ApplyIndents(textStyle, maxSize.Width());
881 }
882 paragraph_->Layout(maxSize.Width());
883 }
884 return true;
885 }
886
GetLineCount() const887 size_t TextLayoutAlgorithm::GetLineCount() const
888 {
889 CHECK_NULL_RETURN(paragraph_, 0);
890 return paragraph_->GetLineCount();
891 }
892 } // namespace OHOS::Ace::NG
893