Pular para o conteúdo

Transforme o espaço de coordenadas: gire, escale, distorça e espelhe

Transforme o espaço de coordenadas do desenho em torno de um pivô que você escolher. Esta receita cobre rotação, escala, distorção e espelhamento. Cada transformação permanece isolada em um bloco de estado gráfico salvo, de modo que não afeta o conteúdo posterior. Ela acompanha examples/21-transforms.php.

Terminal window
composer require nextpdf/core:^3

Você não precisa de nenhum pacote Pro ou Enterprise. A interface de programação de aplicações (API) de transformação vem com o Core e funciona no PHP 8.1 até 8.4.

O conteúdo do Portable Document Format (PDF) é desenhado no espaço do usuário. Por padrão, o espaço do usuário tem origem no canto inferior esquerdo da página, e uma unidade equivale a 1/72 de polegada (ISO 32000-2 §8.3.2). Uma transformação multiplica a matriz de transformação atual (CTM) por uma nova matriz por meio do operador cm (§8.3.4). As transformações são compostas por concatenação de matrizes, portanto a ordem importa.

O NextPDF permite que você trabalhe em um sistema de coordenadas de autoria com origem no canto superior esquerdo. Internamente, ele converte esse sistema para o espaço do usuário nativo com origem no canto inferior esquerdo por meio da projeção toY() nos métodos de transformação. As posições são expressas em unidades do espaço do usuário: pontos PDF, em que 1 pt equivale a 1/72 pol. Para manter uma transformação com escopo local, envolva-a entre startTransform() e stopTransform(). Esses métodos emitem os operadores de estado gráfico q (salvar) e Q (restaurar) (§8.4.2). Tudo o que for desenhado entre eles herda a transformação. Tudo o que vier após stopTransform() retorna à CTM anterior. Cada chamada rotate()/scale()/skewX()/mirrorH() recebe um pivô explícito, de modo que a transformação fique ancorada onde você espera, e não na origem da página.

A superfície da API é gerada a partir do PHPDoc. Os principais pontos de entrada estão no trait \NextPDF\Core\Concerns\HasTransforms:

  • Document::startTransform(): static — emite q e abre um bloco de estado
  • Document::stopTransform(): static — emite Q e fecha o bloco
  • 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";

Este programa autocontido é executado no harness do cookbook. Ele segue de perto a seção de escala de examples/21-transforms.php. Cada transformação permanece em um bloco de estado gráfico salvo com um pivô explícito. As cores e a largura de linha são redefinidas no final, de modo que nada vaza para uma página posterior.

<?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 esperado:

Wrote transforms.pdf

O exemplo completo cobre todas as quatro famílias de transformação: rotação, escala, distorção e espelhamento. Execute-o com php examples/21-transforms.php; ele grava examples/output/21-transforms.pdf.

  • Sempre emparelhe o bloco. Todo startTransform() deve ter um stopTransform() correspondente. Uma contagem desbalanceada de q/Q corrompe o estado gráfico no restante da página (ISO 32000-2 §8.4.2). O NextPDF rastreia a profundidade, mas o contrato desta receita continua sendo de um para um.
  • A ordem não é comutativa. As transformações se compõem por concatenação de matrizes, portanto rotate() seguido de scale() não é o mesmo que scale() seguido de rotate(). Aplique-as dentro de um único bloco na ordem que você pretende.
  • O pivô assume a origem por padrão. Se você omitir o pivô, a transformação gira em torno da origem da página, e não da forma. Em geral, não é isso que você quer, portanto passe o pivô explicitamente.
  • O eixo Y é do espaço de autoria. O y do pivô é a distância medida a partir do canto superior esquerdo do espaço de autoria, e o NextPDF o projeta para o espaço do usuário nativo. Misturar coordenadas PDF brutas com a API de autoria produz um resultado espelhado.
  • Vazamento de estado. A cor, a fonte e a largura de linha definidas dentro de um bloco de transformação persistem após stopTransform(), porque, nesta superfície da API, o Q restaura apenas a CTM. Redefina esses valores explicitamente se uma seção posterior não puder herdá-los, como faz o exemplo de produção.

Uma transformação emite um operador cm mais o par q/Q. Cada componente ocupa apenas alguns bytes e não acrescenta custo mensurável em tempo de execução, portanto a receita permanece dentro do orçamento de 1500 ms / 96 MB. O perfil de reprodutibilidade é estrutural. A saída contém um array /ID no trailer e metadados de criação que não são estáveis entre execuções, portanto você deve normalizá-los antes da comparação. O fluxo de transformação em si é determinístico.

  • Residência de dados e mitigações de informações de identificação pessoal (PII). Não aplicável. Esta receita desenha primitivas geométricas e rótulos curtos. Ela não processa nenhum dado externo ou pessoal.
  • Telemetria segura e higienização de logs. A receita grava uma única linha de progresso fixa. Ela não registra nenhum conteúdo do documento.
  • Modelo de ameaças. Não aplicável. Não há análise de entrada, nem criptografia, nem fronteira de confiança. Uma transformação é uma emissão pura de fluxo de conteúdo.
  • Comportamento no modo Federal Information Processing Standards (FIPS). Não aplicável. Não há nenhuma operação criptográfica.
DeclaraçãoEspecificaçãoCláusulareference_id
Uma transformação concatena uma matriz à CTM com o operador cm.ISO 32000-2§8.3.4
As transformações se compõem por concatenação de matrizes, e a ordem é significativa.ISO 32000-2§8.3.4
q salva e Q restaura o estado gráfico, o que limita o escopo da transformação.ISO 32000-2§8.4.2
A origem padrão do espaço do usuário é o canto inferior esquerdo; uma unidade é 1/72 de polegada.ISO 32000-2§8.3.2

Esta receita segue as cláusulas citadas da ISO 32000-2 sobre estado gráfico e transformação. Ela não afirma conformidade total com a ISO 32000-2; as cláusulas citadas são as únicas que esta receita exercita.