RFC-0195:文本 API 中的位置和范围

RFC-0195:文本 API 中的位置和范围
状态已接受
领域
  • HCI
说明

规定在文本编辑 API 中将 Unicode 标量值用作位置和范围的基本单位。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-08-26
审核日期(年-月-日)2022-10-25

摘要

我们建议 fuchsia.input.text API 应使用 以原子单位表示的 Unicode 标量值 文本编辑位置和范围(例如脱字符号和选择项)。

设计初衷

fuchsia.input.text 命名空间将为文本编辑提供 FIDL 协议 实现文本字段的跨运行时实现、输入、组合和组合, 方法编辑器 (IME)、复制和粘贴、自动更正和相关功能。 这些 API 将包括许多检索、选择和 修改文本范围。作为这些方法设计的基本组成部分, API 必须对编入 Unicode 字符串索引的方式进行标准化。只有这样,才能 例如,当在 Flutter 中实现的屏幕键盘时, 指示 Chromium 网页视图中的文本框“删除这三个字符 ”就应该使用 字符”表示字符当前的位置和位置。

Fuchsia 的树内运行时目前尚未就 字符串操作的单位应为(请参阅 先前艺术作品和参考文件)。目前使用的 我们审核的其他几个非 Fuchsia 平台在 而且还会受到传统设计选择的影响,在许多 早于现代 Unicode 标准的条件下列出。

因为国际化文本编辑是现代化 因为目前还没有统一的统一平台 作为 Fuchsia 可以采用的标准,Fucsia 有机会进行改进 改变现状:为其跨运行时 API 选择自己的单一标准 符合人体工程学的设计,且不受旧版设计的阻碍。

此外,由于 Fuchsia 的文本编辑 API 可用作 多个独立运行时(您不一定知晓)之间的机制, 因此它们必须提供一个明确定义的接口 统一实施,而不对任何 一个运行时。

利益相关方

教员:abarth@google.com

审核者

  • Fuchsia HCI:neelsa@google.com、fmil@google.com

  • 安全:pesk@google.com

  • 隐私权:enoharemaien@google.com

  • Chromium:wez@google.com

  • Flutter:jmccandless@google.com、gspencer@google.com

已咨询:quiche@google.com

社交

此设计以 Google 文档的形式在 Fuchsia HCI 团队和一些 然后以 RFC CL 发布。

设计

关键字“必须”“不得”“必需”“会”“不会”“应” “不应该”“建议”“可以”和“可选”文档内容 如 IETF RFC 2119 中所述。

背景

请参阅 FIDL API 可读性评分准则 >字符串编码 查看更详细的介绍

FIDL 字符串是一串字节,表示采用 UTF-8 编码的 Unicode 文本。

字符串有多种单位可供选择。

  • Unicode 标量值:在 Unicode 中,文本的基本原子是“Unicode” “标量值”,这是 [0x0, 0xD7FF], [0xE000, 0x10FFFF] 范围内的整数,可映射到“抽象字符”。

    Unicode 标量值是 Unicode 代码点, 范围 [0x0, 0x10FFFF]。从 Unicode 中排除的码位 标量值 [0xD800, 0xDFFF] 称为 代理码点。它们分别是 且不能是 用于表示任何已分配的字符。

  • Byte:将字符串划分为字节的输出取决于编码。 例如,FIDL 字符串使用的 UTF-8 编码是可变长度的 编码,每个标量值由 1 到 4 个字节的序列表示。 (例如,k 表示一个字节,ك 表示两个字节, 表示三个字节,𐤊 是 4 个字节。)UTF‐8 标准指定如何解析 以及确定新标量值的开始位置。由于 UTF-8 是 可变长度编码,就不可能确定 或跳到第 n 第 n 个标量值, 。

  • 字素聚类:当 以图形方式呈现)合并为一个用户感知的“字符”, 它在技术上称为“字素星团”。例如字母 包含变音符号 (á̡)、带有性别和肤色选择器的面部表情符号 (💂🏽‍♀️) 以及由两个字母组成的国家/地区代码组合成国旗表情符号 (🇦🇺)。通过 将标量值合并到字素聚类的规则因上下文而异 并且取决于从 Unicode 字符数据库中读取的属性;为 因此它们可能会随着新旧版本的 Unicode 标准。

尽管与 FIDL 及其 UTF‐8 字符串没有直接相关,但许多旧版 使用 UTF‐16 编码的运行时具有一个额外的除法选项:

  • UTF‐16 代码单位:在 UTF‐16 编码中,每个 Unicode 标量值都是 由一个或两个 2 字节序列进行编码,称为 UTF‐16“代码单元”。通过 UTF‐16 标准指定了如何根据代码单元的位确定 无论是单代码单元标量值,还是双代码单元的一部分 代理对

设计

fuchsia.input.text 中代表 一个或多个索引,基本单位应为单个 Unicode 。

例如,在以下假设方法中,Range 用以下术语定义: Unicode 标量值从字符串或文本开头的位置 字段。

protocol ReadableTextField {
    /// Retrieves part of the contents of the text field.
    GetText(struct {
        range Range;
    }) -> (struct {
        // Note that FIDL string field sizes are specified in bytes
        // https://fuchsia.dev/fuchsia-src/reference/fidl/language/language#strings
        contents string:MAX_STRING_SIZE;
    }) error TextFieldError;
};

type Range = struct {
    /// The index of the first scalar value in the range.
    start uint32;
    /// The index _after_ the last scalar value in the range.
    end uint32;
};

如果文本字段包含字符串 abcd😀ef🇦🇺gh,请求的范围为 [2, 8) 将返回子字符串 cd😀ef🇦。(请注意,字形集群 🇦🇺 将 拆分为 🇦🇺。)

实现

在内部,实现人员可以使用任意 Unicode 字符串编码和索引 以自己选择的编程语言提供最佳支持或便捷性,或 库。

不过,fuchsia.input.text 中的协议的所有实现

  • 必须使用 Unicode 标量正确解读文本位置和范围 协议中指定的值索引。
  • 必须将其文本编辑命令发送给其他 fuchsia.input.text 与 Unicode 标量值索引相关的实现。

参考信息:

  • 在 Rust 中,Unicode 标量值是单个 char,而 String&str 可以使用 String::chars()

  • 在 Dart 中,它是一个 rune。字符串的 Unicode 标量值可以迭代 使用 String.runes 属性。

  • 从 C++ 17 开始,标准库的用于处理 Unicode 文本的实用程序 不完整,因此将 icu::UnicodeStringicu::StringCharacterIterator是 。例如,字符串中的 n第 - 个标量值可以是 使用 setIndex32(n) 检索。

性能

对于使用可变长度编码(如 UTF‐8,例如 Rust)或 其字符串采用 UTF-16 编码(例如 Dart),可通过以下方式访问字符串位置或长度: Unicode 标量值是一个线性时间运算。(这只是 针对 UTF‐32 和类似的固定长度编码的恒定时间操作, 空间效率低,不常用。)

适用于需要频繁访问字符串长度并预计存在 长字符串时,缓存长度值或其他 对字符串进行预处理,以便实现摊分的恒定时间。

工效学设计

Unicode 标量值具有以下优势: 乐曲:

  • 这种粒度可防止将 UTF‐8 编码的 字符转换为无效的字节序列。
  • 如有必要,它支持在字形聚类内部进行修改。例如: 在输入“á”之后(“a”后跟“◌”)),它允许通过间隔来删除 重音符号,而不是基字母。

请参阅 缺点、替代方案和未知问题 以便与其他选项进行比较

向后兼容性

此 RFC 涉及新的文本编辑 API,这些 API 是从 作为 Fuchsia 平台的一部分。我们不会预计 除了在 API 之间转换的固有任务以外, FIDL API 的文本位置表示法,以及任何首选表示法 给定语言运行时。

安全注意事项

按整个 Unicode 标量值(而不是按字节或 UTF-16 代码单元可降低字符串无效的可能性 被截断。

按 Unicode 标量值进行原子化可能会导致拆分 字形聚类,这有时是必要的(请参阅人机工程学), 但意外操作可能会导致极端情况, 更改。

但必须接受这一缺点,因为字素聚类依赖于 甚至还可以根据具体实现进行定制, 因此,使用不同 Unicode 库或版本的客户端可能 字符串长度不一致,从而导致数据损坏。

隐私注意事项

除以下几项外,此 RFC 中没有新的隐私权注意事项 在处理用户提供的文本方面,已经存在一些隐私权注意事项。

测试

fuchsia.input.text API 的实现者将负责编写 为其实现设置适当的单元测试和集成测试此 RFC 的 要求。

根据您可以使用的功能 针对 Fuchsia 的兼容性测试:实现 文本编辑 API 可能会经过测试,以便与这些 API 实现更广泛的合规性。对于 例如,测试可能会向客户端发送一系列文本编辑命令 然后验证生成的文本 字段内容符合预期。

建议实现人员在其测试数据中添加非 ASCII 字符串, 包括:

  • 例如, <ph type="x-smartling-placeholder">
      </ph>
    • 具有多个组合变音符号的字符
    • 有肤色和/或性别修饰语的表情符号
    • 旗帜表情符号

文档

fuchsia.input.text 类的 API 文档将明确突出显示 用于涉及字符串位置、范围和 。

缺点、替代方案和未知问题

字节

优点

  • 在 FIDL 字段声明中,string 长度为 以字节明确定义
  • 使用字节可以更轻松地推断内存大小。
  • 对字节数组的数组访问为 O(1)。

缺点

  • 需要进行额外验证,以确保字节序列构成 有效的 UTF‐8。

  • 很容易在无意之中将 UTF-8 字符拆分为不完整字符(并且 因此无效)字节序列。

  • 除非已知 确保要修改的文本只包含 ASCII 字符。

字素集群

优点

  • 在文本编辑器中,脱字符几乎总是位于字形聚类中 边界。

  • 按整个字形聚类选择文本可确保不会使用复杂的表情符号 以不便于用户理解的方式意外拆分(例如,将代码 点,👮🏽‍♀️(中等肤色的女警官的表情符号) 可拆分为 POLICE OFFICER (U+1F46E)EMOJI MODIFIER FITZPATRICK TYPE-4 (U+1F3FD)ZERO WIDTH JOINER (U+200D)FEMALE SIGN (U+2640) VARIATION SELECTOR-16 (U+FE0F))。

缺点

  • 字素聚类规则在不同 Unicode 版本之间可能会发生变化 且依赖于 CLDR

    更重要的是,这些聚类规则集并非 Unicode 版本;具体细节可以因植入方式而异 以及语言区域1之间。通过 FIDL 通信的两个组件(例如 屏幕键盘和渲染文本框的运行时)可能是 使用不同的 Unicode 实现方式, 它们所处理的文字范围之间的相互冲突的假设。

    字形聚类的 Unicode 规范,UAX #29:Unicode Text 细分明确指出

    本文档定义了字形集群的默认规范。它 可以针对特定语言、操作或其他 情况。例如,可以根据语言来调整箭头键的移动方式, 或者可以运用关于特定字体的知识 在可能有助于修改代码的情况下 各个组件例如,这可以用于 (泰国北部文字 Tai Tham(兰纳)的编辑要求。 同样,逐个元素修改字形聚类元素可能更合适 在某些情况下。例如,在给定的系统上,退格键 按代码点删除,而删除键可以删除整个 集群。

UTF‐16 代码单元

优点

  • 许多第三方标准库和运行时均使用 UTF‐16 编码 字符串。

缺点

  • FIDL 以 UTF‐8(而非 UTF‐16)格式传输字符串。推出新的 通过 FIDL 将单元编码为文本编辑 API 缺乏依据,而且会强制实施人员 至少支持两种不同的编码。
  • 与单个字节一样,很容易在不经意间拆分标量值 转换为不匹配的 UTF‐16 代理。

先验技术和参考资料

Flutter

Flutter 似乎已迁移到 在其公共 API 中使用字形聚类,尽管其文档仍然保留 不一致:

  • Dart 的 String 类文档中指出, “字符串由一系列 Unicode UTF‐16 代码单元表示”和“ 字符串中的字符采用 UTF‐16 编码。对 UTF‐16 进行解码 组合代理对,生成 Unicode 代码点,"暗示 “字符”表示“代码单元”。

  • Flutter 并未明确记录 TextPositionTextRange 单位,将偏移量定义为 "字符串中紧跟在后面的字符的索引 文本的表示形式,但不定义字符。

  • Flutter 的 TextField.maxLength 属性定义为

    允许的最大字符数(Unicode 字形聚类) 文字字段。

    下文将对此进行详细说明:

    字符
    有关字符的具体定义,请参阅 characters 软件包,就是 Flutter 使用它来划分字符。一般来说,即使是复杂的字符, 例如代理对和扩展字素聚类 被 Flutter 解释为一个用户感知的单个字符。

Web

JavaScript 字符是 UTF‐16 代码单元。通过 RangeSelectionCaretPosition 类均用于处理字符 偏移。

(不过,对于与 Chromium 运行时的集成,需要注意 Chromium 内部会使用 UTF-8 编码的字符串)。

Android

Android 的 IME API 明确使用 Java char,它是 UTF‐16 代码单元。 例如, android.view.inputmethod.BaseInputConnection.commitText

MacOS 和 iOS

在 Objective-C 中,NSString 文档指出

NSString 对象可对符合 Unicode 标准的文本字符串进行编码,将其表示为 UTF-16 代码单元序列。所有长度、字符索引和范围 以 16 位平台端序值表示,具有索引值 起价:0

不过,Swift 类 String 默认使用字素聚类作为 单位,其中包含用于公开 Unicode 代码点、UTF‐16 代码的其他属性 单位和字节

与文本编辑相关的类使用不同的单位,具体取决于 起源于 Objective-C 或 Swift。通过 UITextInput 协议使用不透明的抽象 类 UITextRangeUITextPosition, 因实现而异。

Windows

Windows Core Text 的文档调用其索引 应用插入符号位置,如

从零开始的数字,表示从 文本流(紧跟在插入符号之前的位置)

“字符数”暗含 UTF‐16 代码单元,因为这是 .NET 的 System.Char 类型的表示。


  1. 字符串 "ch" 中有多少个字形聚类?在en-US中 也就是两个。在 cs-CZ(捷克语)中,它应该只是一个,因为 'ch' 是 一个