| /* | |
| * Copyright 2025 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include <memory> | |
| #include <utility> | |
| #include "include/core/SkTypes.h" | |
| #include "include/private/SkAssert.h" | |
| #include "modules/skottie/include/TextShaper.h" | |
| #include "modules/skunicode/include/SkUnicode.h" | |
| namespace { | |
| // Performs iterator "intersection", i.e. it only returns break positions matching both iterators. | |
| class IntersectingBreakIterator final : public SkBreakIterator { | |
| public: | |
| IntersectingBreakIterator(std::unique_ptr<SkBreakIterator> a, | |
| std::unique_ptr<SkBreakIterator> b) | |
| : fA(std::move(a)) | |
| , fB(std::move(b)) | |
| { | |
| SkASSERT(fA); | |
| SkASSERT(fB); | |
| } | |
| Position first() override { | |
| fCurrent = fA->first(); | |
| SkAssertResult(fB->first() == fCurrent); | |
| fDone = false; | |
| return fCurrent; | |
| } | |
| Position current() override { | |
| return fCurrent; | |
| } | |
| bool isDone() override { | |
| SkASSERT((fA->isDone() || fB->isDone()) == fDone); | |
| return fDone; | |
| } | |
| Position next() override { | |
| SkASSERT(fDone || (fA->current() == fB->current())); | |
| Position pos_a = fCurrent, | |
| pos_b = fCurrent; | |
| while (!fDone) { | |
| if (pos_a < pos_b) { | |
| pos_a = fA->next(); | |
| fDone = fA->isDone(); | |
| } else { | |
| pos_b = fB->next(); | |
| fDone = fB->isDone(); | |
| } | |
| if (pos_a == pos_b) { | |
| break; | |
| } | |
| } | |
| // At this point the positions are either valid and equal (advanced successfully), | |
| // or one of them is negative (for the done iterator); | |
| SkASSERT(pos_a == pos_b || fDone); | |
| SkASSERT((pos_a < 0 || pos_b < 0) == fDone); | |
| return fCurrent = std::min(pos_a, pos_b); | |
| } | |
| bool setText(const char utftext8[], int utf8Units) override { | |
| fDone = false; | |
| fCurrent = 0; | |
| return fA->setText(utftext8, utf8Units) && | |
| fB->setText(utftext8, utf8Units); | |
| } | |
| bool setText(const char16_t utftext16[], int utf16Units) override { | |
| fDone = false; | |
| fCurrent = 0; | |
| return fA->setText(utftext16, utf16Units) && | |
| fB->setText(utftext16, utf16Units); | |
| } | |
| Status status() override { | |
| SkUNREACHABLE; | |
| } | |
| private: | |
| const std::unique_ptr<SkBreakIterator> fA, fB; | |
| Position fCurrent = 0; | |
| bool fDone = false; | |
| }; | |
| // An SkUnicode wrapper which offers stricter line break semantics (it never breaks words). | |
| class StrictLinebreakUnicode final : public SkUnicode { | |
| public: | |
| explicit StrictLinebreakUnicode(sk_sp<SkUnicode> uc) : fUnicode(std::move(uc)) | |
| { | |
| SkASSERT(fUnicode); | |
| } | |
| std::unique_ptr<SkBreakIterator> makeBreakIterator(const char locale[], | |
| BreakType breakType) override { | |
| std::unique_ptr<SkBreakIterator> brk = fUnicode->makeBreakIterator(locale, breakType); | |
| if (!brk || breakType != BreakType::kLines) { | |
| return brk; | |
| } | |
| // When requested a line break iterator, return a composite iterator which prevents | |
| // breaking mid-word. | |
| std::unique_ptr<SkBreakIterator> wbrk = | |
| fUnicode->makeBreakIterator(locale, BreakType::kWords); | |
| return wbrk | |
| ? std::make_unique<IntersectingBreakIterator>(std::move(brk), std::move(wbrk)) | |
| : std::move(brk); | |
| } | |
| // Proxy everything else. | |
| SkString toUpper(const SkString& str) override { return fUnicode->toUpper(str); } | |
| SkString toUpper(const SkString& str, const char* locale) override { | |
| return fUnicode->toUpper(str, locale); | |
| } | |
| bool isControl(SkUnichar utf8) override { return fUnicode->isControl(utf8); } | |
| bool isWhitespace(SkUnichar utf8) override { return fUnicode->isWhitespace(utf8); } | |
| bool isSpace(SkUnichar utf8) override { return fUnicode->isSpace(utf8); } | |
| bool isTabulation(SkUnichar utf8) override { return fUnicode->isTabulation(utf8); } | |
| bool isHardBreak(SkUnichar utf8) override { return fUnicode->isHardBreak(utf8); } | |
| bool isDefaultIgnorable(SkUnichar utf8) override { return fUnicode->isDefaultIgnorable(utf8); } | |
| bool isEmoji(SkUnichar utf8) override { return fUnicode->isEmoji(utf8); } | |
| bool isEmojiComponent(SkUnichar utf8) override { return fUnicode->isEmojiComponent(utf8); } | |
| bool isEmojiModifierBase(SkUnichar utf8) override { | |
| return fUnicode->isEmojiModifierBase(utf8); | |
| } | |
| bool isEmojiModifier(SkUnichar utf8) override { return fUnicode->isEmojiModifier(utf8); } | |
| bool isRegionalIndicator(SkUnichar utf8) override { | |
| return fUnicode->isRegionalIndicator(utf8); | |
| } | |
| bool isIdeographic(SkUnichar utf8) override { return fUnicode->isIdeographic(utf8); } | |
| std::unique_ptr<SkBidiIterator> makeBidiIterator(const uint16_t text[], | |
| int count, | |
| SkBidiIterator::Direction dir) override { | |
| return fUnicode->makeBidiIterator(text, count, dir); | |
| } | |
| std::unique_ptr<SkBidiIterator> makeBidiIterator(const char text[], | |
| int count, | |
| SkBidiIterator::Direction dir) override { | |
| return fUnicode->makeBidiIterator(text, count, dir); | |
| } | |
| std::unique_ptr<SkBreakIterator> makeBreakIterator(BreakType breakType) override { | |
| return fUnicode->makeBreakIterator(breakType); | |
| } | |
| bool getBidiRegions(const char utf8[], | |
| int utf8Units, | |
| TextDirection dir, | |
| std::vector<BidiRegion>* results) override { | |
| return fUnicode->getBidiRegions(utf8, utf8Units, dir, results); | |
| } | |
| bool getWords(const char utf8[], int utf8Units, const char* locale, | |
| std::vector<Position>* results) override { | |
| return fUnicode->getWords(utf8, utf8Units, locale, results); | |
| } | |
| bool getUtf8Words(const char utf8[], | |
| int utf8Units, | |
| const char* locale, | |
| std::vector<Position>* results) override { | |
| return fUnicode->getUtf8Words(utf8, utf8Units, locale, results); | |
| } | |
| bool getSentences(const char utf8[], | |
| int utf8Units, | |
| const char* locale, | |
| std::vector<Position>* results) override { | |
| return fUnicode->getSentences(utf8, utf8Units, locale, results); | |
| } | |
| bool computeCodeUnitFlags(char utf8[], int utf8Units, bool replaceTabs, | |
| skia_private::TArray<SkUnicode::CodeUnitFlags, true>* results) override { | |
| return fUnicode->computeCodeUnitFlags(utf8, utf8Units, replaceTabs, results); | |
| } | |
| bool computeCodeUnitFlags(char16_t utf16[], int utf16Units, bool replaceTabs, | |
| skia_private::TArray<SkUnicode::CodeUnitFlags, true>* results) override { | |
| return fUnicode->computeCodeUnitFlags(utf16, utf16Units, replaceTabs, results); | |
| } | |
| void reorderVisual(const BidiLevel runLevels[], | |
| int levelsCount, | |
| int32_t logicalFromVisual[]) override { | |
| return fUnicode->reorderVisual(runLevels, levelsCount, logicalFromVisual); | |
| } | |
| private: | |
| const sk_sp<SkUnicode> fUnicode; | |
| }; | |
| } // namespace | |
| namespace skottie { | |
| sk_sp<SkUnicode> SK_API MakeStrictLinebreakUnicode(sk_sp<SkUnicode> uc) { | |
| return uc | |
| ? sk_make_sp<StrictLinebreakUnicode>(std::move(uc)) | |
| : nullptr; | |
| } | |
| std::unique_ptr<SkBreakIterator> MakeIntersectingBreakIteratorForTesting( | |
| std::unique_ptr<SkBreakIterator> a, std::unique_ptr<SkBreakIterator> b) { | |
| return std::make_unique<IntersectingBreakIterator>(std::move(a), std::move(b)); | |
| } | |
| } // namespace skottie |