Search by

hankchen / php-canvas-next

HankChenCH

next generation of php-canvas: layer tree (pure structure) + pluggable renderers (image now, pdf later)

Package info

github.com/HankChenCH/php-canvas-next

pkg:composer/hankchen/php-canvas-next

Statistics

Installs: 33

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

v2.3.0 2026-10-22 03:11 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。