@@ -186,4 +186,134 @@ public int getOffsetBefore(CharSequence text, int offset) {
186
186
187
187
return offset - deleteCharCount ;
188
188
}
189
+
190
+ /**
191
+ * Gets the offset of the next character following the given offset, with consideration for
192
+ * multi-byte characters.
193
+ *
194
+ * @see <a target="_new"
195
+ * href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android10-s3-release/core/java/android/text/method/BaseKeyListener.java#111">https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android10-s3-release/core/java/android/text/method/BaseKeyListener.java#111</a>
196
+ */
197
+ public int getOffsetAfter (CharSequence text , int offset ) {
198
+ final int len = text .length ();
199
+
200
+ if (offset >= len - 1 ) {
201
+ return len ;
202
+ }
203
+
204
+ int codePoint = Character .codePointAt (text , offset );
205
+ int nextCharCount = Character .charCount (codePoint );
206
+ int nextOffset = offset + nextCharCount ;
207
+
208
+ if (nextOffset == 0 ) {
209
+ return 0 ;
210
+ }
211
+
212
+ // Line Feed
213
+ if (codePoint == LINE_FEED ) {
214
+ codePoint = Character .codePointAt (text , nextOffset );
215
+ if (codePoint == CARRIAGE_RETURN ) {
216
+ ++nextCharCount ;
217
+ }
218
+ return offset + nextCharCount ;
219
+ }
220
+
221
+ // Flags
222
+ if (isRegionalIndicatorSymbol (codePoint )) {
223
+ if (nextOffset >= len - 1
224
+ || !isRegionalIndicatorSymbol (Character .codePointAt (text , nextOffset ))) {
225
+ return offset + nextCharCount ;
226
+ }
227
+ // In this case there are at least two regional indicator symbols ahead of
228
+ // offset. If those two regional indicator symbols are a pair that
229
+ // represent a region together, the next offset should be after both of
230
+ // them.
231
+ int regionalIndicatorSymbolCount = 0 ;
232
+ int regionOffset = offset ;
233
+ while (regionOffset > 0
234
+ && isRegionalIndicatorSymbol (Character .codePointBefore (text , offset ))) {
235
+ regionOffset -= Character .charCount (Character .codePointBefore (text , offset ));
236
+ regionalIndicatorSymbolCount ++;
237
+ }
238
+ if (regionalIndicatorSymbolCount % 2 == 0 ) {
239
+ nextCharCount += 2 ;
240
+ }
241
+ return offset + nextCharCount ;
242
+ }
243
+
244
+ // Keycaps
245
+ if (isKeycapBase (codePoint )) {
246
+ nextCharCount += Character .charCount (codePoint );
247
+ }
248
+ if (codePoint == COMBINING_ENCLOSING_KEYCAP ) {
249
+ codePoint = Character .codePointBefore (text , nextOffset );
250
+ nextOffset += Character .charCount (codePoint );
251
+ if (nextOffset < len && isVariationSelector (codePoint )) {
252
+ int tmpCodePoint = Character .codePointAt (text , nextOffset );
253
+ if (isKeycapBase (tmpCodePoint )) {
254
+ nextCharCount += Character .charCount (codePoint ) + Character .charCount (tmpCodePoint );
255
+ }
256
+ } else if (isKeycapBase (codePoint )) {
257
+ nextCharCount += Character .charCount (codePoint );
258
+ }
259
+ return offset + nextCharCount ;
260
+ }
261
+
262
+ if (isEmoji (codePoint )) {
263
+ boolean isZwj = false ;
264
+ int lastSeenVariantSelectorCharCount = 0 ;
265
+ do {
266
+ if (isZwj ) {
267
+ nextCharCount += Character .charCount (codePoint ) + lastSeenVariantSelectorCharCount + 1 ;
268
+ isZwj = false ;
269
+ }
270
+ lastSeenVariantSelectorCharCount = 0 ;
271
+ if (isEmojiModifier (codePoint )) {
272
+ break ;
273
+ }
274
+
275
+ if (nextOffset < len ) {
276
+ codePoint = Character .codePointAt (text , nextOffset );
277
+ nextOffset += Character .charCount (codePoint );
278
+ if (codePoint == COMBINING_ENCLOSING_KEYCAP ) {
279
+ codePoint = Character .codePointBefore (text , nextOffset );
280
+ nextOffset += Character .charCount (codePoint );
281
+ if (nextOffset < len && isVariationSelector (codePoint )) {
282
+ int tmpCodePoint = Character .codePointAt (text , nextOffset );
283
+ if (isKeycapBase (tmpCodePoint )) {
284
+ nextCharCount += Character .charCount (codePoint ) + Character .charCount (tmpCodePoint );
285
+ }
286
+ } else if (isKeycapBase (codePoint )) {
287
+ nextCharCount += Character .charCount (codePoint );
288
+ }
289
+ return offset + nextCharCount ;
290
+ }
291
+ if (isEmojiModifier (codePoint )) {
292
+ nextCharCount += lastSeenVariantSelectorCharCount + Character .charCount (codePoint );
293
+ break ;
294
+ }
295
+ if (isVariationSelector (codePoint )) {
296
+ nextCharCount += lastSeenVariantSelectorCharCount + Character .charCount (codePoint );
297
+ break ;
298
+ }
299
+ if (codePoint == ZERO_WIDTH_JOINER ) {
300
+ isZwj = true ;
301
+ codePoint = Character .codePointAt (text , nextOffset );
302
+ nextOffset += Character .charCount (codePoint );
303
+ if (nextOffset < len && isVariationSelector (codePoint )) {
304
+ codePoint = Character .codePointAt (text , nextOffset );
305
+ lastSeenVariantSelectorCharCount = Character .charCount (codePoint );
306
+ nextOffset += Character .charCount (codePoint );
307
+ }
308
+ }
309
+ }
310
+
311
+ if (nextOffset >= len ) {
312
+ break ;
313
+ }
314
+ } while (isZwj && isEmoji (codePoint ));
315
+ }
316
+
317
+ return offset + nextCharCount ;
318
+ }
189
319
}
0 commit comments