Formats7 мин чтения

Как работает редактирование текста в PDF: потоки содержимого, кодировки шрифтов и почему можно править только одну строку за раз

На странице PDF нет абзацев и текстовых блоков, только инструкции нарисовать глифы шрифтом в заданной позиции. Правка строки удаляет старые инструкции и записывает новые на том же месте, и именно это определяет, что можно редактировать, а что нельзя.

PDF123 · Updated 2026-10-14
Как работает редактирование текста в PDF: потоки содержимого, кодировки шрифтов и почему можно править только одну строку за раз

На странице PDF нет ни абзацев, ни текстовых блоков. Есть список инструкций: в этой позиции, этим шрифтом и этого размера нарисовать такие-то глифы. Изменить текст означает удалить инструкции, рисующие эту строку, и записать новые в той же позиции, тем же шрифтом, того же размера и цвета. Поэтому Править PDF правит по одной строке и не умеет переносить текст: это прямое следствие того, как текст хранится.

Как выглядит одна строка текста в файле

Содержимое страницы лежит в потоке содержимого, небольшой программе из операторов. Для текста основную работу делают четыре оператора: BT и ET обрамляют текстовый блок, Tf выбирает шрифт и размер, Td переходит к следующей позиции, Tj рисует строку. Со стандартным шрифтом простейший случай выглядит так:

BT /F1 18 Tf 20 150 Td (Hello World) Tj ET

Это значит: выбрать шрифт F1 размером 18 пунктов, перейти в точку x 20, y 150 и нарисовать Hello World. PDF, напечатанный из Chrome, обычно выглядит иначе: в скобках уже не символы, а номера глифов шрифта, а позицию один раз задаёт Tm. Ниже строка Hello World from Georgia со страницы, экспортированной из Chrome; каждые четыре шестнадцатеричные цифры — один номер глифа:

BT /F4 24 Tf 1 0 0 -1 30 52 Tm <002b0048004f004f00520003003a00520055004f0047000300490055005200500003002a004800520055004a004c0044> Tj ET

002b — это H, 0048 — e, 004f — l, 0003 — пробел, 003a — W и так далее. Эти числа имеют смысл только внутри одного конкретного шрифта; это не Unicode. Когда вы копируете текст, просмотрщик превращает номера обратно в символы по таблице соответствия, хранящейся в другом месте файла.

Отсюда два вывода. Во-первых, в потоке содержимого нигде не сказано «это абзац»: перенос строки — просто ещё один Td или Tm, который перемещает позицию на следующую строку, а сколько строк в абзаце, приходится выводить из позиций. Во-вторых, чтобы записать в строку новый символ, нужен номер этого символа в данном шрифте, а в шрифте часто есть только те глифы, что использовались при записи файла. Это результат подрезания шрифтов.

Потоки можно развернуть с помощью qpdf:

qpdf --qdf --object-streams=disable in.pdf out.pdf

Откройте out.pdf в текстовом редакторе и найдите Tj, чтобы увидеть все строки, нарисованные на странице. О том, что ещё содержит PDF, читайте в статье Что внутри PDF: объекты, потоки и таблица перекрёстных ссылок.

Как редактор собирает «строку» из глифов

Сначала редактор должен сгруппировать разрозненные глифы в строку, по которой можно щёлкнуть. Фрагмент — это группа соседних глифов с одним шрифтом, размером и цветом, у которых зазор до предыдущего глифа не больше одного размера шрифта; перенос строки, новый шрифт или новый цвет фрагмент заканчивают. Текст, идущий справа налево или по вертикали, не группируется и не открывается для редактирования.

Два следствия можно увидеть напрямую. Строка с двумя шрифтами, например китайская строка с английским словом, делится на два соседних фрагмента, и щёлкать по ним нужно отдельно. После правки происходит то же самое: если новый текст набран другим шрифтом, строка становится двумя фрагментами, и правка одного из них не затрагивает другой.

Правка строки: удалить старый текст, затем записать новый

Удаление использует вариант закрашивания только для текста: глифы внутри области строки убираются из потока содержимого, изображения и линии остаются на месте, поверх старого текста не рисуется цветной блок, поэтому копирование и поиск его больше не находят. Меняется только содержимое страницы; если тот же текст есть ещё и в аннотации или в свойствах документа, они не затрагиваются.

Запись добавляет новую текстовую инструкцию в конец потока содержимого страницы, начиная с позиции базовой линии исходной строки и повторяя её размер и цвет. После замены строки выше на Hello World 2 в конец страницы добавляется следующее:

q 1 0 0 1 22.5 148.92 cm 0 0 0 rg
/Span <</ActualText <feff00480065006c006c006f00200057006f0072006c006400200032>>> BDC
BT /F4 18 Tf 0 0 Td <002b0048004f004f00520003003a00520055004f00470003> Tj ET
BT /F9 18 Tf 102.18 0 Td (2) Tj ET
EMC
Q

Первый BT использует исходный шрифт F4 и исходные номера глифов, поэтому Hello World повторяется в точности как был. Цифры «2» нет в исходном подмножестве шрифта, поэтому второй BT подключает новый стандартный шрифт Helvetica (F9), чтобы нарисовать её. q и Q ограничивают преобразование координат и цвет этого блока им самим; редактор также оборачивает исходное содержимое в пару q/Q, чтобы несбалансированное состояние, оставшееся в конце исходной страницы, не повлияло на новый текст. Так выглядел один фрагмент; числа от файла к файлу различаются.

Добавление в конец имеет три последствия:

  • Новый текст рисуется последним, поверх всего остального на странице. Текст, который находился под изображением или цветным блоком, после правки оказывается над ним.
  • Инструмент, извлекающий текст в порядке потока содержимого (например, pdftotext -raw), читает отредактированную строку последней на этой странице, а извлечение по координатам (pdftotext -layout) изменений не показывает. Страница выглядит так же, но порядок чтения изменился.
  • Редактор прикрепляет к новому тексту ActualText, который сообщает просмотрщику, каким настоящим символам соответствуют глифы. Один глиф может соответствовать нескольким символам, и поиск только по номеру глифа иногда возвращает символ совместимости (например, для 文 находится радикал Канси ⽂); ActualText гарантирует, что скопировано будет то, что вы набрали.

Каким шрифтом рисуется новый текст

Безопаснее всего использовать исходный шрифт: форма глифов, насыщенность и ширина совпадают с соседним текстом. Можно ли его использовать, зависит от того, способен ли редактор определить, какой код имеет символ в этом шрифте:

  • Составной шрифт, как выше (Type0, двухбайтные номера глифов, кодировка Identity-H): символ ищется в шрифте, чтобы получить номер глифа, и если он найден, используется. Если не найден, символа в подмножестве нет.
  • Однобайтный простой шрифт (TrueType, Type1) с кодировкой WinAnsi или MacRoman: соответствие символа байту фиксировано, а ширины берутся из таблицы ширин в файле.
  • Шрифт с нестандартной кодировкой (Differences) или опирающийся на встроенную кодировку шрифта: надёжного соответствия нет, поэтому редактор не гадает и переходит к запасному шрифту, иначе текст получился бы искажённым.

Если символа нет в исходном шрифте, используется запасной шрифт. Обычные латинские буквы рисуются ближайшим из Helvetica, Times и Courier, стандартных шрифтов, которые есть у любого просмотрщика, поэтому ничего встраивать не нужно. Всё остальное, например китайские иероглифы, кана, хангыль, тайское письмо, латинские буквы с диакритикой, кириллица и греческое письмо, использует открытый шрифт Noto: сервер берёт символы из запроса и вырезает из полного шрифта только эти глифы с помощью библиотеки HarfBuzz с открытым кодом для создания подмножеств, обычно несколько КБ, а браузер встраивает их в ваш файл. Региональная форма китайского иероглифа определяется языком интерфейса: упрощённая, традиционная, японская или корейская.

Глифы встраиваются, а не ограничиваются записью имени шрифта, потому что шрифт, заданный лишь именем, каждый просмотрщик заменяет тем запасным, какой выберет сам, и насыщенность у разных просмотрщиков может различаться; после встраивания все просмотрщики показывают одни и те же формы.

Почему только одна строка за раз

У редактора нет движка вёрстки. Позиция каждого глифа зафиксирована в файле; новый текст просто размещается вправо от начальной точки по ширинам шрифта, без логики переноса у края, и ничто после него не сдвигается. Если удлинить строку, лишние символы продолжатся вправо, а всё, что выйдет за край страницы, не будет видно. Переписать абзац или добавить строки в таблицу — значит заново сверстать текст, а для этого нужно вернуться к исходному файлу, из которого сделан PDF.

Ширины, кроме того, берутся только из самого шрифта (на сколько сдвигается позиция после каждого глифа), без кернинга и подстановки глифов. Текст, которому не нужен шейпинг, то есть подбор и позиционирование глифов по контексту (латиница, кириллица, греческий и китайские иероглифы), укладывается правильно. Тайским тоновым знакам для правильного положения нужно позиционирование глифов, которого редактор не делает, поэтому они могут сместиться. Арабскому нужны контекстные формы глифов и раскладка справа налево, ивриту нужна раскладка справа налево, а индийским письменностям нужно объединять согласные и знаки гласных в глифы; всё это требует полного шейпинга, которого редактор сейчас не делает, поэтому ввод такого текста отклоняется, а не превращается в неверные символы.

Ещё два ограничения идут от самого файла. Содержимое отсканированной страницы — одно изображение без текстовых операторов, поэтому удалять и менять нечего. После правки файла с цифровой подписью исходное содержимое, которое защищала подпись, оказывается изменённым, и подпись перестаёт быть действительной.

Проверьте сами

Описанные изменения можно увидеть в скачанном файле тремя командами:

pdftotext -raw edited.pdf -      # извлечение в порядке потока содержимого: старого текста нет, отредактированная строка последняя
pdffonts edited.pdf              # список шрифтов: исходные повторно использованные шрифты и новое встроенное подмножество шрифта
qpdf --qdf --object-streams=disable edited.pdf edited.qdf   # разворачивает поток содержимого; добавленный блок BT ... ET находится в конце

Что почитать дальше

Open tool
Process in the browser — no watermark, files removed after the job.
Open tool
Read next
Как дать ИИ-ассистенту инструменты для PDF: быстрый старт с @pdf123/mcp, локально и через хостинг
Подключите @pdf123/mcp к Claude Code, Claude Desktop или Cursor за две минуты, чтобы ИИ-ассистент объединял, сжимал и конвертировал PDF на вашей машине по пути к файлу. Локальный сервер получает только пути; размещённый эндпоинт /mcp требует файл в base64 прямо в аргументах инструмента; PDFX_MCP_ROOT ограничивает каталоги, которые локальный сервер может читать и записывать.
Из Markdown в PDF и обратно: что сохраняет конвертация форматов
Markdown↔PDF сохраняет заголовки, списки и простые таблицы лучше пикселей. Точные шрифты, пагинация и вёрстка не выживают как цикл без потерь.
Что внутри PDF: объекты, потоки и таблица перекрёстных ссылок
PDF — это граф пронумерованных объектов, сжатых потоков и таблица перекрёстных ссылок. Get Info показывает структуру, Repair чинит её при поломке карты.