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