1 /*
2  * Copyright (c) 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/rich_editor/rich_editor_layout_algorithm.h"
17 
18 #include "base/log/ace_trace.h"
19 #include "base/utils/utils.h"
20 #include "core/components/common/properties/text_style.h"
21 #include "core/components_ng/pattern/rich_editor/rich_editor_pattern.h"
22 #include "core/components_ng/pattern/rich_editor/rich_editor_theme.h"
23 #include "core/components_ng/pattern/text/multiple_paragraph_layout_algorithm.h"
24 #include "core/components_ng/pattern/text/text_layout_algorithm.h"
25 #include "core/components_ng/render/paragraph.h"
26 #include "core/components_v2/inspector/inspector_constants.h"
27 
28 namespace OHOS::Ace::NG {
RichEditorLayoutAlgorithm(std::list<RefPtr<SpanItem>> spans,ParagraphManager * paragraphs,std::optional<TextStyle> typingTextStyle)29 RichEditorLayoutAlgorithm::RichEditorLayoutAlgorithm(std::list<RefPtr<SpanItem>> spans, ParagraphManager* paragraphs,
30     std::optional<TextStyle> typingTextStyle)
31     : pManager_(paragraphs), typingTextStyle_(typingTextStyle)
32 {
33     allSpans_ = spans;
34     // split spans into groups by \newline
35     auto it = spans.begin();
36     while (it != spans.end()) {
37         auto span = *it;
38         // only checking the last char
39         if (StringUtils::ToWstring(span->content).back() == L'\n') {
40             span->SetNeedRemoveNewLine(true);
41             std::list<RefPtr<SpanItem>> newGroup;
42             newGroup.splice(newGroup.begin(), spans, spans.begin(), std::next(it));
43             spans_.push_back(std::move(newGroup));
44 
45             it = spans.begin();
46             continue;
47         }
48         span->SetNeedRemoveNewLine(false);
49         // clear placeholder textstyle,it should be modified by text line
50         auto placeholderSpanItem = AceType::DynamicCast<PlaceholderSpanItem>(span);
51         if (placeholderSpanItem) {
52             TextStyle textStyle;
53             placeholderSpanItem->textStyle = textStyle;
54         }
55         ++it;
56     }
57     if (!spans.empty()) {
58         spans_.push_back(std::move(spans));
59     }
60     AppendNewLineSpan();
61     TAG_LOGD(AceLogTag::ACE_RICH_TEXT, "spans=%{private}s", SpansToString().c_str());
62 }
63 
AppendNewLineSpan()64 void RichEditorLayoutAlgorithm::AppendNewLineSpan()
65 {
66     auto lastSpan = allSpans_.back();
67     CHECK_NULL_VOID(lastSpan);
68     if (StringUtils::ToWstring(lastSpan->content).back() == L'\n') {
69         std::list<RefPtr<SpanItem>> newGroup;
70         auto tailNewLineSpan = AceType::MakeRefPtr<SpanItem>();
71         tailNewLineSpan->content = "\n";
72         tailNewLineSpan->SetNeedRemoveNewLine(true);
73         CopySpanStyle(lastSpan, tailNewLineSpan);
74         newGroup.push_back(tailNewLineSpan);
75         spans_.push_back(std::move(newGroup));
76     }
77 }
78 
CopySpanStyle(RefPtr<SpanItem> source,RefPtr<SpanItem> target)79 void RichEditorLayoutAlgorithm::CopySpanStyle(RefPtr<SpanItem> source, RefPtr<SpanItem> target)
80 {
81     if (typingTextStyle_.has_value()) {
82         auto typingTextStyle = typingTextStyle_.value();
83         target->fontStyle->UpdateFontSize(typingTextStyle.GetFontSize());
84         target->textLineStyle->UpdateLineHeight(typingTextStyle.GetLineHeight());
85         return;
86     }
87     if (source->fontStyle->HasFontSize()) {
88         target->fontStyle->UpdateFontSize(source->fontStyle->GetFontSizeValue());
89     }
90 
91     if (source->textLineStyle->HasLeadingMargin()) {
92         auto leadingMargin = source->textLineStyle->GetLeadingMarginValue();
93         leadingMargin.pixmap.Reset();
94         target->textLineStyle->UpdateLeadingMargin(leadingMargin);
95     }
96 
97     if (source->textLineStyle->HasTextAlign()) {
98         target->textLineStyle->UpdateTextAlign(source->textLineStyle->GetTextAlignValue());
99     }
100 
101     if (source->textLineStyle->HasLineHeight()) {
102         target->textLineStyle->UpdateLineHeight(source->textLineStyle->GetLineHeightValue());
103     }
104 }
105 
MeasureEmptyContentSize(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)106 std::optional<SizeF> RichEditorLayoutAlgorithm::MeasureEmptyContentSize(
107     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
108 {
109     auto host = layoutWrapper->GetHostNode();
110     CHECK_NULL_RETURN(host, {});
111     auto pipeline = host->GetContext();
112     CHECK_NULL_RETURN(pipeline, {});
113     auto richEditorTheme = pipeline->GetTheme<RichEditorTheme>();
114     CHECK_NULL_RETURN(richEditorTheme, {});
115     auto defaultCaretHeight = richEditorTheme->GetDefaultCaretHeight().ConvertToPx();
116     auto width = contentConstraint.selfIdealSize.Width().value_or(contentConstraint.maxSize.Width());
117     auto pattern = host->GetPattern<RichEditorPattern>();
118     CHECK_NULL_RETURN(pattern, {});
119     auto presetParagraph = pattern->GetPresetParagraph();
120     if (!presetParagraph) {
121         pattern->PreferredParagraph();
122     }
123     auto contentHeight = defaultCaretHeight;
124     presetParagraph = pattern->GetPresetParagraph();
125     if (presetParagraph) {
126         contentHeight = presetParagraph->GetHeight();
127     }
128     return SizeF(width, static_cast<float>(contentHeight));
129 }
130 
MeasureContentSize(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)131 std::optional<SizeF> RichEditorLayoutAlgorithm::MeasureContentSize(
132     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
133 {
134     auto layoutProperty = DynamicCast<TextLayoutProperty>(layoutWrapper->GetLayoutProperty());
135     CHECK_NULL_RETURN(layoutProperty, {});
136     TextStyle textStyle;
137     ConstructTextStyles(contentConstraint, layoutWrapper, textStyle);
138     CHECK_NULL_RETURN(BuildParagraph(textStyle, layoutProperty, contentConstraint, layoutWrapper), {});
139     pManager_->SetParagraphs(GetParagraphs());
140     return SizeF(pManager_->GetMaxWidth(), pManager_->GetHeight());
141 }
142 
MeasureContent(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)143 std::optional<SizeF> RichEditorLayoutAlgorithm::MeasureContent(
144     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
145 {
146     ACE_SCOPED_TRACE("RichEditorMeasureContent");
147     pManager_->Reset();
148     SetPlaceholder(layoutWrapper);
149     auto optionalTextSize = spans_.empty()
150         ? MeasureEmptyContentSize(contentConstraint, layoutWrapper)
151         : MeasureContentSize(contentConstraint, layoutWrapper);
152     CHECK_NULL_RETURN(optionalTextSize, {});
153     SizeF res = optionalTextSize.value();
154     res.AddHeight(spans_.empty() ? 0 : shadowOffset_);
155     CHECK_NULL_RETURN(res.IsNonNegative(), {});
156     UpdateRichTextRect(optionalTextSize.value(), layoutWrapper);
157     auto maxHeight = contentConstraint.selfIdealSize.Height().value_or(contentConstraint.maxSize.Height());
158     auto contentHeight = std::min(res.Height(), maxHeight);
159     return SizeF(res.Width(), contentHeight);
160 }
161 
BuildParagraph(TextStyle & textStyle,const RefPtr<TextLayoutProperty> & layoutProperty,const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)162 bool RichEditorLayoutAlgorithm::BuildParagraph(TextStyle& textStyle, const RefPtr<TextLayoutProperty>& layoutProperty,
163     const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
164 {
165     auto maxSize = MultipleParagraphLayoutAlgorithm::GetMaxMeasureSize(contentConstraint);
166     if (!CreateParagraph(textStyle, layoutProperty->GetContent().value_or(""), layoutWrapper, maxSize.Width())) {
167         return false;
168     }
169     CHECK_NULL_RETURN(paragraphManager_, false);
170     auto paragraphInfo = paragraphManager_->GetParagraphs();
171     for (auto pIter = paragraphInfo.begin(); pIter != paragraphInfo.end(); pIter++) {
172         auto paragraph = pIter->paragraph;
173         CHECK_NULL_RETURN(paragraph, false);
174         paragraph->Layout(maxSize.Width());
175     }
176     return ParagraphReLayout(contentConstraint);
177 }
178 
CreateParagraph(const TextStyle & textStyle,std::string content,LayoutWrapper * layoutWrapper,double maxWidth)179 bool RichEditorLayoutAlgorithm::CreateParagraph(
180     const TextStyle& textStyle, std::string content, LayoutWrapper* layoutWrapper, double maxWidth)
181 {
182     CHECK_NULL_RETURN(!spans_.empty(), false);
183     if (!paragraphManager_) {
184         paragraphManager_ = AceType::MakeRefPtr<ParagraphManager>();
185     }
186     paragraphManager_->Reset();
187     auto frameNode = layoutWrapper->GetHostNode();
188     CHECK_NULL_RETURN(frameNode, false);
189     auto pipeline = frameNode->GetContextRefPtr();
190     CHECK_NULL_RETURN(pipeline, false);
191     // default paragraph style
192     auto paraStyle = GetParagraphStyle(textStyle, content, layoutWrapper);
193     return UpdateParagraphBySpan(layoutWrapper, paraStyle, maxWidth, textStyle);
194 }
195 
GetRichEditorPattern(LayoutWrapper * layoutWrapper)196 RefPtr<RichEditorPattern> RichEditorLayoutAlgorithm::GetRichEditorPattern(LayoutWrapper* layoutWrapper)
197 {
198     CHECK_NULL_RETURN(layoutWrapper, nullptr);
199     auto host = layoutWrapper->GetHostNode();
200     CHECK_NULL_RETURN(host, nullptr);
201     return host->GetPattern<RichEditorPattern>();
202 }
203 
UpdateRichTextRect(const SizeF & textSize,LayoutWrapper * layoutWrapper)204 void RichEditorLayoutAlgorithm::UpdateRichTextRect(const SizeF& textSize, LayoutWrapper* layoutWrapper)
205 {
206     auto pattern = GetRichEditorPattern(layoutWrapper);
207     CHECK_NULL_VOID(pattern);
208     richTextRect_.SetSize(pattern->IsShowPlaceholder() ? SizeF() : textSize);
209 }
210 
SetPlaceholder(LayoutWrapper * layoutWrapper)211 bool RichEditorLayoutAlgorithm::SetPlaceholder(LayoutWrapper* layoutWrapper)
212 {
213     auto pattern = GetRichEditorPattern(layoutWrapper);
214     CHECK_NULL_RETURN(pattern, false);
215     return pattern->SetPlaceholder(spans_);
216 }
217 
GetShadowOffset(const std::list<RefPtr<SpanItem>> & group)218 float RichEditorLayoutAlgorithm::GetShadowOffset(const std::list<RefPtr<SpanItem>>& group)
219 {
220     float shadowOffset = 0.0f;
221     for (auto& span: group) {
222         if (!span->fontStyle || !span->fontStyle->HasTextShadow()) {
223             continue;
224         }
225         auto shadows = span->fontStyle->GetTextShadowValue();
226         float upOffsetY = 0.0f;
227         float downOffsetY = 0.0f;
228         for (const auto& shadow : shadows) {
229             auto shadowBlurRadius = shadow.GetBlurRadius() * 2.0f;
230             auto shadowOffsetY = shadow.GetOffset().GetY();
231             if (LessOrEqual(shadowOffsetY, 0.0f) &&
232                 LessNotEqual(shadowOffsetY, upOffsetY)) {
233                 upOffsetY = shadowOffsetY - shadowBlurRadius;
234             } else if (GreatOrEqual(shadowOffsetY, 0.0f) &&
235                        GreatNotEqual(shadowOffsetY + shadowBlurRadius, downOffsetY)) {
236                 downOffsetY = shadowOffsetY + shadowBlurRadius;
237             }
238         }
239         shadowOffset = std::max(shadowOffset, downOffsetY - upOffsetY);
240     }
241     return shadowOffset;
242 }
243 
Measure(LayoutWrapper * layoutWrapper)244 void RichEditorLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
245 {
246     MultipleParagraphLayoutAlgorithm::Measure(layoutWrapper);
247     const auto& layoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
248     OptionalSizeF idealSize =
249         CreateIdealSize(layoutConstraint.value(), Axis::HORIZONTAL, MeasureType::MATCH_PARENT_MAIN_AXIS);
250     if (layoutConstraint->maxSize.Width() < layoutConstraint->minSize.Width()) {
251         idealSize.SetWidth(layoutConstraint->minSize.Width());
252     }
253     auto frameSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
254     frameSize.SetWidth(idealSize.ConvertToSizeT().Width());
255     layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize);
256 }
257 
Layout(LayoutWrapper * layoutWrapper)258 void RichEditorLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
259 {
260     auto context = layoutWrapper->GetHostNode()->GetContext();
261     CHECK_NULL_VOID(context);
262     parentGlobalOffset_ = layoutWrapper->GetHostNode()->GetPaintRectOffset() - context->GetRootRect().GetOffset();
263     MultipleParagraphLayoutAlgorithm::Layout(layoutWrapper);
264 }
265 
GetContentOffset(LayoutWrapper * layoutWrapper)266 OffsetF RichEditorLayoutAlgorithm::GetContentOffset(LayoutWrapper* layoutWrapper)
267 {
268     auto contentOffset = SetContentOffset(layoutWrapper);
269     auto host = layoutWrapper->GetHostNode();
270     CHECK_NULL_RETURN(host, contentOffset);
271     auto pattern = host->GetPattern<RichEditorPattern>();
272     CHECK_NULL_RETURN(pattern, contentOffset);
273     richTextRect_.SetOffset(OffsetF(contentOffset.GetX(), pattern->GetTextRect().GetY()));
274     return richTextRect_.GetOffset();
275 }
276 
GetParagraphStyle(const TextStyle & textStyle,const std::string & content,LayoutWrapper * layoutWrapper) const277 ParagraphStyle RichEditorLayoutAlgorithm::GetParagraphStyle(
278     const TextStyle& textStyle, const std::string& content, LayoutWrapper* layoutWrapper) const
279 {
280     auto style = MultipleParagraphLayoutAlgorithm::GetParagraphStyle(textStyle, content, layoutWrapper);
281     style.fontSize = textStyle.GetFontSize().ConvertToPx();
282     if (!pManager_->minParagraphFontSize.has_value() ||
283         GreatNotEqual(pManager_->minParagraphFontSize.value(), style.fontSize)) {
284         pManager_->minParagraphFontSize = style.fontSize;
285     }
286 
287     return style;
288 }
289 
GetFirstTextSpanItem() const290 RefPtr<SpanItem> RichEditorLayoutAlgorithm::GetFirstTextSpanItem() const
291 {
292     auto& spanGroup = GetSpans();
293     auto it = spanGroup.begin();
294     while (it != spanGroup.end()) {
295         if (!DynamicCast<PlaceholderSpanItem>(*it)) {
296             return *it;
297         }
298         ++it;
299     }
300     return *spanGroup.begin();
301 }
302 
HandleEmptyParagraph(RefPtr<Paragraph> paragraph,const std::list<RefPtr<SpanItem>> & spanGroup)303 void RichEditorLayoutAlgorithm::HandleEmptyParagraph(RefPtr<Paragraph> paragraph,
304     const std::list<RefPtr<SpanItem>>& spanGroup)
305 {
306     CHECK_NULL_VOID(paragraph && spanGroup.size() == 1);
307     auto spanItem = spanGroup.front();
308     CHECK_NULL_VOID(spanItem);
309     auto content = spanItem->GetSpanContent(spanItem->GetSpanContent());
310     CHECK_NULL_VOID(content.empty());
311     auto textStyle = spanItem->GetTextStyle();
312     CHECK_NULL_VOID(textStyle);
313     paragraph->PushStyle(textStyle.value());
314 }
315 
GetParagraphStyleSpanItem(const std::list<RefPtr<SpanItem>> & spanGroup)316 RefPtr<SpanItem> RichEditorLayoutAlgorithm::GetParagraphStyleSpanItem(const std::list<RefPtr<SpanItem>>& spanGroup)
317 {
318     auto it = spanGroup.begin();
319     while (it != spanGroup.end()) {
320         if (!AceType::DynamicCast<PlaceholderSpanItem>(*it)) {
321             return *it;
322         }
323         ++it;
324     }
325     return *spanGroup.begin();
326 }
327 
328 } // namespace OHOS::Ace::NG
329