变换坐标空间:旋转、缩放、倾斜与镜像
围绕选定的枢轴点变换绘图坐标空间。这则 recipe(示例)涵盖旋转、缩放、倾斜与镜像。每个变换都隔离在已保存的图形状态区块中,因此不会影响后续内容。它遵循 examples/21-transforms.php 的做法。
composer require nextpdf/core:^3你不需要安装 Pro 或 Enterprise 软件包。变换 API 随 Core 一起提供,可在 PHP 8.1 到 8.4 上运行。
概念说明
标题为“概念说明”的章节PDF 内容绘制在 用户空间(user space) 中。默认情况下,用户空间的原点位于页面左下角,一个单位等于 1/72 英寸(ISO 32000-2 §8.3.2)。变换会通过 cm 运算符,将当前变换矩阵(CTM)与一个新矩阵相乘(§8.3.4)。多个变换以矩阵串联的方式组合,因此顺序很重要。
NextPDF 对外提供一套以左上角为原点的作者坐标系统。在内部,它会在各个变换方法中通过 toY() 投影,将其换算为原生的左下角用户空间。位置使用用户空间单位,也就是 PDF 点(point),其中 1 pt 等于 1/72 英寸。要把变换限制在局部范围内,请将其包在 startTransform() 与 stopTransform() 之间。这些方法会发出 q(保存)与 Q(还原)两个图形状态运算符(§8.4.2)。在这两者之间绘制的所有内容都会继承该变换。stopTransform() 之后的所有内容都会回到先前的 CTM。每次调用 rotate()/scale()/skewX()/mirrorH() 时,都要传入明确的枢轴点,这样变换才会锚定在你预期的位置,而不是页面原点。
API 接口
标题为“API 接口”的章节这份 API 接口由 PHPDoc 生成。承载核心功能的入口点来自 \NextPDF\Core\Concerns\HasTransforms trait:
Document::startTransform(): static— 发出q,开启一个状态区块Document::stopTransform(): static— 发出Q,关闭该区块Document::rotate(float $angle, float $x = 0, float $y = 0): staticDocument::scale(float $sx, float $sy, float $x = 0, float $y = 0): staticDocument::skewX(float $angle, float $x = 0, float $y = 0): static/skewY(...)Document::mirrorH(float $x = 0): static/mirrorV(float $y = 0): staticDocument::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";代码示例 — 生产版
标题为“代码示例 — 生产版”的章节这是一段可独立运行、能纳入测试框架执行的程序。它对应 示例 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 会追踪嵌套深度,但 recipe 层面的约定是一一配对。 - 顺序不可交换。 多个变换以矩阵串联的方式组合,因此先
rotate()再scale(),与先scale()再rotate()并不相同。请在同一个区块内,按照你想要的顺序应用它们。 - 枢轴点默认为原点。 若省略枢轴点,变换就会围绕页面原点旋转,而不是围绕该形状。这通常不是你想要的结果,因此请明确传入枢轴点。
- Y 轴采用作者空间。 枢轴点的
y是相对于作者坐标左上角的距离,NextPDF 会将它投影到原生用户空间。混用原始 PDF 坐标与作者 API,会得到镜像翻转的结果。 - 状态泄漏。 在变换区块内设置的颜色、字体与线宽,在
stopTransform()之后仍会保留,因为在这份 API 接口中,Q只会还原 CTM。如果后续章节不能继承这些值,请像生产版示例那样明确重置它们。
一个变换就是单个 cm 运算符,再加上一对 q/Q。每个部分都只有几个字节,不会带来任何可测量的运行时成本,因此这则 recipe 可以轻松保持在 1500 ms / 96 MB 的预算之内。可重现性配置文件为 结构性(structural)。输出中包含一个 trailer 的 /ID 数组以及创建时的元数据,这些内容并非每次运行都稳定,因此在比对前必须先将它们规范化。变换流本身是确定性的。
安全性备注
标题为“安全性备注”的章节- 数据驻留与 PII 缓解措施。 不适用。这则 recipe 绘制的是几何基本图形与简短标签。它不处理任何外部数据或个人数据。
- 安全遥测与日志清理。 这则 recipe 只输出单行固定的进度信息。不会记录任何文档内容。
- 威胁模型。 不适用。没有任何输入解析、没有密码学运算,也没有信任边界。变换纯粹是内容流输出。
- FIPS 模式行为。 不适用。没有任何密码学运算。
符合性
标题为“符合性”的章节| 陈述 | 规范 | 条款 | 参考 ID |
|---|---|---|---|
变换会通过 cm 运算符,把一个矩阵串联到 CTM 上。 | 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 |
这则 recipe 实现了所引用的 ISO 32000-2 图形状态与变换条款。它并不主张全面符合 ISO 32000-2;所引用的条款仅限于这则 recipe 实际使用到的部分。