hankchen / php-canvas-next
next generation of php-canvas: layer tree (pure structure) + pluggable renderers (image now, pdf later)
v2.3.0
2026-10-22 03:11 UTC
Requires
- php: ^8.3
- endroid/qr-code: ^6.0
Requires (Dev)
- phpunit/phpunit: ^9.6
Suggests
- ext-intl: 可选:图素簇(grapheme)精确切分,缺失时降级为码点切分
- hankchen/php-canvas-image-renderer: 位图渲染后端(intervention/image v4),输出 ImageInterface
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-22 03:11:56 UTC
README
hankchen/php-canvas 的下一代实现:图层树(纯结构)与渲染器(可插拔后端)彻底分离。
Canvas/Layer是纯数据容器,只承载设定与纯布局函数,不含任何渲染状态;- 渲染由 Renderer 驱动:
Renderer::render(Canvas)主动拉取结构树,按图层类型分派绘制; - 本包只含结构、渲染契约与资源物化,位图后端在伴随包 hankchen/php-canvas-image-renderer。
设计文档与对象交互图见 docs/design/layer-renderer-separation.md。
环境要求
- PHP
^8.3、endroid/qr-code^6.0 ext-intl可选(图素簇精确切分,缺失时降级码点切分)- 本包不依赖任何图像扩展;GD/Imagick 由图像渲染后端包引入
安装
composer require hankchen/php-canvas-next
# 位图输出(intervention/image v4):
composer require hankchen/php-canvas-image-renderer
快速上手
use HankChen\CanvasNext\Canvas; use HankChen\CanvasNext\Layer\ImageLayer; use HankChen\CanvasNext\Layer\TextLayer; use HankChen\CanvasNext\Renderer\Image\ImageRenderer; // 来自 php-canvas-image-renderer 包 use HankChen\CanvasNext\Runtime\NullCancellation; $canvas = Canvas::make(400, 300, // priority 越大越先渲染(越垫底),与 php-canvas 一致 ImageLayer::make(400, 300, '#ffffff')->setPriority(10), (new TextLayer()) ->setText('Hello, php-canvas-next') ->setFont('/path/to/font.ttf', 24, '#333333') ->setPosition(20, 40) ->setPriority(1) ); // 第一个参数是渲染取消上下文;无取消需求传 NullCancellation $image = (new ImageRenderer())->render(new NullCancellation(), $canvas); // intervention/image v4 ImageInterface $image->save('/tmp/out.png');
单图层渲染:(new ImageRenderer())->renderLayer(new NullCancellation(), $layer)。
核心概念
结构与渲染分离
Canvas / Layer(纯数据,设定 + 纯布局函数) ← 本包
↓ Renderer::render(Canvas) 主动拉取
AbstractRenderer(模板方法:资源物化 → 建渲染面 → 遍历分派 → 容器下钻 → 收尾) ← 本包
↓ 绘制原语 drawRect / drawImage / drawText
ImageRenderer(intervention/image,独立包) … PdfRenderer(规划中)
- 图层 setter 不产生任何 I/O;远程图片/字体与二维码的下载、生成由
ResourceResolver(本包)在渲染前统一物化,渲染端只见本地路径; - 容器图层(
TableLayer → TableRowLayer → TableCellLayer)在addRow/addCell/addContentLayer时同步父子尺寸(结构侧契约); - 布局计算(断行、autoHeight、对齐原点)是图层上的纯函数,所有渲染端共享同一结果。
文本断行与度量
默认断行器为 UAX #14 简化实现:图素簇切分(emoji 不拆碎)、CJK 字间可断、
拉丁词边界优先、禁则处理(行首不出现 。,、;:!?」』)】 等,行末不出现 「『(【)。
默认度量器为字符启发式(半角 0.55 字宽),两者均可注入替换:
$layer->setLineBreaker($myBreaker); // HankChen\CanvasNext\Contracts\LineBreakerInterface $layer->setMeasurerFactory($myFactory); // HankChen\CanvasNext\Contracts\TextMeasurerFactoryInterface
自定义远程资源下载器(代理、鉴权等):
$renderer = new ImageRenderer(new \HankChen\CanvasNext\ResourceManagers\ResourceResolver($downloader));
结构树序列化
$spec = $canvas->graph(); // 无损结构树,可 JSON 持久化 $canvas = Canvas::fromGraph($spec); // 完整重建(含表格尺寸同步)
graph() 无损携带全部行/单元格/内容与字体完整路径,跨渲染端复用同一份结构。
从 php-canvas 迁移
| 旧(php-canvas) | 新(php-canvas-next) |
|---|---|
Canvas::make(...)->render()->save($p) |
(new ImageRenderer())->render($canvas)->save($p) |
$layer->render() |
(new ImageRenderer())->renderLayer($layer) |
Canvas::getCore() |
render() 的返回值即图像 |
setImage()/setFont() 立即下载 |
setter 只存引用,渲染前由 Resolver 统一物化 |
自定义下载器 $layer->setDownloader() |
new ImageRenderer(new ResourceResolver($downloader)) |
QrCodeLayer::generateQrCodeLayerFromContent($c) |
QrCodeLayer::setText($c)(物化自动进行) |
graph() 的 rowTemplate/cellTemplates |
rows(全量)/cells;字体保存完整路径 |
| — | 新增 Canvas::fromGraph() 反序列化 |
远程资源缓存 sys_temp/canvas/ |
sys_temp/canvas-next/(可与旧库共存) |
| autowrap 字符数断行 | UAX #14 简化断行(词边界 + 禁则),同一文本断行位置可能与旧库不同 |
旧库 hankchen/php-canvas 冻结维护、保持可用;新库不依赖旧库。
开发
composer install
composer test
渲染目验样图脚本位于 image-renderer 包:php scripts/visual-check.php。测试约定见 AGENTS.md。