Salta ai contenuti

Trasformare lo spazio delle coordinate: rotazione, ridimensionamento, inclinazione e ribaltamento

Trasformare lo spazio delle coordinate di disegno attorno a un perno scelto. Questa ricetta illustra rotazione, ridimensionamento, inclinazione e ribaltamento. Ogni trasformazione resta isolata in un blocco di stato grafico salvato, perciò non influisce sul contenuto successivo. La struttura segue examples/21-transforms.php.

Terminal window
composer require nextpdf/core:^3

Non è richiesto un pacchetto Pro o Enterprise. L’API di trasformazione è inclusa in Core e funziona da PHP 8.1 a 8.4.

Il contenuto PDF viene disegnato nello spazio utente. Per impostazione predefinita, lo spazio utente ha l’origine in basso a sinistra della pagina e un’unità equivale a 1/72 di pollice (ISO 32000-2 §8.3.2). Una trasformazione moltiplica la matrice di trasformazione corrente (CTM) per una nuova matrice tramite l’operatore cm (§8.3.4). Le trasformazioni si compongono per concatenazione di matrici, perciò l’ordine è significativo.

NextPDF espone un sistema di coordinate autore con origine in alto a sinistra. Internamente lo converte nello spazio utente nativo con origine in basso a sinistra tramite la proiezione toY() nei metodi di trasformazione. Le posizioni usano le unità dello spazio utente, ovvero i punti PDF, dove 1 pt equivale a 1/72 di pollice. Per mantenere una trasformazione locale, racchiuderla tra startTransform() e stopTransform(). Questi metodi emettono gli operatori di stato grafico q (salva) e Q (ripristina) (§8.4.2). Tutto ciò che viene disegnato tra i due metodi eredita la trasformazione. Tutto ciò che segue stopTransform() torna alla CTM precedente. Ogni chiamata a rotate()/scale()/skewX()/mirrorH() accetta un perno esplicito, perciò la trasformazione si ancora al punto previsto anziché all’origine della pagina.

La superficie dell’API è generata da PHPDoc. I principali punti di ingresso provengono dal trait \NextPDF\Core\Concerns\HasTransforms:

  • Document::startTransform(): static — emette q e apre un blocco di stato grafico
  • Document::stopTransform(): static — emette Q e chiude il blocco
  • 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";

Questo programma autonomo è eseguibile dall’harness. Rispecchia la sezione di ridimensionamento di examples/21-transforms.php. Ogni trasformazione è isolata in un blocco di stato grafico salvato con un perno esplicito. Lo stato del colore e della linea viene reimpostato alla fine, perciò nulla si propaga a una pagina successiva.

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

Wrote transforms.pdf

L’esempio completo copre tutte e quattro le famiglie di trasformazione: rotazione, ridimensionamento, inclinazione e ribaltamento. Eseguirlo con php examples/21-transforms.php, che scrive examples/output/21-transforms.pdf.

  • Chiudere sempre il blocco. Ogni startTransform() richiede un corrispondente stopTransform(). Un bilanciamento errato di q/Q corrompe lo stato grafico per il resto della pagina (ISO 32000-2 §8.4.2). NextPDF tiene traccia della profondità, ma il contratto a livello di ricetta è uno-a-uno.
  • L’ordine non è commutativo. Le trasformazioni si compongono per concatenazione di matrici, perciò rotate() seguito da scale() non equivale a scale() seguito da rotate(). Applicarle all’interno di un unico blocco nell’ordine desiderato.
  • Per impostazione predefinita, il perno è all’origine. Omettendo il perno, la trasformazione ruota attorno all’origine della pagina, non alla forma. Di solito non è il risultato desiderato, perciò passare il perno in modo esplicito.
  • L’asse Y è nello spazio autore. La y del perno è la distanza dall’angolo in alto a sinistra dello spazio autore, che NextPDF proietta nello spazio utente nativo. Mescolare coordinate PDF grezze con l’API autore produce un risultato ribaltato.
  • Propagazione dello stato. Colore, font e spessore della linea impostati all’interno di un blocco di trasformazione persistono dopo stopTransform(), perché in questa superficie dell’API Q ripristina solo la CTM. Reimpostare questi valori in modo esplicito se una sezione successiva non deve ereditarli, come fa l’esempio di produzione.

Una trasformazione è un singolo operatore cm più la coppia q/Q. Ogni elemento occupa pochi byte e non aggiunge alcun costo di runtime misurabile, perciò la ricetta rimane comodamente entro il budget di 1500 ms / 96 MB. Il profilo di riproducibilità è strutturale. L’output contiene un array /ID nel trailer e metadati di creazione che non sono stabili tra le esecuzioni, perciò è necessario normalizzarli prima del confronto. Il flusso di trasformazione in sé è deterministico.

  • Residenza dei dati e mitigazioni dei dati personali (PII). Non applicabile. Questa ricetta disegna primitive geometriche ed etichette brevi. Non elabora alcun dato esterno o personale.
  • Telemetria sicura e pulizia dei log. La ricetta scrive una sola riga di avanzamento fissa. Nessun contenuto del documento viene registrato nei log.
  • Modello di minaccia. Non applicabile. Non vi è alcuna analisi dell’input, alcuna crittografia, né alcun confine di attendibilità. Una trasformazione è pura emissione di content stream.
  • Comportamento in modalità FIPS. Non applicabile. Non vi è alcuna operazione crittografica.
DichiarazioneSpecificaClausolareference_id
Una trasformazione concatena una matrice alla CTM tramite l’operatore cm.ISO 32000-2§8.3.4
Le trasformazioni si compongono per concatenazione di matrici; l’ordine è significativo.ISO 32000-2§8.3.4
q salva e Q ripristina lo stato grafico, circoscrivendo la trasformazione.ISO 32000-2§8.4.2
L’origine predefinita dello spazio utente è in basso a sinistra; un’unità è 1/72 di pollice.ISO 32000-2§8.3.2

Questa ricetta implementa le clausole ISO 32000-2 citate relative allo stato grafico e alle trasformazioni. Non dichiara la conformità generale a ISO 32000-2; le clausole citate sono solo quelle che questa ricetta utilizza.