← На главную

Арабский шрифт ломает поиск в PDF из-за старых кодировок

13.06.2026 12:40 · hackernews

Арабское письмо — это сплошная вязь, без разделения на печатное и рукописное. Буквы соединяются везде: в камне, в рукописях, на экранах. Каждая буква меняет форму в зависимости от соседей — есть изолированная, начальная, срединная и конечная. Шесть букв вообще не соединяются слева, разбивая слова на кластеры. Это не «наряд» поверх настоящей буквы — это и есть сама буква.

Алфавит шире, чем у арабского языка. Персидский добавляет چهار буквы (پ, چ, ژ, گ) и меняет начертание йа и кафа. Урду использует аспирированную ھ, ретрофлексные ٹ, ڈ, ڑ и висячий ے, а пишет в основном насталиком — в шрифте наск это будет фонетически верно, но визуально неузнаваемо. Синдхи, пушту, курдский, уйгурский, кашмири и панджаби берут алфавит и добавляют своё. Шрифт, который называет себя «арабским», не спросив персидских и урду-сообществ, выдаёт для сотен миллионов читателей технически верный, но функционально неправильный текст: у кафа неправильная конечная, хе сливается не там, цифры не из того пояса. Noto Sans Arabic выпускает отдельные субшрифты (NotoNaskhArabic, NotoNastaliqUrdu, NotoSansArabicUI), и цепочки падения в ОС обычно срабатывают. Обычно.

Один кодпоинт, четыре формы, которые выбирает shaping engine во время рендеринга. Срединный хе и изолированный хе для неподготовленного глаза — разные буквы. Латинский шрифт на 26 строчных форм не должен ничего об этом знать. Арабский шрифт обязан иметь мнение обо всём.

Решение, к которому пришли после десятилетий ошибок: кодировка хранит абстрактную букву, шрифт — формы, shaping engine применяет OpenType-фичи (isol, init, medi, fina, rlig для лигатур, mark и mkmk для огласовок). Арабский шрифт — это маленькая программа. Текст, который вы сохраняете, — её вход, а не выход. Слово исполняется заново каждый раз, как вы на него смотрите, словно музыка по нотам.

Проще всего понять это, собирая слово по буквам и наблюдая, как каждая предыдущая пересчитывает форму при добавлении следующей. Например, «Мухаммад»: первая م переходит в начальную форму, когда добавляется ح, ح становится срединной при следующей م, а потом идёт د — одна из шести не соединяющихся букв, и она разрывает поток, превращая третью م в конечную. Четыре кодпоинта в памяти — один непрерывный штрих на экране. Без shaping engine PDF-генератор просто нарисует четыре изолированные буквы.

Старые неправильные решения окаменели в стандарте. До появления shaping engine кодовые страницы DOS и ранней Windows кодировали сами формы: отдельный символ для начального айна, срединного и так далее. Unicode, обещавший обратную совместимость, был вынужден проглотить эти наборы целиком — они живут в блоке Arabic Presentation Forms (U+FB50–U+FEFF). Сотни кодпоинтов, которых не должно быть ни в одном новом документе, но PDF-экстракторы с радостью их выдают, из-за чего поиск в арабских PDF так часто молча проваливается. Стог сена закодирован как формы, иголка — как буквы. Любимый персонаж этого блока — U+FDFD, ﷽ — «бисмилляхи ар-рахмани ар-рахим» одним кодпоинтом. Памятник эпохе, когда рендеринг запекали в кодировку, потому что рендереру не доверяли.

Это больно: два варианта кодировки выглядят одинаково, но сравниваются по-разному. Упоминавшийся в начале статьи баг с поиском клиента — именно это.

А если хотите увидеть мир, где shaping engine, bidi-алгоритм и вся механика отсутствуют, не надо фантазировать. Масса софта их игнорирует: наберите «مرحبا بالعالم، هذا نص عربي» — это «привет, мир, это арабский текст». Каждая буква падает в изолированную форму, строка выводится слева направо задом наперёд. Так рисуют старый Photoshop, matplotlib из коробки, многие npm-генераторы PDF, чековые принтеры. Стандартный Python-костыль — arabic_reshaper + python-bidi — фиксирует это, вставляя в строку уже сформированные глифы из того самого ископаемого блока.

Читать оригинал →