Від нуля до написання PDF (01): Привіт, світ — створення мінімально працездатного PDF
Від нуля до написання PDF (01): Привіт, світ — створення мінімально працездатного PDF
Цілі серії: Розуміти PDF як читабельний формат файлу — почати з "працюючого" мінімального прикладу, а потім поступово розширювати до графіки, багатосторінкових документів, стиснення та повторного використання ресурсів.
Зміст серії
- Частина 01 (ця стаття): Напишіть мінімальний PDF (1 сторінка + 1 рядок тексту) та використовуйте інструменти для завершення його у стандартний PDF, який можна відкрити
- Частина 02: Малювання ліній/прямокутників у потоці вмісту (розуміння шляхів, обводки, заповнення)
- Частина 03: Багатосторінковий PDF (як формується дерево Pages)
- Частина 04: Ближче до реального світу (потоки стиснення, повторне використання ресурсів, необов'язкові структури тощо)
Чому важливо розуміти базову структуру PDF?
PDF (Portable Document Format, переносимий формат документів) є одним з найпопулярніших мов опису сторінок сьогодні. Він відрізняється від HTML/CSS, де "вміст і подання розділені, і можливе повторне форматування (Reflowable)", PDF більше акцентує на фіксованому макеті, що видно, те й отримуєш — незалежно від пристрою, на якому відкривається, верстка залишається незмінною.
Розуміння базової структури PDF має кілька практичних переваг:
- Виправлення проблем з генерацією PDF: Коли ви генеруєте PDF за допомогою бібліотеки і виникає помилка, розуміння базової структури дозволяє швидко локалізувати проблему
- Автоматизація обробки: Пакетне витягування тексту, об'єднання документів, додавання водяних знаків тощо, розуміння структури дозволяє точно виконувати операції
- Аудит безпеки: Розуміння того, які вмісти можуть бути вбудовані в PDF (JavaScript, вкладення, форми тощо), допомагає в аналізі безпеки
- Вивчення дизайну форматів файлів: Дизайн PDF "об'єктна графіка + випадковий доступ" є класичним прикладом, варто вивчити
Підготовка
У цій статті спочатку буде написано "структурно неповний, але логічно правильний" файл hello-broken.pdf, а потім за допомогою pdftk автоматично буде завершено ключові структури та виведено hello.pdf.
- Необхідні інструменти: pdftk (безкоштовний командний інструмент, підтримує Windows/macOS/Linux)
- Вихідні файли:
hello-broken.pdf(написаний вручну),hello.pdf(відкритий після виправлення)
Основна концепція: три шари структури PDF
Найважливіше для розуміння PDF — це створити тришарову модель свідомості:

1. Шар об'єктів (Document Content)
PDF документ складається з багатьох об'єктів, які з'єднані між собою за допомогою непрямих посилань (наприклад, 2 0 R). Типові типи об'єктів:
| Тип | Приклад | Опис |
|---|---|---|
| Name | /Page | Ім'я, що починається з / |
| Цілі/дійсні числа | 50, 36.0 | Числові значення |
| Рядок | (Hello, World!) | Обгорнутий в круглі дужки |
| Масив | [0 0 612 792] | Упорядкована колекція |
| Словник | << /Type /Page >> | Колекція пар ключ-значення |
| Непряма посилання | 2 0 R | Посилання на об'єкт 2 (номер генерації 0) |
| Потік | stream...endstream | Бінарні дані (наприклад, команди малювання, зображення) |
2. Шар вмісту (Page Content)
Справжні "інструкції для малювання тексту/графіки на сторінці" зазвичай записуються в stream ... endstream. Формат: операнди попереду, оператори позаду.
/F0 36 Tf ← Операнд: /F0, 36 Оператор: Tf (встановлення шрифту)
(Hello, World!) Tj ← Операнд: рядок Оператор: Tj (малювання тексту)
3. Шар структури файлу (File Structure)
Дозволяє читачеві швидко випадково отримувати доступ до будь-якого об'єкта, не читаючи файл від початку до кінця:
| Елемент | Дія |
|---|---|
%PDF-1.x | Заголовок файлу, що вказує версію PDF |
xref | Таблиця перехресних посилань: номер об'єкта → байтовий зсув |
trailer | Словник в кінці: вказує на кореневий об'єкт /Root |
startxref | Вказує на початкову позицію таблиці xref |
%%EOF | Позначка закінчення файлу |
Які об'єкти потрібні для мінімального PDF?
Мінімальний PDF, який "може відображати текст", має такі відносини між об'єктами:

Список мінімальних об'єктів:
| Об'єкт | Дія | Ключові поля |
|---|---|---|
| Catalog | Кореневий об'єкт, вхід до документа | /Type /Catalog, /Pages |
| Pages | Дерево сторінок | /Type /Pages, /Kids, /Count |
| Page | Одна сторінка | /Type /Page, /MediaBox, /Resources, /Contents, /Parent |
| Resources | Контейнер ресурсів | /Font (словник шрифтів) |
| Font | Визначення шрифту | /Type /Font, /BaseFont, /Subtype |
| Contents | Потік вмісту | Потік команд малювання |
Практика: Написання hello-broken.pdf
Створіть файл hello-broken.pdf і повністю вставте в нього наступний вміст:
%PDF-1.0
1 0 obj
<< /Type /Pages
/Count 1
/Kids [2 0 R]
>>
endobj
2 0 obj
<< /Type /Page
/MediaBox [0 0 612 792]
/Resources 3 0 R
/Parent 1 0 R
/Contents [4 0 R]
>>
endobj
3 0 obj
<< /Font
<< /F0
<< /Type /Font
/BaseFont /Times-Italic
/Subtype /Type1 >>
>>
>>
endobj
4 0 obj
<< >>
stream
1. 0. 0. 1. 50. 700. cm
BT
/F0 36. Tf
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<< /Type /Catalog
/Pages 1 0 R
>>
endobj
xref
0 6
trailer
<< /Size 6
/Root 5 0 R
>>
startxref
0
%%EOF
Чому цей файл "поганий"?
Ми навмисно пропустили або неправильно заповнили такі вмісти:
| Відсутні/помилкові елементи | Опис |
|---|---|
Зсув xref | Не вказано реальний байтовий зсув для кожного об'єкта |
startxref | Вказано 0, а не реальне місце xref |
/Length | Довжина потоку вмісту не оголошена |
| Бінарна позначка | Відсутній рядок бінарної позначки на початку |
Це все критично важлива інформація для читача, відсутність якої може призвести до неможливості відкриття або відкриття з помилками.
Детальний розгляд ключових інструкцій потоку вмісту
Потік вмісту знаходиться між stream ... endstream в об'єкті 4 0 obj, пояснення по рядках:
1. 0. 0. 1. 50. 700. cm ← Встановлення матриці перетворення (зверніть увагу, що 1. представляє дійсне число 1.0)
BT ← Початок текстового об'єкта
/F0 36. Tf ← Вибір шрифту F0, розмір шрифту 36pt
(Hello, World!) Tj ← Малювання рядка
ET ← Кінець текстового об'єкта
Оператор матриці перетворення cm
1 0 0 1 50 700 cm — це матриця перетворення з 6 елементів [a b c d e f], що відповідає:
| a b 0 | | 1 0 0 |
| c d 0 | = | 0 1 0 |
| e f 1 | | 50 700 1 |
Коли a=1, b=0, c=0, d=1, це чиста матриця зсуву, яка переміщує початок координат (тобто точку (0,0), де відбуваються малювання) до (50, 700). Якщо не зсунути, початок за замовчуванням знаходиться в нижньому лівому куті сторінки.
Оператори тексту
| Оператор | Значення | Приклад |
|---|---|---|
BT | Почати текст, початок текстового об'єкта | BT |
ET | Закінчити текст, кінець текстового об'єкта | ET |
Tf | Встановити шрифт і розмір шрифту | /F0 36 Tf |
Tj | Малювати рядок | (Hello!) Tj |
Використання pdftk для виправлення у відкритий PDF
У каталозі, де знаходиться hello-broken.pdf, виконайте:
pdftk hello-broken.pdf output hello.pdf
Відкрийте hello.pdf за допомогою будь-якого PDF читача, ви повинні побачити на сторінці "Hello, World!" (шрифт Times-Italic, 36pt, розташований у верхньому лівому куті сторінки).
Що pdftk допоміг вам завершити?
| Завершені елементи | Опис |
|---|---|
| Рядок бінарної позначки | Додає рядок непечатних символів після %PDF-1.0, щоб забезпечити розпізнавання як бінарного файлу |
/Length | Обчислює та додає байтову довжину для потоку вмісту |
Таблиця xref | Обчислює байтовий зсув для кожного об'єкта та заповнює його |
startxref | Заповнює реальну початкову позицію таблиці xref |
Чому потрібні xref / trailer / startxref?
Основна мета: випадковий доступ
Уявіть собі PDF на 500 сторінок, якщо немає xref, читачеві, щоб відобразити 450-у сторінку, потрібно буде розібратися з усіма попередніми 449 сторінками — це дуже повільно.
З xref читач може:
- Спочатку прочитати
startxref→ знайти місце xref - Прочитати
trailer→ знайти кореневий об'єкт/Root - Від кореневого об'єкта слідувати за посиланнями → безпосередньо перейти до об'єкта 450-ї сторінки
- За допомогою xref перевірити байтовий зсув цього об'єкта → безпосередньо перейти до нього для читання
Часова складність зменшується з O(n) до O(1).
Практика цієї статті
Рекомендується дійсно внести зміни до hello-broken.pdf, а потім знову виправити його за допомогою pdftk, спостерігаючи за результатом:
| Практика | Зміни | Спостереження |
|---|---|---|
| A | Змінити (Hello, World!) на іншу англійську фразу | Зміна тексту |
| B | Змінити 36 на 12 або 72 | Зміна розміру шрифту |
| C | Змінити 50 700 на 50 100 | Переміщення вниз (координатна система PDF має початок у нижньому лівому куті) |
| D | Змінити /Times-Italic на /Helvetica або /Courier | Зміна шрифту |
| E | Змінити /MediaBox [0 0 612 792] на [0 0 595 842] | Зміна формату паперу з US Letter на A4 |
Порада: Координатна система PDF має початок у нижньому лівому куті сторінки, вісь Y спрямована вгору.
(50, 700)означає відстань 50pt зліва, 700pt знизу.
Поширені запитання
Q: Чому використовуються вбудовані шрифти Type1, а не TrueType?
A: 14 стандартних шрифтів Type1 (Times, Helvetica, Courier тощо) є обов'язковими для вбудовування в PDF читачі, їх не потрібно вбудовувати у файли шрифтів, це найпростіше. У реальних сценаріях зазвичай потрібно вбудовувати шрифти для забезпечення узгодженості між платформами.
Q: Що означають ці числа в /MediaBox [0 0 612 792]?
A: Одиниця виміру — пункт (1 пункт = 1/72 дюйма). 612 × 792 пунктів = 8.5 × 11 дюймів = папір формату US Letter. A4 — це 595 × 842 пунктів.
Q: Що таке номер генерації (наприклад, 0 у 2 0 R)?
A: Використовується для інкрементного оновлення. Коли об'єкт змінюється, номер генерації збільшується на 1. У новостворених PDF всі об'єкти зазвичай мають номер генерації 0.
Анонс наступної статті
У частині 02 ми продовжимо використовувати "ручне написання потоку вмісту", додавши найосновніші операції з графічними шляхами:
m(moveto),l(lineto): визначення шляхуS(stroke): обводкаre(rectangle),f(fill): малювання прямокутника та заповнення
Це дозволить вам на одній сторінці одночасно намалювати: текст заголовка + горизонтальну роздільну лінію + прямокутник, переходячи від "вміння писати" до "вміння малювати графіку".
