PPDF123
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 відбудовує структуру, коли карта ламається.