blob: 308e40e210488bdf2e4faa1f6993961e137f2cd5 [file] [log] [blame]
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#pragma once
#include <unicode/ubrk.h>
#include <wtf/Optional.h>
#include <wtf/text/icu/UTextProviderLatin1.h>
#define USE_ICU_CARET_ITERATOR (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101200)
namespace WTF {
#if USE_ICU_CARET_ITERATOR
static String caretRules()
{
static StaticStringImpl caretRuleString(
// This rule set is based on character-break iterator rules of ICU 57
// <http://source.icu-project.org/repos/icu/icu/tags/release-57-1/source/data/brkitr/>.
// The major differences from the original ones are listed below:
// * Replaced '[\p{Grapheme_Cluster_Break = SpacingMark}]' with '[\p{General_Category = Spacing Mark} - $Extend]' for ICU 3.8 or earlier;
// * Removed rules that prevent a caret from moving after prepend characters (Bug 24342);
// * Added rules that prevent a caret from moving after virama signs of Indic languages except Tamil (Bug 15790), and;
// * Added rules that prevent a caret from moving before Japanese half-width katakara voiced marks.
// * Added rules for regional indicator symbols.
"$CR = [\\p{Grapheme_Cluster_Break = CR}];"
"$LF = [\\p{Grapheme_Cluster_Break = LF}];"
"$Control = [\\p{Grapheme_Cluster_Break = Control}];"
"$VoiceMarks = [\\uFF9E\\uFF9F];" // Japanese half-width katakana voiced marks
"$Extend = [\\p{Grapheme_Cluster_Break = Extend} $VoiceMarks - [\\u0E30 \\u0E32 \\u0E45 \\u0EB0 \\u0EB2]];"
"$SpacingMark = [[\\p{General_Category = Spacing Mark}] - $Extend];"
"$L = [\\p{Grapheme_Cluster_Break = L}];"
"$V = [\\p{Grapheme_Cluster_Break = V}];"
"$T = [\\p{Grapheme_Cluster_Break = T}];"
"$LV = [\\p{Grapheme_Cluster_Break = LV}];"
"$LVT = [\\p{Grapheme_Cluster_Break = LVT}];"
"$Hin0 = [\\u0905-\\u0939];" // Devanagari Letter A,...,Ha
"$HinV = \\u094D;" // Devanagari Sign Virama
"$Hin1 = [\\u0915-\\u0939];" // Devanagari Letter Ka,...,Ha
"$Ben0 = [\\u0985-\\u09B9];" // Bengali Letter A,...,Ha
"$BenV = \\u09CD;" // Bengali Sign Virama
"$Ben1 = [\\u0995-\\u09B9];" // Bengali Letter Ka,...,Ha
"$Pan0 = [\\u0A05-\\u0A39];" // Gurmukhi Letter A,...,Ha
"$PanV = \\u0A4D;" // Gurmukhi Sign Virama
"$Pan1 = [\\u0A15-\\u0A39];" // Gurmukhi Letter Ka,...,Ha
"$Guj0 = [\\u0A85-\\u0AB9];" // Gujarati Letter A,...,Ha
"$GujV = \\u0ACD;" // Gujarati Sign Virama
"$Guj1 = [\\u0A95-\\u0AB9];" // Gujarati Letter Ka,...,Ha
"$Ori0 = [\\u0B05-\\u0B39];" // Oriya Letter A,...,Ha
"$OriV = \\u0B4D;" // Oriya Sign Virama
"$Ori1 = [\\u0B15-\\u0B39];" // Oriya Letter Ka,...,Ha
"$Tel0 = [\\u0C05-\\u0C39];" // Telugu Letter A,...,Ha
"$TelV = \\u0C4D;" // Telugu Sign Virama
"$Tel1 = [\\u0C14-\\u0C39];" // Telugu Letter Ka,...,Ha
"$Kan0 = [\\u0C85-\\u0CB9];" // Kannada Letter A,...,Ha
"$KanV = \\u0CCD;" // Kannada Sign Virama
"$Kan1 = [\\u0C95-\\u0CB9];" // Kannada Letter A,...,Ha
"$Mal0 = [\\u0D05-\\u0D39];" // Malayalam Letter A,...,Ha
"$MalV = \\u0D4D;" // Malayalam Sign Virama
"$Mal1 = [\\u0D15-\\u0D39];" // Malayalam Letter A,...,Ha
"$RI = [\\U0001F1E6-\\U0001F1FF];" // Emoji regional indicators
"$ZWJ = \\u200D;" // Zero width joiner
"$EmojiVar = [\\uFE0F];" // Emoji-style variation selector
"$EmojiForSeqs = [\\u2640 \\u2642 \\u26F9 \\u2764 \\U0001F308 \\U0001F3C3-\\U0001F3C4 \\U0001F3CA-\\U0001F3CC \\U0001F3F3 \\U0001F441 \\U0001F466-\\U0001F469 \\U0001F46E-\\U0001F46F \\U0001F471 \\U0001F473 \\U0001F477 \\U0001F481-\\U0001F482 \\U0001F486-\\U0001F487 \\U0001F48B \\U0001F575 \\U0001F5E8 \\U0001F645-\\U0001F647 \\U0001F64B \\U0001F64D-\\U0001F64E \\U0001F6A3 \\U0001F6B4-\\U0001F6B6 \\u2695-\\u2696 \\u2708 \\U0001F33E \\U0001F373 \\U0001F393 \\U0001F3A4 \\U0001F3A8 \\U0001F3EB \\U0001F3ED \\U0001F4BB-\\U0001F4BC \\U0001F527 \\U0001F52C \\U0001F680 \\U0001F692 \\U0001F926 \\U0001F937-\\U0001F939 \\U0001F93C-\\U0001F93E];" // Emoji that participate in ZWJ sequences
"$EmojiForMods = [\\u261D \\u26F9 \\u270A-\\u270D \\U0001F385 \\U0001F3C3-\\U0001F3C4 \\U0001F3CA \\U0001F3CB \\U0001F442-\\U0001F443 \\U0001F446-\\U0001F450 \\U0001F466-\\U0001F478 \\U0001F47C \\U0001F481-\\U0001F483 \\U0001F485-\\U0001F487 \\U0001F4AA \\U0001F575 \\U0001F590 \\U0001F595 \\U0001F596 \\U0001F645-\\U0001F647 \\U0001F64B-\\U0001F64F \\U0001F6A3 \\U0001F6B4-\\U0001F6B6 \\U0001F6C0 \\U0001F918 \\U0001F3C2 \\U0001F3C7 \\U0001F3CC \\U0001F574 \\U0001F57A \\U0001F6CC \\U0001F919-\\U0001F91E \\U0001F926 \\U0001F930 \\U0001F933-\\U0001F939 \\U0001F93C-\\U0001F93E] ;" // Emoji that take Fitzpatrick modifiers
"$EmojiMods = [\\U0001F3FB-\\U0001F3FF];" // Fitzpatrick modifiers
"!!chain;"
"!!RINoChain;"
"!!forward;"
"$CR $LF;"
"$L ($L | $V | $LV | $LVT);"
"($LV | $V) ($V | $T);"
"($LVT | $T) $T;"
"$RI $RI $Extend* / $RI;"
"$RI $RI $Extend*;"
"[^$Control $CR $LF] $Extend;"
"[^$Control $CR $LF] $SpacingMark;"
"$Hin0 $HinV $Hin1;" // Devanagari Virama (forward)
"$Ben0 $BenV $Ben1;" // Bengali Virama (forward)
"$Pan0 $PanV $Pan1;" // Gurmukhi Virama (forward)
"$Guj0 $GujV $Guj1;" // Gujarati Virama (forward)
"$Ori0 $OriV $Ori1;" // Oriya Virama (forward)
"$Tel0 $TelV $Tel1;" // Telugu Virama (forward)
"$Kan0 $KanV $Kan1;" // Kannada Virama (forward)
"$Mal0 $MalV $Mal1;" // Malayalam Virama (forward)
"$ZWJ $EmojiForSeqs;" // Don't break in emoji ZWJ sequences
"$EmojiForMods $EmojiVar? $EmojiMods;" // Don't break between relevant emoji (possibly with variation selector) and Fitzpatrick modifier
"!!reverse;"
"$LF $CR;"
"($L | $V | $LV | $LVT) $L;"
"($V | $T) ($LV | $V);"
"$T ($LVT | $T);"
"$Extend* $RI $RI / $Extend* $RI $RI;"
"$Extend* $RI $RI;"
"$Extend [^$Control $CR $LF];"
"$SpacingMark [^$Control $CR $LF];"
"$Hin1 $HinV $Hin0;" // Devanagari Virama (backward)
"$Ben1 $BenV $Ben0;" // Bengali Virama (backward)
"$Pan1 $PanV $Pan0;" // Gurmukhi Virama (backward)
"$Guj1 $GujV $Guj0;" // Gujarati Virama (backward)
"$Ori1 $OriV $Ori0;" // Gujarati Virama (backward)
"$Tel1 $TelV $Tel0;" // Telugu Virama (backward)
"$Kan1 $KanV $Kan0;" // Kannada Virama (backward)
"$Mal1 $MalV $Mal0;" // Malayalam Virama (backward)
"$EmojiForSeqs $ZWJ;" // Don't break in emoji ZWJ sequences
"$EmojiMods $EmojiVar? $EmojiForMods;" // Don't break between relevant emoji (possibly with variation selector) and Fitzpatrick modifier
"!!safe_reverse;"
"$RI $RI+;"
"[$EmojiVar $EmojiMods]+ $EmojiForMods;"
"!!safe_forward;"
"$RI $RI+;"
"$EmojiForMods [$EmojiVar $EmojiMods]+;"
);
return caretRuleString;
}
#endif
class TextBreakIteratorICU {
public:
enum class Mode {
Line,
Character,
#if USE_ICU_CARET_ITERATOR
Caret,
#endif
};
void set8BitText(const LChar* buffer, unsigned length)
{
UTextWithBuffer textLocal;
textLocal.text = UTEXT_INITIALIZER;
textLocal.text.extraSize = sizeof(textLocal.buffer);
textLocal.text.pExtra = textLocal.buffer;
UErrorCode status = U_ZERO_ERROR;
UText* text = openLatin1UTextProvider(&textLocal, buffer, length, &status);
ASSERT(U_SUCCESS(status));
ASSERT(text);
ubrk_setUText(m_iterator, text, &status);
ASSERT(U_SUCCESS(status));
utext_close(text);
}
TextBreakIteratorICU(StringView string, Mode mode, const char *locale)
{
UBreakIteratorType type;
switch (mode) {
case Mode::Line:
type = UBRK_LINE;
break;
case Mode::Character:
type = UBRK_CHARACTER;
break;
#if USE_ICU_CARET_ITERATOR
case Mode::Caret:
type = UBRK_CHARACTER;
break;
#endif
default:
ASSERT_NOT_REACHED();
type = UBRK_CHARACTER;
break;
}
bool requiresSet8BitText = string.is8Bit();
const UChar *text = requiresSet8BitText ? nullptr : string.characters16();
int32_t textLength = requiresSet8BitText ? 0 : string.length();
// FIXME: Handle weak / normal / strict line breaking.
UErrorCode status = U_ZERO_ERROR;
#if USE_ICU_CARET_ITERATOR
if (mode == Mode::Caret) {
static NeverDestroyed<String> caretRules = WTF::caretRules();
static NeverDestroyed<StringView::UpconvertedCharacters> upconvertedRules = StringView(caretRules).upconvertedCharacters();
UParseError parseError;
m_iterator = ubrk_openRules(upconvertedRules.get(), caretRules.get().length(), text, textLength, &parseError, &status);
} else
#endif
m_iterator = ubrk_open(type, locale, text, textLength, &status);
ASSERT(U_SUCCESS(status));
if (requiresSet8BitText)
set8BitText(string.characters8(), string.length());
}
TextBreakIteratorICU() = delete;
TextBreakIteratorICU(const TextBreakIteratorICU&) = delete;
TextBreakIteratorICU(TextBreakIteratorICU&& other)
: m_iterator(other.m_iterator)
{
other.m_iterator = nullptr;
}
TextBreakIteratorICU& operator=(const TextBreakIteratorICU&) = delete;
TextBreakIteratorICU& operator=(TextBreakIteratorICU&& other)
{
if (m_iterator)
ubrk_close(m_iterator);
m_iterator = other.m_iterator;
other.m_iterator = nullptr;
return *this;
}
~TextBreakIteratorICU()
{
if (m_iterator)
ubrk_close(m_iterator);
}
void setText(StringView string)
{
if (string.is8Bit()) {
set8BitText(string.characters8(), string.length());
return;
}
UErrorCode status = U_ZERO_ERROR;
ubrk_setText(m_iterator, string.characters16(), string.length(), &status);
ASSERT(U_SUCCESS(status));
}
std::optional<unsigned> preceding(unsigned location) const
{
auto result = ubrk_preceding(m_iterator, location);
if (result == UBRK_DONE)
return { };
return result;
}
std::optional<unsigned> following(unsigned location) const
{
auto result = ubrk_following(m_iterator, location);
if (result == UBRK_DONE)
return { };
return result;
}
bool isBoundary(unsigned location) const
{
return ubrk_isBoundary(m_iterator, location);
}
private:
UBreakIterator* m_iterator;
};
}