RFC-0195:文字 API 中的位置和範圍 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 規定使用 Unicode 純量值做為文字編輯 API 中位置和範圍的基本單位。 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2022-08-26 |
審查日期 (年-月-日) | 2022-10-25 |
摘要
我們建議 fuchsia.input.text
API 應使用 萬國碼純量值做為文字編輯位置和範圍 (例如插入符號和選取範圍) 的原子單位。
提振精神
fuchsia.input.text
命名空間會提供文字編輯和組合用的 FIDL 通訊協定,讓您在跨執行階段實作文字欄位、輸入法編輯器 (IME)、複製和貼上、自動修正和相關功能。這些 API 將包含多種方法,用於擷取、選取及修改文字範圍。這些方法的設計基礎是 API 必須以標準化的方式索引萬國碼字串。這項功能可確保,例如當在 Flutter 中實作的螢幕鍵盤指示 Chromium 網頁檢視畫面中的文字方塊「刪除游標前三個字元」時,鍵盤和瀏覽器會就「三個字元」的意思和游標目前的位置達成共識。
Fuchsia 的樹狀結構內執行階段目前尚未就字串操作的基本單位達成共識 (請參閱「先前技術和參考資料」)。我們審查的其他非 Fuchsia 平台的標準 SDK 也對這個問題有不同的看法,而且還受到舊版設計選擇的影響,在許多情況下,這些設計選擇早於現代 Unicode 標準。
由於國際化文字編輯功能是現代面向使用者的作業系統的重要功能,且 Fuchsia 無法採用現有的統一標準,因此 Fuchsia 有改進現狀的機會,可為跨執行階段 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
社交
這項設計在發布為 RFC CL 之前,已在 Fuchsia HCI 團隊和部分審查人員之間以 Google 文件的形式進行交流。
設計
本文件中的關鍵字「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」和「OPTIONAL」應依 IETF RFC 2119 所述進行解讀。
背景
詳情請參閱「FIDL API 可讀性評量標準 > 字串編碼」。
FIDL 字串是代表 UTF-8 編碼 Unicode 文字的位元組序列。
字串可分為多個單位。
萬國碼純量值:在萬國碼中,文字的基本原子是「萬國碼純量值」,這是
[0x0, 0xD7FF], [0xE000, 0x10FFFF]
範圍內的整數,可對應至「抽象字元」。萬國碼純量值是 萬國碼碼點的子集,屬於
[0x0, 0x10FFFF]
範圍的整數。從 Unicode 標量值[0xD800, 0xDFFF]
中排除的碼點稱為替代碼點。這些字元是保留給 UTF-16 編碼的實作細節,無法用於表示任何已指派的字元。Byte:將字串分割成位元組的輸出內容取決於編碼方式。舉例來說,FIDL 字串使用的 UTF‑8 編碼是可變長度編碼,每個標量值都由 1 到 4 個位元組的序列表示。(例如,
k
是 1 個位元組、ك
是 2 個位元組、ከ
是 3 個位元組,而𐤊
是 4 個位元組)。UTF-8 標準會指定如何剖析位元組序列,以及判斷新向量值的起始位置。由於 UTF‑8 是可變長度編碼,因此無法在固定時間內判斷 UTF‑8 字串中的標量值數量,也無法跳到 n 個標量值。字母組合:某些 Unicode 標量值組合在以圖形方式呈現時,會組合成單一使用者感知的「字元」,在技術上稱為「字母組合」。例如帶有變音符號的字母 (
á̡
)、帶有性別和膚色選取器的表情符號 (💂🏽♀️
),以及兩個字母國家/地區代碼組合成國旗表情符號 (🇦🇺
)。將純量值合併至字母簇的規則會因情境而異,並取決於從萬國碼字元資料庫讀取的屬性;因此,這些規則可能會隨著萬國碼標準的版本變更。
雖然與 FIDL 及其 UTF-8 字串並無直接關聯,但許多使用 UTF-16 編碼的舊版執行階段都提供額外的分割選項:
- UTF‑16 編碼單元:在 UTF‑16 編碼中,每個萬國碼單一值都會透過一或兩個 2 位元組序列編碼,這些序列稱為 UTF‑16「編碼單元」。UTF-16 標準會指定如何根據代碼單位的位元,判斷該單位是單一代碼單位的標量值,還是兩個代碼單位替代字元組合的一部分。
設計
在 fuchsia.input.text
中,如果有任何方法參數或回傳值代表字串中一或多個索引,則基本單位應為單一 Unicode 標量值。
舉例來說,在以下假設方法中,Range
的定義是從字串或文字欄位開頭開始的萬國碼標度值位置。
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 中,萬國碼純量值是單一
char
,且String
或&str
中的純量值可使用String::chars()
進行疊代。在 Dart 中,這會是
rune
。您可以使用String.runes
屬性,對字串的萬國碼純量值進行疊代。在 C++ 17 中,標準程式庫用於操作 Unicode 文字的工具不完整,因此建議改用
icu::UnicodeString
搭配icu::StringCharacterIterator
。舉例來說,您可以使用setIndex32(n)
擷取字串中的第 n 個純量值。
成效
如果執行階段使用變長編碼 (例如 UTF‑8 (例如 Rust) 或 UTF‑16 (例如 Dart)) 來處理字串,則透過 Unicode 單字節值存取字串位置或長度,就是線性時間作業。(這項作業只適用於 UTF‑32 和類似的固定長度編碼,因為這類編碼不節省空間,也不常使用)。
如果用途經常存取字串長度,且預期會出現長字串,建議您快取長度值,或以其他方式預先處理字串,以便實現攤銷常數時間。
人體工學
萬國碼純量值在文字編輯和組合方面具有下列優點:
- 這項精細度可避免 UTF-8 編碼的字元分割為無效的位元組序列。
- 必要時,可用於編輯「單字節群組」內的內容。舉例來說,輸入「á」("a" 後面接「◌́") 後,可以透過退格鍵刪除重音符號,但無法刪除基本字母。
如要比較其他選項,請參閱「缺點、替代方案和未知事項」。
回溯相容性
這個 RFC 與新的文字編輯 API 相關,這些 API 是從頭開始實作,並納入 Fuchsia 平台。除了在 Fidl API 的文字位置表示法與任何指定語言執行階段中偏好的表示法之間轉換的固有任務外,我們不預期會發生回溯相容性問題。
安全性考量
以整個 Unicode 標量值操作文字,而非以位元組或 UTF-16 程式碼單位操作,可降低字串遭到無效截斷的可能性。
以萬國碼純量值進行原子化,可能會導致分割字元組叢集,這有時是可行的做法 (請參閱「人因工程」),但如果隨意執行,可能會導致某些文字意義改變的極端情況。
不過,您必須接受這個缺點,因為構字元叢集會依據 Unicode 版本,甚至會受到實作專屬的調整影響,因此使用不同 Unicode 程式庫或版本的用戶端,可能會在字串長度上產生差異,導致資料毀損。
隱私權注意事項
除了處理使用者提供文字時的隱私權考量外,這份 RFC 並未提出任何新的隱私權考量。
測試
fuchsia.input.text
API 的實作者必須負責為其實作項目編寫適當的單元和整合測試。這些測試應涵蓋本 RFC 的要求。
視 Fuchsia 的相容性測試可用的功能而定,實作文字編輯 API 的用戶端行為可能會經過測試,以便進一步符合這些 API 的規定。舉例來說,測試可能會將一系列文字編輯指令傳送至代管文字欄位的用戶端應用程式,然後驗證產生的文字欄位內容是否符合預期。
建議實作者在測試資料中加入非 ASCII 字串,包括:
- 多個代碼點的字母組合,例如
- 含有多個結合附加符號的字元
- 含有膚色和/或性別修飾符號的表情符號
- 旗幟表情符號
說明文件
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 實作,因此可能會對所處理的文字範圍做出相互衝突的假設。
以下是萬國碼的文字符號叢集規格,UAX #29:萬國碼文字區隔明確指出:
本文件定義了構字元叢集的預設規格。可針對特定語言、作業或其他情況進行自訂。舉例來說,方向鍵的移動方式可以依語言進行調整,也可以使用特定字型的專屬知識,以更精細的方式移動,在需要編輯個別元件的情況下非常實用。例如,這項規定可能適用於泰北文字泰文 (蘭納) 的複雜編輯要求。同樣地,在某些情況下,建議逐一編輯音節叢集元素。舉例來說,在特定系統中,backspace 鍵可能會依程式碼點刪除,而 delete 鍵可能會刪除整個叢集。
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 並未明確記錄其
TextPosition
或TextRange
單位,而是將 offset 定義為「在文字字串表示法中,緊接在位置之後的字元索引」,但未在此處定義 character。
Flutter 的
TextField.maxLength
屬性定義如下:文字欄位允許的字元 (萬國碼字符簇) 數量上限。
請參閱下文的詳細說明:
字元
如需字元的具體定義,請參閱 Pub 上的 characters 套件,Flutter 會使用這個套件來區分字元。一般來說,即使是複雜的字元 (例如替代字元組和擴充的字素群組),Flutter 也能正確解讀,將每個字元視為單一使用者可辨識的字元。
網路
JavaScript 字元是 UTF‑16 程式碼單位。Range
、Selection
和 CaretPosition
類別都會處理字元偏移。
(不過,如果要與 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
預設會使用字元組群做為單位,並提供額外屬性來公開萬國碼碼點、UTF-16 程式碼單位和位元組。
與文字編輯相關的類別會使用不同的單位,這取決於類別是否源自 Objective-C 或 Swift。UITextInput
通訊協定使用不透明的抽象類別 UITextRange
和 UITextPosition
,這兩者取決於實作方式。
Windows
Windows Core Text 的說明文件將這些索引稱為「應用程式定位符號位置」,如下所述:
以零為基數的數字,表示從文字流開頭到方括號之前的字元數量
「字元數量」暗示 UTF-16 程式碼單位,因為 .NET 的 System.Char
類型就是代表這個值。