Перейти к содержимому

Преобразование координатного пространства: поворот, масштабирование, наклон и зеркальное отражение

Преобразуйте координатное пространство рисунка вокруг выбранной точки. В этом рецепте рассматриваются поворот, масштабирование, наклон и зеркальное отражение. Каждое преобразование изолировано в блоке с сохранённым графическим состоянием, поэтому не влияет на последующее содержимое. Рецепт основан на examples/21-transforms.php.

Окно терминала
composer require nextpdf/core:^3

Пакет Pro или Enterprise не требуется. API преобразований входит в Core и работает на PHP 8.1—8.4.

Содержимое Portable Document Format (PDF) выводится в пользовательском пространстве. По умолчанию начало координат пользовательского пространства находится в левом нижнем углу страницы, а одна единица равна 1/72 дюйма (ISO 32000-2 §8.3.2). Преобразование умножает текущую матрицу преобразования (CTM) на новую матрицу с помощью оператора cm (§8.3.4). Преобразования объединяются конкатенацией матриц, поэтому порядок важен.

NextPDF позволяет работать в авторской системе координат с началом в левом верхнем углу. Внутри она преобразуется в собственное пользовательское пространство с началом в левом нижнем углу через проекцию toY() в методах преобразования. Позиции задаются в единицах пользовательского пространства — пунктах PDF, где 1 pt равен 1/72 дюйма. Чтобы преобразование оставалось локальным, заключите его между startTransform() и stopTransform(). Эти методы выводят операторы графического состояния q (сохранение) и Q (восстановление) (§8.4.2). Всё, что нарисовано между ними, наследует преобразование. Всё, что следует после stopTransform(), возвращается к прежней CTM. Каждый вызов rotate()/scale()/skewX()/mirrorH() принимает явную точку, поэтому преобразование привязывается к ожидаемому месту, а не к началу координат страницы.

Поверхность API формируется из PHPDoc. Основные точки входа предоставляет трейт \NextPDF\Core\Concerns\HasTransforms:

  • Document::startTransform(): static — выводит q и открывает блок состояния
  • Document::stopTransform(): static — выводит Q и закрывает блок
  • Document::rotate(float $angle, float $x = 0, float $y = 0): static
  • Document::scale(float $sx, float $sy, float $x = 0, float $y = 0): static
  • Document::skewX(float $angle, float $x = 0, float $y = 0): static / skewY(...)
  • Document::mirrorH(float $x = 0): static / mirrorV(float $y = 0): static
  • Document::translateCtm(float $dx, float $dy): static
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Coordinate Transforms');
$doc->addPage();
$cx = 60.0;
$cy = 60.0;
// Rotate 30° around (cx, cy). The transform is scoped to this block.
$doc->startTransform();
$doc->rotate(30, $cx, $cy);
$doc->setFont('helvetica', '', 14);
$doc->text($cx, $cy, 'Rotated 30 degrees');
$doc->stopTransform();
// Back to the untransformed CTM — this text is upright.
$doc->setFont('helvetica', '', 10);
$doc->text($cx, $cy + 20, 'Not rotated');
$doc->save(__DIR__ . '/transforms.pdf');
echo "Created: transforms.pdf\n";

Эта самодостаточная программа запускается в тестовом окружении cookbook. Она воспроизводит раздел масштабирования из examples/21-transforms.php. Каждое преобразование остаётся в блоке с сохранённым графическим состоянием и явно заданной точкой. В конце состояние цвета и линий сбрасывается, поэтому ничто не переносится на следующую страницу.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Coordinate Transforms');
$doc->addPage();
$doc->setFont('helvetica', 'B', 13);
$doc->cell(0, 8, 'Scaling a reference square at 0.5x, 1.0x, 1.5x, 2.0x', newLine: true);
$doc->ln(6);
$scaleBaseY = $doc->getY();
$scaleFactors = [0.5, 1.0, 1.5, 2.0];
$doc->setDrawColor(30, 58, 138);
$doc->setLineWidth(0.4);
foreach ($scaleFactors as $idx => $factor) {
$cx = 25.0 + $idx * 45;
$cy = $scaleBaseY + 5;
$doc->startTransform();
$doc->scale($factor, $factor, $cx, $cy); // scale about (cx, cy)
$doc->setFillColor(220, 230, 241);
$doc->rect($cx, $cy, 15, 15, 'DF');
$doc->line($cx, $cy, $cx + 15, $cy + 15);
$doc->stopTransform(); // CTM restored here
// Drawn AFTER the block — at the original scale, untransformed.
$doc->setFont('helvetica', '', 8);
$doc->setTextColor(0);
$doc->text($cx, $scaleBaseY + 38, sprintf('%.1fx', $factor));
}
// Explicit state reset so nothing carries into the next section.
$doc->setTextColor(0);
$doc->setFillColor(255);
$doc->setDrawColor(0);
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script twice under
// the structural profile (the transform stream itself is deterministic).
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false && $out !== '' ? $out : __DIR__ . '/transforms.pdf');
echo "Wrote transforms.pdf\n";

Ожидаемый STDOUT:

Wrote transforms.pdf

Полный пример охватывает все четыре семейства преобразований: поворот, масштабирование, наклон и зеркальное отражение. Запустите его командой php examples/21-transforms.php; она создаст examples/output/21-transforms.pdf.

  • Всегда закрывайте блок парой. Каждому startTransform() должен соответствовать парный stopTransform(). Несбалансированное количество q/Q повреждает графическое состояние до конца страницы (ISO 32000-2 §8.4.2). NextPDF отслеживает глубину вложенности, но в рецепте контракт остаётся один к одному.
  • Порядок не коммутативен. Преобразования объединяются конкатенацией матриц, поэтому rotate(), за которым следует scale(), не то же самое, что scale(), за которым следует rotate(). Применяйте их внутри одного блока в нужном порядке.
  • По умолчанию точка совпадает с началом координат. Если опустить точку, преобразование выполняется вокруг начала координат страницы, а не фигуры. Обычно это не то, что нужно, поэтому передавайте точку явно.
  • Ось Y задаётся в авторском пространстве. Координата y точки — это расстояние от левого верхнего угла авторской системы, и NextPDF проецирует её в собственное пользовательское пространство. Если смешивать необработанные координаты PDF с авторским API, результат получится зеркально отражённым.
  • Утечка состояния. Цвет, шрифт и толщина линии, заданные внутри блока преобразования, сохраняются после stopTransform(), поскольку в этой поверхности API Q восстанавливает только CTM. Сбрасывайте эти значения явно, если следующий раздел не должен их наследовать, как в производственном примере.

Преобразование выводит один оператор cm и пару q/Q. Каждая часть занимает всего несколько байтов и не добавляет заметных затрат во время выполнения, поэтому рецепт укладывается в бюджет 1500 мс / 96 МБ. Профиль воспроизводимости — структурный. Вывод содержит массив /ID в трейлере и метаданные создания, которые меняются между запусками, поэтому перед сравнением их необходимо нормализовать. Сам поток преобразования детерминирован.

  • Локализация данных и меры по защите персонально идентифицируемой информации (PII). Неприменимо. Этот рецепт рисует геометрические примитивы и короткие подписи. Он не обрабатывает внешние или персональные данные.
  • Безопасная телеметрия и очистка журналов. Рецепт записывает одну фиксированную строку прогресса. Он не записывает в журнал содержимое документа.
  • Модель угроз. Неприменимо. Здесь нет разбора входных данных, криптографии и границы доверия. Преобразование — это чистый вывод в поток содержимого.
  • Поведение в режиме Federal Information Processing Standards (FIPS). Неприменимо. Криптографических операций нет.
УтверждениеСпецификацияПунктreference_id (идентификатор ссылки)
Преобразование присоединяет матрицу к CTM с помощью оператора cm.ISO 32000-2§8.3.4
Преобразования объединяются конкатенацией матриц, и порядок важен.ISO 32000-2§8.3.4
q сохраняет, а Q восстанавливает графическое состояние, что ограничивает область действия преобразования.ISO 32000-2§8.4.2
По умолчанию начало координат пользовательского пространства находится в левом нижнем углу; одна единица равна 1/72 дюйма.ISO 32000-2§8.3.2

Этот рецепт следует процитированным пунктам ISO 32000-2 о графическом состоянии и преобразованиях. Он не заявляет полного соответствия ISO 32000-2; процитированные пункты — единственные, которые задействует этот рецепт.