Sprawdzenie szerokości uznało, że ciąg można bezpiecznie uciąć. Przecięło kanji na pół.

Nazwisko wprowadzone do tabeli w terminalu okazało się uszkodzone. Nazwisko brzmiało 𠮷田.

Pierwszy znak to nie powszechny 吉. To 𠮷 (U+20BB7). Jest to rzadka forma używana w prawdziwych japońskich nazwiskach. Tabela skróciła komórkę, aby zmieściła się w kolumnie, pozostawiając uszkodzony znak.

Błąd tkwił w jednej linii kodu. Była to optymalizacja, która uznawała, że ciąg znaków można bezpiecznie uciąć według indeksu.

Ciąg znaków w JavaScript ma trzy różne długości: • Jednostki kodu (code units) (.length): "𠮷".length wynosi 2. • Punkty kodu (code points): [..."𠮷"].length wynosi 1. • Szerokość wyświetlania (display width): 𠮷 zajmuje 2 kolumny.

Dla standardowego tekstu angielskiego wszystkie te liczby są takie same. Ten zbieg okoliczności sprawia, że kod wydaje się bezpieczny.

Znak 𠮷 łamie tę zasadę. Ma 2 jednostki kodu, ponieważ jest parą zastępczą (surrogate pair). Zajmuje 2 kolumny, ponieważ jest znakiem szerokim. Liczby się zgadzają (2 = 2), ale z różnych powodów.

Biblioteka cli-table3 stosowała ścieżkę fast path: Jeśli długość jednostek kodu jest równa szerokości wyświetlania, utnij ciąg według indeksu.

Działało to przez lata, ponieważ powszechne znaki japońskie, takie jak 漢, mają długość 1 i szerokość 2. Nigdy nie trafiały one na ścieżkę fast path.

Ścieżka fast path aktywuje się tylko dla rzadkich znaków, takich jak 𠮷 lub emoji. Znaki te mają długość 2 i szerokość 2. Kod uznaje je za proste znaki jednościowe. Przecina je w połowie według indeksu, pozostawiając pojedynczą parę zastępczą. To dlatego terminal wyświetla uszkodzony kwadrat.

Aby to naprawić, należy:

  • Zabezpieczyć ścieżkę fast path, aby wykluczyć pary zastępcze.
  • Przycinać według punktów kodu zamiast jednostek kodu.

Użycie Array.from(str) pomaga, ponieważ iteruje ono po punktach kodu. Dzięki temu masz pewność, że nigdy nie przetniesz znaku na pół.

Lekcja jest prosta: Nigdy nie mierz jedną jednostką, a nie tnij inną. Jeśli mierzysz szerokość wyświetlania lub punkty kodu, musisz ciąć, używając tych samych jednostek.

Przetestuj swój kod znakiem z CJK Extension B lub emoji. ASCII nigdy nie ujawni tego błędu.

Źródło: https://dev.to/greymothjp/a-width-check-said-the-string-was-safe-to-cut-it-split-a-kanji-in-half-4hjk

Opcjalna społeczność edukacyjna: https://greymoth-jp.github.io/cjk-failure-corpus/