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