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/paragraph_manager.h"
17
18 #include <iterator>
19 #include <ostream>
20
21 #include "base/utils/utils.h"
22 #include "core/components/common/properties/text_layout_info.h"
23
24 namespace OHOS::Ace::NG {
GetHeight() const25 float ParagraphManager::GetHeight() const
26 {
27 float res = 0.0f;
28 for (auto&& info : paragraphs_) {
29 res += info.paragraph->GetHeight();
30 }
31 return res;
32 }
33
GetMaxIntrinsicWidth() const34 float ParagraphManager::GetMaxIntrinsicWidth() const
35 {
36 float res = 0.0f;
37 for (auto &&info : paragraphs_) {
38 res = std::max(res, info.paragraph->GetMaxIntrinsicWidth());
39 }
40 return res;
41 }
DidExceedMaxLines() const42 bool ParagraphManager::DidExceedMaxLines() const
43 {
44 bool res = false;
45 for (auto &&info : paragraphs_) {
46 res |= info.paragraph->DidExceedMaxLines();
47 }
48 return res;
49 }
GetLongestLine() const50 float ParagraphManager::GetLongestLine() const
51 {
52 float res = 0.0f;
53 for (auto &&info : paragraphs_) {
54 res = std::max(res, info.paragraph->GetLongestLine());
55 }
56 return res;
57 }
GetMaxWidth() const58 float ParagraphManager::GetMaxWidth() const
59 {
60 float res = 0.0f;
61 for (auto &&info : paragraphs_) {
62 res = std::max(res, info.paragraph->GetMaxWidth());
63 }
64 return res;
65 }
GetTextWidth() const66 float ParagraphManager::GetTextWidth() const
67 {
68 float res = 0.0f;
69 for (auto &&info : paragraphs_) {
70 res = std::max(res, info.paragraph->GetTextWidth());
71 }
72 return res;
73 }
74
GetTextWidthIncludeIndent() const75 float ParagraphManager::GetTextWidthIncludeIndent() const
76 {
77 float res = 0.0f;
78 for (auto &&info : paragraphs_) {
79 auto paragraph = info.paragraph;
80 CHECK_NULL_RETURN(paragraph, 0.0f);
81 auto width = paragraph->GetTextWidth();
82 res = std::max(res, width);
83 }
84 return res;
85 }
86
GetLongestLineWithIndent() const87 float ParagraphManager::GetLongestLineWithIndent() const
88 {
89 float res = 0.0f;
90 for (auto &&info : paragraphs_) {
91 auto paragraph = info.paragraph;
92 CHECK_NULL_RETURN(paragraph, 0.0f);
93 auto width = paragraph->GetLongestLineWithIndent();
94 res = std::max(res, width);
95 }
96 return res;
97 }
98
GetLineCount() const99 size_t ParagraphManager::GetLineCount() const
100 {
101 size_t count = 0;
102 for (auto &&info : paragraphs_) {
103 count += info.paragraph->GetLineCount();
104 }
105 return count;
106 }
107
GetIndex(Offset offset,bool clamp) const108 int32_t ParagraphManager::GetIndex(Offset offset, bool clamp) const
109 {
110 CHECK_NULL_RETURN(!paragraphs_.empty(), 0);
111 if (clamp && LessNotEqual(offset.GetY(), 0.0)) {
112 return 0;
113 }
114 int idx = 0;
115 for (auto it = paragraphs_.begin(); it != paragraphs_.end(); ++it, ++idx) {
116 auto&& info = *it;
117 if (LessOrEqual(offset.GetY(), info.paragraph->GetHeight()) ||
118 (!clamp && idx == static_cast<int>(paragraphs_.size()) - 1)) {
119 return info.paragraph->GetGlyphIndexByCoordinate(offset) + info.start;
120 }
121 // get offset relative to each paragraph
122 offset.SetY(offset.GetY() - info.paragraph->GetHeight());
123 }
124 return paragraphs_.back().end;
125 }
126
GetGlyphPositionAtCoordinate(Offset offset)127 PositionWithAffinity ParagraphManager::GetGlyphPositionAtCoordinate(Offset offset)
128 {
129 TAG_LOGI(AceLogTag::ACE_TEXT,
130 "Get Glyph Position, coordinate = [%{public}.2f %{public}.2f]", offset.GetX(), offset.GetY());
131 PositionWithAffinity finalResult(0, TextAffinity::UPSTREAM);
132 CHECK_NULL_RETURN(!paragraphs_.empty(), finalResult);
133 if (LessNotEqual(offset.GetY(), 0.0)) {
134 return finalResult;
135 }
136 int idx = 0;
137 for (auto it = paragraphs_.begin(); it != paragraphs_.end(); ++it, ++idx) {
138 auto& info = *it;
139 if (LessOrEqual(offset.GetY(), info.paragraph->GetHeight()) ||
140 (idx == static_cast<int>(paragraphs_.size()) - 1)) {
141 auto result = info.paragraph->GetGlyphPositionAtCoordinate(offset);
142 finalResult.position_ = result.position_ + static_cast<size_t>(info.start);
143 TAG_LOGI(AceLogTag::ACE_TEXT,
144 "Current paragraph, originPos = %{public}zu, finalPos =%{public}zu and affinity = %{public}d",
145 result.position_, finalResult.position_, result.affinity_);
146 finalResult.affinity_ = static_cast<TextAffinity>(result.affinity_);
147 return finalResult;
148 }
149 // get offset relative to each paragraph
150 offset.SetY(offset.GetY() - info.paragraph->GetHeight());
151 }
152 auto info = paragraphs_.back();
153 auto result = info.paragraph->GetGlyphPositionAtCoordinate(offset);
154 finalResult.position_ = static_cast<size_t>(info.end);
155 finalResult.affinity_ = static_cast<TextAffinity>(result.affinity_);
156 TAG_LOGI(AceLogTag::ACE_TEXT,
157 "Current paragraph, final position = %{public}zu and affinity = %{public}d", finalResult.position_,
158 finalResult.affinity_);
159 return finalResult;
160 }
161
GetGlyphIndexByCoordinate(Offset offset,bool isSelectionPos) const162 int32_t ParagraphManager::GetGlyphIndexByCoordinate(Offset offset, bool isSelectionPos) const
163 {
164 CHECK_NULL_RETURN(!paragraphs_.empty(), 0);
165 for (auto it = paragraphs_.begin(); it != paragraphs_.end(); ++it) {
166 auto &&info = *it;
167 if (LessOrEqual(offset.GetY(), info.paragraph->GetHeight())) {
168 return info.paragraph->GetGlyphIndexByCoordinate(offset, isSelectionPos) + info.start;
169 }
170 // get offset relative to each paragraph
171 offset.SetY(offset.GetY() - info.paragraph->GetHeight());
172 }
173 return paragraphs_.back().end;
174 }
175
GetWordBoundary(int32_t offset,int32_t & start,int32_t & end) const176 bool ParagraphManager::GetWordBoundary(int32_t offset, int32_t& start, int32_t& end) const
177 {
178 CHECK_NULL_RETURN(!paragraphs_.empty(), false);
179 auto offsetIndex = offset;
180 auto startIndex = 0;
181 auto endIndex = 0;
182 for (auto it = paragraphs_.begin(); it != paragraphs_.end(); ++it) {
183 auto &&info = *it;
184 if (LessNotEqual(offset, info.end)) {
185 auto flag = info.paragraph->GetWordBoundary(offsetIndex, start, end);
186 start += startIndex;
187 end += endIndex;
188 return flag;
189 }
190 // get offset relative to each paragraph
191 offsetIndex = offset - info.end;
192 startIndex = info.end;
193 endIndex = info.end;
194 }
195 return false;
196 }
197
CalcCaretMetricsByPosition(int32_t extent,CaretMetricsF & caretCaretMetric,TextAffinity textAffinity) const198 bool ParagraphManager::CalcCaretMetricsByPosition(
199 int32_t extent, CaretMetricsF& caretCaretMetric, TextAffinity textAffinity) const
200 {
201 CHECK_NULL_RETURN(!paragraphs_.empty(), false);
202 auto offsetIndex = extent;
203 auto offsetY = 0.0f;
204 auto result = false;
205 for (auto it = paragraphs_.begin(); it != paragraphs_.end(); ++it) {
206 auto &&info = *it;
207 if (textAffinity == TextAffinity::UPSTREAM || std::next(it) == paragraphs_.end()) {
208 if (LessOrEqual(extent, info.end)) {
209 result = info.paragraph->CalcCaretMetricsByPosition(offsetIndex, caretCaretMetric, textAffinity);
210 break;
211 }
212 } else {
213 if (LessNotEqual(extent, info.end)) {
214 result = info.paragraph->CalcCaretMetricsByPosition(offsetIndex, caretCaretMetric, textAffinity);
215 break;
216 }
217 }
218 // get offset relative to each paragraph
219 offsetIndex = extent - info.end;
220 offsetY += info.paragraph->GetHeight();
221 }
222 caretCaretMetric.offset += OffsetF(0.0f, offsetY);
223 return result;
224 }
225
GetLineMetricsByRectF(RectF rect,int32_t paragraphIndex) const226 LineMetrics ParagraphManager::GetLineMetricsByRectF(RectF rect, int32_t paragraphIndex) const
227 {
228 auto index = 0;
229 float height = 0;
230 auto iter = paragraphs_.begin();
231 while (index < paragraphIndex) {
232 auto paragraphInfo = *iter;
233 height += paragraphInfo.paragraph->GetHeight();
234 iter++;
235 index++;
236 }
237 auto paragraphInfo = *iter;
238 rect.SetTop(rect.GetY() - height);
239 auto lineMetrics = paragraphInfo.paragraph->GetLineMetricsByRectF(rect);
240 lineMetrics.y += height;
241 return lineMetrics;
242 }
243
GetLineMetrics(size_t lineNumber)244 TextLineMetrics ParagraphManager::GetLineMetrics(size_t lineNumber)
245 {
246 if (GetLineCount() == 0 || lineNumber > GetLineCount() - 1) {
247 TAG_LOGE(AceLogTag::ACE_TEXT,
248 "GetLineMetrics failed, lineNumber is greater than max lines:%{public}zu", lineNumber);
249 return TextLineMetrics();
250 }
251 size_t endIndex = 0;
252 double paragraphsHeight = 0.0;
253 size_t lineNumberParam = lineNumber;
254 for (auto &&info : paragraphs_) {
255 auto lineCount = info.paragraph->GetLineCount();
256 if (lineCount > 0 && lineNumber > lineCount - 1) {
257 lineNumber -= lineCount;
258 paragraphsHeight += info.paragraph->GetHeight();
259 auto lastLineMetrics = info.paragraph->GetLineMetrics(lineCount - 1);
260 endIndex += lastLineMetrics.endIndex + 1;
261 continue;
262 }
263 auto lineMetrics = info.paragraph->GetLineMetrics(lineNumber);
264 lineMetrics.startIndex += endIndex;
265 lineMetrics.endIndex += endIndex;
266 lineMetrics.lineNumber = lineNumberParam;
267 lineMetrics.y += paragraphsHeight;
268 lineMetrics.baseline += paragraphsHeight;
269 return lineMetrics;
270 }
271 return TextLineMetrics();
272 }
273
GetRects(int32_t start,int32_t end,RectHeightPolicy rectHeightPolicy) const274 std::vector<RectF> ParagraphManager::GetRects(int32_t start, int32_t end, RectHeightPolicy rectHeightPolicy) const
275 {
276 std::vector<RectF> res;
277 float y = 0.0f;
278 for (auto&& info : paragraphs_) {
279 std::vector<RectF> rects;
280 if (info.start > end) {
281 break;
282 }
283 if (info.end > start) {
284 auto relativeStart = (start < info.start) ? 0 : start - info.start;
285 if (rectHeightPolicy == RectHeightPolicy::COVER_TEXT) {
286 info.paragraph->GetTightRectsForRange(relativeStart, end - info.start, rects);
287 } else {
288 info.paragraph->GetRectsForRange(relativeStart, end - info.start, rects);
289 }
290
291 for (auto&& rect : rects) {
292 rect.SetTop(rect.Top() + y);
293 }
294 res.insert(res.end(), rects.begin(), rects.end());
295 }
296 y += info.paragraph->GetHeight();
297 }
298 return res;
299 }
300
GetParagraphsRects(int32_t start,int32_t end,RectHeightPolicy rectHeightPolicy) const301 std::vector<std::pair<std::vector<RectF>, TextDirection>> ParagraphManager::GetParagraphsRects(
302 int32_t start, int32_t end, RectHeightPolicy rectHeightPolicy) const
303 {
304 std::vector<std::pair<std::vector<RectF>, TextDirection>> paragraphsRects;
305 float y = 0.0f;
306 for (auto&& info : paragraphs_) {
307 if (info.start > end) {
308 break;
309 }
310 if (info.end > start) {
311 std::vector<RectF> rects;
312 auto relativeStart = (start < info.start) ? 0 : start - info.start;
313 if (rectHeightPolicy == RectHeightPolicy::COVER_TEXT) {
314 info.paragraph->GetTightRectsForRange(relativeStart, end - info.start, rects);
315 } else {
316 info.paragraph->GetRectsForRange(relativeStart, end - info.start, rects);
317 }
318 std::pair<std::vector<RectF>, TextDirection> paragraphRects;
319 for (auto&& rect : rects) {
320 rect.SetTop(rect.Top() + y);
321 }
322 paragraphRects.first = rects;
323 paragraphRects.second = info.paragraphStyle.direction;
324 paragraphsRects.emplace_back(paragraphRects);
325 }
326 y += info.paragraph->GetHeight();
327 }
328 return paragraphsRects;
329 }
330
IsSelectLineHeadAndUseLeadingMargin(int32_t start) const331 bool ParagraphManager::IsSelectLineHeadAndUseLeadingMargin(int32_t start) const
332 {
333 for (auto iter = paragraphs_.begin(); iter != paragraphs_.end(); iter++) {
334 auto curParagraph = *iter;
335 if (curParagraph.paragraph && curParagraph.paragraph->GetParagraphStyle().leadingMargin &&
336 curParagraph.start == start) {
337 return true;
338 }
339 auto next = std::next(iter);
340 if (next != paragraphs_.end()) {
341 auto nextParagraph = *next;
342 if (nextParagraph.paragraph && nextParagraph.paragraph->GetParagraphStyle().leadingMargin &&
343 nextParagraph.start == start + 1) {
344 return true;
345 }
346 }
347 }
348 return false;
349 }
350
GetPlaceholderRects() const351 std::vector<RectF> ParagraphManager::GetPlaceholderRects() const
352 {
353 std::vector<RectF> res;
354 float y = 0.0f;
355 for (auto&& info : paragraphs_) {
356 std::vector<RectF> rects;
357 info.paragraph->GetRectsForPlaceholders(rects);
358 for (auto& rect : rects) {
359 rect.SetTop(rect.Top() + y);
360 }
361 y += info.paragraph->GetHeight();
362
363 res.insert(res.end(), rects.begin(), rects.end());
364 }
365 return res;
366 }
367
ComputeCursorOffset(int32_t index,float & selectLineHeight,bool downStreamFirst,bool needLineHighest) const368 OffsetF ParagraphManager::ComputeCursorOffset(
369 int32_t index, float& selectLineHeight, bool downStreamFirst, bool needLineHighest) const
370 {
371 CHECK_NULL_RETURN(!paragraphs_.empty(), {});
372 auto it = paragraphs_.begin();
373 float y = 0.0f;
374 while (it != paragraphs_.end()) {
375 if (index >= it->start && index < it->end) {
376 break;
377 }
378 y += it->paragraph->GetHeight();
379 ++it;
380 }
381
382 if (index == paragraphs_.back().end) {
383 --it;
384 y -= it->paragraph->GetHeight();
385 }
386
387 CHECK_NULL_RETURN(it != paragraphs_.end(), OffsetF(0.0f, y));
388
389 int32_t relativeIndex = index - it->start;
390 auto&& paragraph = it->paragraph;
391 CaretMetricsF metrics;
392 auto computeSuccess = false;
393 if (downStreamFirst) {
394 computeSuccess = paragraph->ComputeOffsetForCaretDownstream(relativeIndex, metrics, needLineHighest) ||
395 paragraph->ComputeOffsetForCaretUpstream(relativeIndex, metrics, needLineHighest);
396 } else {
397 computeSuccess = paragraph->ComputeOffsetForCaretUpstream(relativeIndex, metrics, needLineHighest) ||
398 paragraph->ComputeOffsetForCaretDownstream(relativeIndex, metrics, needLineHighest);
399 }
400 CHECK_NULL_RETURN(computeSuccess, OffsetF(0.0f, y));
401 selectLineHeight = metrics.height;
402 return { static_cast<float>(metrics.offset.GetX()), static_cast<float>(metrics.offset.GetY() + y) };
403 }
404
ComputeCursorInfoByClick(int32_t index,float & selectLineHeight,const OffsetF & lastTouchOffset) const405 OffsetF ParagraphManager::ComputeCursorInfoByClick(
406 int32_t index, float& selectLineHeight, const OffsetF& lastTouchOffset) const
407 {
408 CHECK_NULL_RETURN(!paragraphs_.empty(), {});
409 auto it = paragraphs_.begin();
410 float y = 0.0f;
411 while (it != paragraphs_.end()) {
412 if (index >= it->start && index < it->end) {
413 break;
414 }
415 y += it->paragraph->GetHeight();
416 ++it;
417 }
418
419 if (index == paragraphs_.back().end) {
420 --it;
421 y -= it->paragraph->GetHeight();
422 }
423
424 CHECK_NULL_RETURN(it != paragraphs_.end(), OffsetF(0.0f, y));
425
426 int32_t relativeIndex = index - it->start;
427 auto&& paragraph = it->paragraph;
428
429 CaretMetricsF caretCaretMetric;
430 auto touchOffsetInCurrentParagraph = OffsetF(static_cast<float>(lastTouchOffset.GetX()),
431 static_cast<float>(lastTouchOffset.GetY() - y));
432 TextAffinity textAffinity;
433 paragraph->CalcCaretMetricsByPosition(relativeIndex, caretCaretMetric, touchOffsetInCurrentParagraph, textAffinity);
434 selectLineHeight = caretCaretMetric.height;
435 return { static_cast<float>(caretCaretMetric.offset.GetX()),
436 static_cast<float>(caretCaretMetric.offset.GetY() + y) };
437 }
438
Reset()439 void ParagraphManager::Reset()
440 {
441 paragraphs_.clear();
442 }
443
ToString() const444 std::string ParagraphManager::ParagraphInfo::ToString() const
445 {
446 return "Paragraph start: " + std::to_string(start) + ", end: " + std::to_string(end);
447 }
448 } // namespace OHOS::Ace::NG