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/utils/utils.h"
19 #include "core/components_ng/pattern/rich_editor/rich_editor_pattern.h"
20 #include "core/components_ng/pattern/rich_editor/rich_editor_theme.h"
21 #include "core/components_ng/pattern/text/text_layout_algorithm.h"
22 #include "core/components_ng/render/paragraph.h"
23
24 namespace OHOS::Ace::NG {
RichEditorLayoutAlgorithm(std::list<RefPtr<SpanItem>> spans,ParagraphManager * paragraphs)25 RichEditorLayoutAlgorithm::RichEditorLayoutAlgorithm(std::list<RefPtr<SpanItem>> spans, ParagraphManager* paragraphs)
26 : pManager_(paragraphs)
27 {
28 // split spans into groups by \newline
29 auto it = spans.begin();
30 while (it != spans.end()) {
31 auto span = *it;
32 // only checking the last char
33 if (StringUtils::ToWstring(span->content).back() == L'\n') {
34 if (std::next(it) != spans.end()) {
35 span->MarkNeedRemoveNewLine(true);
36 } else {
37 span->MarkNeedRemoveNewLine(false);
38 }
39 std::list<RefPtr<SpanItem>> newGroup;
40 newGroup.splice(newGroup.begin(), spans, spans.begin(), std::next(it));
41 spans_.push_back(std::move(newGroup));
42
43 it = spans.begin();
44 continue;
45 }
46 span->MarkNeedRemoveNewLine(false);
47 // clear placeholder textstyle,it should be modified by text line
48 auto placeholderSpanItem = AceType::DynamicCast<PlaceholderSpanItem>(span);
49 if (placeholderSpanItem) {
50 TextStyle textStyle;
51 placeholderSpanItem->textStyle = textStyle;
52 }
53 ++it;
54 }
55 if (!spans.empty()) {
56 spans_.push_back(std::move(spans));
57 }
58 TAG_LOGD(AceLogTag::ACE_RICH_TEXT, "spans=%{public}s", SpansToString().c_str());
59 }
60
MeasureContent(const LayoutConstraintF & contentConstraint,LayoutWrapper * layoutWrapper)61 std::optional<SizeF> RichEditorLayoutAlgorithm::MeasureContent(
62 const LayoutConstraintF& contentConstraint, LayoutWrapper* layoutWrapper)
63 {
64 pManager_->Reset();
65 if (spans_.empty()) {
66 auto pipeline = PipelineContext::GetCurrentContext();
67 CHECK_NULL_RETURN(pipeline, std::nullopt);
68 auto richEditorTheme = pipeline->GetTheme<RichEditorTheme>();
69 CHECK_NULL_RETURN(richEditorTheme, std::nullopt);
70 auto defaultCaretHeight = richEditorTheme->GetDefaultCaretHeight().ConvertToPx();
71 auto width = contentConstraint.selfIdealSize.Width().value_or(contentConstraint.maxSize.Width());
72 return SizeF(width, defaultCaretHeight);
73 }
74
75 SizeF res;
76 int32_t lastPlaceholderIndex = 0;
77 float textHeight = 0.0f;
78 for (auto&& group : spans_) {
79 // layout each paragraph
80 SetSpans(group);
81 SetParagraph(nullptr);
82 auto size = TextLayoutAlgorithm::MeasureContent(contentConstraint, layoutWrapper);
83 if (size) {
84 res.SetWidth(std::max(size->Width(), res.Width()));
85 res.AddHeight(size->Height());
86 }
87
88 // record generated paragraph
89 auto&& paragraph = GetParagraph();
90 if (!paragraph) {
91 continue;
92 }
93 float shadowOffset = GetShadowOffset(group);
94 res.AddHeight(shadowOffset);
95 textHeight += paragraph->GetHeight();
96 auto firstSpan = *group.begin();
97 auto lastSpan = *group.rbegin();
98 if (!firstSpan || !lastSpan) {
99 TAG_LOGE(AceLogTag::ACE_RICH_TEXT, "MeasureContent failed. firstSpan or lastSpan is null.");
100 continue;
101 }
102 pManager_->AddParagraph({ .paragraph = paragraph,
103 .start = firstSpan->position - StringUtils::ToWstring(firstSpan->content).length(),
104 .end = lastSpan->position });
105 std::for_each(group.begin(), group.end(), [&](RefPtr<SpanItem>& item) {
106 auto imageSpanItem = AceType::DynamicCast<ImageSpanItem>(item);
107 if (imageSpanItem && imageSpanItem->placeholderIndex >= 0) {
108 imageSpanItem->placeholderIndex = lastPlaceholderIndex;
109 lastPlaceholderIndex++;
110 } else if (auto placeholderSpanItem = AceType::DynamicCast<PlaceholderSpanItem>(item);
111 placeholderSpanItem) {
112 placeholderSpanItem->placeholderIndex = lastPlaceholderIndex;
113 lastPlaceholderIndex++;
114 }
115 });
116 }
117 if (!res.IsPositive()) {
118 return std::nullopt;
119 }
120 richTextRect_.SetSize(SizeF(res.Width(), textHeight));
121 auto contentHeight = res.Height();
122 if (contentConstraint.selfIdealSize.Height().has_value()) {
123 contentHeight = std::min(contentHeight, contentConstraint.selfIdealSize.Height().value());
124 } else {
125 contentHeight = std::min(contentHeight, contentConstraint.maxSize.Height());
126 }
127 return SizeF(res.Width(), contentHeight);
128 }
129
GetShadowOffset(const std::list<RefPtr<SpanItem>> & group)130 float RichEditorLayoutAlgorithm::GetShadowOffset(const std::list<RefPtr<SpanItem>>& group)
131 {
132 float shadowOffset = 0.0f;
133 for (auto& span: group) {
134 if (!span->fontStyle || !span->fontStyle->HasTextShadow()) {
135 continue;
136 }
137 auto shadows = span->fontStyle->GetTextShadowValue();
138 float upOffsetY = 0.0f;
139 float downOffsetY = 0.0f;
140 for (const auto& shadow : shadows) {
141 auto shadowBlurRadius = shadow.GetBlurRadius() * 2.0f;
142 auto shadowOffsetY = shadow.GetOffset().GetY();
143 if (LessOrEqual(shadowOffsetY, 0.0f) &&
144 LessNotEqual(shadowOffsetY, upOffsetY)) {
145 upOffsetY = shadowOffsetY - shadowBlurRadius;
146 } else if (GreatOrEqual(shadowOffsetY, 0.0f) &&
147 GreatNotEqual(shadowOffsetY + shadowBlurRadius, downOffsetY)) {
148 downOffsetY = shadowOffsetY + shadowBlurRadius;
149 }
150 }
151 shadowOffset = std::max(shadowOffset, downOffsetY - upOffsetY);
152 }
153 return shadowOffset;
154 }
155
Measure(LayoutWrapper * layoutWrapper)156 void RichEditorLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
157 {
158 const auto& layoutConstraint = layoutWrapper->GetLayoutProperty()->GetLayoutConstraint();
159 OptionalSizeF frameSize =
160 CreateIdealSize(layoutConstraint.value(), Axis::HORIZONTAL, MeasureType::MATCH_PARENT_MAIN_AXIS);
161 if (layoutConstraint->maxSize.Width() < layoutConstraint->minSize.Width()) {
162 frameSize.SetWidth(layoutConstraint->minSize.Width());
163 }
164 TextLayoutAlgorithm::Measure(layoutWrapper);
165 }
166
Layout(LayoutWrapper * layoutWrapper)167 void RichEditorLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
168 {
169 auto context = layoutWrapper->GetHostNode()->GetContext();
170 CHECK_NULL_VOID(context);
171 parentGlobalOffset_ = layoutWrapper->GetHostNode()->GetPaintRectOffset() - context->GetRootRect().GetOffset();
172
173 // merge spans
174 std::list<RefPtr<SpanItem>> allSpans;
175 for (auto&& group : spans_) {
176 allSpans.splice(allSpans.end(), group);
177 }
178 SetSpans(allSpans);
179 TextLayoutAlgorithm::Layout(layoutWrapper);
180 }
181
GetContentOffset(LayoutWrapper * layoutWrapper)182 OffsetF RichEditorLayoutAlgorithm::GetContentOffset(LayoutWrapper* layoutWrapper)
183 {
184 auto contentOffset = TextLayoutAlgorithm::GetContentOffset(layoutWrapper);
185 auto host = layoutWrapper->GetHostNode();
186 CHECK_NULL_RETURN(host, contentOffset);
187 auto pattern = host->GetPattern<RichEditorPattern>();
188 CHECK_NULL_RETURN(pattern, contentOffset);
189 richTextRect_.SetOffset(OffsetF(contentOffset.GetX(), pattern->GetTextRect().GetY()));
190 return richTextRect_.GetOffset();
191 }
192
GetPlaceholderRects(std::vector<RectF> & rects)193 void RichEditorLayoutAlgorithm::GetPlaceholderRects(std::vector<RectF>& rects)
194 {
195 rects = pManager_->GetPlaceholderRects();
196 }
197
GetParagraphStyle(const TextStyle & textStyle,const std::string & content,LayoutWrapper * layoutWrapper) const198 ParagraphStyle RichEditorLayoutAlgorithm::GetParagraphStyle(
199 const TextStyle& textStyle, const std::string& content, LayoutWrapper* layoutWrapper) const
200 {
201 auto style = TextLayoutAlgorithm::GetParagraphStyle(textStyle, content, layoutWrapper);
202 style.fontSize = textStyle.GetFontSize().ConvertToPx();
203 if (!pManager_->minParagraphFontSize.has_value() ||
204 GreatNotEqual(pManager_->minParagraphFontSize.value(), style.fontSize)) {
205 pManager_->minParagraphFontSize = style.fontSize;
206 }
207 const auto& spanItem = GetFirstTextSpanItem();
208 CHECK_NULL_RETURN(spanItem, style);
209 auto& lineStyle = spanItem->textLineStyle;
210 CHECK_NULL_RETURN(lineStyle, style);
211 if (lineStyle->propTextAlign) {
212 style.align = *(lineStyle->propTextAlign);
213 }
214 if (lineStyle->propTextIndent) {
215 style.leadingMargin = std::make_optional<LeadingMargin>();
216 style.leadingMargin->size = SizeF(lineStyle->propTextIndent->ConvertToPx(), 0.0f);
217 }
218 if (lineStyle->propLeadingMargin) {
219 if (!style.leadingMargin) {
220 style.leadingMargin = std::make_optional<LeadingMargin>();
221 }
222 style.leadingMargin->size = lineStyle->propLeadingMargin->size;
223 style.leadingMargin->pixmap = lineStyle->propLeadingMargin->pixmap;
224 }
225
226 return style;
227 }
228
GetFirstTextSpanItem() const229 RefPtr<SpanItem> RichEditorLayoutAlgorithm::GetFirstTextSpanItem() const
230 {
231 auto& spanGroup = GetSpans();
232 auto it = spanGroup.begin();
233 while (it != spanGroup.end()) {
234 if (!DynamicCast<PlaceholderSpanItem>(*it)) {
235 return *it;
236 }
237 ++it;
238 }
239 return *spanGroup.begin();
240 }
241
GetPreviousLength() const242 int32_t RichEditorLayoutAlgorithm::GetPreviousLength() const
243 {
244 auto&& paragraphs = pManager_->GetParagraphs();
245 if (paragraphs.empty()) {
246 return 0;
247 }
248 return paragraphs.back().end;
249 }
250 } // namespace OHOS::Ace::NG
251