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
GetPaintRegion(RectF & boundsRect,float x,float y) const274 void ParagraphManager::GetPaintRegion(RectF& boundsRect, float x, float y) const
275 {
276 if (paragraphs_.empty()) {
277 return;
278 }
279 for (const auto& info : paragraphs_) {
280 CHECK_NULL_VOID(info.paragraph);
281 auto rect = info.paragraph->GetPaintRegion(x, y);
282 boundsRect = boundsRect.CombineRectT(rect);
283 y += info.paragraph->GetHeight();
284 }
285 }
286
GetRectsForRange(int32_t start,int32_t end,RectHeightStyle heightStyle,RectWidthStyle widthStyle)287 std::vector<ParagraphManager::TextBox> ParagraphManager::GetRectsForRange(
288 int32_t start, int32_t end, RectHeightStyle heightStyle, RectWidthStyle widthStyle)
289 {
290 std::vector<TextBox> resultTextBoxes;
291 float y = 0.0f;
292 for (const auto& info : paragraphs_) {
293 if (info.start >= end) {
294 break;
295 }
296 int32_t relativeStart = std::max(static_cast<int32_t>(0), start - info.start);
297 int32_t relativeEnd = std::min(info.end - info.start, end - info.start);
298 if (relativeStart >= relativeEnd) {
299 y += info.paragraph->GetHeight();
300 continue;
301 }
302 std::vector<RectF> tempRects;
303 std::vector<TextDirection> tempTextDirections;
304 info.paragraph->TxtGetRectsForRange(
305 relativeStart, relativeEnd, heightStyle, widthStyle, tempRects, tempTextDirections);
306 for (size_t i = 0; i < tempRects.size(); ++i) {
307 tempRects[i].SetTop(tempRects[i].Top() + y);
308 resultTextBoxes.emplace_back(TextBox(tempRects[i], tempTextDirections[i]));
309 }
310 y += info.paragraph->GetHeight();
311 }
312 return resultTextBoxes;
313 }
314
GetRects(int32_t start,int32_t end,RectHeightPolicy rectHeightPolicy) const315 std::vector<RectF> ParagraphManager::GetRects(int32_t start, int32_t end, RectHeightPolicy rectHeightPolicy) const
316 {
317 std::vector<RectF> res;
318 float y = 0.0f;
319 for (auto&& info : paragraphs_) {
320 std::vector<RectF> rects;
321 if (info.start > end) {
322 break;
323 }
324 if (info.end > start) {
325 auto relativeStart = (start < info.start) ? 0 : start - info.start;
326 if (rectHeightPolicy == RectHeightPolicy::COVER_TEXT) {
327 info.paragraph->GetTightRectsForRange(relativeStart, end - info.start, rects);
328 } else {
329 info.paragraph->GetRectsForRange(relativeStart, end - info.start, rects);
330 }
331
332 for (auto&& rect : rects) {
333 rect.SetTop(rect.Top() + y);
334 }
335 res.insert(res.end(), rects.begin(), rects.end());
336 }
337 y += info.paragraph->GetHeight();
338 }
339 return res;
340 }
341
GetParagraphsRects(int32_t start,int32_t end,RectHeightPolicy rectHeightPolicy) const342 std::vector<std::pair<std::vector<RectF>, TextDirection>> ParagraphManager::GetParagraphsRects(
343 int32_t start, int32_t end, RectHeightPolicy rectHeightPolicy) const
344 {
345 std::vector<std::pair<std::vector<RectF>, TextDirection>> paragraphsRects;
346 float y = 0.0f;
347 for (auto&& info : paragraphs_) {
348 if (info.start > end) {
349 break;
350 }
351 if (info.end > start) {
352 std::vector<RectF> rects;
353 auto relativeStart = (start < info.start) ? 0 : start - info.start;
354 if (rectHeightPolicy == RectHeightPolicy::COVER_TEXT) {
355 info.paragraph->GetTightRectsForRange(relativeStart, end - info.start, rects);
356 } else {
357 info.paragraph->GetRectsForRange(relativeStart, end - info.start, rects);
358 }
359 std::pair<std::vector<RectF>, TextDirection> paragraphRects;
360 for (auto&& rect : rects) {
361 rect.SetTop(rect.Top() + y);
362 }
363 paragraphRects.first = rects;
364 paragraphRects.second = info.paragraphStyle.direction;
365 paragraphsRects.emplace_back(paragraphRects);
366 }
367 y += info.paragraph->GetHeight();
368 }
369 return paragraphsRects;
370 }
371
IsSelectLineHeadAndUseLeadingMargin(int32_t start) const372 bool ParagraphManager::IsSelectLineHeadAndUseLeadingMargin(int32_t start) const
373 {
374 for (auto iter = paragraphs_.begin(); iter != paragraphs_.end(); iter++) {
375 auto curParagraph = *iter;
376 if (curParagraph.paragraph && curParagraph.paragraph->GetParagraphStyle().leadingMargin &&
377 curParagraph.start == start) {
378 return true;
379 }
380 auto next = std::next(iter);
381 if (next != paragraphs_.end()) {
382 auto nextParagraph = *next;
383 if (nextParagraph.paragraph && nextParagraph.paragraph->GetParagraphStyle().leadingMargin &&
384 nextParagraph.start == start + 1) {
385 return true;
386 }
387 }
388 }
389 return false;
390 }
391
GetPlaceholderRects() const392 std::vector<RectF> ParagraphManager::GetPlaceholderRects() const
393 {
394 std::vector<RectF> res;
395 float y = 0.0f;
396 for (auto&& info : paragraphs_) {
397 std::vector<RectF> rects;
398 info.paragraph->GetRectsForPlaceholders(rects);
399 for (auto& rect : rects) {
400 rect.SetTop(rect.Top() + y);
401 }
402 y += info.paragraph->GetHeight();
403
404 res.insert(res.end(), rects.begin(), rects.end());
405 }
406 return res;
407 }
408
ComputeCursorOffset(int32_t index,float & selectLineHeight,bool downStreamFirst,bool needLineHighest) const409 OffsetF ParagraphManager::ComputeCursorOffset(
410 int32_t index, float& selectLineHeight, bool downStreamFirst, bool needLineHighest) const
411 {
412 CHECK_NULL_RETURN(!paragraphs_.empty(), {});
413 auto it = paragraphs_.begin();
414 float y = 0.0f;
415 while (it != paragraphs_.end()) {
416 if (index >= it->start && index < it->end) {
417 break;
418 }
419 y += it->paragraph->GetHeight();
420 ++it;
421 }
422
423 if (index == paragraphs_.back().end) {
424 --it;
425 y -= it->paragraph->GetHeight();
426 }
427
428 CHECK_NULL_RETURN(it != paragraphs_.end(), OffsetF(0.0f, y));
429
430 int32_t relativeIndex = index - it->start;
431 auto&& paragraph = it->paragraph;
432 CaretMetricsF metrics;
433 auto computeSuccess = false;
434 if (downStreamFirst) {
435 computeSuccess = paragraph->ComputeOffsetForCaretDownstream(relativeIndex, metrics, needLineHighest) ||
436 paragraph->ComputeOffsetForCaretUpstream(relativeIndex, metrics, needLineHighest);
437 } else {
438 computeSuccess = paragraph->ComputeOffsetForCaretUpstream(relativeIndex, metrics, needLineHighest) ||
439 paragraph->ComputeOffsetForCaretDownstream(relativeIndex, metrics, needLineHighest);
440 }
441 CHECK_NULL_RETURN(computeSuccess, OffsetF(0.0f, y));
442 selectLineHeight = metrics.height;
443 return { static_cast<float>(metrics.offset.GetX()), static_cast<float>(metrics.offset.GetY() + y) };
444 }
445
ComputeCursorInfoByClick(int32_t index,float & selectLineHeight,const OffsetF & lastTouchOffset) const446 OffsetF ParagraphManager::ComputeCursorInfoByClick(
447 int32_t index, float& selectLineHeight, const OffsetF& lastTouchOffset) const
448 {
449 CHECK_NULL_RETURN(!paragraphs_.empty(), {});
450 auto it = paragraphs_.begin();
451 float y = 0.0f;
452 while (it != paragraphs_.end()) {
453 if (index >= it->start && index < it->end) {
454 break;
455 }
456 y += it->paragraph->GetHeight();
457 ++it;
458 }
459
460 if (index == paragraphs_.back().end) {
461 --it;
462 y -= it->paragraph->GetHeight();
463 }
464
465 CHECK_NULL_RETURN(it != paragraphs_.end(), OffsetF(0.0f, y));
466
467 int32_t relativeIndex = index - it->start;
468 auto&& paragraph = it->paragraph;
469
470 CaretMetricsF caretCaretMetric;
471 auto touchOffsetInCurrentParagraph = OffsetF(static_cast<float>(lastTouchOffset.GetX()),
472 static_cast<float>(lastTouchOffset.GetY() - y));
473 TextAffinity textAffinity;
474 paragraph->CalcCaretMetricsByPosition(relativeIndex, caretCaretMetric, touchOffsetInCurrentParagraph, textAffinity);
475 selectLineHeight = caretCaretMetric.height;
476 return { static_cast<float>(caretCaretMetric.offset.GetX()),
477 static_cast<float>(caretCaretMetric.offset.GetY() + y) };
478 }
479
Reset()480 void ParagraphManager::Reset()
481 {
482 paragraphs_.clear();
483 }
484
ToString() const485 std::string ParagraphManager::ParagraphInfo::ToString() const
486 {
487 return "Paragraph start: " + std::to_string(start) + ", end: " + std::to_string(end);
488 }
489 } // namespace OHOS::Ace::NG