Какое самое длинное UTF8-представление строки в форме NFC заданной длины?
Контекст.
Я пишу C в спецификации iCal (RFC 5545). Он определяет максимальную длину строки с разделителями, равную 75 октетам, исключая разделитель. И принцип надежности, и символьная модель W3C склоняют меня к канонизации входных строк, закодированных в форме UTF8, в форму NFC (см. Формы нормализации Unicode).
При чтении строк ввода я хотел бы читать в статически выделенный буфер. Но представление строки в UTF8 может быть больше 75 октетов, даже если ее форма NFC меньше 75. Таким образом, этот буфер должен быть больше 75 октетов. У меня вопрос сколько.
Вопрос.
Какова максимальная длина в октетах строки UTF8, чья форма NFC составляет не более 75 октетов? (Бонусные баллы: чья форма NFC не более N октетов.)
Кроме того, является ли это гарантированным и постоянным или это неуказанное последствие текущего Unicode и может быть изменено?
1 ответ
Вот некоторый код Javascript, который пытается найти кодовую точку Unicode, чье представление UTF-8 сжимается больше всего при преобразовании в NFD и обратно в NFC. Кажется, что ни одна кодовая точка не уменьшается более чем в три раза. Насколько я понимаю алгоритм нормализации Unicode, таким способом нужно проверять только отдельные кодовые точки.
Я думаю, что, по крайней мере, теоретически, это может измениться в будущих версиях Unicode. Но существует политика стабильности в отношении расширения строк при нормализации в NFC (также см. Может ли нормализация Unicode NFC увеличить длину строки?), Поэтому я думаю, что это вряд ли когда-либо изменится:
Канонические отображения (значения свойств Decomposition_Mapping) всегда ограничены, так что ни одна строка при нормализации к NFC не увеличивается в длину более чем в 3 раза (измеряется в единицах кода).
Поэтому выделение начального буфера в три раза больше максимальной длины строки кажется разумным выбором.
var maxRatio = 2;
var codePoints = [];
for (var i=0; i<0x110000; i++) {
// Exclude surrogates
if (i >= 0xD800 && i <= 0xDFFF) continue;
var nfd = String.fromCodePoint(i).normalize('NFD');
var nfc = nfd.normalize('NFC');
var nfdu8 = unescape(encodeURIComponent(nfd));
var nfcu8 = unescape(encodeURIComponent(nfc));
var ratio = nfdu8.length / nfcu8.length;
if (ratio > maxRatio) {
maxRatio = ratio;
codePoints = [ i ];
}
else if (ratio == maxRatio) {
codePoints.push(i);
}
}
console.log(`Max ratio: ${maxRatio}`);
for (codePoint of codePoints) {
// Exclude Hangul syllables
if (codePoint >= 0xAC00 && codePoint <= 0xD7AF) continue;
var nfd = String.fromCodePoint(codePoint).normalize('NFD');
var nfc = nfd.normalize('NFC');
console.log(
codePoint.toString(16).toUpperCase(),
encodeURIComponent(nfd),
encodeURIComponent(nfc)
);
}