2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #include <dali/devel-api/text-abstraction/segmentation.h>
19 #include <dali/public-api/actors/layer.h>
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>
30 namespace Dali::Toolkit::Internal
32 void CommonTextUtils::SynchronizeTextAnchorsInParent(
34 Text::ControllerPtr controller,
35 std::vector<Toolkit::TextAnchor>& anchorActors)
37 for(auto& anchorActor : anchorActors)
39 parent.Remove(anchorActor);
41 if(Dali::Accessibility::IsUp())
43 controller->GetAnchorActors(anchorActors);
44 for(auto& anchorActor : anchorActors)
46 parent.Add(anchorActor);
51 void CommonTextUtils::RenderText(
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)
65 Actor newRenderableActor;
67 if(Text::Controller::NONE_UPDATED != (Text::Controller::MODEL_UPDATED & updateTextType))
71 newRenderableActor = renderer->Render(controller->GetView(),
73 Property::INVALID_INDEX, // Animatable property not supported
78 if(renderableActor != newRenderableActor)
80 UnparentAndReset(backgroundActor);
81 UnparentAndReset(renderableActor);
82 renderableActor = newRenderableActor;
86 backgroundActor = controller->CreateBackgroundActor();
93 const Vector2& scrollOffset = controller->GetTextModel()->GetScrollPosition();
95 float renderableActorPositionX, renderableActorPositionY;
99 renderableActorPositionX = scrollOffset.x + alignmentOffset;
100 renderableActorPositionY = scrollOffset.y;
105 padding = textActor.GetProperty<Extents>(Toolkit::Control::Property::PADDING);
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)
111 std::swap(padding.start, padding.end);
114 renderableActorPositionX = scrollOffset.x + alignmentOffset + padding.start;
115 renderableActorPositionY = scrollOffset.y + padding.top;
118 renderableActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY));
120 // Make sure the actors are parented correctly with/without clipping
121 Actor self = stencil ? stencil : textActor;
123 Actor highlightActor;
125 for(std::vector<Actor>::iterator it = clippingDecorationActors.begin(),
126 endIt = clippingDecorationActors.end();
133 if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "HighlightActor")
135 highlightActor = *it;
138 clippingDecorationActors.clear();
140 self.Add(renderableActor);
144 if(decorator && decorator->IsHighlightVisible())
146 self.Add(backgroundActor);
147 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
148 backgroundActor.LowerBelow(highlightActor);
152 renderableActor.Add(backgroundActor);
153 backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
154 backgroundActor.LowerToBottom();
160 cursorLayerActor.RaiseToTop();
163 SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
167 std::size_t TextControlAccessible::GetCharacterCount() const
169 return GetWholeText().size();
172 std::size_t TextControlAccessible::GetCursorOffset() const
177 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
179 if(!ValidateRange(GetWholeText(), startOffset, endOffset))
184 auto rect = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
185 auto extents = GetExtents(type);
193 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
195 // Since DALi supports only one selection, indices other than 0 are ignored
196 if(selectionIndex > 0)
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);
206 return {startOffset, endOffset, text};
209 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
211 auto text = GetWholeText();
213 if(!ValidateRange(text, startOffset, endOffset))
220 std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
221 std::string substituteCharacterUtf8;
222 std::string substituteText;
224 Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
226 while(substituteText.length() < endOffset - startOffset)
228 substituteText.append(substituteCharacterUtf8);
231 return substituteText;
234 return text.substr(startOffset, endOffset - startOffset);
237 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
239 Accessibility::Range range{};
243 // Returning empty object, as there is no possibility to parse the textfield
244 // when its content is hidden.
248 auto text = GetWholeText();
249 auto textSize = text.size();
253 case Dali::Accessibility::TextBoundary::CHARACTER:
255 if(offset < textSize)
257 range.content = text[offset];
258 range.startOffset = offset;
259 range.endOffset = offset + 1;
264 case Dali::Accessibility::TextBoundary::WORD:
265 case Dali::Accessibility::TextBoundary::LINE:
267 std::vector<char> breaks(textSize, '\0');
269 if(boundary == Dali::Accessibility::TextBoundary::WORD)
271 TextAbstraction::Segmentation::Get().GetWordBreakPositionsUtf8(reinterpret_cast<const uint8_t*>(text.c_str()), textSize, breaks.data());
275 TextAbstraction::Segmentation::Get().GetLineBreakPositionsUtf8(reinterpret_cast<const uint8_t*>(text.c_str()), textSize, breaks.data());
278 std::size_t index = 0u;
279 std::size_t counter = 0u;
281 while(index < textSize && counter <= offset)
294 if(boundary == Dali::Accessibility::TextBoundary::WORD)
298 if(boundary == Dali::Accessibility::TextBoundary::LINE)
304 if((counter > 0) && ((counter - 1) == offset))
306 range.content = text.substr(start, index - start + 1);
307 range.startOffset = start;
308 range.endOffset = index + 1;
311 if(boundary == Dali::Accessibility::TextBoundary::LINE)
319 case Dali::Accessibility::TextBoundary::SENTENCE: // Not supported by default
320 case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
330 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
332 // Since DALi supports only one selection, indices other than 0 are ignored
333 if(selectionIndex > 0)
338 GetTextController()->SetSelection(0, 0);
343 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
348 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
350 // Since DALi supports only one selection, indices other than 0 are ignored
351 if(selectionIndex > 0)
356 // Lack of ValidateRange() is intentional
358 GetTextController()->SetSelection(startOffset, endOffset);
363 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
365 if(linkIndex < 0 || linkIndex >= GetLinkCount())
370 auto anchor = GetTextAnchors()[linkIndex];
372 return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
375 std::int32_t TextControlAccessible::GetLinkCount() const
377 return static_cast<std::int32_t>(GetTextAnchors().size());
380 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
382 return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
385 std::string TextControlAccessible::GetWholeText() const
389 GetTextController()->GetText(text);
394 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
396 return Toolkit::Text::STAR;
399 bool TextControlAccessible::IsHiddenInput() const
404 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
406 auto size = string.size();
408 if(end <= begin || begin >= size || end > size)
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.).
420 Accessibility::States EditableTextControlAccessible::CalculateStates()
422 using Dali::Accessibility::State;
424 auto states = DevelControl::ControlAccessible::CalculateStates();
425 auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
427 states[State::EDITABLE] = true;
428 states[State::FOCUSABLE] = true;
429 states[State::FOCUSED] = (Self() == focusControl);
434 std::size_t EditableTextControlAccessible::GetCursorOffset() const
436 return GetTextController()->GetCursorPosition();
439 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
441 if(offset > GetCharacterCount())
446 GetTextController()->ResetCursorPosition(offset);
447 RequestTextRelayout();
452 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
454 auto text = GetWholeText();
456 if(!ValidateRange(text, startPosition, endPosition))
461 GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
466 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
468 if(!CopyText(startPosition, endPosition))
473 return DeleteText(startPosition, endPosition);
476 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
478 auto text = GetWholeText();
480 if(!ValidateRange(text, startPosition, endPosition))
485 return SetTextContents(std::move(text.erase(startPosition, endPosition - startPosition)));
488 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
490 auto text = GetWholeText();
492 if(!ValidateRange(text, startPosition, startPosition + 1) && !(startPosition == text.size()))
497 return SetTextContents(std::move(text.insert(startPosition, newText)));
500 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
502 GetTextController()->SetText(std::move(newContents));
507 } // namespace Dali::Toolkit::Internal