[dali_2.3.20] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / text-controls / common-text-utils.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 // EXTERNAL INCLUDES
18 #include <dali/devel-api/text-abstraction/segmentation.h>
19 #include <dali/public-api/actors/layer.h>
20
21 // INTERNAL INCLUDES
22 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
23 #include <dali-toolkit/devel-api/controls/control-devel.h>
24 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
25 #include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
26 #include <dali-toolkit/internal/text/character-set-conversion.h>
27 #include <dali-toolkit/internal/text/hidden-text.h>
28 #include <dali-toolkit/internal/text/text-view.h>
29
30 namespace Dali::Toolkit::Internal
31 {
32 void CommonTextUtils::SynchronizeTextAnchorsInParent(
33   Actor                             parent,
34   Text::ControllerPtr               controller,
35   std::vector<Toolkit::TextAnchor>& anchorActors)
36 {
37   for(auto& anchorActor : anchorActors)
38   {
39     parent.Remove(anchorActor);
40   }
41   if(Dali::Accessibility::IsUp())
42   {
43     controller->GetAnchorActors(anchorActors);
44     for(auto& anchorActor : anchorActors)
45     {
46       parent.Add(anchorActor);
47     }
48   }
49 }
50
51 void CommonTextUtils::RenderText(
52   Actor                             textActor,
53   Text::RendererPtr                 renderer,
54   Text::ControllerPtr               controller,
55   Text::DecoratorPtr                decorator,
56   float&                            alignmentOffset,
57   Actor&                            renderableActor,
58   Actor&                            backgroundActor,
59   Actor&                            cursorLayerActor,
60   Toolkit::Control&                 stencil,
61   std::vector<Actor>&               clippingDecorationActors,
62   std::vector<Toolkit::TextAnchor>& anchorActors,
63   Text::Controller::UpdateTextType  updateTextType)
64 {
65   Actor newRenderableActor;
66
67   if(Text::Controller::NONE_UPDATED != (Text::Controller::MODEL_UPDATED & updateTextType))
68   {
69     if(renderer)
70     {
71       newRenderableActor = renderer->Render(controller->GetView(),
72                                             textActor,
73                                             Property::INVALID_INDEX, // Animatable property not supported
74                                             alignmentOffset,
75                                             DepthIndex::CONTENT);
76     }
77
78     if(renderableActor != newRenderableActor)
79     {
80       UnparentAndReset(backgroundActor);
81       UnparentAndReset(renderableActor);
82       renderableActor = newRenderableActor;
83
84       if(renderableActor)
85       {
86         backgroundActor = controller->CreateBackgroundActor();
87       }
88     }
89   }
90
91   if(renderableActor)
92   {
93     const Vector2& scrollOffset = controller->GetTextModel()->GetScrollPosition();
94
95     float renderableActorPositionX, renderableActorPositionY;
96
97     if(stencil)
98     {
99       renderableActorPositionX = scrollOffset.x + alignmentOffset;
100       renderableActorPositionY = scrollOffset.y;
101     }
102     else
103     {
104       Extents padding;
105       padding = textActor.GetProperty<Extents>(Toolkit::Control::Property::PADDING);
106
107       // Support Right-To-Left of padding
108       Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(textActor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
109       if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
110       {
111         std::swap(padding.start, padding.end);
112       }
113
114       renderableActorPositionX = scrollOffset.x + alignmentOffset + padding.start;
115       renderableActorPositionY = scrollOffset.y + padding.top;
116     }
117
118     renderableActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY));
119
120     // Make sure the actors are parented correctly with/without clipping
121     Actor self = stencil ? stencil : textActor;
122
123     Actor highlightActor;
124
125     for(std::vector<Actor>::iterator it    = clippingDecorationActors.begin(),
126                                      endIt = clippingDecorationActors.end();
127         it != endIt;
128         ++it)
129     {
130       self.Add(*it);
131       it->LowerToBottom();
132
133       if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "HighlightActor")
134       {
135         highlightActor = *it;
136       }
137     }
138     clippingDecorationActors.clear();
139
140     self.Add(renderableActor);
141
142     if(backgroundActor)
143     {
144       if(decorator && decorator->IsHighlightVisible())
145       {
146         self.Add(backgroundActor);
147         backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
148         backgroundActor.LowerBelow(highlightActor);
149       }
150       else
151       {
152         renderableActor.Add(backgroundActor);
153         backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
154         backgroundActor.LowerToBottom();
155       }
156     }
157
158     if(cursorLayerActor)
159     {
160       cursorLayerActor.RaiseToTop();
161     }
162
163     SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
164   }
165 }
166
167 std::size_t TextControlAccessible::GetCharacterCount() const
168 {
169   return GetWholeText().size();
170 }
171
172 std::size_t TextControlAccessible::GetCursorOffset() const
173 {
174   return 0u;
175 }
176
177 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
178 {
179   if(!ValidateRange(GetWholeText(), startOffset, endOffset))
180   {
181     return {0, 0, 0, 0};
182   }
183
184   auto rect    = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
185   auto extents = GetExtents(type);
186
187   rect.x += extents.x;
188   rect.y += extents.y;
189
190   return rect;
191 }
192
193 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
194 {
195   // Since DALi supports only one selection, indices other than 0 are ignored
196   if(selectionIndex > 0)
197   {
198     return {};
199   }
200
201   auto indices     = GetTextController()->GetSelectionIndexes();
202   auto startOffset = static_cast<std::size_t>(indices.first);
203   auto endOffset   = static_cast<std::size_t>(indices.second);
204   auto text        = GetText(startOffset, endOffset);
205
206   return {startOffset, endOffset, std::move(text)};
207 }
208
209 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
210 {
211   auto text = GetWholeText();
212
213   if(!ValidateRange(text, startOffset, endOffset))
214   {
215     return {};
216   }
217
218   if(IsHiddenInput())
219   {
220     std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
221     std::string   substituteCharacterUtf8;
222     std::string   substituteText;
223
224     Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
225
226     while(substituteText.length() < endOffset - startOffset)
227     {
228       substituteText.append(substituteCharacterUtf8);
229     }
230
231     return substituteText;
232   }
233
234   return text.substr(startOffset, endOffset - startOffset);
235 }
236
237 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
238 {
239   Accessibility::Range range{};
240
241   if(IsHiddenInput())
242   {
243     // Returning empty object, as there is no possibility to parse the textfield
244     // when its content is hidden.
245     return range;
246   }
247
248   auto text     = GetWholeText();
249   auto textSize = text.size();
250
251   switch(boundary)
252   {
253     case Dali::Accessibility::TextBoundary::CHARACTER:
254     {
255       if(offset < textSize)
256       {
257         range.content     = text[offset];
258         range.startOffset = offset;
259         range.endOffset   = offset + 1;
260       }
261       break;
262     }
263
264     case Dali::Accessibility::TextBoundary::WORD:
265     case Dali::Accessibility::TextBoundary::LINE:
266     {
267       std::vector<char> breaks(textSize, '\0');
268
269       if(boundary == Dali::Accessibility::TextBoundary::WORD)
270       {
271         TextAbstraction::Segmentation::Get().GetWordBreakPositionsUtf8(reinterpret_cast<const uint8_t*>(text.c_str()), textSize, breaks.data());
272       }
273       else
274       {
275         TextAbstraction::Segmentation::Get().GetLineBreakPositionsUtf8(reinterpret_cast<const uint8_t*>(text.c_str()), textSize, breaks.data());
276       }
277
278       std::size_t index   = 0u;
279       std::size_t counter = 0u;
280
281       while(index < textSize && counter <= offset)
282       {
283         auto start = index;
284         if(breaks[index])
285         {
286           while(breaks[index])
287           {
288             index++;
289           }
290           counter++;
291         }
292         else
293         {
294           if(boundary == Dali::Accessibility::TextBoundary::WORD)
295           {
296             index++;
297           }
298           if(boundary == Dali::Accessibility::TextBoundary::LINE)
299           {
300             counter++;
301           }
302         }
303
304         if((counter > 0) && ((counter - 1) == offset))
305         {
306           range.content     = text.substr(start, index - start + 1);
307           range.startOffset = start;
308           range.endOffset   = index + 1;
309         }
310
311         if(boundary == Dali::Accessibility::TextBoundary::LINE)
312         {
313           index++;
314         }
315       }
316       break;
317     }
318
319     case Dali::Accessibility::TextBoundary::SENTENCE:  // Not supported by default
320     case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
321     default:
322     {
323       break;
324     }
325   }
326
327   return range;
328 }
329
330 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
331 {
332   // Since DALi supports only one selection, indices other than 0 are ignored
333   if(selectionIndex > 0)
334   {
335     return false;
336   }
337
338   GetTextController()->SetSelection(0, 0);
339
340   return true;
341 }
342
343 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
344 {
345   return false;
346 }
347
348 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
349 {
350   // Since DALi supports only one selection, indices other than 0 are ignored
351   if(selectionIndex > 0)
352   {
353     return false;
354   }
355
356   // Lack of ValidateRange() is intentional
357
358   GetTextController()->SetSelection(startOffset, endOffset);
359
360   return true;
361 }
362
363 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
364 {
365   if(linkIndex < 0 || linkIndex >= GetLinkCount())
366   {
367     return nullptr;
368   }
369
370   auto anchor = GetTextAnchors()[linkIndex];
371
372   return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
373 }
374
375 std::int32_t TextControlAccessible::GetLinkCount() const
376 {
377   return static_cast<std::int32_t>(GetTextAnchors().size());
378 }
379
380 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
381 {
382   return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
383 }
384
385 std::string TextControlAccessible::GetWholeText() const
386 {
387   std::string text;
388
389   GetTextController()->GetText(text);
390
391   return text;
392 }
393
394 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
395 {
396   return Toolkit::Text::STAR;
397 }
398
399 bool TextControlAccessible::IsHiddenInput() const
400 {
401   return false;
402 }
403
404 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
405 {
406   auto size = string.size();
407
408   if(end <= begin || begin >= size || end > size)
409   {
410     return false;
411   }
412
413   // TODO: Check whether the range [begin, end) describes a valid substring:
414   // 1. It does not break multi-byte UTF-8 sequences.
415   // 2. It does not break graphemes (compound emojis, glyphs with combining characters etc.).
416
417   return true;
418 }
419
420 Accessibility::States EditableTextControlAccessible::CalculateStates()
421 {
422   using Dali::Accessibility::State;
423
424   auto states       = DevelControl::ControlAccessible::CalculateStates();
425   auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
426
427   states[State::EDITABLE]  = true;
428   states[State::FOCUSABLE] = true;
429   states[State::FOCUSED]   = (Self() == focusControl);
430
431   return states;
432 }
433
434 std::size_t EditableTextControlAccessible::GetCursorOffset() const
435 {
436   return GetTextController()->GetCursorPosition();
437 }
438
439 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
440 {
441   if(offset > GetCharacterCount())
442   {
443     return false;
444   }
445
446   GetTextController()->ResetCursorPosition(offset);
447   RequestTextRelayout();
448
449   return true;
450 }
451
452 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
453 {
454   auto text = GetWholeText();
455
456   if(!ValidateRange(text, startPosition, endPosition))
457   {
458     return false;
459   }
460
461   GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
462
463   return true;
464 }
465
466 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
467 {
468   if(!CopyText(startPosition, endPosition))
469   {
470     return false;
471   }
472
473   return DeleteText(startPosition, endPosition);
474 }
475
476 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
477 {
478   auto text = GetWholeText();
479
480   if(!ValidateRange(text, startPosition, endPosition))
481   {
482     return false;
483   }
484
485   return SetTextContents(std::move(text.erase(startPosition, endPosition - startPosition)));
486 }
487
488 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
489 {
490   auto text = GetWholeText();
491
492   if(!ValidateRange(text, startPosition, startPosition + 1) && !(startPosition == text.size()))
493   {
494     return false;
495   }
496
497   return SetTextContents(std::move(text.insert(startPosition, newText)));
498 }
499
500 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
501 {
502   GetTextController()->SetText(std::move(newContents));
503
504   return true;
505 }
506
507 } // namespace Dali::Toolkit::Internal