Search by

cloud-castle / document

alex-4-17

Универсальная работа с документами для PHP 8.1+: единая объектная модель, парсинг и создание документов, сохранение в 10 форматах (Markdown, HTML, DOCX, ODT, RTF, PDF, EPUB, JSON, XML, TXT) и конвертация между любыми из них. Безопасно по умолчанию: защита от XXE, zip-бомб и HTML-инъекций.

v1.1.0 2026-09-28 07:45 UTC

This package is auto-updated.

Last update: 2026-09-28 08:24:01 UTC


README

🇷🇺 Русский · 🇬🇧 English · 🇩🇪 Deutsch · 🇫🇷 Français · 🇪🇸 Español · 🇮🇹 Italiano

CloudCastle Document

CloudCastle Document

Packagist Version PHP Version License Downloads Monthly Downloads Stars Advisories

Quality Publish Репозиторий Релиз Задачи Запросы слияния Вики Лента

PHPStan Psalm PHPMD PHPCS coverage Infection OpenSSF

Универсальная работа с документами на чистом PHP 8.1+: единая объектная модель, чтение и запись десяти форматов — Markdown, HTML5+CSS, TXT, DOCX, ODT, RTF, PDF, EPUB, JSON и XML — и конвертация «любой → любой» одной строкой. Собственные писатель и читатель PDF (TrueType, оглавление, закладки, колонтитулы), инструментарий уровня Word (таблицы с объединением ячеек, сноски, списки всех видов, изображения, стили) и безопасность по умолчанию: XXE, zip-бомбы, path traversal и опасные схемы URL отклоняются без настройки.

Установка

composer require cloud-castle/document

Требуется PHP 8.1+ с расширениями dom, libxml, mbstring, zlib (входят в типовую поставку PHP). Контейнеры DOCX, ODT и EPUB читает и пишет cloud-castle/archive на чистом PHP, поэтому расширение zip не нужно.

Быстрый старт

<?php

use CloudCastle\Document\Document;

// Конвертация «любой → любой» одной строкой.
Document::convert('отчёт.docx', 'отчёт.pdf');

// Чтение любого поддерживаемого формата в объектную модель.
$document = Document::open('статья.md');
echo $document->plainText();

// Создание документа и сохранение в несколько форматов.
$document = Document::create()
    ->title('Квартальный отчёт')
    ->author('Отдел аналитики')
    ->header('Компания — {PAGE} из {PAGES}')
    ->addTableOfContents('Содержание')
    ->addHeading('Итоги квартала')
    ->addParagraph('Выручка выросла на 12 % по сравнению с прошлым кварталом.')
    ->addList(['рост розницы', 'запуск двух регионов', 'снижение оттока'])
    ->addTable([
        ['Показатель', 'Значение'],
        ['Выручка', '84,3 млн'],
        ['Маржа', '31 %'],
    ]);

$document->save('отчёт.pdf');
$document->save('отчёт.docx');
$document->save('отчёт.html');

Дозапись и правка существующих документов

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

use CloudCastle\Document\Document;
use CloudCastle\Document\Element\Paragraph;

// Дозапись: открыть, добавить содержимое, сохранить.
$document = Document::open('отчёт.docx')
    ->addHeading('Дополнение от 17.09')
    ->addParagraph('Абзац, дописанный программно.');
$document->save('отчёт.docx');

// Точечные правки по позициям блоков.
$document->insertAt(0, Paragraph::of('Преамбула сверху'));
$document->replaceAt(2, Paragraph::of('Полностью новый третий блок'));
$document->removeAt(5);

// Исправление текста по всему документу (метаданные, таблицы,
// колонтитулы и сноски включительно; адреса ссылок не трогаются).
$fixed = $document->replaceText('ООО «Ромашка»', 'АО «Ромашка»');

// Шаблоны: подстановка значений в «${имя}» по всему документу —
// абзацы, таблицы, колонтитулы, водяной знак, метаданные; адреса
// ссылок и атрибуты оформления не затрагиваются.
$contract = Document::open('шаблон-договора.docx')->fillTemplate([
    'номер' => '42-Б',
    'дата'  => '17.09.2026',
]);
$contract->save('договор-42Б.pdf');

replaceText() и fillTemplate() возвращают новый документ — исходный остаётся нетронутым; методы insertAt()/replaceAt()/removeAt() меняют документ на месте и удобны в цепочках.

Страницы

Документ можно наполнять двумя способами. Сплошным потоком — как в «Быстром старте»: тогда разбивка на страницы выполняется автоматически при выводе в постраничные форматы (PDF, печатные DOCX/ODT). Либо явно постранично: addPage() открывает страницу, каждый метод add*() принимает элемент и возвращает объект страницы Page, поэтому страница собирается цепочкой; следующий вызов addPage() завершает её разрывом.

use CloudCastle\Document\Document;

$document = Document::create()
    ->title('Годовой отчёт')      // мета-информация задаётся на документе
    ->author('Отдел аналитики');

$document->addPage()               // страница 1
    ->addHeading('Введение')
    ->addParagraph('Цели и рамки отчёта.')
    ->addPage()                    // страница 2
    ->addHeading('Итоги')
    ->addTable([['Показатель', 'Значение'], ['Выручка', '84,3 млн']])
    ->save('отчёт.pdf');           // страницы PDF совпадают с явными

$page = $document->page();         // текущая (последняя) страница
$page->blocks();                   // блоки только этой страницы
$page->plainText();                // текст только этой страницы
$page->document();                 // возврат к документу из цепочки

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

$document->pageCount();            // количество страниц
$document->pages();                // список объектов Page
$first = $document->page(1);       // страница по номеру (с единицы)

$first->replaceAt(1, Paragraph::of('новый текст'))  // правка внутри страницы
    ->insertAt(0, Paragraph::of('преамбула'))
    ->removeAt(2)
    ->addParagraph('дописано в конец первой страницы')
    ->save('после-правки.pdf');    // сохранение всего документа

Страницы при чтении тоже восстанавливаются: Document::open() любого формата с постраничной структурой (DOCX, ODT, RTF, PDF, EPUB-главы, HTML c page-break, TXT c \f, Markdown, JSON/XML) даёт документ, в котором page(n) возвращает страницу со всеми её таблицами, изображениями и текстом.

Параметры страницы настраиваются на документе или странице; без явных значений действуют настройки нового документа MS Word (поля 2/1,5/2/3 см, A4):

$document->margins(56.7, 42.5, 56.7, 85.0) // верх/право/низ/лево, pt
    ->lineSpacing(1.15)                    // межстрочный, как в Word
    ->paragraphSpacing(8.0)                // интервал после абзаца
    ->numberPages('стр. {PAGE} из {PAGES}'); // нумерация в подвале

Каскад стилей

Каждый элемент — объект со своими атрибутами оформления: стиль слова накладывается поверх стиля абзаца, настройки ячейки приоритетнее строки и таблицы:

use CloudCastle\Document\Element\{Paragraph, TextRun, Table, TableRow, TableCell};
use CloudCastle\Document\Style\TextStyle;

new Paragraph(
    [new TextRun('важное', TextStyle::regular()->withColor('#cc0000'))],
    style: TextStyle::regular()->withBold()->withFontSize(13.0),
); // оба слова жирные 13pt, выделенное — красное

new Table(
    [new TableRow(
        [new TableCell([Paragraph::of('своя рамка')], borderWidth: 2.0, borderColor: '#cc0000')],
        background: '#eef4ff',      // фон строки — на все её ячейки
    )],
    borderWidth: 0.75,              // сетка таблицы
    borderColor: '#3355aa',
);

Каскад действует в HTML, DOCX, ODT, RTF и PDF и без потерь сохраняется в JSON/XML.

Диаграммы и списки задач

Диаграмма — блок документа с данными, а не картинка: PDF рисует её векторно, HTML и EPUB — встроенным SVG, DOCX, ODT и RTF — фигурами, Markdown и TXT — таблицей значений. Обратное чтение возвращает тот же объект, поэтому диаграмма переживает конвертацию в обе стороны.

use CloudCastle\Document\Document;
use CloudCastle\Document\Element\{Chart, ChartSeries, ChartType, ListBlock, ListItem};

Document::create()
    ->add(Chart::bars(['Янв' => 120, 'Фев' => 245, 'Мар' => 178], 'Выручка'))
    ->add(new Chart(
        ChartType::Line,
        [new ChartSeries('План', [100.0, 140.0, 180.0]), new ChartSeries('Факт', [120.0, 245.0, 178.0])],
        ['Янв', 'Фев', 'Мар'],
        'План и факт',
    ))
    ->add(new ListBlock([
        ListItem::task('Собрать требования', checked: true),
        ListItem::task('Проверить конвертацию'),
    ]))
    ->save('отчёт.docx');

Списки задач (ListItem::task()) пишутся флажками: - [x] в Markdown, <input type="checkbox"> в HTML, символом ☒/☐ в DOCX, ODT и RTF, нарисованной рамкой с галочкой в PDF — и читаются обратно как задачи во всех этих форматах.

Блок-схемы mermaid из Markdown разбираются в объект Diagram (узлы, связи, направление) и рисуются вектором в PDF, SVG в HTML и EPUB, фигурами в DOCX, ODT и RTF; Markdown получает обратно исходный блок


## Изображения: локальные, встроенные и по ссылке

Картинки из разметки попадают в модель тремя путями: `data:`-URI,
относительная ссылка на соседний файл (`assets/banner.svg`) и обычный
сетевой адрес (`https://img.shields.io/…`). Во всех случаях в документ
встраиваются сами данные, поэтому значок или логотип виден и в PDF, и в
DOCX, и в RTF, а не заменяется подписью.

Векторные картинки остаются векторными: в PDF они рисуются примитивами,
в DOCX и RTF — метафайлом EMF, в HTML, EPUB и ODT сохраняется исходный
SVG. Обратное чтение возвращает именно SVG, а не его растровую копию.

Загрузка по сети ограничена: только схемы `http` и `https`, только
публичные адреса (`localhost`, `10.0.0.0/8`, `169.254.0.0/16` и прочие
внутренние диапазоны отклоняются до запроса и на каждом редиректе),
не более 8 МиБ и 8 секунд на файл, повторный адрес берётся из кэша.

```php
use CloudCastle\Document\Support\RemoteImagePolicy;

RemoteImagePolicy::disable();                       // не ходить в сеть совсем
RemoteImagePolicy::limit(2 * 1024 * 1024, 3);       // 2 МиБ и 3 секунды на файл
RemoteImagePolicy::allowHosts(['img.shields.io']);  // только доверенные хосты
```

## Перенос вёрстки PDF в офисные форматы

PDF хранит не абзацы, а координаты строк, поэтому при переводе в DOCX,
ODT и RTF вёрстка собирается заново по геометрии оригинала:

- строка ставится по базовой линии исходника: расстояние до неё
  считается от подъёма шрифта и высоты строки, как это делают редакторы;
- разбиение повторяется по переносам оригинала, а не пересчитывается
  по ширине, где расходятся кернинг и ширины цифр;
- начертания документа едут в пакет под своим именем: подмножеству из
  PDF восстанавливаются таблицы `cmap` и `name`, а имя присваивается
  только тому шрифту, которому хватает глифов на весь текст;
- линейки-разделители, плашки за словами и границы колонок страницы
  переносятся как оформление, а не теряются;
- текстовая рамка не выходит за край листа: иначе редактор сдвигает
  её целиком и строка уезжает со своего места.

## Правка PDF без потери вёрстки

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

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

```php
$document = Document::open('договор.pdf')->replaceText('ООО «Ромашка»', 'АО «Ромашка»');
$document->save('договор-2.pdf');   // графика и разметка исходника на месте
$document->save('договор-2.docx');  // то же размещение в текстовых рамках
```

## Оформление страницы и таблиц

Лист документа несёт собственное оформление, и оно переживает
конвертацию: ровная заливка, градиент, водяной знак, поля и колонтитулы
читаются из исходного файла и выводятся всюду, где формат это позволяет.

```php
use CloudCastle\Document\Document;
use CloudCastle\Document\Element\{Paragraph, Table, TableBorders, TableRow};
use CloudCastle\Document\Style\TextStyle;

$document = Document::create()
    ->pageBackground('#fff8e7')                 // ровная заливка листа
    ->pageGradient('#fff8e7', '#4472c4', 45.0)  // градиент: от, до, угол
    ->watermark('ЧЕРНОВИК');

$document->add(new Table(
    [
        TableRow::header(['Показатель', 'Значение']),
        TableRow::of(['Выручка', '84,3 млн']),
    ],
    sides: TableBorders::HeaderBottom,          // только линия под шапкой
));

$document->addParagraph('Разрядка заголовка')
    ->add(Paragraph::of('в р а з р я д к у', style: TextStyle::regular()->withLetterSpacing(3.0)));
```

Набор линий таблицы (`TableBorders`) задаётся отдельно от толщины и
цвета: полная сетка, только горизонтали, линия под шапкой или вовсе без
линий. Шапка таблицы, помеченная заголовочной, повторяется на каждой её
странице в PDF, DOCX, ODT и RTF. Разрядка набора
(`TextStyle::withLetterSpacing()`) читается из PDF, DOCX, ODT, RTF и
HTML и выводится во все форматы, где она выразима.

## Поддерживаемые форматы

| Формат | Чтение | Запись | Особенности |
|---|:---:|:---:|---|
| Markdown (`md`) | ✅ | ✅ | CommonMark-подмножество, таблицы GFM, сноски, front matter |
| HTML5+CSS (`html`) | ✅ | ✅ | санитизация белым списком, инлайн-стили, колонтитулы |
| Текст (`txt`) | ✅ | ✅ | абзацы, страницы через form feed |
| DOCX (`docx`) | ✅ | ✅ | стили, списки, `gridSpan`/`vMerge`, сноски, колонтитулы, изображения |
| ODT (`odt`) | ✅ | ✅ | автостили, объединения ячеек, сноски, колонтитулы |
| RTF (`rtf`) | ✅ | ✅ | кодовые страницы, `\uN`, поля TOC/HYPERLINK, изображения |
| PDF (`pdf`) | ✅ | ✅ | собственный писатель и читатель, TrueType, оглавление, закладки |
| EPUB 3 (`epub`) | ✅ | ✅ | главы по spine, метаданные DC, изображения из архива |
| JSON (`json`) | ✅ | ✅ | родной lossless-формат, версия схемы, байт-в-байт |
| XML (`xml`) | ✅ | ✅ | родной lossless-формат, версия схемы, байт-в-байт |

## Сравнение с аналогами

Все таблицы генерируются автоматически (`php benchmarks/run.php`,
`php benchmarks/inventory.php`, `php tools/comparison-sync.php`) на
зафиксированных в `benchmarks/composer.lock` версиях аналогов.

### Функциональность

<!-- FEATURES:START -->
| Возможность | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Чтение Markdown | ✅ | — | ✅ | ✅ | — | — | — | — | document, commonmark, parsedown |
| Чтение HTML | ✅ | 🟡 | — | — | ✅ | ✅ | ✅ | — | document и ещё 3 |
| Чтение DOCX | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Чтение ODT | ✅ | 🟡 | — | — | — | — | — | — | document |
| Чтение RTF | ✅ | 🟡 | — | — | — | — | — | — | document |
| Чтение PDF | ✅ | — | — | — | — | — | — | ✅ | document, pdfparser |
| Чтение EPUB | ✅ | — | — | — | — | — | — | — | document |
| Запись Markdown | ✅ | — | — | — | ✅ | — | — | — | document, html-to-markdown |
| Запись HTML | ✅ | ✅ | ✅ | ✅ | — | — | — | — | document и ещё 3 |
| Запись DOCX | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Запись ODT | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Запись RTF | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Запись PDF | ✅ | 🟡 | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Запись EPUB | ✅ | — | — | — | — | — | — | — | document |
| Родной lossless-формат (JSON/XML) | ✅ | — | — | — | — | — | — | — | document |
| Конвертация «любой → любой» | ✅ | 🟡 | — | — | — | — | — | — | document |
| Объектная модель документа (AST) | ✅ | ✅ | ✅ | — | — | — | — | 🟡 | document, phpword, commonmark |
| Таблицы (colspan + rowspan) | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | — | document и ещё 3 |
| Извлечение изображений из PDF | ✅ | — | — | — | — | — | — | 🟡 | document |
| CSS-блочная модель (фон, рамка, отступы контейнеров) | ✅ | — | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Изображения (встроенные и внешние) | ✅ | ✅ | 🟡 | 🟡 | 🟡 | ✅ | ✅ | 🟡 | document и ещё 3 |
| Сноски | ✅ | ✅ | 🟡 | — | — | — | ✅ | — | document, phpword, mpdf |
| Оглавление (генерация) | ✅ | ✅ | — | — | — | — | ✅ | — | document, phpword, mpdf |
| Колонтитулы с номерами страниц | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Метаданные документа | ✅ | ✅ | 🟡 | — | — | 🟡 | ✅ | ✅ | document и ещё 3 |
| Кириллица в PDF без настройки | ✅ | — | — | — | — | 🟡 | ✅ | ✅ | document, mpdf, pdfparser |
| Защита от XXE по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| Защита от zip-бомб | ✅ | — | — | — | — | — | — | — | document |
| Санитизация HTML по белому списку | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| Запрет опасных схем URL | ✅ | — | ✅ | 🟡 | — | 🟡 | 🟡 | — | document, commonmark |
| Загрузка изображений по ссылке с защитой от SSRF | ✅ | — | — | — | — | 🟡 | 🟡 | — | document |
| Zero-dependency ядро (только PSR/cloud-castle) | ✅ | — | — | ✅ | — | — | — | — | document, parsedown |
| Дозапись и правка открытых документов | ✅ | 🟡 | — | — | — | — | — | — | document |
| Шаблоны «${имя}» с подстановкой значений | ✅ | ✅ | — | — | — | — | — | — | document, phpword |
| Страницы: доступ по номеру, правка, чтение из файлов | ✅ | 🟡 | — | — | — | — | — | 🟡 | document |
| Настройки страницы: поля, интервалы, нумерация (Word-дефолты) | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Каскад стилей: слово › абзац, ячейка › строка › таблица | ✅ | 🟡 | — | — | — | ✅ | ✅ | — | document, dompdf, mpdf |
| Водяной знак | ✅ | 🟡 | — | — | — | — | ✅ | — | document, mpdf |
| Градиентная заливка листа | ✅ | — | — | — | — | 🟡 | 🟡 | — | document |
| Разрядка набора (letter-spacing) | ✅ | 🟡 | — | — | — | 🟡 | 🟡 | — | document |
| Набор линий таблицы: сетка, горизонтали, линия под шапкой | ✅ | 🟡 | — | — | — | 🟡 | 🟡 | — | document |
| Повтор шапки таблицы на каждой странице | ✅ | ✅ | — | — | — | 🟡 | ✅ | — | document, phpword, mpdf |
| Работа с ZIP без расширения ext-zip | ✅ | — | — | — | — | — | — | — | document |
| **Итого (из 43)** | **43** | 21.5 | 6.5 | 5 | 3 | 11.5 | 17 | 5 | document |
<!-- FEATURES:END -->

### Производительность

Замеры: идентичный документ (60 разделов: заголовок, абзац со стилями,
список, таблица), лучший из 7 прогонов, отдельный процесс на каждый замер.

<!-- PERF:START -->
| Сценарий | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Markdown → HTML | 2.7 мс | — | 23.2 мс | 3.4 мс | — | — | — | — | document |
| HTML → Markdown | 2.4 мс | — | — | — | 5.2 мс | — | — | — | document |
| Запись DOCX | 3.3 мс | 10.8 мс | — | — | — | — | — | — | document |
| Запись PDF | 70.6 мс | — | — | — | — | 247.3 мс | 129.3 мс | — | document |
| Чтение PDF | 24.8 мс | — | — | — | — | — | — | 39.5 мс | document |
<!-- PERF:END -->

### Потребление памяти

<!-- MEMORY:START -->
| Сценарий | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Markdown → HTML | 0.0 МБ | — | 0.0 МБ | 0.0 МБ | — | — | — | — | document, commonmark, parsedown |
| HTML → Markdown | 0.0 МБ | — | — | — | 0.0 МБ | — | — | — | document, html-to-markdown |
| Запись DOCX | 0.0 МБ | 4.0 МБ | — | — | — | — | — | — | document |
| Запись PDF | 2.0 МБ | — | — | — | — | 2.0 МБ | 0.0 МБ | — | mpdf |
| Чтение PDF | 2.0 МБ | — | — | — | — | — | — | 8.0 МБ | document |
<!-- MEMORY:END -->

### Безопасность

<!-- SECURITY:START -->
| Свойство безопасности | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Защита от XXE по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| Защита от zip-бомб | ✅ | — | — | — | — | — | — | — | document |
| Санитизация HTML по белому списку | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| Запрет опасных схем URL | ✅ | — | ✅ | 🟡 | — | 🟡 | 🟡 | — | document, commonmark |
| Загрузка изображений по ссылке с защитой от SSRF | ✅ | — | — | — | — | 🟡 | 🟡 | — | document |
| **Итого (из 5)** | **5** | 1 | 1.5 | 1 | 0 | 1.5 | 1.5 | 0 | document |
<!-- SECURITY:END -->

Соответствие стандартам и практикам безопасности composer-пакетов
(CWE-классы защит, OWASP ASVS, политика раскрытия, строгие типы):

<!-- SECSTD:START -->
| Стандарт / практика безопасности | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| CWE-611: защита от XXE включена по умолчанию | ✅ | ✅ | — | — | — | 🟡 | 🟡 | — | document, phpword |
| CWE-409: лимиты распаковки архивов (zip-бомбы) | ✅ | — | — | — | — | — | — | — | document |
| CWE-22: контроль путей при распаковке (path traversal) | ✅ | 🟡 | — | — | — | — | — | — | document |
| CWE-918: загрузка по ссылке только на публичные адреса (SSRF) | ✅ | — | — | — | — | — | — | — | document |
| CWE-79: экранирование/санитизация HTML-вывода по умолчанию | ✅ | — | 🟡 | 🟡 | — | — | — | — | document |
| OWASP ASVS V5: валидация входа на границе, типизированные отказы | ✅ | 🟡 | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | document, commonmark |
| Политика раскрытия уязвимостей (SECURITY.md) | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | — | document и ещё 5 |
| Без известных advisories в актуальной версии (composer audit) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | document и ещё 7 |
| declare(strict_types=1) во всех файлах пакета | ✅ | — | ✅ | — | ✅ | — | — | — | document, commonmark, html-to-markdown |
| Тесты безопасности в наборе пакета (XXE, бомбы, схемы URL) | ✅ | 🟡 | 🟡 | 🟡 | — | — | — | — | document |
| **Итого (из 10)** | **10** | 4.5 | 5 | 2.5 | 3.5 | 3 | 3 | 1.5 | document |
<!-- SECSTD:END -->

### Качество кода

<!-- QUALITYTABLE:START -->
| Практика качества | document | phpword | commonmark | parsedown | html-to-markdown | dompdf | mpdf | pdfparser | 🏆 Победитель |
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| PHPStan (максимальный уровень + strict-rules) | ✅ | 🟡 | ✅ | — | 🟡 | — | 🟡 | 🟡 | document, commonmark |
| Psalm | ✅ | — | ✅ | — | — | — | — | — | document, commonmark |
| Мутационное тестирование (Infection) | ✅ | — | — | — | — | — | — | — | document |
| Архитектурные слои (Deptrac) | ✅ | — | — | — | — | — | — | — | document |
| Тесты утечек памяти в CI | ✅ | — | — | — | — | — | — | — | document |
| Тесты безопасности (XXE, бомбы, схемы URL) | ✅ | 🟡 | 🟡 | — | — | — | — | — | document |
| Контроль покрытия per-file в CI | ✅ | — | — | — | — | — | — | — | document |
| **Итого (из 7)** | **7** | 1 | 2.5 | 0 | 0.5 | 0 | 0.5 | 0.5 | document |
<!-- QUALITYTABLE:END -->

## Честно о пакете

**Сильные стороны**

- Единственный PHP-пакет с конвертацией «любой → любой» между десятью
  форматами через одну объектную модель: остальные библиотеки закрывают
  по одному-два направления.
- Быстрее аналогов в 4 из 5 сценариев (запись DOCX — в 8 раз, запись PDF —
  в 2,8–6,8 раза, HTML → Markdown — в 2,4 раза) при меньшем потреблении
  памяти (чтение PDF — 2 МБ против 8 МБ у smalot/pdfparser).
- Безопасность по умолчанию, подтверждённая тестами: XXE, zip-бомбы,
  path traversal, опасные схемы URL, санитизация HTML; внешние изображения
  никогда не скачиваются при разборе.
- Ядро без внешних зависимостей: только пакеты `cloud-castle/*`
  (архивы, файловая система, сериализация) и стандартные расширения PHP;
  ни `ext-zip`, ни офисный пакет на сервере не нужны.
- 2140 тестов, покрытие 99,4 % строк при пороге 99,2 %, PHPStan max +
  strict, Psalm, PHPMD, PHPCS, Rector, Deptrac и мутационное
  тестирование — без единого подавления.

**Слабые стороны — честно**

- Пакет моложе и менее распространён, чем аналоги с многолетней историей.
- Потребление памяти местами приносится в жертву производительности
  и функциональности: полная объектная модель держит документ целиком
  (генераторы и ленивые потоки сглаживают это — 2 МБ на чтение PDF
  против 8 МБ у smalot/pdfparser, — но потоковой обработки «строка за
  строкой» без модели пакет не предлагает).

## Где применять

| Сфера | Что брать | Почему |
| --- | --- | --- |
| Веб-сервисы конвертации файлов | `Document::convert()` | Один пакет вместо связки из трёх-четырёх: DOCX → PDF, MD → EPUB и любое другое направление |
| Генерация отчётов и договоров | `Document::create()` + `fillTemplate()` | Единый код на все форматы вывода; шаблоны «${имя}», колонтитулы, оглавление, водяные знаки |
| Приём документов от пользователей | `Document::fromString()` | Защита от XXE, zip-бомб, SSRF и опасных схем URL включена всегда |
| Полнотекстовый поиск и индексация | `plainText()` | Извлечение текста из десяти форматов, включая PDF — быстрее и экономнее smalot/pdfparser |
| Хранение документов в БД/очередях | форматы `json` / `xml` | Родная lossless-сериализация с версией схемы, байт-в-байт восстановление |
| Издательские конвейеры | `save('книга.epub')` | Главы, навигация и метаданные EPUB 3 из одной модели |
| Массовые правки существующих файлов | `replaceText()`, `insertAt()` | Точечное редактирование DOCX/ODT/RTF без офисного пакета на сервере |
| Документная HTML/CSS-вёрстка в PDF | элемент `Box` | Фоны, рамки и отступы контейнеров с корректным разрывом по страницам |

**Когда пакет не нужен.** Если требуется пиксельная эмуляция браузера
(float, flex, grid, `position`) в PDF — этого нет ни в одной PHP-библиотеке
в полном объёме; ближе всех mpdf и dompdf. Если нужно читать макросы,
диаграммы или историю правок DOCX — модель хранит содержимое и оформление,
а не OLE-объекты. Для распознавания сканов нужен OCR — эта задача сюда
не входит.

## Документация

- [Полное оглавление документации](doc/README.md) — страница на каждую возможность.
- [Диаграммы](wiki/docs/ru/features/Charts.md) · [Блок-схемы](wiki/docs/ru/features/Diagrams.md) ·
  [Страницы](wiki/docs/ru/features/Pages.md) · [Шаблоны](wiki/docs/ru/features/Templates.md)
- [Архитектура и жизненный цикл](doc/architecture.md) — слои и mermaid-диаграммы.
- [CHANGELOG](CHANGELOG.md) · [Политика безопасности](SECURITY.md) ·
  [Как внести вклад](CONTRIBUTING.md) · [Поддержка](SUPPORT.md)

## Разработка

```bash
composer install
composer check        # полный конвейер: lint → psalm → phpstan → phpmd → phpcs
                      #   → rector → deptrac → тесты → память → утечки
                      #   → производительность → пороги покрытия
composer test         # только тесты
composer fix          # автопоправки стиля
```

## Лицензия

[MIT](LICENSE).

---

**🇷🇺 Русский** · [🇬🇧 English](docs/en/README.md) · [🇩🇪 Deutsch](docs/de/README.md) · [🇫🇷 Français](docs/fr/README.md) · [🇪🇸 Español](docs/es/README.md) · [🇮🇹 Italiano](docs/it/README.md)