在Dart中处理字素簇

Sur*_*gch 4 unicode icu dart flutter grapheme-cluster

据我所知,Dart不支持字素簇,尽管有人说支持它:

在实施之前,通过字素簇进行迭代的选择有哪些?例如,如果我有这样的字符串:

String family = '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}'; // ??
String myString = 'Let me introduce my $family to you.';
Run Code Online (Sandbox Code Playgroud)

在五个代码点的家庭表情符号后面有一个光标:

在此处输入图片说明

如何将光标向左移动一个用户可感知的字符?

(在这种特殊情况下,我知道了字素簇的大小,因此我可以做到,但是我真正要问的是找到任意长的字素簇的长度。)

更新资料

我从本文中看到,Swift使用了系统的ICU库。Flutter中可能有类似的可能。

补充代码

对于那些想玩我上面的示例的人,这里是一个演示项目。按钮将光标向右或向左移动。当前需要按8个按钮才能将光标移到家庭表情符号上。

在此处输入图片说明

主镖

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Grapheme cluster testing')),
        body: BodyWidget(),
      ),
    );
  }
}

class BodyWidget extends StatefulWidget {
  @override
  _BodyWidgetState createState() => _BodyWidgetState();
}

class _BodyWidgetState extends State<BodyWidget> {

  TextEditingController controller = TextEditingController(
      text: 'Let me introduce my \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467} to you.'
  );

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          controller: controller,
        ),
        Row(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('<<'),
                onPressed: () {
                  _moveCursorLeft();
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('>>'),
                onPressed: () {
                  _moveCursorRight();
                },
              ),
            ),
          ],
        )
      ],
    );
  }

  void _moveCursorLeft() {
    int currentCursorPosition = controller.selection.start;
    if (currentCursorPosition == 0)
      return;
    int newPosition = currentCursorPosition - 1;
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }

  void _moveCursorRight() {
    int currentCursorPosition = controller.selection.end;
    if (currentCursorPosition == controller.text.length)
      return;
    int newPosition = currentCursorPosition + 1;
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }
}
Run Code Online (Sandbox Code Playgroud)

Tru*_*inh 8

更新:使用https://pub.dartlang.org/packages/icu

样例代码:

import 'package:flutter/material.dart';


import 'dart:async';
import 'package:icu/icu.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Grapheme cluster testing')),
        body: BodyWidget(),
      ),
    );
  }
}

class BodyWidget extends StatefulWidget {
  @override
  _BodyWidgetState createState() => _BodyWidgetState();
}

class _BodyWidgetState extends State<BodyWidget> {
  final ICUString icuText = ICUString('Let me introduce my \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467} to you.\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}');
  TextEditingController controller;
  _BodyWidgetState() {
    controller = TextEditingController(
      text: icuText.toString()
  );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          controller: controller,
        ),
        Row(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('<<'),
                onPressed: () async {
                  await _moveCursorLeft();
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('>>'),
                onPressed: () async {
                  await _moveCursorRight();
                },
              ),
            ),
          ],
        )
      ],
    );
  }

  void _moveCursorLeft() async {
    int currentCursorPosition = controller.selection.start;
    if (currentCursorPosition == 0)
      return;
    int newPosition = await icuText.previousGraphemePosition(currentCursorPosition);
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }

  void _moveCursorRight() async {
    int currentCursorPosition = controller.selection.end;
    if (currentCursorPosition == controller.text.length)
      return;
    int newPosition = await icuText.nextGraphemePosition(currentCursorPosition);
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }
}

Run Code Online (Sandbox Code Playgroud)

原始答案:

在Dart / Flutter完全实现ICU之前,我认为您最好的选择是使用PlatformChannel传递本机Unicode字符串(iOS Swift4 +或Android Java / Kotlin)以在那里进行迭代/操作,然后将结果发送回去。

  • 对于Swift4 +,它是您提到的即用型(不是Swift3-,不是ObjC)
  • 对于Java / Kotlin,将Oracle替换BreakIteratorICU库,效果更好。除了导入语句外,没有其他更改。

我之所以建议使用本机操作(而不是在Dart上进行操作)是因为Unicode有太多要处理的事情,例如规范化,规范对等,ZWNJ,ZWJ,ZWSP等。

如果需要一些示例代码,请注释掉。


Sur*_*gch 6

2020年更新

使用Dart 团队提供的角色包。现在它是处理字素簇的官方方法。

用于text.characters获取字素簇。用户text.characters.iterator移动它们。我仍在研究如何转换CharacterRangeTextSelection. 当我有更多细节时,我会更新这个答案。

注意:这是对我旧答案的完全重写。详细信息请参阅编辑历史记录。